Easy, named attachments with attachment_fu
I always seem to have models that have many attachments, but some of them are special. Like on this site I have a Site class that has a tout image, a before image, and an after image (for redesigns).
I wanted one polymorphic Attachment model to handle all of it.
I now use this module to make it easy on myself. It's a spin off the Advanced Rails Recipes file upload.
In the controller the @site.save becomes @site.save_with_attachments and @site.update becomes @site.update_with_attachments
In my model I add
1 2 3 4 |
class Site < ActiveRecord::Base include AttachmentUpload attachment_names :tout_img, :before_img, :after_img end |
In my sites/edit.html.erb I add
1 2 3 4 5 6 7 8 9 |
<%= image_tag @site.attachments.tout_img.public_filename(:thumb) if @site.attachments.tout_img %> <p> <b>Tout Pic</b><br /> <%= f.file_field :uploaded_data_tout_img, :class => :text %> </p> <p> <%= label :site, :remove_tout, "Remove Photo", :class => "left" %> <%= f.check_box :remove_tout_img %> </p> |
(For new.html.erb remove everything but the file_field.)
And then lib/attachment_upload.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
module AttachmentUpload # Add the attachment_names to the including model def self.included(base) #:nodoc: base.extend AttachmentUpload @@class = base end # List the different named attacments # add the attr_accessor for each one # add the named association through the has_many :attachments def attachment_names(*names) @@attachment_names = names has_many :attachments, :as => :attachee, :dependent => :destroy do names.each do |name| class_eval <<-EOS #{@@class}.send(:attr_accessor, :uploaded_data_#{name}, :remove_#{name}) def #{name}(reload=false) @#{name} = nil if reload @#{name} ||= find(:first, :conditions => ["attachment_type = ?",'#{name}']) end EOS end end end # for create def save_with_attachments begin @@attachment_names.each { |attachment| do_attachment(attachment) } save! rescue add_errors(@attachment) end end # for update def update_with_attachments(params) begin self.transaction do update_attributes(params) @@attachment_names.each { |attachment| do_attachment(attachment) } save! end rescue add_errors(@attachment) end end # popuplates the Attachment.new def do_attachment(attachment_name) @attachment = Attachment.new if eval("remove_#{attachment_name} == '1' && self.attachments.#{attachment_name}") eval("self.attachments.#{attachment_name}.destroy") elsif eval("uploaded_data_#{attachment_name} && uploaded_data_#{attachment_name}.size > 0") eval("self.attachments.#{attachment_name}.destroy if self.attachments.#{attachment_name}") @attachment.uploaded_data = eval("uploaded_data_#{attachment_name}") @attachment.attachment_type = attachment_name.to_s @attachment.thumbnails.clear @attachment.save! self.attachments << @attachment end @attachment end # Assigns Attachment errors to base def add_errors(attachment) if attachment.errors.on(:size) errors.add_to_base("#{attachment.filename} file (#{attachment.size}) is too big (1MB max).") end if attachment.errors.on(:content_type) errors.add_to_base("#{attachment.filename} content-type (#{attachment.content_type}) is not valid.") end false end end |
db/migrations/001createattachments.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
create_table :attachments do |t| t.integer :position t.string :attachee_type t.integer :attachee_id t.string :attachment_type t.integer :size # file size in bytes t.string :content_type # mime type, ex: application/mp3 t.string :filename # sanitized filename t.integer :height # in pixels t.integer :width # in pixels t.integer :parent_id # id of parent image (on the same table, a self-referencing foreign-key). t.string :thumbnail # the 'type' of thumbnail this at t.timestamps end |
app/models/attachment.rb
1 2 3 4 5 6 7 8 9 10 |
class Attachment < ActiveRecord::Base belongs_to :attachee, :polymorphic => true has_attachment :content_type => ['application/pdf', :image], :max_size => 3.megabyte, :storage => :file_system, :path_prefix => 'public/attachments', :resize_to => '800x800>', :thumbnails => {:tout => '580>', :thumb => 'x150' } validates_as_attachment end |
I would love some fedback but I haven't had a chance to add comments yet...