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
-~----------~----~----~----~------~----~------~--~---