4
votes

Goal: One Rails model (table) with multiple models which inherit from it, each which define their own paperclip has_attached_file configurations.

I used to have a single Upload class which I was using with paperclip. The problem is that as I added more file types (pdfs, word documents etc.) they were still being processed as images via the "style" and "convert_options". Further, now I need to have some files stored on S3 and others stored locally.

That said, I've been restructuring things such that I now have an S3File, Contract and other models all which inherit from Upload which still inherits from ActiveRecord::Base.

# app/models/project.rb
class Project < ActiveRecord::Base
  has_many :contracts, :dependent => :destroy
    accepts_nested_attributes_for :contracts, :allow_destroy => true

  has_many :s3_files, :dependent => :destroy
    accepts_nested_attributes_for :s3_files, :allow_destroy => true
  # ...
end

# app/models/upload.rb
class Upload < ActiveRecord::Base
  belongs_to :project
end

# app/models/contract.rb
class Contract < Upload
  has_attached_file :item,
    :url  => "/projects/:project_id/uploads/:id/:basename.:extension",
    :path => ":rails_root/public/:class/:attachment/:id/:basename.:extension"

  do_not_validate_attachment_file_type :item
end

# app/models/s3_file.rb
class S3File < Upload
  has_attached_file :s3file,
    storage: :s3,
    url: ':s3_domain_url',
    path: lambda {|u| 'files/:basename.:extension' }

  do_not_validate_attachment_file_type :s3file
end

Now in a console when I try and query the data, it returns Upload objects and not the S3File or Contract.

irb(main):005:0> Project.first.contracts
=> #<Upload id: 14833, project_id: 9717, upload_type: "private", item_file_name: "abcd.pdf", item_category: "contracts", item_content_type: "application/pdf", item_file_size: 671367, rake_processed: 0, name: "", created_at: "2013-05-30 20:05:02", updated_at: "2013-05-30 20:05:02">

Having the response as an Upload type is problematic because it has no paperclip attachment. These are defined on the subclasses which each have a unique url, path and storage definitions for the has_attached_file.

I saw the multiple models and paperclip and polymorphic multiple models paperclip questions but in those questions each of the "multiple models" inherit from ActiveRecord::Base, my goal is to avoid that if I can since the data structures are the same.

My questions about this are:

  • Is this the right approach for having files types with various storage backends with one table? Being files, they mostly all have the same attributes, so having multiple tables for each of these models seems unnecessary.
  • Do I need to be using polymorphic associations? How would I define those here? I couldn't get it to work, plus it doesn't seem appropriate since the Contract and S3File are Upload type anyway, they do not have their own table.
  • Is it a good idea to use separate has_attached_file (:item or :s3file) names or is it beneficial to have them remain the same?
1

1 Answers

2
votes

STI with paperclip has been discussed in several issues #293, #601, #605 and is a supported feature of paperclip now.

Is STI the right approach?

It was the right approach in my case since I didn't want to use multiple tables for each model. In order to solve the problem of having the wrong class when querying project.contracts and getting Upload type classes, this is a documented Rails feature. Simply adding class_name: "Contract to the has_many on your model will address this:

Per the associations documentation on customizing the query:

Customizing the query

Associations are built from Relations, and you can use the Relation syntax to customize them. For example, to add a condition:

 class Blog < ActiveRecord::Base   
     has_many :published_posts, -> { where published: true }, class_name: 'Post' 
 end 

Are polymorphic associations necessary

In this case with STI, no. If each model was db backed then yes a polymorphic association could help. See the answer on polymorphic associations with multiple models for an example.

Is it a good idea to use the same has_attached_file

Ever since the aforementioned pull request has been merged this is a non-issue in paperclip. One can overwrite the options, callbacks, styles, url or path in a subclass and enjoy the benefits of OOP to suit the needs of the application.