# Making attachment\_fu polymorphic

**URL:** <https://www.ruby-forum.com/t/making-attachment-fu-polymorphic/114870>\
**Category:** Rails\
**Created:** [September 18, 2007, 11:51am UTC](https://www.ruby-forum.com/t/making-attachment-fu-polymorphic/114870 "2007-09-18T11:51:33Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![casper\_the\_ghost](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/c/58956e/32.png) [@casper\_the\_ghost](https://www.ruby-forum.com/u/casper_the_ghost)\
**Post date:** [September 18, 2007, 11:51am UTC](https://www.ruby-forum.com/t/making-attachment-fu-polymorphic/114870/1 "2007-09-18T11:51:33Z")

</div>

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](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 C.'s excellent tutorial:

## [http://clarkware.com/cgi/blosxom/2007/02/24](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

---

<div class="post-metadata">

**Author:** ![casper\_the\_ghost](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/c/58956e/32.png) [@casper\_the\_ghost](https://www.ruby-forum.com/u/casper_the_ghost)\
**Post date:** [September 27, 2007, 6:57pm UTC](https://www.ruby-forum.com/t/making-attachment-fu-polymorphic/114870/2 "2007-09-27T18:57:12Z")

</div>

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
> 
> # [Creative Commons — Attribution-ShareAlike 2.5 Generic — CC BY-SA 2.5](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

---

<div class="post-metadata">

**Author:** ![casper\_the\_ghost](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/c/58956e/32.png) [@casper\_the\_ghost](https://www.ruby-forum.com/u/casper_the_ghost)\
**Post date:** [September 27, 2007, 7:28pm UTC](https://www.ruby-forum.com/t/making-attachment-fu-polymorphic/114870/3 "2007-09-27T19:28:54Z")

</div>

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

# [Creative Commons — Attribution-ShareAlike 2.5 Generic — CC BY-SA 2.5](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 C.'s excellent tutorial:

## [http://clarkware.com/cgi/blosxom/2007/02/24](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

# 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
