Chapter 9. Advanced ActiveRecord

ActiveRecord is a simple object-relational mapping (ORM) framework compared to other popular ORM frameworks, such as Hibernate in the Java world. Don’t let that fool you, though: Under its modest exterior, ActiveRecord has some pretty advanced features. To really get the most effectiveness out of Rails development, you need to have more than a basic understanding of ActiveRecord—things like knowing when to break out of the one-table/one-class pattern, or how to leverage Ruby modules to keep your code clean and free of duplication.

In this chapter, we wrap up this book’s comprehensive coverage of ActiveRecord by reviewing callbacks, observers, single-table inheritance (STI), and polymorphic models. We also review a little bit of information about metaprogramming and Ruby domain-specific languages (DSLs) as they relate to ActiveRecord.

Callbacks

This advanced feature of ActiveRecord allows the savvy developer to attach behavior at a variety of different points along their model’s life cycle, such as after initialization, before database records are inserted, updated or removed, and so on.

Callbacks can do a variety of tasks, ranging from simple things such as logging and massaging of attribute values prior to validation, to complex calculations. Callbacks can halt the execution of the life-cycle process taking place. Some callbacks can even modify the behavior of the model class on the fly. We’ll cover all of those scenarios in this section, but first let’s get a taste of what a callback looks like. Check out the following silly example:

class Beethoven < ActiveRecord::Base

  before_destroy :last_words
  ...

  protected

    def last_words
      logger.info "Friends applaud, the comedy is over"
    end

end

So prior to dying (ehrm, being destroy’d), the last words of the Beethoven class will always be logged for posterity. As we’ll see soon, there are 14 different opportunities to add behavior to your model in this fashion. Before we get to that list, let’s cover the mechanics of registering a callback.

Callback Registration

Overall, the most common way to register a callback method is to declare it at the top of the class using a typical Rails macro-style class method. However, there’s a less verbose way to do it also. Simply implement the callback as a method in your class. In other words, I could have coded the prior example as follows:

class Beethoven < ActiveRecord::Base
  ...

  protected

    def before_destroy
      logger.info "Friends applaud, the comedy is over"
    end

end

This is a rare case of the less-verbose solution being bad. In fact, it is almost always preferable, dare I say it is the Rails way, to use the callback macros over implementing callback methods, for the following reasons:

  • Macro-style callback declarations are added near the top of the class definition, making the existence of that callback more evident versus a method body later in the file.

  • Macro-style callbacks add callback methods to a queue. That means that more than one method can be hooked into the same slot in the life cycle. Callbacks will be invoked in the order in which they were added to the queue.

  • Callback methods for the same hook can be added to their queue at different levels of an inheritance hierarchy and still work—they won’t override each other the way that methods would.

  • Callbacks defined as methods on the model are always called last.

One-Liners

Now, if (and only if) your callback routine is really short,[1] you can add it by passing a block to the callback macro. We’re talking one-liners!

class Napoleon < ActiveRecord::Base
  before_destroy {|r| logger.info "Josephine..." }
  ...
end

Protected or Private

Except when you’re using a block, the access level for callback methods should always be protected or private. It should never be public, since callbacks should never be called from code outside the model.

Believe it or not, there are even more ways to implement callbacks, but we’ll cover those techniques further along in the chapter. For now, let’s look at the lists of callback hooks available.

Matched before/after Callbacks

In total, there are 14 types of callbacks you can register on your models! Twelve of them are matching before/after callback pairs, such as before_validation and after_validation. (The other two, after_initialize and after_find, are special, and we’ll discuss them later in this section.)

List of Callbacks

This is the list of callback hooks available during a save operation. (The list varies slightly depending on whether you’re saving a new or existing record.)

  • before_validation

  • before_validation_on_create

  • after_validation

  • after_validation_on_create

  • before_save

  • before_create (for new records) and before_update (for existing records)

  • ActiveRecord talks to the database and actually does an INSERT or UPDATE

  • after_create (for new records) and before_update (for existing records)

  • after_save

Delete operations have their own two callbacks:

  • before_destroy

  • ActiveRecord talks to the database and actually does a DELETE

  • after_destroy is called after all attributes have been frozen (read-only)

Halting Execution

If you return a Boolean false (not nil) from a callback method, ActiveRecord halts the execution chain. No further callbacks are executed. The save method will return false, and save! will raise a RecordNotSaved error.

Keep in mind that since the last expression of a Ruby method is returned implicitly, it is a pretty common bug to write a callback that halts execution unintentionally. If you have an object with callbacks that mysteriously fails to save, make sure you aren’t returning false by mistake.

Callback Usages

Of course, the callback you should use for a given situation depends on what you’re trying to accomplish. The best I can do is to serve up some examples to inspire you with your own code.

Cleaning Up Attribute Formatting with before_validate_on_create

The most common examples of using before_validate callbacks have to do with cleaning up user-entered attributes. For example, the following CreditCard class (as cited in the Rails API docs) cleans up its number attribute so that false negatives don’t occur on validation:

class CreditCard < ActiveRecord::Base
  ...
  private

    def before_validation_on_create
      # Strip everything in the number except digits
      self.number = number.gsub(/[^0-9]/, "")
    end

end

Geocoding with before_save

Assume that you have an application that tracks addresses and has mapping features. Addresses should always be geocoded before saving, so that they can be displayed rapidly on a map later.[2]

As is often the case, the wording of the requirement itself points you in the direction of the before_save callback:

