Skip to main content

Mobomo webinars-now on demand! | learn more.

Rails ActiveRecord models have a lifecycle that developers are allowed to hook into. But while most of us know about before_save and after_update, there are a few lesser unknown callbacks that are good to know about before you reinvent them. In this post, I'll cover all of the available ActiveRecord lifecycle callbacks, and also show how you can define custom callbacks for normal ruby objects.

Meet the Callbacks

The Rails guide for ActiveRecord Validations and Callbacks is a good starting point for an introduction of the available callbacks and what they do. Most developers will be familiar with the validation and persistence callbacks, so let's start with these

:before_validation, :after_validation :before_save, :after_save :before_create, :after_create :before_update, :after_update :before_destroy, :after_destroy 

The callbacks above are self explanatory and commonly used, but if you're unfamiliar with them, or need a refresher, check out the Rails guide on the topic.

Around Callbacks

For save, create, update, and destroy, Rails also gives extra helper methods for defining both a before and after save callback at the same time.

For example, suppose you wanted to trigger your own custom callback while a model was being destroyed. You can do so by defining and triggering your own callback as follows:

class SomeModel < ActiveRecord::Base   define_callbacks :custom_callback    around_destroy :around_callback    def around_callback     run_callbacks :custom_callback do       yield  # runs the actual destroy here     end   end end 

Custom Callbacks without ActiveRecord

Most of the time, your Rails models will be using ActiveModel, but sometimes it makes sense to use a plain old ruby object. Wouldn't it be nice if we could define callbacks in the same way? Fortunately, the callback system is neatly abstracted into ActiveSupport::Callbacks so it's easy to mix into any ruby class.

# Look Ma, I'm just a normal ruby class! class Group   include ActiveSupport::Callbacks   define_callbacks :user_added    def initialize(opts = {})     @users = []   end    # Whenever we add a new user to our array, we wrap the code   # with `run_callbacks`. This will run any defined callbacks   # in order.   def add_user(u)     run_callbacks :user_added do       @users << u     end   end end 

For a fully documented and runnable example, check out this github project. It'll also give some extra explanation about call order and inheritance.

Other Useful Callbacks

  • :after_initialize is called right after an object has been unmarshalled from the database. This allows you to do any other custom initialization you want. Instead of defining an initialize method on a model, use this instead.
  • :after_find hasn't been useful in my experience. I haven't run into a case where I wanted to manipulate documents after a find action. It could potentially be useful for metrics and profiling.
  • :after_touch. ActiveRecord allows you to touch a record or its association to refresh its updated_at attribute. I've found this callback useful to triggering notifications to users after a model has been marked as updated, but not actually changed.
  • :after_commit is an interesting and tricky callback. Whenever ActiveRecord wants to make a change to a record (create, update, destroy), it wraps it around a transaction. after_commit is called after you're positive that something has been written out to the database. Because it is also called for destroys, it makes sense to scope the callback if you intend to use it only for saves. Be warned that after_commit can be tricky to use if you're using nested transactions. That'll probably be the topic of another post though.
# call for creates, updates, and deletes after_commit :all_callback  # call for creates and updates after_commit :my_callback, :if => :persisted? 
  • :after_rollback is the complement to after_commit. I haven't used it yet, but I can see it as being useful for doing manual cleanup after a failed transaction.

Go Forth and Callback!

While many of our models will be backed with ActiveRecord, or some ActiveModel compatitible datastore, it's nice to see how easy it is to follow a similar pattern in normal ruby without having to depend on Rails.

Categories
Author

I use the Chrome history tab when I forget about something I've looked up in the past. I initially thought that the data would be stored in a CSV or XML file and thought I could do some string munging for kicks and giggles. To my delight, when I looked in the "Application Support" directory for Chrome, I found several data-rich sqlite databases ready for mining. With a few Ruby tricks, I found some cool data. All the code this article covers is available on the chrome_spy project.

With chrome_spy, you can answer some of these queries:

  • what have I searched for recently?
  • which sites do I visit most frequently?
  • what urls do I type into the address bar frequently?
  • what have I downloaded and how big are the files?

And that's only the surface. Let's dive straight into the technical details.

Since the data is stored in sqlite, I decided to use trusty old ActiveRecord to wrap around the tables:

     require 'sqlite3'     require 'active_record'      ActiveRecord::Base.establish_connection({       :adapter => "sqlite3",       :database => File.expand_path("~/Library/Application Support/Google/Chrome/Default/History")     }) 

