Can anyone please explain Polymorphic association in details with an example?
You're correct about the definition of polymorphic associations. The term "single association" means that we can use a combination of record type
and record id
to serve multiple belongs_to
relationships. I understand that this may sound as vague as the definition itself. So I'll use the example mentioned in the Rails guide description of polymorphic associations:
class Picture < ApplicationRecord
belongs_to :imageable, polymorphic: true
end
class Employee < ApplicationRecord
has_many :pictures, as: :imageable
end
class Product < ApplicationRecord
has_many :pictures, as: :imageable
end
Here, the picture
model is a polymorphic one, which has two columns, imageable_id
and imageable_type
. It uses these two columns to establish relationships with any models, which have a has_many :pictures, as: :imageable
relation. In the above example, when we add a Picture
for an Employee
with id 3, a record is created in the Picture
model as:
#<Picture id: 1, url: "some_url", imageable_type: "Employee", imageable_id: 3....>
Note how the imageable_type and imageable_id help us identify which exact record in our system, this picture belongs to. For a Product
with id 3, the picture would be as follows:
#<Picture id: 1, url: "some_url", imageable_type: "Product", imageable_id: 3....>
Without polymorphic associations, we would have had to add 2 columns, employee_id
and product_id
to have belongs_to
relationships with the models described above. Imagine a scenario where 5-6 models different could have pictures!
What are the advantages of using polymorphic associations?
They help in having a uniform API for interacting with a shared resource (like pictures, comments etc)
Allow easy addition of new models which can share the resource, without adding new columns. This also helps in avoiding multiple unused columns in a record
Why does a polymorphic association in Rails with source_type set result in the wrong SQL statement?
I found a solution that works around the suspected bug: There is an undocumented method called polymorphic_name
that ActiveRecord uses to determine what model name to use when doing polymorphic lookups.
When I change the Organization model to:
class Organization < ApplicationRecord
self.table_name = 'organisations'
has_many :roles, as: :access_to
has_many :users, through: :roles
def self.polymorphic_name
"Organisation"
end
end
then Organization.first.users
generates the SQL I want:
SELECT "users".* FROM "users" INNER JOIN "roles"
ON "users"."id" = "roles"."user_id"
WHERE "roles"."access_to_id" = ?
AND "roles"."access_to_type" = ?
LIMIT ? [
["access_to_id", 1],
["access_to_type", "Organisation"],
["LIMIT", 11]]
Commit that fixed my example: https://github.com/RSpace/polymorphic-issue/commit/648de2c4afe54a1e1dff767c7b980bb905e50bad
I'd still love to hear why the other approach doesn't work though. This workaround seems risky, as I simply discovered this method by digging through the Rails code base, and it's only used internally: https://github.com/rails/rails/search?q=polymorphic_name&unscoped_q=polymorphic_name
EDIT: I now understand why setting source_type: "Organisation"
results in a lookup in the organisations
table rather than the users
table, as the source_type
option controls both model, table and polymorphic name as per the documentation. There is still a bug around getting "access_to_type" set twice, but fixing that won't get my use case working, as source_type
is first and foremost for controlling, well, the source type of the association. I will instead pursue to get the polymorphic_name
method documented and thus be part of the official ActiveRecord API.
Rails Polymorphic Association with multiple associations on the same model
I have done that in my project.
The trick is that photos need a column that will be used in has_one condition to distinguish between primary and secondary photos. Pay attention to what happens in :conditions
here.
has_one :photo, :as => 'attachable',
:conditions => {:photo_type => 'primary_photo'}, :dependent => :destroy
has_one :secondary_photo, :class_name => 'Photo', :as => 'attachable',
:conditions => {:photo_type => 'secondary_photo'}, :dependent => :destroy
The beauty of this approach is that when you create photos using @post.build_photo
, the photo_type will automatically be pre-populated with corresponding type, like 'primary_photo'. ActiveRecord is smart enough to do that.
ActiveRecord Polymorphic association
Use two join models instead, its both simpler and will avoid the major cons of polymorphism:
- No foreign key support as the DB does not know what table the association points to.
- Joins are tricky since you have to query the table to know what table to join. Which is really bad for a join table.
Since join tables just consist of two columns and the model has very little logic using polymorphism does not really give you anything but headaches.
class User < ApplicationRecord
has_many :user_conversations, dependent: :destroy
has_many :conversations, through: :user_conversations
has_many :chatroom_users, dependent: :destroy
has_many :chatrooms, through: :chatroom_users
end
class UserConversation < ApplicationRecord
belongs_to :user
belongs_to :conversation
end
class Conversation < ApplicationRecord
has_many :messages, as: :context, dependent: :destroy
has_many :user_conversations, dependent: :destroy
has_many :users, through: :user_conversations
end
class ChatroomUser < ApplicationRecord
belongs_to :user
belongs_to :chatroom
end
class Chatroom < ApplicationRecord
belongs_to :venue
has_many :messages, as: :context, dependent: :destroy
has_many :chatroom_users, dependent: :destroy
has_many :users, through: :user_chatrooms
end
Get specific type of polymorphic association
According to APIdock:
:foreign_type
Specify the column used to store the associated object’s type, if this is a polymorphic association. By default this is guessed to be
the name of the association with a “_type” suffix. So a class that
defines a belongs_to :taggable, polymorphic: true association will use
“taggable_type” as the default :foreign_type.
So, you should use foreign_type
in the source
association to specify what column stores the associated object's type.
I think you want two methods html
and pdf
, so you can use it when the source is either Html
or Pdf
. In this case, I think you should create two methods for it, for example:
def html
source if source_type == "Html"
end
def pdf
source if source_type == "Pdf"
end
Rails 5.2 Polymorphic Association with polymorphic conditional
This is a very interesting question that I had for several months. Then I found a solution for it.
In your Listing
model, in order to be able to include your polymorphic
model, you'll need to tell your model that they are related.
class Car < ApplicationRecord
belongs_to :user
has_one :listing, as: :listable
has_one :firm, as: :firmable
has_one :seller, as: :sellable
end
class Truck < ApplicationRecord
belongs_to :user
has_one :listing, as: :listable
has_one :firm, as: :firmable
has_one :seller, as: :sellable
end
class Listing < ApplicationRecord
belongs_to :listable, polymorphic: true
has_many :favorites, dependent: :destroy
has_many :users, through: :favorites
#magic happens here
belongs_to :car, -> { includes(:listings).where(listings: { listable_type: Car.to_s }) }, foreign_key: :listable_id
belongs_to :truck, -> { includes(:listings).where(listings: { listable_type: Truck.to_s }) }, foreign_key: :listable_id
end
and now, you can simply do: Listing.includes(:car, :truck)
and it will work perfectly :-)
For your case:
Listing.includes(:car, :truck).where(cars: { user_id: 1 }).or(Listing.includes(:car, :truck).where(trucks: { user_id: 1 }))
Related Topics
Rails Pass Params/Arguments to Activerecord Callback Function
How to Set the Httponly Flag on a Cookie in Ruby on Rails
Does Rails Come with a "Not Authorized" Exception
How to Deal with Ruby 2.1.2 Memory Leaks
Connecting to Oracle Db Using Ruby
How to Dynamically Call Accessor Methods in Ruby
How to Return Correct Http Error Codes from Ruby on Rails Application
How to Set a Cookie with a (Ruby) Rack Middleware Component
Display Base64 Encoded Image in Rails
What's the Cleanest Way to Override Activerecord's Find for Both Models and Collections
How to Detect User Agent in Rails 3.1
Does Ruby Support Unicode and How Does It Work
Passenger: Cannot Load Such File Rubygems/Builder
How to Transfer Files Using Ssh and Scp Using Ruby Calls
Ruby on Rails Uncapitalize First Letter