I am working on a small model mixin called attachment_kung to make
attachment_fu polymorphic, so you no longer need a different table and
Model class for every associated attachment (Productimage, Ad_doc,
etc). All you really need is one model and table to handel all your
attachments - in some cases, anyway. I have the code working, but have
run into one small hitch that I can’t seem to ferret out: after the
attachment is saved to the db (I am using mySQL 5) and I want to
display a list of the attachments, the order they are returned in is
strange. I say ‘strange’ because I, at least, cannot descern what they
are ORDERed BY. They seem to come back grouped (an image an its
thumbnails will come back adjecent to one another), but the order of
the groups (or even the elements within the groups) is unpredictable.
Any help would be appriciated, my code is below.
Thanks in advance,
Paul Saieg
module Attachment_kung
Attachment_kung: Polymorphic Support For Attachment_fu
Version 0.1
---------------------------------------------------------------
– Kung, a potent concentration of effort –
---------------------------------------------------------------
Originally written by Paul Saieg, 14 September 2007
email me with comments at classicist at gmail dot com
Licensed under a Creative Commons Attribution-ShareAlike 2.5
License
Creative Commons — Attribution-ShareAlike 2.5 Generic — CC BY-SA 2.5
################################### DESCRIPTION
###############################################
Attachment_kung supports a polymorphic use of attachment_fu, so
you no longer need a different table
and Model class for every associated attachment (Productimage,
Ad_doc, etc.). Instead it relies
on a single Model class called ‘Attachment’ and a table called
‘attachments’ (or whatever you want
to call them). It dynamically creates a class called
#{CallingClass}_attachment and then returns an
instance of that class with its polymorphic attributes
(whateverable_type/id) set to match its parent
object. It is called by an object, not a class, because a
polymorphic attachment
must necessarily belong to a particular Model object. DRY up your
life.
################################### USE
#######################################################
To setup Attachment_kung,
1. Create the Attachment_fu model and table (I used Attachment
and attachments),
set them up for polymorphism. Add to the table a column called
path_prefix.
(I used: belongs_to :attachable, :polymorphic => true for the
model and attachable_type and
attachable_id for the table); and configure the Attachment_fu
model with
has_attachment :storage => :file_system
include Attachment_kung
Make sure Attachment_kung is last, or you will not be able
to display your attachments. Also,
I have not tested Attachment_kung with :storage => :s3
or :storage => :db_system. I ought to
work, but one never knows until one tries. Sadly, at present,
Attachment_kung has the limitation
that it can only handle one storage system at a time. Maybe
in a further iteration it will let
one use both the file_system and s3 at the same time. Maybe.
2. Mix in Attachment_kung to whatever Model(s) you want to have
the attachments and configure them as
you normally would for attachment_fu,except use
has_polymorphic_attachment instead of
has_attachment and validates_as_polymorphic_attachment instead
of validates_as_attachment.
Write the configuration params just as you normally would. For
more info on
setting up attachemnt_fu see Mike C.'s excellent tutorial:
http://clarkware.com/cgi/blosxom/2007/02/24
3. Give your Attachment_kung-ed Model the correct has_many for
polymorphism
(I used: has_many :attachments, :as => :attachable);
4. Set the belongs_to and table_name methods to inside
Attachment_kung’s @class_def variable to
match what you’ve done so far (the default is Attachment and
attachments). Also set
the attachables hash in Attachment_kung’s intialize to match
the names of your model’s
polymorphic attrubutes (the defaults are :attachable_id
and :attachable_type). Finally, set the
‘file_system’ case default in in intialize statement to
‘public/#{table_name}’ as per your previous
choice (again, the default is ‘public/attachments’)
5. Install the attachment_fu plugin and double check to make sure
things are set up right.
To use Attachment_kung,
1. In your controller, get a Model with Attachment_kung mixed in
from the database
(@p = Product.find(1), we’ll say)
2. Call the new_attachment method and pass it the params with the
uploaded_data. The line
should look something like this: @attachment =
@p.new_attachment(params[:attachment])
3. Attachments, before they are saved, have a class name of
#{CallingClass}_attachment
(ie if a Product calls new_attachment then the returned
object’s class will be Product_attachment).
Attachments, after they are saved, can be called either with
the Attachment class (or whatever you
created as your Attachment_fu model) or by the usual
polymorphic means. All of the usual methods
are available from Attachment_fu (like public_filename etc.)
For a more on polymorphic Models see Chad F.'s Rails
Recipes.
################################### FAIR WARNING
##############################################
The new_attachment method makes use of EVAL. In case you don’t
know, eval runs whatever string
it gets as ruby code. If you pass eval “rm *” it will ruin
your day. Be very careful about
how you use it, and NEVER EVER open it to your users. Also,
Attachment_kung has not yet been
tested for use with Attachment_fu’s database or s3 strorage.
This software comes with no warranty,
expressed or implied.
#############################################################################################
@@attachment_config = []
@@validates_as_attachment = nil
adds methods to the class that is mixing the module in
def self.append_features(someClass)
@@klass = someClass
def someClass.has_polymorphic_attachment(config)
@@attachment_config = config
end
def someClass.validates_as_polymorphic_attachment
@@validates_as_attachment = true
end
super
end
may only work for macs. If this is giving you trouble on a PC, try
reversing the direction of the '/'s
this over writes the public_filename method in attachment_fu.
NOTA BENE: this over writes attachment_fu’s public_filename, if
you over write attachment_fu’s
full_filename, you may also have to over write Attachment_kung’s
public_filename.
def public_filename(thumbnail = nil)
if path_prefix != “”
path_prefix.gsub!(‘public’,‘’) if path_prefix.include?
(‘public’)
path_prefix + ‘/’ + (“%08d” %
main_attachment_id).scan(/…/).map{|e|e.to_s + ‘/’}.to_s + filename
else
begin
Technoweenie::AttachmentFu::Backends::S3Backend::s3_url
rescue NoMethodError
raise ‘Attachment has a bad path, please delete it and upload
it again’
end
end
end
gets the attachment’s id, or its parent’s id if it has one
def main_attachment_id
((respond_to?(:parent_id) && parent_id) || id).to_i
end
creates the dynamic attachment class and returns an object of that
type with its polymorphic
attributes set to reflect the caller
def new_attachment(params)
# name of calling class
class_name = self.class.to_s
# id of calling object
parent = self.object_id
# options from has_polymorphic_attachment
options = {}
options.merge!(@@attachment_config)
# whether validate_as_polymorphic_attachment is set in caller
(default is nil)
valid = @@validates_as_attachment
@class_def = %{
class Object::#{class_name}_attachment < ActiveRecord::Base
# set polymorphic column names (whateverable_id/type)
belongs_to :attachable, :polymorphic => true
# set table into which model will be saved
self.table_name = "attachments"
# Initialize polymorphic attributes (attachable_id/type) with
data from parent obj;
# also initialize value of path_prefix column for
public_filename to use.
# NOTE: if you are working with different polymorphic column
names be sure to update them
# from my :attachable_id and :attachable_type to whatever you
are using. Also, be sure to
# update the default value for ‘path’ in the file_system case
from ‘attachements’
# to whatever your table name is.
def initialize(attributes={})
@p = ObjectSpace._id2ref(#{parent})
path = if "#{@@attachment_config[:path_prefix]}".to_s == ""
pth = case "#{@@attachment_config[:storage]}".to_s
when "file_system"
"public/attachments"
when "db_system"
""
when "s3"
""
else
""
end
else
pth = "#{@@attachment_config[:path_prefix]}".to_s
end
if respond_to?(:path_prefix)
attachables = {:attachable_id => @p.id, :attachable_type
=> @p.class.to_s, :path_prefix => path}
else
attachables = {:attachable_id => @p.id, :attachable_type
=> @p.class.to_s}
end
attributes.merge!(attachables)
super(attributes)
end
# Passes the params from the parent model (passed to
has_polymorphic_attachment) to
# the attachment_fu’s has_attachment. Also runs
attachment_fu’s validations,
# if validates_as_polymorphic_attachment was called by the
calling Model
def self.attach(opts, valid)
has_attachment opts
validates_as_attachment if valid
end
##END Class
end
}
eval @class_def
eval("#{class_name}_attachment").attach(options, valid)
attachment = eval("#{class_name}_attachment").new(params)
return attachment
end
END MODULE
end