I've been using Chrome for a while now, so there were multiple archived 'History' databases (e.g. History Index 2010-07), but the most recent database is named 'History'. Rather than inspecting the schema on each of the tables, ActiveRecord has a module SchemaDumper that generates a very readable schema dump:

     puts ActiveRecord::SchemaDumper.dump 

From here, it's pretty straightforward to map the tables to ActiveRecord models. For each 'create_table' declaration, I declared a new model. For example, to define a model for urls:

     class Url < ActiveRecord::Base     end 

At this point, you should be able to query for Urls through the models:

     > Url.first      => #      > Url.where(:title => "Google").count      => 21      > Url.first.last_visit_time      => 12940646404770210  

Everything was looking peachy up until the last_visit_time. At first I thought it was an epoch timestamp, or a JS timestamp. After looking at a few other timestamps, I noticed that it's 17 digits long rather than the usual 10 digits. The frustrating part was that some fields used epoch timestamps, but other fields would use this 17-digit timestamp. I wrote a little helper module to clean up the typecast from these columns:

     module TimestampAccessors       def timestamp_accessors(*attributes)         attributes.each do |attr|           name = attr.to_s            # Some timestamps have 17 digits           # Since 10000000000 is year 2286, so I'm assuming that no dates are longer           # than 10 digits           define_method(name) {             raw = read_attribute(name).to_s.slice(0, 10)             Time.at(raw.to_i)           }            define_method(name+'=') { |t|             write_attribute(name, t.to_i)           }         end       end     end     ActiveRecord::Base.extend(TimestampAccessors) 

Then for every table that has timestamp columns, I can declare them in the model. For example:

     class Url < ActiveRecord::Base       timestamp_accessors :last_visit_time     end 

Let's retry that same query from before:

     > Url.first.last_visit_time      => Mon Jan 03 06:24:00 -0800 2011 

That's much better.

Some table names and column names don't follow Rails conventions, so there's a little extra work to specify associations and some tables. For example, the SegmentUsage model is backed by the 'segment_usage' table rather than 'segment_usages':

     class SegmentUsage < ActiveRecord::Base       set_table_name "segment_usage"     end 

Another example is when Visit uses 'url' as a foreign key to Url rather than 'url_id':

     class Visit  'url'     end 

With just these thin wrappers, we can easily come up with queries to answer the questions at the beginning of this article. To find the most recent searches, we can do:

     > ChromeSpy.recent_searches      => ["intridea", "thegist", "dimsum icons", ...] 

The definition for this method is just a simple ActiveRecord query:

     def recent_searches       KeywordSearchTerm.includes('url').order('urls.last_visit_time desc').map(&:term)     end 

At this point, you should take a break and share your search history with a friend. Context-free search terms can be hilarious and embarrassing. A few my girlfriend could not stop laughing at:

  • super meat boy
  • exercise mix tapes
  • huntsville bed intruder
  • factory girl
  • haml (she thought I repeatedly misspelled 'ham')

To answer the other questions at the beginning of the article:

Which sites do I visit most frequently?

   ChromeSpy.most_frequent_sites 

What urls do I type into the address bar frequently?

   ChromeSpy.most_frequently_typed_addresses 

What have I downloaded and how big are the files?

   ChromeSpy.recent_downloads 

While ChromeSpy may not be the most useful example, it shows how ActiveRecord can be applied outside of Rails. A similar thought process can be reused for other problems where quick data manipulations or reporting is needed. Whether those reports are useful, or just plain silly is entirely up to you. Now go forth and find some funny recent searches you've done!

Categories
Author

This is something that many may already use as a best practice, but if not it’s something simple and convenient to add to your repertoire. Sometimes you may have a model that requires additional information if a certain condition is met. For example, I may require a user to add more information about themselves if they wish to be listed publicly, whereas I would not if they do not wish to be listed. By combining ActiveSupport’s Object#with_options and ActiveRecord’s conditional validations, we can implement this behavior in a straightforward and readable manner (assuming here that there is a boolean field called “listed” in the database that is exposed as a checkbox or similar to the user):

class User < ActiveRecord::Base   # Our standard validations   validates_presence_of :login   validates_uniqueness_of :login    # Validations for listed users   with_options :if => :listed? do |l|     l.validates_presence_of :email     l.validates_length_of :description, :minimum => 100   end end

It’s a simple technique that piggybacks off of Rails’s automatic construction of existence query methods (in this case, listed?) for fields in the database combined with the mapping power of with_options and standard conditional validations.

