Polymorphic Association in Rails

Here is an account of my first use of Rails 1.1’s polymorphism in Model associations.

I had looked at all the examples at the wiki and a couple of other blogs but still wasn’t clear.

You appreciate most, the problems that you solve yourselves 🙂

Here is the problem I solved using Polymorphic associations.

Consider the scenario where there are Users of an application; who can act as both buyers and providers. Each user can infact have multiple buyer and provider profiles.
The application also has a messaging system for users to contact each other.
As a part of the application requirement, we need to keep track of the profiles of the message sender and receiver.

A user logs in using the login name and password present in the User model. I am omitting details so please assume for this example that each logged in user, has either a buyer profile or a provider profile associated to it, when they use the messaging system.

Here are the model files

A user can have many buyer_profiles and many provider_profiles


class User < ActiveRecord::Base
  has_many :buyer_profiles
  has_many :provider_profiles
end

A buyer profile belongs to a user and can have many messages associated to it as a sender and as a receiver.


class BuyerProfile < ActiveRecord::Base
  belongs_to :user
  has_many :messages, :as => :sender
  has_many :messages, :as => :receiver
end

A provider profile also belongs to a user, and can have many messages associated to it as a sender and as a receiver.


class ProviderProfile < ActiveRecord::Base
  belongs_to :user
  has_many :messages, :as => :sender
  has_many :messages, :as => :receiver 
end

The message model holds the polymorphic magic, via the two interfaces sender and receiver. A message belongs to sender which can be polymorphic,either a buyer or a provider. And also a message belongs to a receiver, which again is polymorphic, either a provider or a buyer.


class Message < ActiveRecord::Base
  belongs_to :sender, :polymorphic => true
  belongs_to :receiver, :polymorphic => true
end

The code above requires that I have these four special fields in my messages table to handle polymorphism. Here is the migration for messages


class CreateMessagesTable < ActiveRecord::Migration
  def self.up
    create_table :messages do |t|
      t.column :sender_id, :integer
      t.column :sender_type, :string
      t.column :receiver_id, :integer
      t.column :receiver_type, :string
      t.column :subject, :string
      t.column :message, :string
    end
  end

  def self.down
    drop_table :messages
  end
end

I ran the following code on the console to check if everything is working as expected.


r = BuyerProfile.find(1)
s = ProviderProfile.find(1)
m = Message.new
m.sender = s
m.receiver = r
m.subject = "test subject"
m.message = "test message"
m.save!

And it worked 🙂

I could access profles and users from the message object like this.


m = Message.find(1)
m.receiver                 # refer to the receivers profile object
m.receiver.class          # get receiver type, where BuyerProfile or SellerProfile
m.sender                   # refers to the sender profile object
m.sender.class            # get sender type, where BuyerProfile or SellerProfile
m.receiver.user           # refer the User object of the message receiver
m.sender.user             # refer the User object of the message sender

Polymorphism saved me a lot of time and also a whole new table which I was thinking of adding to handle this functionality prior to polymorphism.

Added : And yes, now I am thinking about what I would need to write to get all the messages sent or received by a User.

Comments

  1. >> Added : And yes, now I am thinking about what I would need to write to get all the messages sent or received by a User.

    Hi, have you already found out how to do this?

  2. Incredibly helpful. Thanks for taking the time to write this up – it took all of five minutes to get this sort of thing working in my project. Much appreciated.

  3. Great post. I was wondering how to delete a message from say a BuyerProfile object?

    e.g.
    bp = BuyerProfile.find :first
    bp.messages.count # outputs 2
    bp.messages.first.destroy

    Destroy should delete the first message, but it won’t do so. Any reason wjy?

  4. i believe since the BuyerProfile and ProviderProfile are inherited from the activerecord base class it would mean that i need to create a table called buyerprofiles and providerprofiles which has a column called user_id

    Please let me know if i am correct?

  5. Great Example,

    Thanks for illustrating this out in such simple fashion. Saved at least a couple of my hours 🙂

    -Thanks

  6. Holy crap – your blog post was the first to make sense to me! I know what polymorphism is but none of the examples made sense until I saw yours!

    In my case, I have an item that has an owner, but the owner can be of differing types (Employee, Department, etc.).

    Thanks!

Leave a Reply

Your email address will not be published. Required fields are marked *

− five = two