Sharing Code Between ActiveRecord Models

Posted by Trevor in Ruby/Rails on January 29, 2008

I was frustrated with the amount of duplicated code sprinkled in my models that are using the attachment_fu plugin to deal with file uploads. It's not like I need to write a plugin or anything fancy - I just want to be a little more DRY.

So, how does one simply share code between ActiveRecord models? It's actually quite easy.

First, drop some code into a module in a .rb file in your lib directory. For example:

 
# lib/attachment_fu_extensions.rb
 
module AttachmentFuExtensions
 
  # include some standard attachment_fu stuff
  def self.included(klass)
    klass.send :validates_as_attachment
    klass.send :validates_uniqueness_of, :filename
    klass.send :validates_presence_of, :user_id
    klass.send :attr_protected, :id, :parent_id, :user_id, :created_at, :updated_at
  end
 
  # upload into a single directory (e.g. public/uploads) instead of subdirectories based on the id
  def full_filename(thumbnail = nil)
    file_system_path = (thumbnail ? thumbnail_class : self).attachment_options[:path_prefix].to_s
    File.join(RAILS_ROOT, file_system_path, thumbnail_name_for(thumbnail))
  end
 
  # prevent users from uploading index files that would be served instead of the index template/action
  def validate
    errors.add("filename", "is invalid") if filename? && %w(index.html index.htm).include?(filename.downcase)
  end
 
end
 

That code will automatically be loaded when your application starts, provided that the filename and module follow the standard Rails naming conventions.

Then, simply include the relevant module in the relevant models. For example:

 
# app/models/upload.rb
 
class Upload < ActiveRecord::Base
 
  has_attachment :storage => :file_system, :path_prefix => 'public/uploads', :max_size => 10.megabytes
  include AttachmentFuExtensions
 
  # ...
 
end
 

That's it. Extending ActiveRecord models and DRYing out your app is pretty easy, eh?

Update: Walter pointed out that I should put modules like this into lib/ instead of config/initializers/, so I've updated this post to reflect that. I'm not sure why that didn't occur to me. He also told me about the trick for sharing calls to validates_as_attachment and so forth - I didn't even know that was possible!

3 Comments

[...] almost effortless » Sharing Code Between ActiveRecord Models (tags: rubyonrails activerecord) [...]

 josh

Shave some keystrokes:

def self.included(klass)
klass.class_eval do
validates_blah
has_whatever
attr_accessor :fooferall
end
end

[...] you’d like to use this for multiple models, consider refactoring it into a module that you can include [...]

Leave a comment

WP_Big_City