In a Rails view, which objects are in the response chain? - ruby-on-rails

When I call a method foo() or refer to an attribute #bar in my view file, which object is going to respond?
For example if I have a partial _ebook.html.erb that looks like
<h2>Your eBook</h2>
<p>Title: <%= #title %></p>
<p>Due Date: <%= due_date(customer) %>
etc.
which object provides #title, due_date and customer? Do those bubble up to the BooksController and its modules/superclasses?
Also, if my render includes locals, for example
render partial: "ebooks", locals: {baz: #qux}
in which object is baz being stored?

It's not other objects provide your template with local variables, it's your template being read by the controller where the object exist in the context.
The erb template annotate variables such as #bar and taken by the controller to render accordingly. If controller actually has a #foo and you want to use it as the #bar in template you plugin locals: {bar: #foo}

I've found most of the answer, at least for Rails 3.2.x. Here's my poorly written description:
As part of rendering, ActionView::Base#prepare creates an instance of an anonymous subclass of ActionView::Base and adds a couple of helper modules.
Then the view_assigns method stores the controller's instance variables in a hash in that instance.
The local variables are passed in as a hash and stored in an instance variable called locals in the same instance.

Related

Rails rendering instance variable from application.html.erb

I am following the Agile Web Development with Rails 4 book and I'm a bit confused on a part about rendering. The simple version of the question is... within the application.html.erb file there it says
render #cart
This is confusing because I thought that there needed to be a controller associated with that view in order to know which partial and #cart variable to use. Is it simply by naming convention that this line looks for a partial like _cart.html.erb? And in that case does it not actually know what #cart is until it renders that partial?
Some clarification would be lovely. Thanks!
This is a shorthand syntax. From the docs:
Every partial also has a local variable with the same name as the partial (minus the underscore). You can pass an object in to this local variable via the :object option:
<%= render partial: "customer", object: #new_customer %>
Within the customer partial, the customer variable will refer to #new_customer from the parent view.
If you have an instance of a model to render into a partial, you can use a shorthand syntax:
<%= render #customer %>
Assuming that the #customer instance variable contains an instance of the Customer model, this will use _customer.html.erb to render it and will pass the local variable customer into the partial which will refer to the #customer instance variable in the parent view.

When do we need the instance variable and when do we need string name for a partial

I am reading Agile Rails Web Dev book and so far for partials I had learned than we can call their name in a string form or if there is a collection of objects we can pass the object name and rails will figure out that it needs to loop through it as a collection.
Now I saw this code and suddenly all I had learned got confused:
<%= render #cart %>
My question is what is it #cart, Why it is not 'cart' ? And how should I have known that?
The Rails Guide suggests that when you do "render #cart", Rails introspects the model name of #cart and looks for a partial by that name in the current view path.
The implementation of render :partial actually calls to_partial_path on the passed object. A User object would by default return 'users/user'. So I'd check to see if the Cart class implements to_partial_path to return 'layouts/cart'.
I could be wrong but I believe that behind the scenes Rails will render a partial for anything that has a to_partial_path method. In this case I'm assuming #cart is an ActiveRecord object and all ActiveRecord objects respond to to_partial_path.
Try adding this to your template and see what it outputs. It should be the path of your partial.
<%= #cart.to_partial_path %>

How to Pass ActiveRecord Objects as Parameters to Helper Methods in Rails

Many helper methods, such as redirect_to, link_to, and url_for, can take an ActiveRecord object as a parameter instead of a hash that specifies the controller and action. I've seen the parameter passed different ways in different documentation. It sometimes gets passed as a symbol, sometimes as an instance variable, and sometimes as a local variable.
I'm confused about how the different parameter styles get expanded to return urls. I know that following REST conventions should create a url constructed of a controller and action but am unsure when Rails needs a specific parameter style to construct that url. Please help me understand the use cases for passing the ActiveRecord object as a symbol, an instance variable, or a local variable. Are there different requirements based on the method call? Or are there underlying differences in url construction?
Here are some examples:
From the API docs:
link_to "Profile", #profile
redirect_to post
<%= url_for(#workshop) %>
<%= form_for :person do |f| %> (this is described as the “generic #form_for”)
From the Ruby on Rails Guides:
<%= link_to 'New book', new_book_path %>
redirect_to(#book)
form_for(#article)
From the Rails 3 Way:
'link_to' "Help", help_widgets_path, :popup => 1
redirect_to post
url_for(timesheets_path)
form_for offer do |f|
Note: Upon further research, it seems that form_for is able to accept a local variable in the case where the calling view template passes a :locals hash as a parameter. The keys are the locals that can be used in the partial and the values are the instance variables from the template. Is that the correct understanding?
You can pass objects to link_to and url_for
You can also pass an object to a path helper
Both #post and post are objects. #post is an instance variable, and post is either a local variable or a method that will return a post
The only "weird" one is the form_for :post variety. This is old school Rails syntax, and will change this to the form_for #post syntax under the hood.

render :partial returning an array when defined in ApplicationController but string when in ApplicationHelper

This method:
def admin_buttons
render :partial => 'portable/admin/admin_buttons'
end
returns this (not html_safe) when defined in ApplicationControler and made a helper with helper_method:
["my partial's output "]
But it returns the expected string normally when defined in ApplicationHelper.
I don't understand. This is new behavior as far as I know in rails 3.1
Simply put, don't call the controller's render in helpers. It just does not work that way
render in the controller and render in a helper can't be used interchangeably. This isn't new in Rails 3.1.
When you call render in the controller it eventually does call render on the view, the result of which is stored as its response_body. The response body is eventually returned in the way Rack expects, as a string array (what you see as your output).
These links may shed some more light on how this works:
- The controller's definition of render (metal)
- It's superclass method, where response_body is set (abstract_controller)

How to pass parameters from a controller to a template?

It seems that setting multiple instance variables in a controller's action (method) causes problems in the template, only the very first instance variable got passed to the template. Is there any way to pass multiple variables to the template? Thanks! And why, in Ruby's perspective, does the template get access to the instance variables in an action?
You might also want to look into the :locals option of render. Which accepts a hash such that keys are symbols that map to local variable names in your template, and the values are the values to set those local variables to.
Example:
render "show", :locals => {:user => User.first, :some_other_variable => "Value"}
and this template
User ID: <%= user.id %><br>
Some Other Variable: <%=some_other_variable%>
will produce:
User ID: 1<br>
Some Other Variable: Value
When you're reusing partials across multiple controllers. Setting local variables with the :locals option is simpler and much more DRY than using instance variables.
you shouldn't have any problem setting multiple instance variables. For example:
class CarsController < ApplicationController
def show
#car = Car.find(:first)
#category = Category.find(:first)
end
end
will allow you to access both #car and #category in cars/show.html.erb
The reason this works is nothing inherent to ruby, but some magic built into rails. Rails automatically makes any instance variable set in a controller action available to the corresponding view.

Resources