true or false

true or false

“We’ll just add a flag in the database.”

I hate that phrase, it just sounds like a hack. I’ve seen databases with tables that are nothing but flags. You just know that all through the code are a bunch of ‘if’ statements, conditionally doing all kinds of things on those flags.

So a while ago, I decided to pretend databases didn’t support boolean data types. I refused to add any more flags to my tables, and instead make classes out of each of the flags.

For example, say we have users who have to confirm their account before they can log into the site.

Instead of this:

class User < ActiveRecord::Base
end

and the table:

  users (id, name, confirmed, confirmed_on) -- We also want the time when they confirmed their account

We have this:

class User < ActiveRecord::Base

  has_one :confirmation

end

class Confirmation < ActiveRecord::Base
 
  belongs_to :user

end

And our tables:

  users (id, name)

  confirmations (id, user_id, created_on)

In my mind, this was good because by using Rails’ created_on attribute I could get the confirmation timestamp for free. I no longer had to write something like this in my code:

user.confirmed_on = Time.now

After doing this for a couple of states, I realized this was dumb. I just created a bunch of classes that had no interesting behavior, and complicated all my queries with joins. So I refactored the code back, to use flags in the users table. This was much simpler and better.

But what if some object’s behavior depended on what state it was currently in?

For example, say we have documents that start out in a draft state, move to a reviewed state and then finally to a published state. And what differs in each state is the validation that’s performed when you attempt to save the document. So the validation that gets performed on a document depends on its current state.

Let’s create a class for each state to be responsible for the state specific validation, and let the document collaborate with them. Forget about the specifics for each state’s validation, I’m going to just make up some differences.

So we’ve got:

class Document < ActiveRecord::Base

  has_one :state

  validates_associated :state

  def before_validation_on_create
    self.state = Draft.new :document => self   # apparently Rails does not set the foreign key until after validation
  end

end

class State < ActiveRecord::Base
  
  belongs_to :document

  def validate
    raise NoMethodError, 'Must be implemented by subclasses to perform state specific validation'
  end 

end

class Draft < State

  # in a draft state, documents must have a title
  def validate
    if document.title.blank?
      document.errors.add :title, "can't be blank"
    end
  end

end

class Reviewed < State
 
  # in a reviewed state, documents must have a grade (A, B, C, etc.)
  def validate
    if document.grade.blank?
      document.errors.add :grade, "can't be blank"
    end
  end

end

class Published < State
  
  # in a published state, documents must have a license (Creative Commons, etc.)
  def validate
    if document.license.blank?
      document.errors.add :license, "can't be blank"
    end
  end

end

So whenever a Document is first created, we put it automatically in a Draft state in the Document’s #before_validation_on_create callback. And somewhere we have a form that POSTs to an action in some controller to allow someone to change a Document’s state:

  # POST id=1&state=Reviewed
  def update
    @document = Document.find params[:id]
    @document.state = params[:state].constantize.new :document => @document   # same hack as in Document#before_validation_on_create
    if @document.save
      redirect_to document_path(@document)
    else
      render :action => :edit
    end
  end

(#constantize is a Rails helper method that will turn a String into its corresponding class object, e.g. ‘Reviewed’.constantize returns the Reviewed class object.)

We use Rails’ #validates_associated to automatically validate the Document’s associated State object (i.e. send it #validate) in order to perform the state specific validation whenever a Document is saved.

Let’s take a step back and look at this code. So we’ve got Document, that’s fine and 4 State classes. Do we need those 4 State classes? I mean all we’re doing is validation, if there were more interesting behavior maybe they’d be justified but let’s try to get rid of them.

So instead of using classes to represent states, let’s use boolean flags.

Our schema goes from:

  documents (id, title, body, grade, license)

  states (id, type, document_id)

to this:

  documents (id, title, body, grade, license, draft, reviewed, published) -- draft, reviewed and published are flags

In our State classes, it felt weird to write code like this:

  if document.title.blank?
    document.errors.add :title, "can't be blank"
  end

Because that’s exactly what ActiveRecord::Validations::ClassMethods#validates_presence_of does for us for free (including the error messages). I know we can reuse #validates_presence_of somehow.

Now looking at the Rails’ doc for #validates_presence_of I see it takes an ‘if’ keyword parameter that determines if the validation should proceed. I think that’s what were looking for.

Let’s refactor this trash:

class Document < ActiveRecord::Base

  validates_presence_of :title, :if => :draft?

  validates_presence_of :grade, :if => :reviewed?

  validates_presence_of :license, :if => :published?

  def before_validation_on_create
    self.draft = true
  end

end

Now that’s better. #validates_presence_of’s ‘if’ keyword parameter gives us our state specific validation.

And that controller action to update a Document object’s state becomes:

  # POST id=1&reviewed=1
  def update
    @document = Document.find params[:id]
    if @document.update_attributes params[:document]
      redirect_to document_path(@document)
    else
      render :action => :edit
    end
  end

Boilerplate and simple. The rewritten version gets rid of that :document => hack in order to force the association before validation takes place:

In the action I had:

@document.state = params[:state].constantize.new :document => @document

And in Document#before_validation_on_create:

self.state = Draft.new :document => self

I didn’t like that hack, so I’m glad I was able to get rid of it (If anyone knows a workaround I’d be glad to try it out).

So at first, I implemented the various states a Document could be in using classes, just like I did my User Confirmation feature because I didn’t want to use boolean flags in my database. Then I decided that boolean flags are all right, and simplified the code a lot by eliminating 4 classes. The first design was an implementation of the classic GoF State pattern but in this situation the State pattern is almost supported by Rails by using #validates_presence_of’s ‘if’ keyword parameter. That’s not to say the first implementation is not applicable in Rails, it’s just that in this example that only dealt with simple validation, it wasn’t appropriate. If each state had a lot more complex validation and/or interesting behavior, then I would support the first implementation of using classes for each state.

Jared Carroll Developer

Sharpen your programing skills by completing coding exercises that are reviewed by other developers at Upcase today.