Skip to navigation

Simple step-based validation with ActiveRecord

Published November 13, 2007 - Updated November 13, 2007

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

Archived comments

Comments were previously allowed on articles. Though no new comments are being accepted you can see the old comments below.

  1. 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/

  2. 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.

  3. Michael Koukoullis

    Dude I love your rails voodoo

  4. Nick Kallen

    Very very elegant. Although I hate what you did with the controller.

  5. 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.

  6. 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.

  7. 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])
    
  8. Nick Kallen

    Tim — don’t do it. Encapsulation!! the caller of project.save should know nothing of the state machine that lies within!

  9. 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.

  10. 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.

  11. 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.

  12. 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…

Previously: DropShots

Next up: Cap deploy via SCP

Thoughts

toolmantim

I’m Tim Lucas, a user experience developer currently in Sydney Australia.

I occasionally write, snap photos, present on various technical topics, tweet my going-ons, share teh codes and post tidbits to the scrapbook.

Most recently I published Simplifying ticket sales on sydneyoperahouse.com (February 16, 2010)

Work with me via Agency Rainford, or shoot an email to and say hello.

Powered by web2.0 koolaid