text_shouts_controller.rb - What is it all doing?

I have several questions regarding what exactly the text_shouts_controller is doing with its polymorphic relationship to the shout model. I’ll go ahead and paste the whole controller.

class TextShoutsController < ApplicationController
def create
  content = build_content
  shout = current_user.shouts.build(content: content)
  if shout.save
    redirect_to dashboard_path
  else
    flash.alert = "Could not shout."
    redirect_to dashboard_path
  end

private

def build_content
  TextShout.new(text_shout_parameters)
end

def text_shout_parameters
  params.require(:text_shout).permit(:body)
end
end

My confusion primarily lies at the beginning of the create action. I understand that we are setting the variable ‘content’ to a method that creates a new instance of a TextShout. But I have no idea what the next line

shout = current_user.shouts.build(content: content)

is doing. If someone could walk me through this line of code like I’m a golden retriever that would be great. I am particularly confused by the (content: content) bit. Is the ‘content’ the same as the variable we set above? Why are there two ‘contents’ in the parentheses?

Next if you could explain the text_shout_parameters method:

params.require(:text_shout).permit(:body)

What exactly is this doing? And what is the require(:text_shout) referring to? And, whatever the :text_shout part is doing, where is the method getting that piece from (when rails sees :text_shout where does rails go to next)?

Thanks so much. I’m sorry for being long-winded.

shout = current_user.shouts.build(content: content)

This code is building a new shout associated with the current_user and setting the polymorphic content attribute on the Shout class to the variable content which you created with the build_content method. Personally I think it easier to read using the old hash syntax:

shout = current_user.shouts.build(:content => content)

So the :content is the hash key with the variable content as its value.

Your second question regarding the params.require… That’s the syntax required by the strong_parameters gem which was added to the gemfile in the workshop and which is default in Rails 4. It replaces the Rails 3 attr_accessible declaration.

See GitHub - rails/strong_parameters: Taint and required checking for Action Pack and enforcement in Active Model for more.

Thank you for your response.And I apologize if I’m making you repeat yourself but I can’t wrap my head around the concept of :content.

In shout.rb:

belongs_to :content, polymorphic: true

Is the above ‘:content’ the key to the hash? How is this content and the content in the create method related?

And then in the _shout partial:

<%= 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 %>
    ago.
<% end %>

what is ‘content’ in shout.content referring to since content isn’t a model or a column on a table? My apologies, I’ve just trying to see how all the pieces fit together.

Thank you!

So content is actually an attribute on the model. By using:

belongs_to :content, polymorphic: true

you are telling rails that the content attribute is actually a polymorphic object. Then Rails looks and the content_type and content_id to identify the related object.

Instantiating the object like this:

shout = current_user.shouts.build(:content => content)

is no different from instantiating an object with non-polymorphic attributes using a hash to set attributes, e.g.

user = User.new(:email => "foo@bar.com", :username => "fooboy")

So, if you go into your rails console and type:

    Shout.first.content

You will see either a TextShout or a PhotoShout returned depending on the content_type column that is stored in your Shouts table. That’s the power of polymorphism.

So, in essence since content is a polymorphic attribute/relation that returns an actual object instead of a string or integer(etc) like your other attributes would.

Check out the Rails guides:

So in your question about the _shout partial:

When you call render on an ActiveRecord object in Rails, Rails will use naming conventions which derives from the class of the object.

For example, when you say:

render shout.content

You are actually receiving back an object (through polymorphism) of a class of either TextShout or PhotoShout. Rails will then take that and look for “views/text_shouts/_text_shout.html.erb” or “views/photo_shouts/_photo_shout.html.erb” depending on the class of the object being rendered.

So if you’re missing either of those partials you’ll get a ‘missing partial’ error even though you never directly called either _photo_shout or _text_shout.

1 Like

Awesome. This is great, thank you. One last question…

Is the ‘content’ attribute, along with content_type/content_id, performing some rails magic through naming conventions? For example, would this still work if the content attribute was changed to something else but the columns content_type and content_id stayed the same? When using polymorphism should you always use attributename_type and attributename_id as columns?

Thanks again. I really appreciate your time.

Yes it using using the Rails magic held together by naming conventions.

So yeah, polymorphism should always use attributename_type and attributename_id to conform to the convention.