Active Record tips and tricks
Published over 5 years ago

Just a small collection of tips/tricks which I use a lot ( or try to ), that others might find helpful.

concerned_with

In most of the Rails applications that I work with, the primary model ( User model for example ) ends up being at least 1000 lines long. Thanks to Rick’s quick/awesome solution, we can easily split a model into different “concerns”.

RAILS_ROOT/config/initializers/concerns.rb

class << ActiveRecord::Base
  def concerned_with(*concerns)
    concerns.each do |concern|
      require_dependency "#{name.underscore}/#{concern}"
    end
  end
end

class << ActiveRecord::Base
def concerned_with(*concerns)
concerns.each do |concern|
require_dependency "#{name.underscore}/#{concern}"
end
end
end

Using concerned_with, lets split the User model into 2 different concerns and 3 different files :

  • app/models/user.rb – Main model
  • app/models/user/validations.rb – User validations concern
  • app/models/user/authentication.rb – User authentication concern

RAILS_ROOT/app/models/user.rb

class User < ActiveRecord::Base
  concerned_with :validations, :authentication
end

class User < ActiveRecord::Base
concerned_with :validations, :authentication
end

RAILS_ROOT/app/models/user/validations.rb

class User < ActiveRecord::Base
  validates_presence_of :name
end

class User < ActiveRecord::Base
validates_presence_of :name
end

RAILS_ROOT/app/models/user/authentication.rb

class User < ActiveRecord::Base
  def self.authenticate(name, password)
    find_by_name_and_password(name, password)
  end
end

class User < ActiveRecord::Base
def self.authenticate(name, password)
find_by_name_and_password(name, password)
end
end

_Pay close attention to the directory structure and how concerns just open the existing class definition, make sure you don’t re-inherit the class from AR::Base inside concerns UPDATE : See comment by Clifford Heath

log_to

I’ve always used "_log_to_ ":http://weblog.jamisbuck.org/2007/1/31/more-on-watching-activerecord for monitoring query output if irb ( script/console ). But with the recent connection pool changes, it stopped working. So here’s a new version :

def log_to(stream=$stdout)
  ActiveRecord::Base.logger = Logger.new(stream)
  ActiveRecord::Base.connection_pool.clear_reloadable_connections!
end

So.Many.Joins

As you might already know, you could use association names while constructing a join query with ActiveRecord::Base.find. For example :

class User < ActiveRecord::Base
  has_many :items
end

Now, if you want to find all the users who have a black item, you could query like :

User.all :joins => :items, :conditions => { :"items.color" => 'black' }

Now the black magic part. Not many people know that you can supply the same join key more than once too. So :

User.all :joins => [:items, :items]

will produce a query like :

SELECT `users`.* FROM `users` INNER JOIN `items` ON items.user_id = users.id INNER JOIN `items` items_users ON items_users.user_id = users.id 

This is very useful when you need to make complex sql queries. For example, if you want find all the users who have at least one “black” AND at least one “red” item :

User.all :joins => [:items, :items], :conditions => {:"items.color" => "red", :"items_users.color" => 'black'}

Of course, you’d want to be a little careful with joining too many tables if your tables are very large or if/when performance becomes a problem, etc. YMMV.

Force save

If you want to save an object even if the validations fail ( like, if your boss forces you to ) :

object.save(false)

Find By Bang!

This is new in edge. You can now use dynamic finders with a bang(!). If there is no result found, RecordNotFound exception will be raised :

User.find_by_name!('lifo')