class Address < ActiveRecord::Base
  include GeoKit::Geocoders

  before_save :geolocate
  validates_presence_of :line_one, :state, :zip
  ...

  private

    def geolocate
      res = GoogleGeocoder.geocode(to_s)
      self.latitude = res.lat
      self.longitude = res.lng
    end
end

Before we move on, there are a couple of additional considerations. The preceding code works great if the geocoding succeeds, but what if it doesn’t? Do we still want to allow the record to be saved? If not, we should halt the execution chain:

def geolocate
  res = GoogleGeocoder.geocode(to_s)
  return false if not res.success     # halt execution

  self.latitude = res.lat
  self.longitude = res.lng
end

The only problem remaining is that we give the rest of our code (and by extension, the end user) no indication of why the chain was halted. Even though we’re not in a validation routine, I think we can put the errors collection to good use here:

def geolocate
  res = GoogleGeocoder.geocode(to_s)
  if res.success
    self.latitude = res.lat
    self.longitude = res.lng
  else
    errors.add_to_base("Geocoding failed. Please check address.")
    return false
  end
end

If the geocoding fails, we add a base error message (for the whole object) and halt execution, so that the record is not saved.

Paranoia with before_destroy

What if your application has to handle important kinds of data that, once entered, should never be deleted? Perhaps it would make sense to hook into ActiveRecord’s destroy mechanism and somehow mark the record as deleted instead?

The following example depends on the accounts table having a deleted_at datetime column.

class Account < ActiveRecord::Base
  ...
  def before_destroy
     update_attribute(:deleted_at, Time.now) and return false
  end

end

I chose to implement it as a callback method so that I am guaranteed it will execute last in the before_destroy queue. It returns false so that execution is halted and the underlying record is not actually deleted from the database.[3]

It’s probably worth mentioning that there are ways that Rails allows you to unintentionally circumvent before_destroy callbacks:

  • The delete and delete_all class methods of ActiveRecord::Base are almost identical. They remove rows directly from the database without instantiating the corresponding model instances, which means no callbacks will occur.

  • Model objects in associations defined with the option :dependent => :delete_all will be deleted directly from the database when removed from the collection using the association’s clear or delete methods.

Cleaning Up Associated Files with after_destroy

Model objects that have files associated with them, such as attachment records and uploaded images, can clean up after themselves when deleted using the after_destroy callback. The following method from Rick Olson’s excellent AttachmentFu[4] plugin is a good example:

# Destroys the file.  Called in the after_destroy callback
def destroy_file
  FileUtils.rm(full_filename)
  ...
