Rails local_assign vs. local variables - ruby-on-rails

Learning from the Rails guide, I don't understand how local_assign works below:
To pass a local variable to a partial in only specific cases use the
local_assigns.
index.html.erb
<%= render user.articles %>
show.html.erb
<%= render article, full: true %>
_articles.html.erb
<h2><%= article.title %></h2>
<% if local_assigns[:full] %>
<%= simple_format article.body %>
<% else %>
<%= truncate article.body %>
<% end %>
This way it is possible to use the partial without the need to declare
all local variables.
How is the partial even being rendered by the show action when it has the name _articles, which will only show for the index action? I also don't see why you use add the option of full: true when you could have just used locals: {full:true}. What's the difference?

Regarding the use of local_assigns:
The point of this section of the guide is to show how to access optional locals in your partials. If a local variable name full may or may not be defined inside your partial, simply accessing full will cause an error when the local is not defined.
You have two options with optional locals:
First, using local_assigns[:variable_name], which will be nil when the named local wasn't provided, or the value of the variable.
Second, you can use defined?(variable_name) which will be nil when the variable is not defined, or truthy (the string "local_variable") when the local is defined.
Using defined? is simply a guard against accessing an undefined variable, you will still have to actually access the variable to get its value:
if local_assigns[:full]
if defined?(full) && full
As for your specific questions:
How is the partial even being rendered by the show action when it has the name _articles, which will only show for the index action?
This is a typo. The correct partial name is _article.html.erb. Regardless of the action, index or show, the correct partial name is the singular of the model. In the case of rendering a collection of models (as in index.html.erb), the partial should still be singularly named.
I also don't see why you use add the option of full: true when you could have just used locals: {full:true}. What's the difference?
The point is that the full: true syntax is shorter. You have two identical options:
render partial: #article, locals: { full: true }
or
render #article, full: true.
The second is significantly shorter and less redundant.

Related

How to provide defaults value for partial parameters in Rails?

I need to render a shared partial which can receive a few parameters from several views, but I don't want to pass all parameters everytime. If I call the template without all parameters, I get an error.
Is there a way to define default values for parameters, only if they haven't been defined when calling render 'name_of_partial?
This should do the trick:
<% my_param ||= 'default value' %>
A partial that contains this can be rendered with or without providing my_param.
After reading the docs, and some head scratching, I was able to define default values for parameters not passed to the template.
# in views/shared/template.html.erb
<% my_param = 'default_value' unless binding.local_variable_defined?(:my_param) %>
# Now you can call the partial with or without setting `my_param`
# Now you can call the partial without parameters...
<%= render 'shared/my_template' %>
# ...or with parameters
<%= render 'shared/my_template', my_param: 'non-default value' %>
Tested with Ruby 2.3.1 and upwards.

Rails 5.2 partial counter: "undefined local variable or method" error

