Polymorphic class associations

Hi there,

I’d be happy if someone can help me with the following:
The application is for uploading all kinds of datafiles which should be
displayed accordingly to their mimetypes.

This is the class/database hierarchy of what I want to archive:

Class:Filegroup
has_many :binary
Attributes are e.g. groupname, timestamps of upload/update and so on

Class:Binary
This is the generall description of the uploaded file
Attributes included are e.g. original_filename, mimetype and so on

If someone is uploading a file with a known mimetype, a (sub-)class
should be created with specific details about the file e.g.

Class:Image
Including width, height…

Class:Text
Textstyle (php, xml)…

I’d like to access the list of all files through something like:
Filegroup.find(xxx).files iterates over it and render it automatically
as it is specified in each of those sub-classes (so images whould be a
thumbnail, text is shown fully and binary just a simple download link)

I found the Polymorphic-attribute for the database associations but this
is the other way round, isn’t it?
Also I took a look at
http://archive.robwilkerson.org/2009/08/26/learning-ruby-on-rails-file-upload/index.html
but this isn’t exactly what I need because the class which should be
used should be defined automatically.
(E.g. somewhere is specified which class should be used for which
mimetype)

Thank you for your time and help.

On Thu, Dec 1, 2011 at 1:23 PM, Kioko – [email protected] wrote:

Attributes are e.g. groupname, timestamps of upload/update and so on

is the other way round, isn’t it?
Also I took a look at

http://archive.robwilkerson.org/2009/08/26/learning-ruby-on-rails-file-upload/index.html

but this isn’t exactly what I need because the class which should be
used should be defined automatically.
(E.g. somewhere is specified which class should be used for which
mimetype)

I believe you need a mix of:

  • STI from Binary to Image, Text etc.
  • has_many relationships from Filegroup to
    these different classes.

I set-up a project on
https://github.com/petervandenabeele/files
with as core files:

class Binary < ActiveRecord::Base
end

class Filegroup < ActiveRecord::Base
has_many :binaries
has_many :images
has_many :texts
end

class Image < Binary

def show
“this is an image of width #{width} and height #{height}”
end

end

class Text < Binary

def show
“This is a text of size #{size}”
end

end

and schema.rb :

ActiveRecord::Schema.define(:version => 20111203233119) do

create_table “binaries”, :force => true do |t|
t.string “filename”
t.integer “filegroup_id”
t.string “type”
t.datetime “created_at”
t.datetime “updated_at”
t.integer “height”
t.integer “width”
t.integer “size”
end

create_table “filegroups”, :force => true do |t|
t.string “groupname”
t.datetime “created_at”
t.datetime “updated_at”
end

end

And then I can call:

fg.images.build
fg.texts.build

to build instances of derived classes, but also

fg.binaries

to get back all binaries (of different subclasses) where each
binary (image, text) has it’s own class specific implementation
of a show method (different for an Image and for a Text).

Is this what you where looking for ?

Peter

+++++++++++++++++++++++
peterv@ASUS:~/data/temp/files$ rails c
Loading development environment (Rails 3.1.3)
001:0> fg = Filegroup.create(:groupname => “home”)
(0.1ms) SHOW search_path
(0.1ms) BEGIN
SQL (12.3ms) INSERT INTO “filegroups” (“created_at”, “groupname”,
“updated_at”) VALUES ($1, $2, $3) RETURNING “id” [[“created_at”, Sat,
03
Dec 2011 23:46:03 UTC +00:00], [“groupname”, “home”], [“updated_at”,
Sat,
03 Dec 2011 23:46:03 UTC +00:00]]
(0.6ms) COMMIT
=> #<Filegroup id: 1, groupname: “home”, created_at: “2011-12-03
23:46:03”,
updated_at: “2011-12-03 23:46:03”>

002:0> fg.images.create(:width => “400”, :height => “200”)
(0.2ms) BEGIN
SQL (1.5ms) INSERT INTO “binaries” (“created_at”, “filegroup_id”,
“filename”, “height”, “size”, “type”, “updated_at”, “width”) VALUES ($1,
$2, $3, $4, $5, $6, $7, $8) RETURNING “id” [[“created_at”, Sat, 03 Dec
2011 23:46:34 UTC +00:00], [“filegroup_id”, 1], [“filename”, nil],
[“height”, 200], [“size”, nil], [“type”, “Image”], [“updated_at”, Sat,
03
Dec 2011 23:46:34 UTC +00:00], [“width”, 400]]
(0.6ms) COMMIT
=> #<Image id: 1, filename: nil, filegroup_id: 1, type: “Image”,
created_at: “2011-12-03 23:46:34”, updated_at: “2011-12-03 23:46:34”,
height: 200, width: 400, size: nil>

003:0> fg.texts.create(:size => 4000)
(0.2ms) BEGIN
SQL (0.8ms) INSERT INTO “binaries” (“created_at”, “filegroup_id”,
“filename”, “height”, “size”, “type”, “updated_at”, “width”) VALUES ($1,
$2, $3, $4, $5, $6, $7, $8) RETURNING “id” [[“created_at”, Sat, 03 Dec
2011 23:47:02 UTC +00:00], [“filegroup_id”, 1], [“filename”, nil],
[“height”, nil], [“size”, 4000], [“type”, “Text”], [“updated_at”, Sat,
03
Dec 2011 23:47:02 UTC +00:00], [“width”, nil]]
(0.7ms) COMMIT
=> #<Text id: 2, filename: nil, filegroup_id: 1, type: “Text”,
created_at:
“2011-12-03 23:47:02”, updated_at: “2011-12-03 23:47:02”, height: nil,
width: nil, size: 4000>

004:0> fg.binaries
Binary Load (0.9ms) SELECT “binaries”.* FROM “binaries” WHERE
“binaries”.“filegroup_id” = 1
=> [#<Image id: 1, filename: nil, filegroup_id: 1, type: “Image”,
created_at: “2011-12-03 23:46:34”, updated_at: “2011-12-03 23:46:34”,
height: 200, width: 400, size: nil>, #<Text id: 2, filename: nil,
filegroup_id: 1, type: “Text”, created_at: “2011-12-03 23:47:02”,
updated_at: “2011-12-03 23:47:02”, height: nil, width: nil, size: 4000>]

Note that the 2 objects are of class Image and class Text (not of

class
Binary) !

005:0> fg.binaries.each{|b| puts b.show}
this is an image of width 400 and height 200
This is a text of size 4000

Dear Peter V.,

thanks for your really long answer and sorry for my late reply… I’m
really short on time these days… :frowning:

A day before you wrote your answer I came up with a different solution:

http://noxx.penya.de/ruby-attachments.txt
This is the “main class” which was the binary in my first post. This
class handles the upload (payload) and creates subclasses depending on
the mimetyp.

If e.g. an image was uploaded it builds this class:
http://noxx.penya.de/ruby-images.txt
Images have some more attributes as width/height in the database table
so it’s possible to search through them.

The general binary class for all other or - in my case also the image
file itself and the created thumbnail - is represented by this class:
http://noxx.penya.de/ruby-binaries.txt
Binaries handles all files and also the deletion if something is
destroyed.

Thus, the binary can belong directly to the attachment or upload itself
if the mimetyp is not recognized or as the “data” of the image.

I splitted image/binary and so on because if I save it all in one table
I have many NULL-rows and it’s hard to handle.

But it should be easy to extend, too…

In the end I can call @attachments.all, get back all the uploaded files
as the specific class and let the render choose which view to display.

I hope I could give you an impression of my solution and maybe you have
some suggestions what would be good to improve.

Thanks for your time and again for your reply, too.
Greetings.