← Back to Upcase

Defining polymorphic associations


(Aaron Renner) #1

I’m going through the second video in Intermediate Rails, and I’m not sure I understand how you got polymorphic associations working with only defining one side of the relationship. All of the examples I’ve seen (section 2.9 of http://guides.rubyonrails.org/association_basics.html for example), have you set it up on both sides of the association using the :as option.

class Picture < ActiveRecord::Base
 belongs_to :imageable, polymorphic: true
end
 
class Employee < ActiveRecord::Base
  has_many :pictures, as: :imageable
end

class Product < ActiveRecord::Base
  has_many :pictures, as: :imageable
end

However in Shouter it ended up like this:

class Shout < ActiveRecord::Base
  belongs_to :content, polymorphic: true
end

class PhotoShout < ActiveRecord::Base
end

class TextShout < ActiveRecord::Base
end

Do you not have to set up the has_one/has_many on the other side of the relationship if you don’t want to access it from that side?


(Jon Seidel) #2

I had the same question when I was digging into the relationship. What I came to realize was that Matt’s example is sorf of an ‘inversion’ of the example given in RailsGuides. Here’s how I worked it out.

In the RG example, a Picture is ‘subordinate’ to Employee and Product, kinda like an OrderItem is subordinate to an Order… that’s what I consider the ‘classic’ setup.

In the shouter example, a Shout is actually the ‘master’ object and is not subordinate to either TextShout or PhotoShout. Since the “belongs_to … polymorphic: true” sets up the required content_type and content_id that we need to show that relationship, we’re done and we don’t need the reflective “has_many”.

As far as the ERD (Entity Relationship Diagram) is concerned, In my mind, I put Shout on the left and PhotoShout / TextShout on the right as compared to the RG diagram which has Picture on the right-hand side of the diagram.

HTH a bit…


(Sean Griffin) #3

Do you not have to set up the has_one/has_many on the other side of the relationship if you don’t want to access it from that side?

That’s correct. You only need to define the association in the direction that you want to access it. In that example, @shout.content will work, but @photo_shout.shout will not. No other problems will come from defining an association on only a single side, however.

In fact it’s usually a good idea to only define associations where you need them. If you define everything on both models, you’ll very quickly end up with 20 has_manys on User in most applications.


(Aaron Renner) #4

Thanks @seangriffin. It’s good to know there aren’t any other consequences if you don’t define the relationships on both sides.


(Aaron Renner) #5

@JESii. I agree, that was a pretty cool way to use the belongs_to relationship. It seemed like a much better alternative to using single table inheritance.


(Esop) #6

How does Shout gain the content attribute that is used in this partial?

#_shout.html.erb

<%= div_for shout do %>
  <%= link_to shout.user.username, shout.user %>
  shouted
  <%= render shout.content %>
  <%= link_to time_ago_in_words(shout.created_at),shout %>
<% end %>

Also, I thought that the belongs_to macro took a classname? But content appears to be used like a “ghost class” or “ghost attribute” in the belongs_to association for lack of better terms.

# shout.rb

class Shout < ActiveRecord::Base
      belongs_to :content, polymorphic: true
      belongs_to :user
      default_scope { order("created_at DESC") }
 end

schema.rb

ActiveRecord::Schema.define(version: 20130723202938) do

  create_table "shouts", force: true do |t|
    t.integer  "user_id"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.string   "content_type"
    t.integer  "content_id"
  end

  add_index "shouts", ["content_type", "content_id"], name: "index_shouts_on_content_type_and_content_id"
  add_index "shouts", ["user_id"], name: "index_shouts_on_user_id"

  create_table "text_shouts", force: true do |t|
    t.string "body"
  end


  add_index "users", ["username"], name: "index_users_on_username"

end

(Eric Paxton) #7

@Esop has a similar question to mine, so I won’t go into the same details.

From what I can tell you just whatever you pass as :content to the model will be dealt with according to the established relationship.

For instance I have the following class:

class Shout < ActiveRecord::Base
  belongs_to :user
  default_scope { order("created_at DESC") }
  belongs_to :content, polymorphic: true

   def self.text_shouts
     where(content_type: 'TextShout')
   end
   
   def self.build user, content
     user.shouts.build(content: content)
   end
   
   def self.build_shout_content type="text", parameters
     if type== 'photo'
       PhotoShout.new(parameters)
     else
       TextShout.new(parameters)
     end
   end
end

So my assumption, is it doesn’t matter what you call :content. What matters is what you assign to it. If you called it :foo, as long as you assigned something to :foo it will still make the correct associations with the data.