Close
Glad You're Ready. Let's Get Started!

Let us know how we can contact you.

Thank you!

We'll respond shortly.

LABS
CUDdly Models

Teddy Bear

Better Rails Code through

…ActiveRecords with no public methods that have side-effects–other than Create, Update, and Destroy (CUD).

CUDly Models

In a typical web application, some “triggered actions” or “side-effects” occur in response to various events. Examples: A confirmation email is sent when a User registers. A ping is sent to Technorati when a BlogArticle is published. Cookies are set when a user logs in. Two Friendship objects are created between two users when one approves the other’s FriendshipRequest.

Often these side-effects are modeled with public methods on an ActiveRecord. For instance,

class FriendshipRequest < ActiveRecord::Base
  def accept!
    self.status = ACCEPTED
    save!
    Friendship.create!(:from => from, :to => to)
    Friendship.create!(:to => from, :from => to)
  end
end

This is code is dangerous, as shown below. The principal of CUDly models is eliminate all public methods on your ActiveRecord that have any side effects. CUDly models are much safer. Let’s replace the dangerous code with the equivalent, more friendly, CUDly code:

class FriendshipRequest
  after_update :create_mutual_friendship

  private
  def send_email_on_accept
    if status == ACCEPTED
      Friendship.create!(:from => from, :to => to)
      Friendship.create!(:to => from, :from => to)
    end
  end
end

The above CUDly code exploits the richness of ActiveRecord’s lifecycle callbacks to trigger the side-effect. This illustrates a general principle: in a perfectly CUDly world, constrain the interface to your models such that no methods have side effects other than Create, Update, and Destroy.

There are several virtues to CUDly models: encapsulation, transactions, consistent interface, and lifecycle power.

Encapsulation

Your side-effects can be hidden behind the standard ActiveRecord interface. And your Controllers will be skinny as can be! Compare:

class FriendshipRequestsController
  def update
    friendship_request = FriendshipRequest.find(params[:id])
    if params[:friendship_request][:state] == ACCEPTED
      friendship_request.accept!
    else
      friendship_request.reject!
    end
  end
end

Instead, you can write an equivalent, Formulaic Controller:

class FriendshipRequestsController
  def update
    friendship_request = FriendshipRequest.find(params[:id])
    friendship_request.attributes = params[:friendship_request]
    friendship_request.save
  end
end

Transactions

Thanks to ActiveRecord, the CUDly approach has rich transactional semantics. In the CUDly implementation, if any of the Friendship.create! invocations fails, the entire transaction is rolled back, meaning the you cannot put the world in an incoherent state (where Tom and Dick are only half-friends). The equivalent non-CUDly code is the onerous and obese:

class FriendshipRequest < ActiveRecord::Base
  def accept!
    self.status = ACCEPTED
    self.class.transaction do
      save!
      Friendship.create!(:from => from, :to => to)
      Friendship.create!(:to => from, :from => to)
    end
  end
end

Who wants to cuddle with code like that?!

A Rich Consistent Interface

ActiveRecord already has a wonderful pattern: build, then test. It’s so simple, and yet so powerful. Why not re-use it?

f = Friendship.new
if f.save ...

#Save returns true or false, and it sets errors on the model to be displayed by the user. Using CUDly code, you continue to do that:

class FriendshipRequestsController
  def update
    friendship_request = FriendshipRequest.find(params[:id])
    friendship_request.attributes = params[:friendship_request]
    if friendship_request.save
      flash[:notice] = ...
    else
      flash[:error] = ...
      render :action => :edit
    end
  end
end

Try doing that with unCUDly #accept and #reject methods! Surely it will be unCUDly and ungodly!

Leverage the Power of the Lifecycle

A fundamental limitation of public, side-effecting methods is that they can be called at any time for any reason. Suppose we had something like this:

class MyModel < ...
  def foo=(bar)
    send_an_email!
  end

This could be called as part of #attributes=, triggering the email deilvery regardless of whether the model was valid and could be saved! In the CUDly implementation, on the other hand:

class MyModel
  after_save :send_an_email
  def send_an_email
  ...