Categories
Author

ActiveRecord callbacks can be super-handy, but every once in a while, they get in the way.

I was recently working on a client project and I had to create a rake task to import a large set of data from a spreadsheet.  One of the models that was being imported had an after_save callback that sent out an email notification.  I didn't really want 3500 emails to be sent out whenever this rake task was ran, so I needed to disable the callback while the import task was running.

Fortunately, this is easy to do.

Say you've got a model like so...

class Thing < ActiveRecord::Base    before_save :do_stuff      def do_stuff      raise "This thing is doing stuff..."    end  end  

In the config/initializers directory, I created a file called extensions.rb.  You can call it whatever you like.  I chose 'extensions' because I use the same file for any minor Ruby or Rails class extensions.  In it, I put this...

class ActiveRecord::Base    def self.without_callback(callback, &block)      method = self.send(:instance_method, callback)      self.send(:remove_method, callback)      self.send(:define_method, callback) {true}      yield      self.send(:remove_method, callback)      self.send(:define_method, callback, method)    end  end   

What this does is grab the callback and store it in the method variable.  Remember, this code only circumvents the 'do_stuff' method, not anything declared as a before_save callback.  It then redefines the method to merely return true, yields to the block, and then redifines the the method with the contents of the method variable. 

Yielding to the block allows you to not have to worry about maintaining the method contents, or restoring it once you're done.  Also, if you were so inclined, you could easily modify this code to accept an array of method names and disable all of them

Once the extension is in place, you can do this...

Thing.without_callback(:do_stuff) do    thing = Thing.new    thing.save  end

...without 'do_stuff' ever being called.

Some people will probably argue that if you need to disable your callback, then your model should be defined differently, but I disagree.  I think this is perfectly acceptable in many cases.  Granted you probably wouldn't want to do this all throughout your application code, but I see no problem with using this technique in a test, or data migration, or in my case, a rake task.

Categories
Author

Rails’s named_scopes are great, and the ability to chain named_scopes together are even better. I am sure many of us have done something like this before:

   User.active.live_in('Virginia') 

That would give you the active users who live in the state of Virginia. You would define something like these in your User model:

   named_scope :active, lambda { |status| { :conditions => { :status => status } } }   named_scope :live_in lambda { |state| { :conditions => { :state => state } } } 

One recent client project has a model where the database table has 30-something columns. Doing these named_scopes became tedious and repetitive. That makes it not fun anymore.

So Dave Pranata and I wrote the searchable_attributes Rails plugin so we could stop worrying about explicitly defining these named_scopes once and for all.

To use it, you just install it like any other Rails plugin:

 script/plugin install git://github.com/rayvinly/searchable_attributes.git 

The plugin will automatically define a bunch of named_scopes for you if you include this in your model:

 class User < ActiveRecord::Base   searchable_attributes end 

You can then search on your models with something like this:

   def index     @users = User.with_first_name_equal('Gilbert').with_last_name_equal_if_not_null('Arenas').with_address_like('Main Street').with_age_greater_than(25).with_state_equal(['VA', 'MD', 'DC']).with_position_end_with('Guard').with_salary_within_inclusive((100..10000)).with_injured_equal(true).with_join_date_greater_than_or_equal_to(5.years.ago)   end 

Take a look in the plugin’s lib/searchable_attributes.rb for a list of automatically defined named_scopes.

Categories
Author

The addition of RESTful routing and the #to_param method in Rails has undoubtedly improved both the ease and usefulness of URL generation in Rails apps. However, there has come to be a rather insidious tendency to have an auto-generated id as the true reference for a given item. This may make sense for some or even many apps, but by and large records should be referred to by their content, not their ids, in URLs if at all possible. With this goal in mind, I have released from_param, a simple addition to ActiveRecord that makes it dead simple to use better URL finders.

Example

First of all, from_param is meant to simply be the complement of to_param, that is, you should be able to pass Model.from_param(some_parameter) the same way you would pass Model.from_xml(some_xml). Let’s examine how this works in practice:

class User < ActiveRecord::Base   def to_param     "#{id}-#{login.downcase}"   end end  class UsersController < ApplicationController   # GET /users/1-mbleigh   def show     @user = User.from_param(params[:id]) # => <User id=1 login="mbleigh">   end end

Simple enough, and it should look very familiar except that instead of a User.find or User.find_by_id, we have User.from_param. In fact, that’s exactly what this code does: from_param by default will call find_by_id since that is how the default to_param is configured.

