How to pass variables to partials? - ruby-on-rails

I have a common problem that normally I would solve with locals, but this time wont work.
I have the following block:
<% #user.followers.each do |follower| %>
<%= render "follower_card" %>
<% end %>
And the following partial:
<div class="row follower-card">
<%= image_tag follower.avatar_url(:smallthumb), class: "col-4 inline avatar_medium circle" %>
<ul class="col-8 inline">
<li><%= follower.name %></li>
<li><%= follower.location %></li>
<li class="lightgray small inline"><span class="bold"><%= follower.photos.count %></span> Spots</li> -
<li class="lightgray small inline"><span class="bold"><%= follower.albums.count %></span> Spotbooks</li>
</ul>
</div>
I'm getting the following error:
undefined local variable or method `follower' for #<#<Class:0x007fe791a4c8d0>:0x007fe799b14b98>

This should work (specifying the follower variable):
<%= render "follower_card", follower: follower %>
Anyway, I recommend you to use collection rendering for performance reasons. Take a look here: http://guides.rubyonrails.org/layouts_and_rendering.html. Should be something like:
<%= render "follower_card", collection: #user.followers %>
Related question: Render partial :collection => #array specify variable name
Note
Old syntax to pass variables to partials is also valid (verbose, but less elegant IMHO):
<%= render partial: "follower_card", locals: { follower: follower } %>

This is because you are not passing the variable to the partial. The scope of the partial is limited to itself, and you are not making follower available inside. You will have to use:
<% #user.followers.each do |follower| %>
<%= render "follower_card", locals: {follower: follower} %>
<% end %>
Is the proper way.

<%= render "follower_card", follower: follower %>
or
<%= render partial: "follower_card", locals: {follower: follower} %>

Related

Render in a different order on the page breaks the routing

I have Challenges containing Puns, and it is possible to vote on puns. On the Challenge Show page, all puns are rendered and show their votes count. This is currently on the view page:
<%= render #challenge.puns.reverse %>
<br>
<div id="form">
<%= render "puns/form" %>
</div>
I want the puns form to appear above the items (puns) already submitted. But if swap them around, like this:
<div id="form">
<%= render "puns/form" %>
</div>
<%= render #challenge.puns.reverse %>
I get a controller error and pun.id is not suddenly not available and the voting link breaks.
No route matches {:action=>"upvote", :challenge_id=>"9", :controller=>"puns", :id=>nil}, missing required keys: [:id]
Here is the puns/form part that is causing the issue
<% if signed_in? %>
<% if current_user.voted_for? pun %>
<%= pun.votes_for.size %>
<span class="pun_text"><%= link_to pun.pun_text, challenge_pun_path(#challenge, pun.id) %></span>
<% else %>
<%= link_to like_challenge_pun_path(#challenge, pun.id), method: :put do %>
<span class="heart_like">❤</span> <%= pun.votes_for.size %>
<% end %>
<span class="pun_text"><%= link_to pun.pun_text, challenge_pun_path(#challenge, pun.id) %></span>
<% end %>
<% end %>
It is the like_challenge_pun_path that throws an error but I cannot understand why. I am declaring #challenge again here, so it should be able to get the id.
Here is the form for the puns:
<%= form_for([#challenge, #challenge.puns.build]) do |f| %>
<span class=".emoji-picker-container">
<%= f.text_area :pun_text, placeholder: "Add pun", data: { emojiable: true } %>
</span>
<%= f.submit %>
<% end %>
Also, here is my routes setup
resources :challenges do
resources :puns do
member do
put "like", to: "puns#upvote"
put "dislike", to: "puns#downvote"
end
end
end
and the corresponding action to upvote
def upvote
#pun = #challenge.puns.find(params[:id])
#pun.upvote_by current_user
redirect_to #challenge
end
Can anyone help?
I think the code is for the puns collection.
I assume the issue is that in the form you have something like:
#challenge.puns.build
So in #challenge.puns collection appears not persisted record (without id), so path for this model cannot be generated.
As a quick solution I suggest:
<%= render #challenge.puns.reverse.select(&:persisted?) %>
UPDATE:
As I assumed you have
<%= form_for([#challenge, #challenge.puns.build]) do |f| %>
You can also try:
<%= form_for([#challenge, Pun.new]) do |f| %>
Or solve it in the controller. But need to see controller code for it.

RoR Rnder a partial cant find method

I have a classes User and Company, I want to re-use the users partial as the to render company staff.
In my CompaniesController I have:
def staff
#company=Company.find(params[:id])
#users=#company.works_fors.paginate(page: params[:page], :per_page => 10)
#title=#company.name+" staff."
end
And in my staff.html.erb template I have:
<% if #users.any? %>
<ul class="users follow">
<%= render #users %>
</ul>
<%= will_paginate %>
<% end %>
This is the works_fors/_works_for partial:
<%= render :partial => 'user' %>
Which Renders
<li>
<%= gravatar_for user, size: 50 %>
<%= link_to user.name, user %>
<% if current_user.developer? && !current_user?(user) %>
| <%= link_to "delete", user, method: :delete,
data: { confirm: "You sure?" } %>
<% end %>
</li>
However this throws an error on the user object as it cant find the method
undefined local variable or method `user' for~~
I think this is because Im calling the user object from within companies but there is a defined relationship, or do I need to redefine in companies ?
It's hard to tell, but it appears that what you call #users in your controller is in fact not a User collection, but a WorkFor collection.
#users = #company.works_fors...
What you mean is:
#works_fors = #company.works_fors...
This means that staff.html.erb is working with a works_for collection. So you should rename the variable in your template to avoid confusion.
# staff.html.erb
<% if #works_fors.any? %>
<ul class="users follow">
<%= render #works_fors %>
</ul>
<%= will_paginate #works_fors %>
<% end %>
Now we know we are rendering a works_for partial. So an instance of works_for is be available inside the partial. We need to ask it for its associated user instance, and pass it to the render method.
# works_fors/_works_for.html.erb
<%= render works_for.user %>
As a bonus, you can save yourself some queries by preloading the users.
#works_fors = #company.works_fors.includes(:user)...