Thanks to the flexibility of #before_validation_on_create, #before_create, #after_update, #after_initialize, #after_find, etc., you can ensure that your triggered action only happens after successful validation, or regardless of validation, or only on update, or only on destroy–you name it! Try enforcing that with a public method!

Make Your Code a CUDly Code

There is a beautiful symmetry in having all side-effecting methods “funneled” through the three “dangerous” methods (create, update, and destroy). It appeals to my sense of elegance and order. I’ve used this design strategy 100% for the last few months and it’s been a smashing success! It truly is the way ActiveRecord was meant to be used. So give it a try!

Comments
  1. Simon says:

    One thing that’s sort of missing from either version of your code is state checking — is this is a valid state transition? (Am I actually able to become “ACCEPTED” — what if I already am?)

    Which is actually a slightly annoying thing to do using Rails if you’re just using state= type methods and after_update — you’ll need to add on some code to check against the previous value. Or put the check in state=, but if you reject it there, then perhaps all the other assignments you’ve done are invalid. (You’d have to throw an exception and catch it.)

    What this does is actually increase coupling between your model and your view (or at least your controller) — what if I don’t want to maintain state using an attribute, or I want to maintain state using two attributes (or something like that).

    I agree on the emailing thing though, that needs to be done after a successful save.

  2. Tom Armitage says:

    Presumably in your second code block, you mean

    def after_update :create_mutual_friendship

    and not the function you’ve defined there?

  3. Tom Armitage says:

    Presumably in your second code block, you mean

    def after_update :create_mutual_friendship

    and not the function you’ve defined there?

  4. Nick, good points. I like the idea of not having to worry about model state and only allowing CUDly modifications. I’ve found a few times where that is the only way I could safely update the model.

    You could also save yourself one line in your model by replacing:

    friendship_request.attributes = params[:friendship_request]
    friendship_request.save
    

    with:

    friendship_request.update_attributes params[:friendship_request]
    

    Lastly, I’ve never been a fan of sending emails from the model. I think they fit better in observers.

  5. Brian says:

    I agree with brandon on the the email thing. The only problem I see with this is you basically have to create a lot of virtual attributes. For instance with user activation. Right now I have a singleton method find and activate. It’d be nice to mask that with an update, but then I’d have to pass additional params to first find the user and then create a virtual attributes that would store the activation code and then check that against the current user. I like it in most cases, but it has its flaws.

  6. Mark Wilden says:

    I’ve got to say that I’m not convinced by the email example, either.

    If you have a method that sends an email whenever an attribute is assigned to, and that’s actually not what you want to do, then you’ve got a bug. Making methods that do the wrong thing, and then making them public is always going to lead to trouble.

    However, if that’s in fact the correct behavior (highly doubtful), then sending the email from the setter is the simplest thing that could possibly work. Making the same thing happen in #update is going to be more complex.

    In fact, restricting our public writable interface to three methods means the probability that those methods will be more complicated than the several methods they replace. What if there’s more than one way to update (or add or delete)? Do you pass another parameter to indicate this? (no different from having multiple methods) Or does the model figure it out?

    The other thing, that others have mentioned, is that email is a presentation feature, and should be avoided in the model, if possible.

    I found your entry stimulating and thought-provoking (else I wouldn’t be commenting:).

    ///ark

  7. Jon Yurek says:

    I can’t get behind the callbacks. If I’m creating Friendships in an after_update, I’ll be creating two Friendships every time I call save, update_attributes, and even update_attribute (which a lot of people call to bypass validations, but it still calls callbacks). If I don’t want that to happen, I’d need a host of other attributes to make sure that code in the callback is only executed when I want, which adds bloat and confusion.

    Why have the callback if it will only be used once? You’re accepting the invitation and establishing a Friendship. This is not generic code! Put the code where it needs to go. Don’t spread the code over the whole file when it makes sense to keep it in the same method. Context, consistency, and clarity are more important than feeding a skinny method fetish.

  8. Brian says:

    I was playing around with this. I think the best way to implement it is to use virtual attributes as flags and then use callbacks or overrides to delegate behavior to methods depending on which flags are set. This keeps logic out of the callbacks and keeps everything neat, clean, and readable.

  9. Simon says:

    Modeling it with an actsas_statemachine thing is a good idea; I’ve been working on something similar.

    You make a good point about the error messages, and you do say “this is how ActiveRecord is meant to be used”. You may be right, but that does actually highlight an issue with Rails. You can only have one save method, and one set of validations (unless you want to get happy with :if clauses).

    I still think the coupling between the model and controller/view is too high; there also isn’t a clear interface — what attributes do I need to set to change from one state to another? If there were a method, you could document exactly what’s needed. (I suppose you could document what’s needed either way, actually, but it still seems a little dirty).

    So, it’s fine if you’re just changing one attribute (state), but if it’s more complex than that (state changes have parameters), it gets a bit dirty.

  10. Nick Kallen says:

    Simon — I disagree about the coupling. See my examples of Formulaic controllers above. The Controller NEEDS to be ignorant of anything going on in the model, all it can do is call attributes= and save; it shouldn’t pass in attributes beyond what’s in the http request.

    Interestingly, the view is coupled to model… But there’s no way around this, regardless of whether you’re CUDly. The invocation of the method has to be triggered from a link_to or a form post; thus, the view has to trigger the different side effects either with query strings, form bodies, or hitting a different url altogether. The great thing about CUDly models is that the Controller layer withers away altogether, and you can expose your models almost as if they were Resources directly.

    wrt/ to Validations, ActiveRecord is fairly powerful. Yes, there’s :if, but there are also custom validations:

    validate_on_update :something_something
    def something_something end
    
    validate :something_else
    def something_else
      errors.add(:virtual_attribute, "This state txn is invalid") if xxx
    end
    
    validate_on_create :blah
    def blah end
    

    The only thing lacking, IMO, is the ability to validate a destroy. The only way to halt a destroy is:

    before_destroy :halt
    def halt
      false
    end
    

    You cannot safely provide error messages, since no validation phase happened.

  11. Rabbit says:

    Regarding this…

    before_destroy :halt
      def halt
        false
      end
    

    How come the equivalent methods in an observer don’t behave the same? For example…

    class UserObserver < ActiveRecord::Observer
    
      def before_destroy(user)
        return false if user.director?
      end
    
    end
    

    Still lets the user be destroyed. Is there documentation on these discrepancies?

  12. Nick,

    Right on. Here’s a simplified example extracted from an app I wrote:

    class Order < ActiveRecord::Base
      before_update :update_status
      has_many :order_lines
    
      def canceled?
        status == "CANCELED"
      end
    
      def closed?
        status == "CLOSED"
      end
    
      def opened?
        status == "OPENED"
      end
    
      def original_status
        new_record? ? nil : Order.find(id).status
      end
    
      def status_changed?
        status != original_status
      end
    
      private
    
      def decrease_quantity_in_stock
        order_lines.each { |line| line.decrease_quantity_in_stock }
      end
    
      def increase_quantity_in_stock
        order_lines.each { |line| line.increase_quantity_in_stock }
      end
    
      def update_status
        if status_changed?
          decrease_quantity_in_stock if opened?
          increase_quantity_in_stock if canceled?
        end
      end
    
    end
    
  13. Joe says:

    Nick

    I find this approach really very interesting indeed, I currently use acts_as_state_machine and really didn’t like the idea of adding extra actions to my controllers for every event that I defined, or on the other hand adding logic to the update action to manage these events.

    I was just wondering if you got anywhere with your CUDly version of aasm, I would be very interested to see how you have tackled this if you would be happy to share it.

    Thanks for an insightful article.

    Joe

  14. Matthew says:

    These ideas really sit well with me.

    Regarding Brandon’s example, you can now get the _changed? functions for free with Rails 2.1 (aka “dirty attributes”, see Railscast #109: http://railscasts.com/episodes/109)

Post a Comment

Your Information (Name required. Email address will not be displayed with comment.)

* Copy This Password *

* Type Or Paste Password Here *