toolmantim

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?

@project.save_as_summary(params[:project])

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?

@project = Project.find(params[:id]).as_summary_step
if @project.update_attributes(params[:project])

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.