Polymorphic Associations

© 2012 - 2017 thoughtbot, inc. Upcase, the design of a robot, and thoughtbot are registered trademarks of thoughtbot, inc.


This is a companion discussion topic for the original entry at https://thoughtbot.com/upcase/videos/intermediate-rails-3

Isn’t the reference to shout.content.body in dashboard/show.html.erb a violation of the law of demeter (and Sandy Mertz’s rules).

I added delegate :body, to: :content to the shouts model and then changed it to shout.body in the dashboard/show.html.erb

Now after this, it fails when trying to save the Shout with invalid length. (it fails the validation from TextShout and pass nil to content). Probably missing some validates_associated

So I came about this error on create action a shout. It turns out to he the shout/_shout.html.erb
<%= link_to shout.username, shout.user %> in the video but it should actually look like this
<%= link_to shout.username, users_path(shout.user) %>

Can you post your code?

You should post your code. In rails, when you set up routes for a model through resource with the show action, using in a get request will automatically route to the show with the models id passed. So essentially, if you’re routes are set up correctly, users_path(shout.user) is a redundant way of saying just shout.user. There’s an example shown in the following documentation: ActionView::Helpers::UrlHelper. You’ll see it in the second example, and it’s the documentation, so if you want to read up on it, there you go! Check it out! :slight_smile:

Rails.application.routes.draw do

  constraints Clearance::Constraints::SignedIn.new do
root to: "dashboards#show"
  end
  resources :shouts, only: [:create, :show]

  root to: "homes#show"
  resources :passwords, controller: "clearance/passwords", only: [:create, :new]
  resource :session, only: [:create]

  resources :users, only: [:create] do
resource :password,
  controller: "clearance/passwords",
  only: [:create, :edit, :update]
  end

  get "/sign_in" => "sessions#new", as: "sign_in"
  delete "/sign_out" => "sessions#destroy", as: "sign_out"
  get "/sign_up" => "users#new", as: "sign_up"
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end

_show.html.erb

<div>
	<%= link_to shout.username, users_path(shout.user) %> 
	<%= render shout.content %>
	<%= link_to shout do %>
		<%= time_ago_in_words(shout.created_at) %> ago
	<% end %>
</div>
<br>
1 Like

The problem is here. You need to make it:

resources :users, only: [:create, :show]

Then you should be able to do

link_to shout.username, shout.user

I hope that helps! :slight_smile:

1 Like

I went through whole example in lesson and I run into one problem. ShoutsController#create validations for body are not run, which leads into weird state when Shout is saved, but TextShout is not. What is proper way of propagating validations from association in Rails? Do I have to write custom validation method?
Thanks for help.

UPDATE: As most of the times, answer was quite simple I just had to read Rails docs more carefully. :slight_smile:

Would someone help me understand why we are using reversible. I’m not quite understanding the directions of migrations. It’s clear to me when we do an additional migration (which I believe is migrating ‘up’) we are creating a record in TextShout and updating the shouts table. What’s happening when we are migrating down? When does that even occur?

I’m having trouble with carrying over the shouts to a text shouts. My migration was successful but when I checked my database they were not in the text shouts table.

What is the difference between the polymorphic association used in this lesson and doing something like:

TextShout has_many :contents, through: :shouts.
PhotoShout has_many :contents, through: :shouts.

I’d like to know if this is a different or same approach.

Thanks.

This is called many_to_many relation and what you’re trying to do here is to connect tables like that:

TextShout -- > Shout < -- Content

This could not make sense, and let me show you what that means:
Let’s for example look at a situation, where we have Users and Books. We want to connect them to each other, but many users can have many books and vice-versa. So we are going to connect them by creating a connecting model called UserBook

User -- > UserBook < -- Book

User has_many :books, through: :user_books
What many to many relation is going to achieve you - connect several users to several books by creating an inbetween table in the database.

So in your case it means you are trying to connect several text_shouts to several contents (in essence - a model Content) by creating an inbetween table called ‘shouts’. Which is not doing what we wanted to do.

Thanks. This makes sense. Your last example of polymorphic association is similar to what I’m used to.

One clarification I’d like to request:

  • The Shout model includes a line that says belongs_to :content, polymorphic: true, I’d expect the TextShout (and later on PhotoShout) to have a line that will be a counterpart to this - has_one :shout, as: content

Please tell me why this corresponding line in the TextShout and PhotoShout aren’t included.