Per the information box on https://guides.rubyonrails.org/layouts_and_rendering.html#using-partials:
Rails also makes a counter variable available within a partial called
by the collection, named after the title of the partial followed by
_counter. For example, when rendering a collection #products the partial _product.html.erb can access the variable product_counter
which indexes the number of times it has been rendered within the
enclosing view.
However, I am getting and error when referencing the counter in my partial. Here is the parent view:
<%= render partial: 'comments/comment_template', collection: #post.comments, as: :c %>
Here is the relevant part of _comment_template.html.erb:
<%= comment_template_counter %>
And here is the error:
undefined local variable or method `comment_template_counter' for #<#<Class: [etc.]
What am I missing?
I believe the documentation is incorrect. As pointed out by pedroadame at https://coderwall.com/p/t0no0g/render-partial-with-collection-has-hidden-index, when using the :as option, I need to use the name of the variable rather than the name of the partial.
Furthermore, if elsewhere in my app I am only rendering the partial once instead of as a collection, I need to sidestep the (same) error message by checking whether the counter is defined.
So in my partial, this now works:
<%= c_counter if defined? c_counter %>

Is it possible to render a view with "get parameters"?

Is it possible to render a view with get parameters ?
For example, something like :
render "/projects/sheets?id=43"
It's because I need to render a view which uses url parameters to work properly.
I tried many ways, but it only creates parameters that I can get only in the controller and that are not available after.
It's because I want to have a view that contains the html code of many other views.
This is my current code :
allProjects.html.erb :
<% Project.where(productchief: user.id).order(:title).each do |project| %>
<%= render "/projects/sheets?id=#{project.id}" #This doesn't work. %>
<% end %>
It's because I want to have a view that contains the content of all the other views in my website to allow the users to print all this content in one time.
I don't think it's possible to do it in the way you are trying to do it. You will need to change /projects/sheets to be a partial and render that instead and pass through local variables.
So to clarify /projects/sheets.html.erb becomes /projects/_sheets.html.erb and you would then invoke as:
<%= render partial: "/projects/sheets", locals: { :project_id = project.id } %>
Then within the partial _sheets.html.erb you can make reference to project_id
Generally you should be able to access the params in a view but unless there's a very specific reason you can't, I suggested altering your routes. I may be missing some info from your original question, but let's say you have routes as:
get 'projects/sheets', to: "projects#index", as: :projects
get 'projects/sheets/:id', to: "projects#show", as: :project
That would change your urls a little but would still leave the params available. Using the routes above, for example and going to: localhost:3000/projects/sheets/5?something_fun=geeks_are_us will give the following params:
{"something_fun"=>"geeks_are_us", "controller"=>"projects", "action"=>"show", "id"=>"5"}
And if you're looking to render multiple items via a partial, you can pass the assigned variable for (in your case) 'project' to render the views but it is a partial as you appear to be rendering from a view already.
So something like:
<% #projects.each do |project| %>
<%= render partial: project, locals: {project: project} %>
<% end %>
This would try to render projects/_project.html.erb and sending project as a variable. If you need other variables in your view, just pass them in the locals hash.
Hope this is some help.

How do I pass an incrementing counter into a partial when not rendering from an instance variable?

I have a partial: views/nodes/_node.html.erb, which I render on any view like so:
<%= render #nodes %>
When I render it like that, per the Rails Guides, I can use node_counter within the partial as a counter to keep track of various elements within the partial (or say assign an ID that increments every time the partial is rendered).
Now I want to use this same partial, but not rendered like that but rather like this:
<%= render partial: "nodes/node", locals: {node: event.eventable} %>
When I do that, I get this error:
undefined local variable or method `node_counter' for #<#<Class:0x007fa3b6ca9778>:0x007fa3b58a60c8>
That is because I never specified/passed node_counter as a local variable.
So how do I specify node_counter to start at 0 and always increment every time this partial is rendered - if I can't do it the Rails Way?
For reference, the Rails Guide says this:
Rails also makes a counter variable available within a partial called by the collection, named after the member of the collection followed by _counter. For example, if you're rendering #products, within the partial you can refer to product_counter to tell you how many times the partial has been rendered. This does not work in conjunction with the as: :value option.
In your calling view, you can use each_with_index:
<% #foo.each_with_index do |foo, index| %>
<%= render 'nodes/node', f:foo, node_counter:index %>
Then in your _nodes/node partial, node_counter will increment starting at 0.
The reason that node_counter is not available for you in your second call is that you are calling the partial with a member, not a collection.

Difference between render 'posts' and render posts (without quotes)

I was going through a video and where he was rendering something like below example:
<div>
<%= render posts %> # no quotes to posts
</div>
Though he has even created a partial with _posts.html.erb, he is calling with quotes to posts.
Though he has mentioned something about it like it calls, active record model, then class and then something...i could not understand it properly. Can anyone explain clearly this with simple example.
Render with quotes
<%=render 'post'%>
Rails is going to look in the current folder for a partial file which starts with _
Render without quotes
Is still going to use the same partial, but post in this case is a variable. I think this is translating to this:
<%= render partial: "post", locals: {any_string: your_variable(in this case is post)} %>
Again I haven't checked that.
The _post.html.erb is the partial, which can look like this:
<b><%=any_string%></b>
If your_variable which was assigned to any_string will contain the string 'My name is'
Your partial will print 'My name is' in bold.
Anyway partial are more complex, and they are used for DRY-ing (Don't repeat yourself) the code.
You can see more examples here.
With quotes then you are explicitly rendering a partial of that name. Without quotes something quite interesting is happening. posts (without quotes) is a variable that will be an activemodel list of records.
Now what the call to render does is it will look at the type of each of the models and then find the correct partial for the model (which will be the name of the model camel_cased) and render each one in turn.
EDIT:
If you have a model called Post and you assign some of those records to a variable (he uses posts I assume but I'll use foo to disambiguate) like so:
foo = Post.all
then by calling render foo the render function will see that you have an activerecord collection of records, it will then check the model associated with these records (Post in our example) and will loop through all of them rendering them to a partial called _post.html.erb with a local variable for each record assigning the record to post.
<%= render foo %>
is equivalent to:
<% foo.each do |my_post| %>
<%= render partial: "post", locals: {post: my_post} %>
<% end %>

Resources