Polymorphic Association

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?

  1. They help in having a uniform API for interacting with a shared resource (like pictures, comments etc)

  2. 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



Leave a reply



Submit