rescue
  logger.info "Exception destroying  #{full_filename ... }"
  logger.warn $!.backtrace.collect { |b| " > #{b}" }.join("
")
end

Special Callbacks: after_initialize and after_find

The after_initialize callback is invoked whenever a new ActiveRecord model is instantiated (either from scratch or from the database). Having it available prevents you from having to muck around with overriding the actual initialize method.

The after_find callback is invoked whenever ActiveRecord loads a model object from the database, and is actually called before after_initialize, if both are implemented. Because after_find and after_initialize are called for each object found and instantiated by finders, performance constraints dictate that they can only be added as methods, and not via the callback macros.

What if you want to run some code only the first time that a model is ever instantiated, and not after each database load? There is no native callback for that scenario, but you can do it using the after_initialize callback. Just add a condition that checks to see if it is a new record:

def after_initialize
 if new_record?
   ...
 end
end

In a number of Rails apps that I’ve written, I’ve found it useful to capture user preferences in a serialized hash associated with the User object. The serialize feature of ActiveRecord models makes this possible, since it transparently persists Ruby object graphs to a text column in the database. Unfortunately, you can’t pass it a default value, so I have to set one myself:

class User < ActiveRecord::Base
  serialize :preferences # defaults to nil
  ...

  private

    def after_initialize
      self.preferences ||= Hash.new
    end
end

Using the after_initialize callback, I can automatically populate the preferences attribute of my user model with an empty hash, so that I never have to worry about it being nil when I access it with code such as user.preferences [:show_help_text] = false. Of course, you would only want to store data in serialized columns that you had no interest in querying with SQL in the future.

Ruby’s metaprogramming capabilities combined with the ability to run code whenever a model is loaded using the after_find callback are a powerful mix. Since we’re not done learning about callbacks yet, we’ll come back to uses of after_find later on in the chapter, in the section “Modifying ActiveRecord Classes at Runtime.”

Callback Classes

It is common enough to want to reuse callback code for more than one object that Rails gives you a way to write callback classes. All you have to do is pass a given callback queue an object that responds to the name of the callback and takes the model object as a parameter.

Here’s our paranoid example from the previous section as a callback class:

class MarkDeleted
  def self.before_destroy(model)
     model.update_attribute(:deleted_at, Time.now) and return false
  end
end

The behavior of MarkDeleted is stateless, so I added the callback as a class method. Now you don’t have to instantiate MarkDeleted objects for no good reason. All you do is pass the class to the callback queue for whichever models you want to have the mark-deleted behavior:

class Account < ActiveRecord::Base
  before_destroy MarkDeleted
  ...
end

class Invoice < ActiveRecord::Base
  before_destroy MarkDeleted
  ...

end

Multiple Callback Methods in One Class

There’s no rule that says you can’t have more than one callback method in a callback class. For example, you might have special audit log requirements to implement:

class Auditor
  def initialize(audit_log)
    @audit_log = audit_log
  end

  def after_create(model)
    @audit_log.created(model.inspect)
  end

  def after_update(model)
    @audit_log.updated(model.inspect)
  end

  def after_destroy(model)
    @audit_log.destroyed(model.inspect)
  end
end

To add audit logging to an ActiveRecord class, you would do the following:

class Account < ActiveRecord::Base
  after_create Auditor.new(DEFAULT_AUDIT_LOG)
  after_update Auditor.new(DEFAULT_AUDIT_LOG)
  after_destroy Auditor.new(DEFAULT_AUDIT_LOG)
  ...
end

Wow, that’s kind of ugly, having to add three Auditors on three lines. We could extract a local variable called auditor, but it would still be repetitive. This might be an opportunity to take advantage of Ruby’s open classes, the fact that you can modify classes that aren’t part of your application.

Wouldn’t it be better to simply say acts_as_audited at the top of the model that needs auditing? We can quickly add it to the ActiveRecord::Base class, so that it’s available for all our models.

On my projects, the file where “quick and dirty” code like the method in Listing 9.1 would reside is lib/core_ext/active_record_base.rb, but you can put it anywhere you want. You could even make it a plugin (as detailed in Chapter 19, “Extending Rails with Plugins”). Just make sure to require it from config/environment.rb or it’ll never get loaded.

Example 9.1. A Quick-and-Dirty “Acts As Audited” Method

class ActiveRecord::Base
  def self.acts_as_audited(audit_log=DEFAULT_AUDIT_LOG)
    auditor = Auditor.new(audit_log)
    after_create auditor
    after_update auditor
    after_destroy auditor
  end
end

Now, the top of Account is a lot less cluttered:

class Account < ActiveRecord::Base
  acts_as_audited
  ...
end

Testability

When you add callback methods to a model class, you pretty much have to test that they’re functioning correctly in conjunction with the model to which they are added. That may or may not be a problem. In contrast, callback classes are super-easy to test in isolation.

The following test method verifies correct operation of our Auditor callback class (using the Mocha mocking library available at http://mocha.rubyforge.org/):

def test_auditor_logs_created
  (model = mock).expects(:inspect).returns('foo')
  (log = mock).expects(:created).with('foo')

  Auditor.new(log).after_create(model)
end

Chapter 17, “Testing,” and Chapter 18, “RSpec on Rails,” cover testing with Test::Unit and RSpec, respectively.

Observers

The single responsibility principle is a very important tenet of object-oriented programming. It compels us to keep a class focused on a single concern. As you’ve learned in the previous section, callbacks are a useful feature of ActiveRecord models that allow us to hook in behavior at various points of a model object’s life cycle. Even if we pull that extra behavior out into callback classes, the hook still requires code changes in the model class definition itself. On the other hand, Rails gives us a way to hook in that is completely transparent to the model class: Observers.

Here is the functionality of our old Auditor callback class as an observer of Account objects:

class AccountObserver < ActiveRecord::Observer
  def after_create(model)
    DEFAULT_AUDIT_LOG.created(model.inspect)
  end

  def after_update(model)
    DEFAULT_AUDIT_LOG.updated(model.inspect)
  end

  def after_destroy(model)
    DEFAULT_AUDIT_LOG.destroyed(model.inspect)
  end
end

Naming Conventions

When ActiveRecord::Observer is subclassed, it breaks down the name of the subclass by stripping off the “Observer” part. In the case of our AccountObserver in the preceding example, it would know that you want to observe the Account class. However, that’s not always desirable behavior. In fact, with general-purpose code such as our Auditor, it’s positively a step backward, so it’s possible to overrule the naming convention with the use of the observe macro-style method. We still extend ActiveRecord::Observer, but we can call the subclass whatever we want and tell it explicitly what to observe:

class Auditor < ActiveRecord::Observer
  observe Account, Invoice, Payment

  def after_create(model)
    DEFAULT_AUDIT_LOG.created(model.inspect)
  end

  def after_update(model)
    DEFAULT_AUDIT_LOG.updated(model.inspect)
  end

  def after_destroy(model)
    DEFAULT_AUDIT_LOG.destroyed(model.inspect)
  end
end

Registration of Observers

If there weren’t a place for you to tell Rails which observers to load, they would never get loaded at all, since they’re not referenced from any other code in your application. As mentioned in Chapter 1, “Rails Environments and Configuration,” your application’s boilerplate config/environment.rb has a commented-out line where you should define the observers to be loaded:

# Activate observers that should always be running
config.active_record.observers = [:auditor]

Timing

Observers are notified before the in-object callbacks are triggered. Otherwise, it wouldn’t be possible to act on the whole object in something like a before_destroy observer without having the object’s own callbacks executed first.

Single-Table Inheritance (STI)

A lot of applications start out with a User model of some sort. Over time, as different kinds of users emerge, it might make sense to make a greater distinction between them. Admin and Guest classes are introduced, as subclasses of User. Now, the shared behavior can reside in User, and subtype behavior can be pushed down to subclasses. However, all user data can still reside in the users table—all you need to do is introduce a type column that will hold the name of the class to be instantiated for a given row.

To continue explaining single-table inheritance, let’s turn back to our example of a recurring Timesheet class. We need to know how many billable_hours are outstanding for a given user. The calculation can be implemented in various ways, but in this case we’ve chosen to write a pair of class and instance methods on the Timesheet class:

class Timesheet < ActiveRecord::Base
  ...

  def billable_hours_outstanding
    if submitted?
      billable_weeks.map(&:total_hours).sum
    else
      0
    end
  end

  def self.billable_hours_outstanding_for(user)
    user.timesheets.map(&:billable_hours_outstanding).sum
  end

end

I’m not suggesting that this is good code. It works, but it’s inefficient and that if/else condition is a little fishy. Its shortcomings become apparent once requirements emerge about marking a Timesheet as paid. It forces us to modify Timesheet’s billable_hours_outstanding method again:

def billable_hours_outstanding
  if submitted? and not paid?
    billable_weeks.map(&:total_hours).sum
  else
    0
  end
end

That latest change is a clear violation of the open-closed principle,[5] which urges you to write code that is open for extension, but closed for modification. We know that we violated the principle, because we were forced to change the billable_hours_outstanding method to accommodate the new Timesheet status. Though it may not seem like a large problem in our simple example, consider the amount of conditional code that will end up in the Timesheet class once we start having to implement functionality such as paid_hours and unsubmitted_hours.

So what’s the answer to this messy question of the constantly changing conditional? Given that you’re reading the section of the book about single-table inheritance, it’s probably no big surprise that we think one good answer is to use object-oriented inheritance. To do so, let’s break our original Timesheet class into four classes.

class Timesheet < ActiveRecord::Base
  # non-relevant code ommitted

  def self.billable_hours_outstanding_for(user)
    user.timesheets.map(&:billable_hours_outstanding).sum
  end
end

class DraftTimesheet < Timesheet
  def billable_hours_outstanding
    0
  end
end

class SubmittedTimesheet < Timesheet
  def billable_hours_outstanding
    billable_weeks.map(&:total_hours).sum
  end
end

Now when the requirements demand the ability to calculate partially paid timesheets, we need only add some behavior to a PaidTimesheet class. No messy conditional statements in sight!

class PaidTimesheet < Timesheet
  def billable_hours_outstanding
    billable_weeks.map(&:total_hours).sum - paid_hours
  end
end

Mapping Inheritance to the Database

Mapping object inheritance effectively to a relational database is not one of those problems with a definitive solution. We’re only going to talk about the one mapping strategy that Rails supports natively, which is single-table inheritance, called STI for short.

In STI, you establish one table in the database to holds all of the records for any object in a given inheritance hierarchy. In ActiveRecord STI, that one table is named after the top parent class of the hierarchy. In the example we’ve been considering, that table would be named timesheets.

Hey, that’s what it was called before, right? Yes, but to enable STI we have to add a type column to contain a string representing the type of the stored object. The following migration would properly set up the database for our example:

class AddTypeToTimesheet < ActiveRecord::Migration
  def self.up
    add_column :timesheets, :type, :string
  end

  def self.down
    remove_column :timesheets, :type
  end
end

No default value is needed. Once the type column is added to an ActiveRecord model, Rails will automatically take care of keeping it populated with the right value. Using the console, we can see this behavior in action:

>> d = DraftTimesheet.create
>> d.type
=> 'DraftTimesheet'

When you try to find an object using the find methods of a base STI class, Rails will automatically instantiate objects using the appropriate subclass. This is especially useful in cases such as the timesheet example we’ve been describing, where we retrieve all the records for a particular user and then call methods that behave differently depending on the object’s class.

>> Timesheet.find(:first)
=> #<DraftTimesheet:0x2212354...>

STI Considerations

Although Rails makes it extremely simple to use single-table inheritance, there are a few caveats that you should keep in mind.

To begin with, you cannot have an attribute on two different subclasses with the same name but a different type. Since Rails uses one table to store all subclasses, these attributes with the same name occupy the same column in the table. Frankly, there’s not much of a reason why that should be a problem unless you’ve made some pretty bad data-modeling decisions.

More importantly, you need to have one column per attribute on any subclass and any attribute that is not shared by all the subclasses must accept nil values. In the recurring example, PaidTimesheet has a paid_hours column that is not used by any of the other subclasses. DraftTimesheet and SubmittedTimesheet will not use the paid_hours column and leave it as null in the database. In order to validate data for columns not shared by all subclasses, you must use ActiveRecord validations and not the database.

Third, it is not a good idea to have subclasses with too many unique attributes. If you do, you will have one database table with many null values in it. Normally, a tree of subclasses with a large number of unique attributes suggests that something is wrong with your application design and that you should refactor. If you have an STI table that is getting out of hand, it is time to reconsider your decision to use inheritance to solve your particular problem. Perhaps your base class is too abstract?

Finally, legacy database constraints may require a different name in the database for the type column. In this case, you can set the new column name using the class method set_inheritance_column in the base class. For the Timesheet example, we could do the following:

class Timesheet < ActiveRecord::Base
  set_inheritance_column 'object_type'
end

Now Rails will automatically populate the object_type column with the object’s type.

STI and Associations

It seems pretty common for applications, particularly data-management ones, to have models that are very similar in terms of their data payload, mostly varying in their behavior and associations to each other. If you used object-oriented languages prior to Rails, you’re probably already accustomed to breaking down problem domains into hierarchical structures.

Take for instance, a Rails application that deals with the population of states, counties, cities, and neighborhoods. All of these are places, which might lead you to define an STI class named Place as shown in Listing 9.2. I’ve also included the database schema for clarity:[6]

Example 9.2. The Places Database Schema and the Place Class

# == Schema Information
#
# Table name: places
#
#  id                :integer(11)   not null, primary key
#  region_id         :integer(11)
#  type              :string(255)
#  name              :string(255)
#  description       :string(255)
#  latitude          :decimal(20, 1)
#  longitude         :decimal(20, 1)
#  population        :integer(11)
#  created_at        :datetime
#  updated_at        :datetime

class Place < ActiveRecord::Base
end

Place is in essence an abstract class. It should not be instantiated, but there is no foolproof way to enforce that in Ruby. (No big deal, this isn’t Java!) Now let’s go ahead and define concrete subclasses of Place:

class State < Place
  has_many :counties, :foreign_key => 'region_id'
  has_many :cities, :through => :counties
end

class County < Place
  belongs_to :state, :foreign_key => 'region _id'
  has_many :cities, :foreign_key => 'region _id'
end

class City < Place
  belongs_to :county, :foreign_key => 'region _id'
end

You might be tempted to try adding a cities association to State, knowing that has_many :through works with both belongs_to and has_many target associations. It would make the State class look something like this:

class State < Place
  has_many :counties, :foreign_key => 'region_id'
  has_many :cities, :through => :counties
end

That would certainly be cool, if it worked. Unfortunately, in this particular case, since there’s only one underlying table that we’re querying, there simply isn’t a way to distinguish among the different kinds of objects in the query:

Mysql::Error: Not unique table/alias: 'places': SELECT places.* FROM
places INNER JOIN places ON places.region_id = places.id WHERE
((places.region_id = 187912) AND ((places.type = 'County'))) AND
((places.`type` = 'City' ))

What would we have to do to make it work? Well, the most realistic would be to use specific foreign keys, instead of trying to overload the meaning of region_id for all the subclasses. For starters, the places table would look like the example in Listing 9.3.

Example 9.3. The Places Database Schema, Revised

# == Schema Information
#
# Table name: places
#
#  id                :integer(11)   not null, primary key
#  state_id          :integer(11)
#  county_id         :integer(11)
#  type              :string(255)
#  name              :string(255)
#  description       :string(255)
#  latitude          :decimal(20, 1)
#  longitude         :decimal(20, 1)
#  population        :integer(11)
#  created_at        :datetime
#  updated_at        :datetime

The subclasses would be simpler without the :foreign_key options on the associations. Plus you could use a regular has_many relationship from State to City, instead of the more complicated has_many :through.

class State < Place
  has_many :counties
  has_many :cities
end

class County < Place
  belongs_to :state
  has_many :cities
end

class City < Place
  belongs_to :county
end

Of course, all those null columns in the places table won’t win you any friends with relational database purists. That’s nothing, though. Just a little bit later in this chapter we’ll take a second, more in-depth look at polymorphic has_many relationships, which will make the purists positively hate you.

Abstract Base Model Classes

In contrast to single-table inheritance, it is possible for ActiveRecord models to share common code via inheritance and still be persisted to different database tables. The technique involves creating an abstract base model class that persistent subclasses will extend. It’s actually one of the simpler techniques that we broach in this chapter.

Let’s take the Place class from the previous section (refer to Listing 9.3) and revise it to be an abstract base class in Listing 9.4. It’s simple really—we just have to add one line of code:

Example 9.4. The Abstract Place Class

class Place < ActiveRecord::Base
  self.abstract = true
end

As I said, quite simple. Marking an ActiveRecord model abstract is essentially the opposite of making it an STI class with a type column. You’re telling Rails: “Hey, I don’t want you to assume that there is a table named places.”

In our running example, it means we would have to establish tables for states, counties, and cities, which might be exactly what we want. Remember though, that we would no longer be able to query across subtypes with code like Place.find(:all).

Abstract classes is an area of Rails where there aren’t too many hard-and-fast rules to guide you—experience and gut feeling will help you out.

In case you haven’t noticed yet, both class and instance methods are shared down the inheritance hierarchy of ActiveRecord models. So are constants and other class members brought in through module inclusion. That means we can put all sorts of code inside Place that will be useful to its subclasses.

Polymorphic has_many Relationships

Rails gives you the ability to make one class belong_to more than one type of another class, as eloquently stated by blogger Mike Bayer:

The “polymorphic association,” on the other hand, while it bears some resemblance to the regular polymorphic union of a class hierarchy, is not really the same since you’re only dealing with a particular association to a single target class from any number of source classes, source classes which don’t have anything else to do with each other; i.e. they aren’t in any particular inheritance relationship and probably are all persisted in completely different tables. In this way, the polymorphic association has a lot less to do with object inheritance and a lot more to do with aspect-oriented programming (AOP); a particular concept needs to be applied to a divergent set of entities which otherwise are not directly related. Such a concept is referred to as a cross-cutting concern, such as, all the entities in your domain need to support a history log of all changes to a common logging table. In the AR example, an Order and a User object are illustrated to both require links to an Address object.[7]

In other words, this is not polymorphism in the typical object-oriented sense of the word; rather, it is something unique to Rails.

In the Case of Models with Comments

In our recurring Time and Expenses example, let’s assume that we want both BillableWeek and Timesheet to have many comments (a shared Comment class). A naive way to solve this problem might be to have the Comment class belong to both the BillableWeek and Timesheet classes and have billable_week_id and timesheet_id as columns in its database table.

class Comment < ActiveRecord::Base
  belongs_to :timesheet
  belongs_to :expense_report
end

That approach is naive because it would be difficult to work with and hard to extend. Among other things, you would need to add code to the application to ensure that a Comment never belonged to both a BillableWeek and a Timesheet at the same time. The code to figure out what a given comment is attached to would be cumbersome to write. Even worse, every time you want to be able to add comments to another type of class, you’d have to add another nullable foreign key column to the comments table.

Rails solves this problem in an elegant fashion, by allowing us to define what it terms polymorphic associations, which we covered when we described the :polymorphic => true option of the belongs_to association in Chapter 7, “ActiveRecord Associations.”

The Interface

Using a polymorphic association, we need define only a single belongs_to and add a pair of related columns to the underlying database table. From that moment on, any class in our system can have comments attached to it (which would make it commentable), without needing to alter the database schema or the Comment model itself.

class Comment < ActiveRecord::Base
  belongs_to :commentable, :polymorphic => true
end

There isn’t a Commentable class (or module) in our application. We named the association :commentable because it accurately describes the interface of objects that will be associated in this way. The name :commentable will turn up again on the other side of the association:

class Timesheet < ActiveRecord::Base
  has_many :comments, :as => :commentable
end

class BillableWeek < ActiveRecord::Base
  has_many :comments, :as => :commentable
end

Here we have the friendly has_many association using the :as option. The :as marks this association as polymorphic, and specifies which interface we are using on the other side of the association. While we’re on the subject, the other end of a polymorphic belongs_to can be either a has_many or a has_one and work identically.

The Database Columns

Here’s a migration that will create the comments table:

class CreateComments < ActiveRecord::Migration
  def self.up
    create_table :comments do |t|
      t.column :text,             :text
      t.column :commentable_id,   :integer
      t.column :commentable_type, :string
    end
  end
end

As you can see, there is a column called commentable_type, which stores the class name of associated object. We can see how this works using the Rails console:

>> c = Comment.create(:text => "I could be commenting anything.")
>> t = TimeSheet.create
>> b = BillableWeek.create
>> c.update_attribute(:commentable, t)
=> true
>> "#{c.commentable_type}: #{c.commentable_id}"
=> "Timesheet: 1"
>> c.update_attribute(:commentable, b)
=> true
>> "#{c.commentable_type}: #{c.commentable_id}"
=> "BillableWeek: 1"

As you can tell, both the Timesheet and the BillableWeek that we played with in the console had the same id (1). Thanks to the commentable_type attribute, stored as a string, Rails can figure out which is the related object.

Has_many :through and Polymorphics

There are some logical limitations that come into play with polymorphic associations. For instance, since it is impossible for Rails to know the tables necessary to join through a polymorphic association, the following hypothetical code will not work.

class Comment < ActiveRecord::Base
  belongs_to :user
  belongs_to :commentable, :polymorphic => true
end

class User < ActiveRecord::Base
  has_many :comments
  has_many :commentables, :through => :comments
end

>> User.find(:first).comments
ActiveRecord::HasManyThroughAssociationPolymorphicError: Cannot have
a has_many :through association 'User#commentables' on the polymorphic
object 'Comment#commentable'.

If you really need it, has_many :through is possible with polymorphic associations, but only by specifying exactly what type of polymorphic associations you want. To do so, you must use the :source_type option. In most cases, you will also need to use the :source option, since the association name will not match the interface name used for the polymorphic association:

class User < ActiveRecord::Base
  has_many :comments
  has_many :commented_timesheets, :through => :comments,
           :source => :commentable, :source_type => 'Timesheet'
  has_many :commented_billable_weeks, :through => :comments,
           :source => :commentable, :source_type => 'BillableWeek'
end

It’s verbose, and the whole thing is arguably starting to lose its elegance if you go this route, but it works:

>> User.find(:first).commented_timesheets
=> [#<Timesheet:0x575b98 @attributes={}> ]

Considerations about has_many

As we work toward the end of this book’s coverage of ActiveRecord, you might have noticed that we haven’t really touched on a subject of particular importance to many programmers: foreign-key constraints in the database. That’s mainly because use of foreign-key constraints simply isn’t the Rails way to tackle the problem of relational integrity. To put it mildly, that opinion is controversial and some developers have written off Rails (and its authors) for expressing it.

There really isn’t anything stopping you from adding foreign-key constraints to your database tables, although you’d do well to wait until after the bulk of development is done. The exception, of course, is those polymorphic associations, which are probably the most extreme manifestation of the Rails opinion against foreign-key constraints. Unless you’re armed for battle, you might not want to broach that particular subject with your DBA.

Modules for Reusing Common Behavior

In this section, we’ll talk about one strategy for breaking out functionality that is shared between disparate model classes. Instead of using inheritance, we’ll put the shared code into modules.

In the section “Polymorphic has_many Relationships,” we described how to add a commenting feature to our recurring sample Time and Expenses application. We’ll continue fleshing out that example, since it lends itself to factoring out into modules.

The requirements we’ll implement are as follows: Both users and approvers should be able to add their comments to a Timesheet or ExpenseReport. Also, since comments are indicators that a timesheet or expense report requires extra scrutiny or processing time, administrators of the application should be able to easily view a list of recent comments. Human nature being what it is, administrators occasionally gloss over the comments without actually reading them, so the requirements specify that a mechanism should be provided for marking comments as “OK” first by the approver, then by the administrator.

Again, here is the polymorphic has_many :as that we used as the foundation for this functionality:

class Timesheet < ActiveRecord::Base
  has_many :comments, :as => :commentable
end

class ExpenseReport < ActiveRecord::Base
  has_many :comments, :as => :commentable
end

class Comment < ActiveRecord::Base
  belongs_to :commentable, :polymorphic => true
end

Next we create a controller and action for the administrator that list the 10 most recent comments with links to the item to which they are attached.

class RecentCommentsController < ApplicationController
  def show
    @recent_comments = Comment.find( :all, :limit => 10,
        :order => 'created_at DESC' )
  end
end

Here’s some of the simple view template used to display the recent comments.

<ul>
  <% @recent_comments.each do |comment| %>
    <li>
      <h4><%= comment.created_at -%></h4>
      <%= comment.text %>
      <div class="meta">
        Comment on:
        <%= link_to comment.commentable.title,
          content_url( comment.commentable ) -%>
      </div>
    </li>
  <% end %>
</ul>

So far, so good. The polymorphic association makes it easy to access all types of comments in one listing. But remember each comment needs to be marked “OK” by the approver and/or administrator. Comments should not appear once they’ve been marked as reviewed.

We won’t go into the comment approval interface here. Suffice it to say that a Comment has a reviewed attribute that returns true after it has been marked “OK.”

In order to find all of the unreviewed comments for an item, we can use an association extension by modifying the model class definitions as follows:

class Timesheet < ActiveRecord::Base
  has_many :comments, :as => :commentable do
    def approved
      find(:all, :conditions => {:reviewed => false })
    end
  end
end

class ExpenseReport < ActiveRecord::Base
  has_many :comments, :as => :commentable do
    def approved
      find(:all, :conditions => {:reviewed => false })
    end
  end
end

I’m not happy with this code and I hope by now you know why. It’s not DRY! Both Timesheet and ExpenseReport currently have their own identical methods for finding unreviewed comments. Essentially, they both share a common interface. They’re commentable!

The way that we define common interfaces that share code in Ruby is to include a module in each of those classes, where the module contains the code common to all implementations of the common interface.

So let’s go ahead and define a Commentable module to do just that, and include it in our model classes:

module Commentable
  has_many :comments, :as => :commentable do
    def approved
      find( :all,
        :conditions => ['approved = ?', true ] )
    end
  end
end

class Timesheet < ActiveRecord::Base
  include Commentable
end

class ExpenseReport < ActiveRecord::Base
  include Commentable
end

Whoops, this code doesn’t work! To fix it, we need to understand an essential aspect of the way that Ruby interprets our code dealing with open classes.

A Review of Class Scope and Contexts

In many other interpreted, OO programming languages, you have two phases of execution—one in which the interpreter loads the class definitions and says “this is the definition of what I have to work with,” followed by the phase in which it executes the code. This makes it difficult (though not necessarily impossible) to add new methods to a class dynamically during execution.

In contrast, Ruby lets you add methods to a class at any time. In Ruby, when you type class MyClass, you’re doing more than simply telling the interpreter to define a class; you’re telling it to “execute the following code in the scope of this class.”

Let’s say you have the following Ruby script:

1  class Foo < ActiveRecord::Base
2    has_many :bars
3  end

4  class Foo
5    belongs_to :spam
6  end

When the interpreter gets to line 1, you are telling it to execute the following code (up to the matching end) in the context of the Foo class object. Because the Foo class object doesn’t exist yet, it goes ahead and creates the class. At line 2, we execute the statement has_many :bars in the context of the Foo class object. Whatever the has_many message does, it does right now.

When we again say class Foo at line 4, we are once again telling the interpreter to execute the following code in the context of the Foo class object, but this time, the interpreter already knows about class Foo; it doesn’t actually create another class. Therefore, on line 5, we are simply telling the interpreter to execute the belongs_to :spam statement in the context of that same Foo class object.

In order to execute the has_many and belongs_to statements, those methods need to exist in the context in which they are executed. Because these are defined as class methods in ActiveRecord::Base, and we have previously defined class Foo as extending ActiveRecord::Base, the code will execute without a problem.

However, when we defined our Commentable module like this:

module Commentable
  has_many :comments, :as => :commentable do
    def approved
      find( :all,
        :conditions => ['approved = ?', true ] )
    end
  end
end

...we get an error when it tries to execute the has_many statement. That’s because the has_many method is not defined in the context of the Commentable module object.

Given what we now know about how Ruby is interpreting the code, we now realize that what we really want is for that has_many statement to be executed in the context of the including class.

The included Callback

Luckily, Ruby’s Module class defines a handy callback that we can use to do just that. If a Module object defines the method included, it gets run whenever that module is included in another module or class. The argument passed to this method is the module/class object into which this module is being included.

We can define an included method on our Commentable module object so that it executes the has_many statement in the context of the including class (Timesheet, ExpenseReport, and so on):

module Commentable
  def self.included(base)
    base.class_eval do
      has_many :comments, :as => :commentable do
        def approved
          find(:all, :conditions => ['approved = ?', true ])
        end
      end
    end
  end
end

Now, when we include the Commentable module in our model classes, it will execute the has_many statement just as if we had typed it into each of those classes’ bodies.

Modifying ActiveRecord Classes at Runtime

The metaprogramming capabilities of Ruby, combined with the after_find callback, open the door to some interesting possibilities, especially if you’re willing to blur your perception of the difference between code and data. I’m talking about modifying the behavior of model classes on the fly, as they’re loaded into your application.

Listing 9.5 is a drastically simplified example of the technique, which assumes the presence of a config column on your model. During the after_find callback, we get a handle to the unique singleton class[8] of the model instance being loaded. Then we execute the contents of the config attribute belonging to this particular Account instance, using Ruby’s class_eval method. Since we’re doing this using the singleton class for this instance, rather than the global Account class, other account instances in the system are completely unaffected.

Example 9.5. Runtime Metaprogramming with after_find

class Account < ActiveRecord::Base
  ...
  private

    def after_find
      singleton = class << self; self; end
      singleton.class_eval(config)
    end
end

I used powerful techniques like this one in a supply-chain application that I wrote for a large industrial client. A lot is a generic term in the industry used to describe a shipment of product. Depending on the vendor and product involved, the attributes and business logic for a given lot vary quite a bit. Since the set of vendors and products being handled changed on a weekly (sometimes daily) basis, the system needed to be reconfigurable without requiring a production deployment.

Without getting into too much detail, the application allowed the maintenance programmers to easily customize the behavior of the system by manipulating Ruby code stored in the database, associated with whatever product the lot contained.

For example, one of the business rules associated with lots of butter being shipped for Acme Dairy Co. might dictate a strictly integral product code, exactly 10 digits in length. The code, stored in the database, associated with the product entry for Acme Dairy’s butter product would therefore contain the following two lines:

validates_numericality_of :product_code, :only_integer => true
validates_length_of       :product_code, :is => 10

Considerations

A relatively complete description of everything you can do with Ruby metaprogramming, and how to do it correctly, would fill its own book. For instance, you might realize that doing things like executing arbitrary Ruby code straight out of the database is inherently dangerous. That’s why I emphasize again that the examples shown here are very simplified. All I want to do is give you a taste of the possibilities.

If you do decide to begin leveraging these kinds of techniques in real-world applications, you’ll have to consider security and approval workflow and a host of other important concerns. Instead of allowing arbitrary Ruby code to be executed, you might feel compelled to limit it to a small subset related to the problem at hand. You might design a compact API, or even delve into authoring a domain-specific language (DSL), crafted specifically for expressing the business rules and behaviors that should be loaded dynamically. Proceeding down the rabbit hole, you might write custom parsers for your DSL that could execute it in different contexts —some for error detection and others for reporting. It’s one of those areas where the possibilities are quite limitless.

Ruby and Domain-Specific Languages

My former colleague Jay Fields and I pioneered the mix of Ruby metaprogramming, Rails, and internal[9] domain-specific languages while doing Rails application development for ThoughtWorks clients. I still occasionally speak at conferences and blog about writing DSLs in Ruby.

Jay has also continued writing and speaking about his evolution of Ruby DSL techniques, which he calls Business Natural Languages (or BNL for short[10]). When developing BNLs, you craft a domain-specific language that is not necessarily valid Ruby syntax, but is close enough to be transformed easily into Ruby and executed at runtime, as shown in Listing 9.6.

Example 9.6. Example of Business Natural Language

employee John Doe
compensate 500 dollars for each deal closed in the past 30 days
compensate 100 dollars for each active deal that closed more than
365 days ago
compensate 5 percent of gross profits if gross profits are greater
than
1,000,000 dollars
compensate 3 percent of gross profits if gross profits are greater
than
2,000,000 dollars
compensate 1 percent of gross profits if gross profits are greater
than
3,000,000 dollars

The ability to leverage advanced techniques such as DSLs is yet another powerful tool in the hands of experienced Rails developers.

Conclusion

With this chapter we conclude our coverage of ActiveRecord, one of the most significant and powerful Rails frameworks. We examined how callbacks and observers let us factor our code in a clean and object-oriented fashion. We also expanded our modeling options by considering single-table inheritance and ActiveRecord’s distinctive polymorphic relationships.

At this point in the book, we’ve covered two parts of the MVC pattern: the model and the controller. It’s now time to delve into the third and final part: the view.

References

1.

If you are browsing old Rails source code, you might come across callback macros receiving a short string of Ruby code to be eval’d in the binding of the model object. That way of adding callbacks was deprecated in Rails 1.2, because you’re always better off using a block in those situations.

2.

I recommend the excellent GeoKit for Rails plugin available at http://geokit.rubyforge.org/.

3.

Real-life implementation of the example would also need to modify all finders to include deleted_at is NULL conditions; otherwise, the records marked deleted would continue to show up in the application. That’s not a trivial undertaking, and luckily you don’t need to do it yourself. There’s a Rails plugin named ActsAsParanoid by Rick Olson that does exactly that, and you can find it at http://svn.techno-weenie.net/projects/plugins/acts_as_paranoid.

4.

Get AttachmentFu at http://svn.techno-weenie.net/projects/plugins/attachment_fu.

5.

http://en.wikipedia.org/wiki/Open/closed_principle has a good summary.

6.

For autogenerated schema information added to the top of your model classes, try Dave Thomas’s annotate_models plugin at http://svn.pragprog.com/Public/plugins/annotate_models.

7.

http://techspot.zzzeek.org/?p=13

8.

I don’t expect this to make sense to you, unless you are familiar with Ruby’s singleton classes, and the ability to evaluate arbitrary strings of Ruby code at runtime. A good place to start is http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html.

9.

The qualifier internal is used to differentiate a domain-specific language hosted entirely inside of a general-purpose language, such as Ruby, from one that is completely custom and requires its own parser implementation.

10.

Googling BNL will give you tons of links to the Toronto-based band Barenaked Ladies, so you’re better off going directly to the source at http://bnl.jayfields.com.

 

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.77.153