I'm trying to make an ajax call but get a 500 (Internal Server Error) due to a template error. Here's what I get in my server logs:
ActionView::Template::Error (undefined local variable or method `dish' for #<#<Class:0x007fcd62f1b008>:0x007fcd6516bab8>):
1: <% if current_user.liked? dish %>
2: <%= link_to "Unlike", unlike_restaurant_dish_path(#restaurant, dish), method: :put, remote: true %>
3: <% else %>
4: <%= link_to "Like", like_restaurant_dish_path(#restaurant, dish), method: :put, remote: true %>
app/views/restaurants/dish_partials/_like_toggle.html.erb:1:in `_app_views_restaurants_dish_partials__like_toggle_html_erb___2598971629205628451_70260057733340'
app/views/dishes/like.js.erb:1:in `_app_views_dishes_like_js_erb__1312876563634492607_70260057701620'
app/controllers/dishes_controller.rb:6:in `like'
From what I understand its not finding the block variable dish that I'm passing in my each block in my partial. What needs to be done so that they're recognized?
Dishes Controller
class DishesController < ApplicationController
before_action :load_restaurant_and_dish, only: [:like, :unlike, :dislike, :undislike]
def like
#dish.liked_by current_user
respond_to do |format|
format.html { redirect_to #restaurant }
format.js
end
end
...
private
def load_restaurant_and_dish
#restaurant = Restaurant.find(params[:id])
#dish = Dish.find(params[:id])
end
end
views/dishes/like.js.erb
$(".restaurant__dish").html('<%= escape_javascript(render partial: "restaurants/dish_partials/like_toggle", dish: dish) %>');
views/restaurants/dish_partials/_dishes.html.erb
<% #dishes.each do |dish| %>
<div class="restaurant__dish">
<b><%= dish.name %></b>
<%= render "restaurants/dish_partials/like_toggle", dish: dish %>
</div>
<% end %>
views/restaurants/dish_partials/_like_toggle.html.erb
<% if current_user.liked? dish %>
<%= link_to "Unlike", unlike_restaurant_dish_path(#restaurant, dish), method: :put, remote: true %>
<% else %>
<%= link_to "Like", like_restaurant_dish_path(#restaurant, dish), method: :put, remote: true %>
<% end %>
<% if current_user.disliked? dish %>
<%= link_to "Undislike", undislike_restaurant_dish_path(#restaurant, dish), method: :put, remote: true %>
<% else %>
<%= link_to "Dislike", dislike_restaurant_dish_path(#restaurant, dish), method: :put, remote: true %>
<% end %>
views/dishes/like.js.erb
$(".restaurant__dish").html('<%= escape_javascript(render "restaurants/dish_partials/like_toggle", dish: #dish) %>');
or
$(".restaurant__dish").html('<%= escape_javascript(render partial: "restaurants/dish_partials/like_toggle", locals: { dish: #dish }) %>');
you're missing # before dish variable.
Related
I'm creating a prayer website with the ability to comment on a public prayer. When I try to create a comment on a prayer, it returns four errors:
Prayer must exist
User must exist
User can't be blank
Prayer can't be blank
And then every comment creation form on the page is automatically filled in with the text I put in the first comment box, and all of them have the same 4 errors on them. I tried to use the hidden_field_tag to put in the comment form for the right user id and prayer id but they aren't put into the hash for the new comment object, they are separate.
This is the debug stuff at the bottom of the page:
#<ActionController::Parameters {"authenticity_token"=>"abcdefg", "comment"=>#<ActionController::Parameters {"content"=>"Comment comment 1 2 3"} permitted: false>, "user_id"=>"1", "prayer_id"=>"301", "commit"=>"Comment", "controller"=>"comments", "action"=>"create"} permitted: false>
controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController
def home
if logged_in?
#prayer = current_user.prayers.build
#comment = current_user.comments.build
#feed_items = current_user.feed.paginate(page: params[:page])
end
end
end
controllers/comments_controller.rb
class CommentsController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy]
before_action :correct_user, only: :destroy
def create
#comment = Comment.create(comment_params)
if #comment.save
flash[:success] = "Comment created!"
redirect_to root_url
else
#feed_items = current_user.feed.paginate(page: params[:page])
render 'static_pages/home', status: :unprocessable_entity
end
end
def destroy
#comment.destroy
flash[:success] = "Comment deleted"
redirect_back_or_to( root_url, status: :see_other )
end
private
def comment_params
params.require(:comment).permit(:content, :prayer_id, :user_id)
end
def correct_user
#comment = current_user.comments.find_by(id: params[:id])
redirect_to root_url, status: :see_other if #comment.nil?
end
end
models/comment.rb
class Comment < ApplicationRecord
belongs_to :prayer
belongs_to :user
default_scope -> { order( created_at: :desc) }
validates :user_id, presence: true
validates :prayer_id, presence: true
validates :content, presence: true, length: { maximum: 140 }
end
views/comments/_comment.html.erb
<li id="comment-<%= comment.id %>">
<%= link_to gravatar_for(comment.user, size: 30), comment.user %>
<span class="user"><%= link_to comment.user.name, comment.user %></span>
<span class="comment-content"><%= comment.content %></span>
<span class="timestamp">
Posted <%= time_ago_in_words(comment.created_at) %> ago.
<% if current_user?(comment.user) %>
<%= link_to "delete comment", comment, data: { "turbo-method": :delete,
"turbo-confirm": "Are you sure?"} %>
<% end %>
</span>
</li>
views/prayers/_prayer.html.erb
<li id="prayer-<%= prayer.id %>">
<%= link_to gravatar_for(prayer.user, size: 50), prayer.user %>
<span class="user"><%= link_to prayer.user.name, prayer.user %></span>
<span class="content">
<%= prayer.content %>
<% if prayer.image.attached? %>
<%= image_tag prayer.image.variant(:display) %>
<% end %>
</span>
<span class="timestamp">
Posted <%= time_ago_in_words(prayer.created_at) %> ago.
<% if current_user?(prayer.user) %>
<%= link_to "delete", prayer, data: { "turbo-method": :delete,
"turbo-confirm": "Are you sure?"} %>
<% end %>
</span>
<span>
<%= render 'shared/comment_form', prayer_id: prayer.id %>
</span>
<span>
<% if prayer.comments.any? %>
<ol class="comments">
<% prayer.comments.each do |comment| %>
<%= render comment %>
<% end %>
</ol>
<% end %>
</span>
</li>
** views/shared/_comment_form.html.erb * **
<%= form_with(model: #comment) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<center>
<div class="field">
<%= f.text_area(:content, placeholder: "Comment on this prayer...") %>
</div>
<div><%= hidden_field_tag :user_id, #user.id %></div>
<div><%= hidden_field_tag :prayer_id, prayer_id %></div>
<%= f.submit "Comment", class: "btn btn-primary" %>
</center>
<% end %>
views/shared/_error_messages.html.erb
<% if object != nil && object.errors.any? %>
<div id="error_explanation">
<div class="alert alert-danger">
The form contains <%= pluralize(object.errors.count, "error") %>.
</div>
<ul>
<% object.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
views/shared/_feed.html.erb
<% if #feed_items.any? %>
<ol class="prayers">
<%= render #feed_items %>
</ol>
<%= will_paginate #feed_items,
params: { controller: :static_pages, action: :home } %>
<% end %>
With help from Maxence, I discovered I need to use f.hidden_field versus hidden_field_tag
so I changed the views/shared/_comment_form.html.erb to the following:
<%= form_with(model: #comment) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<center>
<div class="field">
<%= f.text_area(:content, placeholder: "Comment on this prayer...") %>
</div>
<div><%= f.hidden_field :user_id, value: #user.id %></div>
<div><%= f.hidden_field :prayer_id, value: prayer_id %></div>
<%= f.submit "Comment", class: "btn btn-primary" %>
</center>
<% end %>
I am allowing the dispenser to approve the dispenser_review using a remote link in rails. The approval in the DB is working, but the updating of the link on the page is not.
Rendered dispenser_reviews/update_approved_review.js.erb (1.6ms)
Completed 500 Internal Server Error in 22.4ms
NoMethodError - undefined method `keys' for #<Array:0x0000000e632500>:
actionpack (3.2.17) lib/action_view/renderer/partial_renderer.rb:330:in `find_partial'
dispensers/show.html.erb
<% #dispenser_reviews.all.each do |review| %>
<p id="approval<%= review.id %>"><%= render :partial => 'approve_review', locals: { review: review } %></p>
<% end %>
dispensers/_approve_review
<% if review.approved %>
<i class="icon-check-sign"></i>
<% else %>
<%= link_to "Approve", approve_dispenser_review_path(review), :remote=>true %> <%= link_to "Approve with response", "#" %>
<% end %>
dispenser_reviews_controller.rb
def approve_dispenser_review
#review = DispenserReview.find(params[:id])
#review.approved = true
#review.save
respond_to do |format|
format.js { render :action => "update_approved_review", :layout => false }
end
end
dispenser_reviews/update_approved_review.js.erb
$("#approval<%= #review.id %>").html("<%= escape_javascript(render(:partial => 'dispensers/approve_review', locals: [review: #review] )) %>")
Update dispenser_reviews/update_approved_review.js.erb as
$("#approval<%= #review.id %>").html("<%= escape_javascript(render(:partial => 'dispensers/approve_review', locals: {review: #review} ))
locals: {review: #review} and not locals:[review: #review].
Pass a Hash not an Array of Hash.
We have a tags resource:
resources :tags, only: [:index, :show, :update, :destroy]
And I'm trying to copy the functionality of the tags/tag partial:
<%= render partial: 'tags/tag', collection: tags %> ## <~ my copy ##
<%= render partial: 'collections/collection', collection: collections %>
<%= render partial: 'saved_searches/saved_search', collection: saved_searches %>
<%= render partial: 'tags/tag', collection: tags %>
<%= render feeds %>
Here is the _tag.html.erb partial:
<% hide_drawer = (session[:view_mode] == 'view_unread' && tag.unread_count == 0 && !tag.user_feeds.any?) ? true : false %>
<li data-tag-id="<%= tag.id %>" data-feed-id="tag-<%= tag.id %>" class="<%= selected("tag_#{tag.id}") %>">
<%= link_to tag_path(tag.id), remote: true, class: 'feed-link tag-link', data: { behavior: 'selectable reset_entry_position show_entries open_item feed_link', mark_read: {type: 'tag', data: tag.id, message: "Mark #{tag.name} as read?"}.to_json } do %>
<span class="favicon-wrap">
<span class="favicon favicon-tag"></span>
</span>
<%= content_tag :span, tag.unread_count, class: 'count' + hide_count(tag.unread_count) %>
<% unless hide_drawer %>
<%= render partial: 'tags/tag_visibility', locals: {user: #user, tag: tag} %>
<% end %>
<%= tag.name %>
<% end %>
<div class="drawer <%= hide_drawer ? ' hide' : '' %>" data-hidden="<%= drawer_visible?(tag.id) ? 'false' : 'true' %>" style="<%= drawer_visible?(tag.id) ? '' : 'height: 0;' %>">
<ul>
<%= render tag.user_feeds %>
</ul>
<i class="drawer-arrow"></i>
</div>
</li>
And here is the show method:
def show
#user = current_user
update_selected_feed!("tag", params[:id])
#tag = Tag.find(params[:id])
#feed_ids = Tagging.where(tag_id: #tag, user_id: #user).pluck(:feed_id)
feeds_response
#append = !params[:page].nil?
#type = 'tag'
#data = params[:id]
#collection_title = #tag.name
#collection_favicon = 'favicon-tag'
respond_to do |format|
format.js { render partial: 'shared/entries' }
end
end
What I'm trying to do is reuse the same partial and only output the tag named "Mailbox". Would I need to add some logic to the method to have the partial show only the tag named "Mailbox"? How would I tell the partial to display only a single "Mailbox" tag? Any suggestions or help would be appreciated I'm a new Rails developer.
Two ways.
Pass local
<%= render partial: 'tags/tag', locals: {tag: tags.find{|t| t.name == "Mailbox"}}%>
Pass an array with just just one tag
<%= render partial: 'tags/tag', collection: tags.collect{|t| t.name == "Mailbox"} %>
I have two models : User and Product. One user has many owned_products, and a product belongs to an owner. The products have a available:boolean.
I want to make a list of owned_products that can be toggled from available to unavailable with a button. Here is what I did, using the M. Hartl example :
app/views/shared/_owned_products_list.html.erb
<ol class="products">
<% #owned_products.each do |product| %>
<%= link_to(product.name, product) %>
<%= render 'products/available_form', product: product %>
<% end %>
</ol>
app/views/products/_available_form.html.erb
<div id="available_button_<%=product.id%>">
<% if product.available? %>
<%= form_for(product, remote: true) do |f| %>
<div><%= f.hidden_field :available, value: nil %></div>
<%= f.submit t('product.available.undo'), class: "btn btn-small" %>
<% end %>
<% else %>
<%= form_for(product, remote: true) do |f| %>
<div><%= f.hidden_field :available, value: true %></div>
<%= f.submit t('product.available.do'), class: "btn btn-primary btn-small" %>
<% end %>
<% end %>
</div>
app/controllers/products_controller.rb
.
.
.
def update
#product = Product.find(params[:id])
if #product.update_attributes(product_params)
respond_to do |format|
format.html do
flash[:success] = t('flash.success.product.update')
redirect_to #product
end
format.js
end
else
render 'edit'
end
end
.
.
.
app/views/products/update.js.erb
$("#available_form_<%=#product.id%>").html("<%= escape_javascript(render('available_button', product: #product)) %>")
But it doesn't work : The available button is not refreshed at all :
When I click on the available (or unavailable) button, nothing change. If I refresh the whole page, it toggles, regardless the number of clicks...
Do you know where I failed ?
EDIT
OK, I got it, it was a dumb fault : my available_form id was available_button_<%=#product.id%> and not available_form_<%=#product.id%>...
So here is the right one :
app/views/products/update.js.erb
$("#available_button_<%=#product.id%>").html("<%= escape_javascript(render('available_button', product: #product)) %>")
I assume that you're using rails with jquery and jquery_ujs, make it simple:
in app/views/shared/_owned_products_list.html.erb
<ol class="products">
<% #owned_products.each do |product| %>
<%= link_to product.name, product %>
<%= link_to product.available ? 'turn off' : 'turn on', '#', :class => 'toggle-availability', :'data-id' => product.id %>
<% end %>
</ol>
in your js-script file:
$.ajaxSetup({
headers: {
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
}
});
$(document).on('click', '.toggle-availability', function() {
var el = $(this);
var status = el.text();
$.post('/products/toggle', { id: el.data('id') }, function() {
el.text(status == 'turn off' ? 'turn on' : 'turn off');
});
return false;
});
in the controller file:
def toggle
if request.xhr?
product = Product.find(params[:id])
# note here that you should also check the owner of the product
product.available = product.available.nil? ? true : false
product.save
end
render :nothing => true
end
and in your routes.rb add:
resources :products do
collection do
get :toggle
end
end
The following code does the following: If the user, voted up show him a delete vote form and a vote down form. If the user voted down, show him a vote up form and the delete vote form. Otherwise just show the vote up and vote down form (I omitted some of the code so the question doesn't get too long).
(scenario: user voted down):
posts_controller.rb:
def show
#post = Post.find(params[:id])
#replies = #post.replies.paginate(page: params[:page])
#reply = #post.replies.build
#vote = Vote.new
store_location
end
votes_controller.rb:
class VotesController < ApplicationController
before_filter :signed_in_user
def create
#votable = find_votable
# Destroy the vote first in case the user already voted
if already_voted?
#vote = #votable.votes.find_by_user_id(current_user.id)
#vote.destroy
end
#vote = #votable.votes.build(params[:vote])
#vote.user_id = current_user.id
#votable.save
respond_to do |format|
format.html { redirect_back }
format.js
end
end
def destroy
#votable = find_votable
#vote = #votable.votes.find_by_user_id(current_user.id)
#vote.destroy
#votable.reload
respond_to do |format|
format.html { redirect_back }
format.js
end
end
private
def find_votable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
def already_voted?
#votable.votes.exists?(:user_id => current_user.id)
end
end
posts/vote_form
<div class="vote-form">
<% if #post.votes.exists?(:user_id => current_user.id) %>
<% if #post.votes.find_by_user_id(current_user.id).polarity == -1 %>
<%= form_for ([#post, #vote]), remote: true do |f| %>
<%= f.hidden_field :polarity, value: 1 %>
<div class="form-actions">
<%= button_tag type: :submit, class: "btn btn-small vote" do %>
<i class="icon-thumbs-down"></i> Vote up
<% end %>
</div>
<% end %>
<%= form_for ([#post, #post.votes.find_by_user_id(current_user.id)]),
method: :delete,
remote: true do |f| %>
<div class="form-actions">
<%= button_tag type: :submit, class: "btn btn-small btn-primary unvote" do %>
<i class="icon-thumbs-up"></i> Vote down
<% end %>
</div>
<% end %>
<% end %>
votes/create.js:
$('#<%= #votable.class.name.downcase %>-<%= #votable.id %> .vote-form').html(
"<%= escape_javascript(render('shared/delete_vote')) %>"
);
shared/_delete_vote.html.erb:
<% if #votable.votes.find_by_user_id(current_user.id).polarity == -1 %>
<%= form_for ([#votable, #votable.votes.new]), remote: true do |f| %>
<div class="form-actions">
<%= button_tag type: :submit, class: "vote-down btn btn-small" do %>
<i class="icon-thumbs-up"></i> Vote up
<% end %>
</div>
<% end %>
<%= form_for ([#votable, #vote]), method: :delete, remote: true do |f| %>
<div class="form-actions">
<%= button_tag type: :submit, class: "vote-up btn btn-small btn-primary" do %>
<i class="icon-thumbs-up"></i> Vote down
<% end %>
</div>
<% end %>
<% end %>
So, now everything works fine, except that I get this error when I click vote down, and then vote up right after (without refreshing the page):
ActionView::Template::Error (undefined method `polarity' for nil:NilClass):
1: <% if #votable.votes.find_by_user_id(current_user.id).polarity == 1 %>
2: <%= form_for ([#votable, #vote]), method: :delete, remote: true do |f| %>
What could be the problem?
EDIT:
I realized that the line in the error message is the problem (not #votable):
<% if #votable.votes.find_by_user_id(current_user.id).polarity == 1 %>
Strange, I thought it was the same as
<% if #post.votes.find_by_user_id(current_user.id).polarity == 1 %>
I think the problem is here:
def destroy
#votable = find_votable
#vote = #votable.votes.find_by_user_id(current_user.id)
#vote.destroy
#votable.reload
respond_to do |format|
format.html { redirect_back }
format.js
end
end
You destroy the vote for current user, so it can not be found inside view:
#votable.votes.find_by_user_id(current_user.id).polarity
That's why polarity is called on nil. You should reword the logic of your vote manipulations.
EDIT:
As the easiest solution, you may replace
if #votable.votes.find_by_user_id(current_user.id).polarity == -1
with
if #votable.votes.find_by_user_id(current_user.id).blank?