yekta
yekta

Reputation: 3433

Multiple models with paperclip using STI

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:

Upvotes: 4

Views: 772

Answers (1)

yekta
yekta

Reputation: 3433

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.

Upvotes: 2

Related Questions