Ive been working to implement a like/unlike feature which works with ajax. I have already built and tested the models and associations which seem to working fine. What I am having problems with is rendering the actual like/unlike button in the view. I have been using this https://github.com/Aufree/ting/tree/master/app/controllers github projects like/unlike system as reference for building my own.
Heres my controller likeships_controller.rb
before_action :authenticate_user!
include LikeshipsHelper
def create
current_user.like!(params[:likeable_type], params[:likeable_id])
respond_to do |format|
format.html { redirect_to correct_path }
format.js
end
end
def destroy
current_user.unlike!(params[:likeable_type], params[:likeable_id])
respond_to do |format|
format.html { redirect_to correct_path }
format.js
end
end
Heres my likeship_helper.rb
module LikeshipsHelper
def likeable_likes_tag like
#count = Likeship.where("likeable_type = ? and likeable_id = ?", like[:likeable_type], like[:likeable_id]).count
if #count > 0
likes_count = #count
else
likes_count = ''
end
if current_user && current_user.liking?(like)
link_title = "unlike"
link_path_method = "delete"
fa_icon = '<i class="red heart icon"></i>'
else
link_title = "like"
link_path_method = "post"
fa_icon = '<i class="red heart empty icon"></i>'
end
if current_user.blank?
"#{likes_count}#{fa_icon}".html_safe
else
link_to "#{likes_count}#{fa_icon}".html_safe,
likeships_path(like),
title: link_title,
class: "animated zoomIn",
method: link_path_method,
remote: true
end
end
def correct_path
if params[:likeable_type].to_s == "Post"
post_path(params[:likeable_id])
end
end
end
these are my views
_like.html.erb
<i class="red heart icon">
<%= form_for(current_user.likeships.build) do |f| %>
<%= f.hidden_field :likeable_type, value: #likeable[:likeable_type] %>
<%= f.hidden_field :likeable_id, value: #likeable[:likeable_id] %>
<%= f.submit %>
<% end %>
</i>
_likes.html.erb
<% if user_signed_in? %>
<% if current_user.liking?(#likeable) %>
<%= render 'likeships/unlike' %>
<% else %>
<%= render 'likeships/like' %>
<% end %>
<% end %>
EDIT: Now if i use this in the _post.html.erb view no error is thrown but no like/unlike button appears either.
<div class="post_wrapper">
<div class="media_wrapper">
<span class="name"><%= link_to post.user.name, post.user, class: "name" %></span>
<p class="media"><%= post.body_html %></p>
<div class="date_and_like">
<span class="item" id="like-post-#{#post.id}" >
<% #likeable = Likeship.likeable(#post) %>
<%= likeable_likes_tag #likeable %>
</span>
<span class="timestamp"><%= time_ago_in_words(post.created_at) %> ago </span>
</div>
</div>
heres the create.js.erb file
$("#like-post-<%= params[:likeable_id] %>").html("<%= j(likeable_likes_tag #likeable) %>")
So I also tried to render the likes form in the post view with this
but it gave me an error that
undefined method `[]' for nil:NilClass
Ive been stumped for hours trying to figure this out any help would be awesome, also let me know if you need any additional info, Thanks.
Related
I'm working on a Pinterest-like app to learn Rails, where users can collect items and add them to collections.
I have an "Add to collection" button which opens a modal with a list of all collections of a user. This works fine on individual item show pages, but not on my item index page (which lists all items that have been posted).
This is my collections controller:
def index
#item = Item.find(params[:item_id])
#selected_collections = selected_collections(#item.id)
#collections = current_user.collections
respond_to do |format|
format.js {
render
}
end
end
My items controller:
def index
#items = Item.all.order(created_at: :desc).paginate(:page => params[:page], :per_page => 20)
#collections = current_user.collections
#selected_collections = selected_collections(item.id)
end
def show
#item = Item.find_by_id(params[:id])
return render_not_found if #item.blank?
#collections = current_user.collections
#selected_collections = selected_collections(#item.id)
end
This is my Add-to-collection button, which should be shown for each item on the index:
<%= link_to "+ Add", item_collections_path(item, format: :js), class: 'btn btn-secondary btn-30', data: {toggle: 'modal', target: '#CollectionModal'}, remote: true, format: :js %>
And part of the corresponding modal:
<div class="modal-body">
<%= render 'collections/collections_list', :collections=>#collections, :selected_collections=>#selected_collections %>
</div>
_collection_list.html.erb
<div id="collection_list">
<% #collections.each do |collection| %>
<%= render 'collections/collection_checkbox', collection: collection, selected_collections: selected_collections %>
<% end %>
<%= render 'collections/collection_checkbox', collection: Collection.new, selected_collections: selected_collections %>
<div style='display: none;' class="new_collection_template">
<%= render 'collections/collection_checkbox', collection: Collection.new, selected_collections: selected_collections %>
</div>
_collection_checkbox.html.erb
<div class="form-check">
" <%= 'checked' if selected_collections.include?(collection.id) %> <%= "disabled" unless collection.id %>>
<%= collection.collection_name %>
<% if !collection.id %>
<input type="text" class="collection-name" placeholder="New collection" />
<% end %>
On my index I get the following error message:
undefined local variable or method `item' for
ItemsController:0x007f69022aefa0 Did you mean? item_url items_url item_path #items
Screenshot of Error Message
I assume that this is because I don't have individual #item defined on my index. On the item show pages I can simply find the params in the URL. But how can I solve this problem on the index?
Thanks for your help, much appreciated :)
I've implemented a like/unlike function in my app. On the show page for an item, you can like/unlike without any issues. On a user's profile page though, where it lists all the foods they've uploaded, I have two issues: First, when I click the like button for one food, it triggers it for every food and then the button text under every food says "unlike this food", instead of only having the button text change for just the food that was clicked on. The like gets saved to the db for the correct food, but obviously I don't want the button text to change for foods that I haven't liked. Second, when I try to like a different food on the page without refreshing, the liked gets saved in the db as the original food that I clicked on, instead of the one I actually clicked on.
users.show.html.erb
<% #user.foods.each do |food| %>
<div class="row col-md-12">
<div class="food col-md-4">
<h3 class="title"><%= link_to food.title.capitalize, food_path(food.id) %></h3>
<%= link_to (image_tag (food.image.url)), food_path(food.id) %>
<%= render 'shared/vote_form', :food => food %>
<%= link_to pluralize(food.votes.count, "person"), user_votes_path(:user_id => current_user.id, :food_id => food.id) %> <%= food.votes.count == 1 ? 'wants' : 'want' %> to gobble this
</div>
<div class="description col-md-6">
<% if #user.id == current_user.id %>
<% if food.description.length == 0 %>
<p><%= link_to "Add Description", edit_food_path(food) %></p>
<% else %>
<p><%= food.description %><p>
<p><%= link_to "Edit Description", edit_food_path(food) %></p>
<% end %>
<% if food.recipe.length == 0 %>
<p><%= link_to "Add Recipe", edit_food_path(food) %></p>
<% end %>
<% else %>
<% if food.description.length == 0 %>
<p>No Description</p>
<% else %>
<p><%= food.description %><p>
<% end %>
<% if food.recipe.length == 0 %>
<p>No Recipe</p>
<% else %>
<p><%= link_to "View Recipe", food_path(food) %></p>
<% end %>
<% end %>
</div>
</div>
<% end %>
votes controller
class VotesController < ApplicationController
def index
#votes = Food.find(params[:food_id]).votes
end
def new
#vote = current_user.votes.new
end
def create
#user = current_user
#vote = current_user.votes.build(vote_params)
if #vote.save!
#food = #vote.food
respond_to do |format|
format.html {redirect_to :back, notice: "Liked!"}
format.js
end
puts #vote.food.id
else
puts "No"
redirect_back(fallback_location: root_path)
end
end
def show
#user = current_user
#vote = Vote.find(params[:id])
end
def destroy
#vote = Vote.find(params[:id])
#food = #vote.food
if #vote.destroy!
respond_to do |format|
format.html {redirect_to :back, notice: "Unliked!"}
format.js
end
else
puts "NOOOOOO"
end
end
private
def vote_params
params.require(:vote).permit(:food_id)
end
end
vote partial
<% unless current_user.votes.pluck(:food_id).include?(food.id) %>
<%= button_to "Like This Food", user_votes_path(current_user, { vote: { food_id: food.id } }), :remote => true %>
<% else %>
<% vote = food.votes.where(user_id: current_user.id).first %>
<%= button_to "Unlike This Food", user_vote_path(current_user, vote), :remote => true, method: "delete" %>
<% end %>
create.js.erb
$('.button_to').replaceWith("<%= j (render :partial => 'shared/vote_form', :locals => { :food => #food, :food_id => #food.id }) %>");
destroy.js.erb
$('.button_to').replaceWith("<%= j (render :partial => 'shared/vote_form', :locals => { :food => #food }) %>");
Take a look at your create.js.erb (destroy.js.erb has the same problem). You use select all elements with the class button_to - that is all buttons on the page. Then you call replaceWith that replaces all these buttons with the button provided as the argument. The result is that all buttons get replaced with the like button corresponding to the food you just liked. This is responsible for affecting all buttons on the page and making them refer the food you liked first.
The solution is to add an id attribute to the buttons that would include the ID of the food the button pertains to. For example, if Food with ID 1 is Lettuce then you need to add id="like-1" to the button and change $('.button_to') to $("#like-1").
i'm trying to show all the videos in my app so when i'm adding a new video and want to redirect to movie_path so i did this in my video controller:
def create
#video = Video.new(video_params)
if #video.save
flash[:success] = 'Video added!'
redirect_to movie_path(#movies)
else
render :new
end
end
it gives me an error:
undefined method `any?' for nil:NilClass
this is my show page that want to show the video:
<% if #videos.any? %>
<div class="container">
<div id="player-wrapper"></div>
<% #videos.in_groups_of(3) do |group| %>
<div class="row">
<% group.each do |video| %>
<% if video %>
<div class="col-md-4">
<div class="yt_video thumbnail">
<%= link_to image_tag("https://img.youtube.com/vi/#{video.uid}/mqdefault.jpg", alt: video.title,
class: 'img-rounded'),
"https://www.youtube.com/watch?v=#{video.uid}", target: '_blank' %>
<div class="caption">
<h5><%= video.title %></h5>
<p>Published at <%= video.published_at.strftime('%-d %B %Y %H:%M:%S') %></p>
<p>
<span class="glyphicon glyphicon glyphicon-thumbs-up"></span> <%= video.likes %>
<span class="glyphicon glyphicon glyphicon-thumbs-down"></span> <%= video.dislikes %>
</p>
</div>
</div>
</div>
<% end %>
<% end %>
</div>
<% end %>
this is movie controller:
def index
#movies = Movie.all.order(:cached_votes_score => :desc)
#movies = #movies.paginate(:page => 1, :per_page => 8)
end
def show
#reviews = Review.where(movie_id: #movie.id).order("created_at DESC")
end
def new
#movie = current_user.movies.build
#movie = Movie.new
#categories = Category.all.map{|c| [ c.name, c.id ] }
end
and this is the routes i have:
nil don't have method any? so you must protect your code against it
instead this line:
<% if #video.any? %>
write this:
<% if #video.try(:any?) %>
I think you have not defined #videos, you have defined #video, so you should try this:
<% if #video.any? %>
Took me a while but finally was able to have a user_id and friend_id associate with each other through friendships model. The problem I'm having is:
When clicking "add friend"
<% #users.each do |user| %>
<% if user.user_name != current_user.user_name %>
<% if #friendshiplink.nil? %>
<%= user.user_name %>
<%= link_to "Add Friend", friendships_path(friend_id: user.id), method: :post %>
<% else %>
<%= link_to(
("Unfollow"),
"/friendships/#{ #friendship.id }",
method: :delete ) %>
<% end %>
<% end %>
<% end %>
I get in respond is:
undefined method `id' for nil:NilClass
Extracted source (around line #86):
<% else %>
<%= link_to(
("Unfollow"),
"/friendships/#{ #friendship.id }",
method: :delete ) %>
<% end %>
<% end %>
Please let me know if I'm missing any code in order to further understand what might be the issue
Rake Routes
friendships POST /friendships(.:format) friendships#create
friendship DELETE /friendships/:id(.:format) friendships#destroy
Friendship Controller
class FriendshipsController < ApplicationController
def create
# #friendship = current_user.friendships.build
# #friendship.friend_id = params[:friend_id]
# #friendship.user_id = current_user.id
#friendship = current_user.friendships.build(friend_id: params[:friend_id])
if #friendship.save
flash[:notice] = "Added friend."
redirect_to "/users/#{ params[:friend_id] }"
else
flash[:notice] = "Unable to add friend"
redirect_to root_url
end
end
def destroy
#friendship = current_user.friendships.find(params[:id])
#friendship.destroy
flash[:notice] = "Removed friendship."
redirect_to current_user
end
private
def friendship_params
params.require(:friendship).permit(:user_id, :friend_id)
end
end
User Controller
def index
#user = User.new
#users = User.all
if current_user
#leaders = #current_user.leaders
end
end
def create
#user = User.new(user_params)
if #user.save
session[:user_id] = #user.id
cookies[:user_id] = #user.id
flash[:notice] = "Successfully Registerd"
redirect_to "/"
else
flash[:alert] = #user.errors.full_messages
redirect_to "/"
end
end
def new
#user = User.new
end
def edit
#user = User.friendly.find(params[:id])
current_user
end
def show
#users = User.all
#user = User.friendly.find(params[:id])
current_user
if #current_user
#followerlink = Follower.where(leader_id: #user.id,
follower_id: #current_user.id).first
#friendshiplink = Friendship.where(friend_id: #user.id,
user_id: #current_user.id).first
end
end
def update
#user = User.friendly.find(params[:id])
if #user.update(user_params)
flash[:notice] = "You have successfully update your information"
redirect_to "/"
else
flash[:alert] = #user.errors.full_messages
redirect_to "/"
end
end
def destroy
#user = User.friendly.find(params[:id])
#user.destroy
end
private
def user_params
params.require(:user).permit(:background, :username_or_email, :first_name, :last_name, :email, :password, :user_name, :avatar, :gender, :zip_code, :birthdate)
end
Where code is in user/view
<div id="user_profile">
<div id="profile_top">
<p class="profile_logo"></p>
<nav>
<div class="profile_loginout">
<%= link_to ("LOGOUT"), "/sessions/new",method: :delete %>
</div>
<div class="profile_user-links">
<a href="/users/<%= current_user.id %>">
<% if current_user.user_name.present? %>
<%= link_to current_user.user_name, user_path(current_user) %>
<% else %>
<%= current_user.first_name %>
<% end %>
</a>
<b class="size">|</b>
Settings
<b class="size">|</b>
</div>
</nav>
</div>
<div id="profile_to">
<div class="profile_background_picture">
<%= image_tag #user.background.url(:medium) %>
</div>
<div class="profile_picture">
<%= image_tag #user.avatar.url(:medium) %>
</div>
</div>
</div>
<% if #current_user && #user.id != #current_user.id %>
<% if !#followerlink %>
<form action="/followers" method="POST">
<input type="hidden"
name="authenticity_token"
value="<%= form_authenticity_token %>">
<input type="hidden"
name="leader_id"
value=<%= #user.id %>>
<input type="submit" value="Follow" class="followlink">
</form>
<% else %>
<div class="followlink">
<%= link_to(
("Unfollow"),
"/followers/#{ #followerlink.id }",
method: :delete ) %>
</div>
<% end %>
<% end %>
<p>Username: <%= #user.user_name %></p>
<h2>Friends</h2>
<ul>
<% for friendship in #user.friendships %>
<li>
(<%= link_to "remove", friendship, method: :delete %>)
</li>
<% end %>
</ul>
<p><%= link_to "Find Friends", users_path %></p>
<h2> Users who Have Befriended you </h2>
<ul>
<% for user in #user.inverse_friends %>
<li> <%= h user.user_name %></li>
<% end %>
</ul>
<% #users.each do |user| %>
<% if user.user_name != current_user.user_name %>
<% if #friendshiplink.nil? %>
<%= user.user_name %>
<%= link_to "Add Friend", friendships_path(friend_id: user.id), method: :post %>
<% else %>
<%= link_to "Unfollow", friendships_unfollow_path(#friendship), method: :delete %>
<% end %>
<% end %>
<% end %>
Thank you for all the help or hints in solving the issue. Greatly appreciate it.
On top of my head, not sure it's working but try it...
In routes:
delete '/friendships/:id', to: 'friendships_controller#destroy', as: 'friendships_unfollow'
In view:
<%= link_to "Unfollow", friendships_unfollow_path(#friendship), method: :delete %>
It is unclear where do you get your #friendship variable.
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?