Rails render partial with block and collection

This is similar to this question but with a collection:
<div class="panel-body">
<%= render layout: 'today_items_list', locals: {items: #pos} do |po| %>
<% #output_buffer = ActionView::OutputBuffer.new %>
<%= link_to "##{po.id}", po %>
<%= po.supplier.name %>
<% end %>
</div>
with partial/layout:
.tableless_cell.no_padding
%h3.no_margin_vertical= title
%ul.no_margin_vertical
- for item in items
%li= yield(item)
This renders as you expect but if I omit the weird '#output_buffer = ActionView::OutputBuffer.new', the buffer is not cleared and the list is rendered this way:
<li>
#4833Supplier name
</li>
<li>
#4833Supplier name
#4835Supplier name 2
</li>
<li>
#4833Supplier name
#4835Supplier name 2
#4840Supplier name 3
</li>
Never clearing the buffer between block invocation. What am I missing here?
(Riding on Rails 3.2.22)
I don't have much experience with Rails 3.2; with newer versions, you're able to pass collections to partials:
<%= render #pos, layout: 'today_items_list' %>
#app/views/pos/_today_items_list.html.erb
.tableless_cell.no_padding
%h3.no_margin_vertical= title
%ul.no_margin_vertical
<%= yield %>
#app/views/post/_po.html.erb
<%= link_to "##{po.id}", po %>
<%= po.supplier.name %>
You can read more about collections etc (I presume for Rails 4+) here.

Rendering a partial in rails. Specifying the partial for a resource gives an error, but not specifying a partial works fine. What gives?

I've got this working now quite accidentally, but I don't understand what causes it to break when I explicitly specify what partials are to be used for rendering the resource/s. Can anyone explain it?
The index template for my Posts controller contained the following line, which was giving me an error:
<%= render partial: 'posts', collection: #posts %>
The error (in my browser) said:
NoMethodError in Posts#index
Showing /Users/applebum/Sites/rails_projects/eventful2/app/views/posts/_posts.html.erb where line #1 raised:
undefined method `any?' for #<Post:0x000001064b21f0>
Extracted source (around line #1):
1: <% if posts.any? %>
2: <div id="posts">
3: <% posts.each do |post| %>
4: <%= render partial: "posts/post", locals: { post: post } %>
Changing the problem line to
<%= render #posts %>
made the error disappear and the posts appear (displayed nicely in markup from the appropriate partials) as I had wanted and expected them to.
Here's my _posts.html.erb partial:
<% if posts.any? %>
<div id="posts">
<% posts.each do |post| %>
<%= render partial: "posts/post", locals: { post: post } %>
<% # render :partial => "comments/comments", :collection => post.comments %>
<% end %>
</div>
<% end %>
And the _post.html.erb partial it's referring to, if that matters:
<div class="post" id="post_<%= "#{post.id}" %>">
<div class="post_inner">
<%= link_to avatar_for(post.user, size: "small"), post.user.profile %>
<div class="post_body">
<div class="user-tools">
<% if can? :destroy, post %>
<%= link_to '<i class="fi-x"></i>'.html_safe, post, :method => :delete, remote: true, :class => "delete", :confirm => "Are you sure you want to delete this post?", :title => post.content %>
<% end %>
</div>
<h5 class="username">
<%= link_to post.user.name, post.user.profile %>
<span class="timestamp">• <%= time_ago_in_words(post.created_at) %> ago</span>
</h5>
<div class="content">
<%= post.content %>
</div>
<ul class="foot">
<li>Like<li>
<li>Share</li>
</ul>
</div>
</div>
</div>
And the relevant bits from the controller:
class PostsController < ApplicationController
respond_to :html, :js # Allow for AJAX requests as well as HTML ones.
before_filter :load_postable
load_and_authorize_resource
def index
#post = Post.new
#posts = #postable.posts
end
private #################
def load_postable
klass = [User, Event].detect { |c| params["#{c.name.underscore}_id"] } # Look for which one of these there's a ***_id parameter name for
#postable = klass.find(params["#{klass.name.underscore}_id"]) # Call find on that, passing in that parameter. eg Event.find(1)
end
Can anyone explain to me what's going on here? I couldn't find anything in the Layouts and Rendering guide at rubyonrails.org.
Thanks!
Your error comes from assuming :collection and #posts mean the same thing when rendering. From Rails Docs (point 3.4.5):
Partials are very useful in rendering collections. When you pass a collection to a partial via the :collection option, the partial will be inserted once for each member in the collection
So, if you use that, for each post, you will be doing post.any? which fails as any? isn't defined for a single post.
From the same docs, you should check if render returns Nil to see if the collection is empty:
<h1>Posts</h1>
<%= render(#posts) || "There are no posts." %>
PD: Use the partial to render only one post, not all of them.
GL & HF.

when #user is blank, how to make this if statement work?

I have a header partial linked to my application.html.erb that looks like this:
<header class="unselectable">
<h2 class="float_left">
<% if #user.try(:errors).present? %>
<%= render 'shared/error_messages' %>
<% else %>
<%= #title %>
<% end %>
</h2>
<nav class="round">
<ul>
<% if logged_in? %>
<li><%= link_to "Home", current_user %></li>
<li><%= link_to "Settings", edit_user_path %></li>
<li><%= link_to "Log out", logout_path %></li>
<% else %>
<li><%= link_to "Log in", login_path %></li>
<% end %>
</ul>
</nav>
</header>
This is all well and good unless the page that loads doesn't have an #user variable (such as an about or logout page) in which case i get this:
undefined method `errors' for nil:NilClass
How can I make this work? I tried changing the logic to render the title unless #user.errors.any?but that didn't work either. I'm sure this is a simple fix but I can't figure it out!
EDIT added the fixes suggested (updated in the header partial above) and now get this error:
No route matches {:action=>"edit", :controller=>"users"} which seems to be coming from the edit_user_path
You can use the method .try(:something):
<% if #user.try(:errors).present? %>
<%= render 'shared/error_messages' %>
<% else %>
<%= #title %>
<% end %>
If #user is nil, the .try(:errors) will not raise an error.
The .present? method works for nil too:
.
>> nil.present?
#=> false
>> false.present?
#=> false
>> [].present?
#=> false
>> ''.present?
#=> false
>> 'bonjour'.present?
#=> true
>> ['bonjour'].present?
#=> true
.present? is a combination of .nil? AND .empty?
.present? is actually the opposite result of .blank?
I highly question the need for #user in your partial which is rendered in your application layout, hence its need in every page of your application. I argue that this is not good design at all because now you're relying on a global variable in all views of your application.
I think what you really mean to use is the flash. In which case you want something like this in application.html.erb.
<% flash.each do |key, value| %>
<%= content_tag :div, value, class: key %>
<% end %>
This should be set in the appropriate controller action before it's view is rendered so that the error message displys according to the request that was just made.
If your error messages come from your models, then this should be part of what actually generates these error messages. Typically this is a call to either create or update actions in the controller. In which case you should have the error_messages partial rendered with the form when your validations do not pass and the form is rendered again with the model object.
<%= form_for #user do |f| %>
<%= render 'shared/error_messages', :object => f.object %>
<!-- and so on -->
<% end %>
This way you can be confident that the #user object is always available for the partial to render without any errors since we're explicitly passing the object to the partial itself, and the partial is being rendered with the correct context. Using #users in your partial itself is the equivalent of using a global variable, hence the entire application relying on that global variable to exist.
The #user object is now accessed with a local variable in the partial as object (or whatever your decide to end up naming it).
<% object.errors.full_messages.each do |message| %>
<li>* <%= message %></li>
<% end %>
You can reformulate to like this:
<header>
<h2 class="float_left">
<% if #user.try(:errors).try(:any?) %>
<%= render 'shared/error_messages' %>
<% else %>
<%= #title %>
<% end %>
</h2>
...
</header>
Or add errors_any? to model:
class User
def errors_any?
self.try(:errors).try(:any?)
end
end
And to this:
<header>
<h2 class="float_left">
<% if #user.try(:errors_any?) %>
<%= render 'shared/error_messages' %>
<% else %>
<%= #title %>
<% end %>
</h2>
...
</header>

Resources