Simple step-based validation with ActiveRecord
November 13, 2007 22:20 (Sydney Australia), updated about 3 hours later
class Project < ActiveRecord::Base
attr_accessor :step
private
def self.step(name, &block)
with_options(:if => "step == :#{name}") do |opts|
opts.instance_eval(&block)
end
end
public
step :summary do
validates_presence_of :title
end
end
Which can be used like so:
map.resources :projects, :member => {:summary => :any}
class ProjectsController < ApplicationController
def summary
@project = Project.find(params[:id])
if request.put?
@project.step = :summary
@project.attributes = params[:project]
if @project.save
flash[:notice] = "Project summary updated"
redirect_to project_path(@project)
end
end
end
end
and if that doesn’t quench your step-based validation thirst there’s always Jay Field’s all-you-can-eat validatable plugin.
Update: and what about figuring out if a project is valid for a given step?
class Project < ActiveRecord::Base
# ...
def step_complete?(step_name)
project_for_step = dup
project_for_step.errors.clear
project_for_step.step = step_name
project_for_step.valid?
end
end
Comments
justin
maybe you should look at acts_as_state_machine ??
Good docs here :
http://rails.aizatto.com/2007/05/24/ruby-on-rails-finite-state-machine-plugin-acts_as_state_machine/
Tim Lucas
justin: acts_as_state_machine is overkill for what I needed, and my brain just doesn’t work in state machines. I also don’t want the object to have a single current state.
For this instance I’d much prefer to use the simpler solution which does exactly what I need and nothing more, that I can understand by simply looking at the code, has much less chance of breaking with Rails’ changes and produces much more readable code.
Michael Koukoullis
Dude I love your rails voodoo
Nick Kallen
Very very elegant. Although I hate what you did with the controller.
Tim Lucas
Nick: I hear ya. I also tried a version of the controller with separate edit/update for each step via routes but it turns out less concise w/o adding a route mapper helper method. I was this close to posting the alternative controller and route code but it was just a distraction from the main point of step-based validation.
topfunky
I like the idea, but the syntax seems awkward. What about building a dynamic method for each step?
Then that would return true or false if the item validated and saved successfully.
Tim Lucas
hrmm yes… though I’d like to keep to the standard valid/save/update methods.
What about?
Nick Kallen
Tim—don’t do it. Encapsulation!! the caller of project.save should know nothing of the state machine that lies within!
Tim Lucas
but there is no state machine. A project can be valid for multiple steps at the one time… it’s not a step-by-step progress kind of thing.
It’s more about perspectives than states, much the same as Jay’s validatable plugin.
Ryan Allen
That’s a nice way of doing conditional validation. I didn’t quite get what you were doing at first but I got it now :) As for state machines, I’ve used them for managing lifecycles of domain objects, and the code that it yields is very, very concise (I wrote my own state machine library as I didn’t realise acts_as_state_machine was more mature, when I first checked it ages ago it was a bit bunk).
I think the problem with state machines and them ‘not fitting’ is they’re muddled with theory and lots of examples in C… From what I can tell you can apply them to lots of things (like how Zed applies them to parsing HTTP w/ Ragel in Mongrel) if you cut through the cruft and use a high level API .
Ryan Allen
Another possibility is creating separate models for each perspective rather than clumping them all in to one model. Hard to say without seeing more specifically what you’re trying to model.
Justin
Wow, this would have come in handy on a project several months ago. I think we ended up just putting the object in a session and checking each validation by hand until we could finally save on the last step. Very very dirty. I feel disgusting even talking about it now. I am so ashamed…
To comment on this article you must have javascript enabled.