Now let’s take something a little more complicated: a blog post title. I definitely don’t want an id in the URL if it’s a permalink, so how can I make an arbitrary parameter found as easily as the id? Simple, just add a param column to your table! Take a look:

class Post < ActiveRecord::Base   def to_param     "#{created_at.strftime("%Y-%m-%d")}-#{title.gsub(" ","-").downcase.gsub(/[^a-z0-9-]/,"")}"   end end  class PostsController < ApplicationController   # GET /posts/2008-04-26-from-param-plugin-released   @post = Post.from_param(params[:id]) # => <Post title="From Param: Plugin Released" created_at="2008-04-26"> end

From Param will auto-magically save the to_param of your model to the specified parameter column (defaults to “param” but you can set it by calling set_param_column) and then automatically know to find by that column if it exists when from_param is called. This way, all you have to do is define a to_param that will be unique to your record and everything else is handled for you!

This plugin is really quite simple, but it establishes a convention that I feel has been missing from Rails for some time: a standard method to call to retrieve a record based on its URL parameter.

Installation

To install the plugin on Edge Rails or Rails 2.1 and greater:

script/plugin install git://github.com/mbleigh/from_param.git

On earlier versions of Rails:

git clone git://github.com/mbleigh/from_param.git vendor/plugins/from_param

Resources

If you have any suggestions for expanding the usefulness of the plugin or run into any problems, please report them on the From Param Lighthouse Project.

Categories
Author

Often times in an application you might want to know how many of a given model were created today. Rather than writing a custom method for each model, let’s keep it DRY and extend ActiveRecord:

class ActiveRecord::Base   def self.new_today     if self.new.respond_to?(:created_at)       self.count(:all, :conditions => "created_at > (NOW() - 60*60*24)")     else       nil     end   end end

This will return the number of records created in the last 24 hours by calling the model with new_today (e.×. User.new_today). It will return nil if the model does not respond to the created_at Rails timestamp attribute.

Categories
Author

I’ve been working a lot with seed data in my applications lately, and it’s obviously a problem that can be pretty aggravating to deal with. I ultimately liked the db-populate approach the best with the addition of the ActiveRecord::Base.create_or_update method. My one problem with it is that it’s based entirely on fixed ids for the fixtures, which is a pain to deal with when loading up your attributes from arrays. Here’s an example of what I was doing:

i = 1  { "admin"     => ["Administrator", 1000],    "member"    => ["Member", 1],    "moderator" => ["Moderator", 100],   "disabled"  => ["Disabled User", -1] }.each_pair do |key, val|   Role.create_or_update(:id => i, :key => key, :name => val[0], :value => val[1])   i += 1 end

I really don’t like having to put the i in there to increment up. Not only is it messier code, but it would be dangerous if I wanted to move around the roles. I also don’t want to have to have an id explicitly in each entry since I really don’t care what the id is. So I thought I would hack up a better solution for this create_or_update scenario and this is what I came up with:

class << ActiveRecord::Base   def create_or_update(options = {})     self.create_or_update_by(:id, options)   end      def create_or_update_by(field, options = {})     find_value = options.delete(field)     record = find(:first, :conditions => {field => find_value}) || self.new     record.send field.to_s + "=", find_value     record.attributes = options     record.save!     record   end      def method_missing_with_create_or_update(method_name, *args)     if match = method_name.to_s.match(/create_or_update_by_([a-z0-9_]+)/)       field = match[1].to_sym       create_or_update_by(field,*args)     else       method_missing_without_create_or_update(method_name, *args)     end   end      alias_method_chain :method_missing, :create_or_update end

Basically, this allows me to call create_or_update with an arbitrary attribute as my “finder” by calling Model.create_or_update_by(:field, ...). To give it a little taste of syntactic sugar, I threw in a method missing to allow you to name a field in the method call itself. So now the code I wrote before can become this:

{ "admin"     => ["Administrator", 1000],    "member"    => ["Member", 1],    "moderator" => ["Moderator", 100],   "disabled"  => ["Disabled User", -1] }.each_pair do |key, val|   Role.create_or_update_by_key(:key => key, :name => val[0], :value => val[1]) end

This is much cleaner and prettier to look at, and also makes sure that it is keying off of the value that I really care about. This new create_or_update combined with db-populate creates the most powerful seed data solution I’ve yet come across.

Categories
Author
1
Subscribe to Activerecord