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 :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!
Shave some keystrokes:
def self.included(klass)
klass.class_eval do
validates_blah
has_whatever
attr_accessor :fooferall
end
end