paul.saieg-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org
2007-Sep-18 09:50 UTC
Making attachment_fu polymorphic
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 # http://creativecommons.org/licenses/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 Clark''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 Fowler''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 --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
paul.saieg-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org
2007-Sep-27 16:29 UTC
Re: Making attachment_fu polymorphic
Here this is version 0.2 of attachment_kung. It has a few bugs worked out, is a bit faster, but the order of the ''attachments'' table is still unpredictable. Any help would be appreciated. Thanks in advance, Paul Saieg> #Attachment_kung: Polymorphic Support For Attachment_fu > # Version 0.2 > # 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 > # http://creativecommons.org/licenses/by-sa/2.5/module Attachment_kung @@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 # Gets the full path to the filename in this format: # # # This assumes a model name like MyModel # # public/#{table_name} is the default filesystem path # RAILS_ROOT/public/my_models/5/blah.jpg # # The optional thumbnail argument will output the thumbnail''s filename. def full_filename(thumbnail = nil) file_system_path = (thumbnail ? thumbnail_class : self).path_prefix.to_s File.join(RAILS_ROOT, file_system_path, *partitioned_path(thumbnail_name_for(thumbnail))) end # may only work for macs. If this is giving you trouble on a PC, try reversing the direction of the ''/''s 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 belongs_to :attachable, :polymorphic => true self.table_name = "attachments" # intialize object, only tested with :storage_type => :file_system def initialize(attributes={}) @p = nil @p = ObjectSpace._id2ref(#{parent}) path = if "#{@@attachment_config[:path_prefix]}".to_s == "" "public/attachments" else "#{@@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 @p = nil attributes.merge!(attachables) super(attributes) end 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 --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
paul.saieg-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org
2007-Sep-27 17:11 UTC
Re: Making attachment_fu polymorphic
I am pleased to announce that attachment_kung has been tested and works. The ordering problem had nothing to do with the code, but was the result of using a myISAM table rather than an InnoDB table. An InnoDB table is crucial for the ordering to work. Below is the completed code. Enjoy it, and please let me know if anyone runs into any problems (I haven''t tested every possible use case). - Paul Saieg module Attachment_kung # # Attachment_kung: Polymorphic Support For Attachment_fu # Version 0.3 # # 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 # http://creativecommons.org/licenses/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. If you are using mySQL, make sure the table is InnoDB ## (myISAM will not work). 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 Clark''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 Fowler''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 # Gets the full path to the filename in this format: # # # This assumes a model name like MyModel # # public/#{table_name} is the default filesystem path # RAILS_ROOT/public/my_models/5/blah.jpg # # The optional thumbnail argument will output the thumbnail''s filename. def full_filename(thumbnail = nil) file_system_path = (thumbnail ? thumbnail_class : self).path_prefix.to_s File.join(RAILS_ROOT, file_system_path, *partitioned_path(thumbnail_name_for(thumbnail))) end # May only work for macs. If this is giving you trouble on a PC, try # reversing the direction of the ''/''s 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 = nil @p = ObjectSpace._id2ref(#{parent}) path = if "#{@@attachment_config[:path_prefix]}".to_s == "" "public/attachments" else "#{@@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 @p = nil attributes.merge!(attachables) super(attributes) end # passes attachment_fu it''s options hash and runs its validations 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 --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---