Acts as Votable errors after user logs out - ruby-on-rails

I have implemented act as votable, everything works well as long as a user is logged in, once a user logs out, I get the following error.
undefined method `voted_up_on?' for nil:NilClass
My index.html.erb
<p class="small-text float center ">
<% if current_user.voted_up_on?(startup) %>
<%= link_to '<i class="material-icons md-light">change_history</i>
</br>'.html_safe, downvote_startup_path(startup), method: :put %>
<small>votes: <%= startup.get_upvotes.size %></small>
<% current_user && current_user.voted_down_on?(startup) %>
<%= link_to '<i class="material-icons md-dark">change_history</i>
</br> '.html_safe, upvote_startup_path(startup), method: :put %>
<small>votes: <%= startup.get_upvotes.size %></small>
<% end %>
</p>
My Controller
before_action :find_startup, only: [:show, :edit, :destroy, :update, :upvote, :downvote]
def upvote
#startup.upvote_from current_user
redirect_to #startup, notice: "Upvoted successfully!"
end
def downvote
#startup.downvote_from current_user
redirect_to #startup, notice: "downvoted successfully!"
end
My routes
resources :startups do
member do
put :upvote
put :downvote
end
resources :comments, only: [:create, :destroy]
end
What am I missing?

In your index.html.erb you have this line of code <% if current_user.voted_up_on?(startup) %>, when you logout current_user method will be nil. Ensure that the current_user value is not nil before rendering the logic in your index.html.erb
something like
<% if current_user %>
...
# your logic that makes use of current_user object goes here
...
<% end %>

The issue is that after the user logs out you have no current_user. So the easiest solution to this would be to verify there is a current user first.
<p class="small-text float center ">
<% if current_user && current_user.voted_up_on?(startup) %>
<%= link_to '<i class="material-icons md-light">change_history</i>
</br>'.html_safe, downvote_startup_path(startup), method: :put %>
<small>votes: <%= startup.get_upvotes.size %></small>
<% end %> <-- couldn't find the closing end so maybe it goes here?
<% if current_user && current_user.voted_down_on?(startup) %>
<%= link_to '<i class="material-icons md-dark">change_history</i>
</br> '.html_safe, upvote_startup_path(startup), method: :put %>
<small>votes: <%= startup.get_upvotes.size %></small>
<% end %>
</p>
Your code was a little confusing, I think that second part was also an if conditional but maybe you forgot to write the if? Either way the fix is the check for current user in that first if conditional because a user can also access this page without being logged in, so you need to check for that, otherwise you are calling voted_up_on? on a nil object.

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.

Acts_As_Votable Gem Automatically Liking Everything

I'm using the acts_as_votable gem to like and unlike "Deals" in my Ruby on Rails project. My user is set to act_as_voter and my deal is set to acts_as_votable, but for some reason everything is set to like as soon as a new user is created, and they can't unlike the deal. For some reason my list of deals all have an unlike button and it doesn't actually do anything but refresh the page. Here's some of my code.
app/views/catalog/index.html.erb
<ul class="deals_list">
<% #deals.each do |deal| %>
<li>
<div>
...
<div class="favorite">
<% if account_signed_in? and current_account.accountable_type == "Personnel" %>
<%= image_tag("dark-favorite.png") %>
<% if deal.liked_by current_account %>
<%= link_to unlike_deal_path(deal), method: :put do %>
Unlike
<% end %>
<% else %>
<%= link_to like_deal_path(deal), method: :put do %>
Like
<% end %>
<% end %>
<% end %>
</div>
</li>
<% end %>
</ul>
app/controllers/deals_controller.rb
def like
#deal = Deal.find(params[:id])
#deal.liked_by current_account
redirect_back(fallback_location: catalog_index_url)
end
def unlike
#deal = Deal.find(params[:id])
#deal.unliked_by current_account
redirect_back(fallback_location: catalog_index_url)
end
config/routes.rb
resources :deals do
member do
put 'like', to: "deals#like"
put 'unlike', to: "deals#unlike"
end
end
Be sure and read the entire Readme because you're using the library wrong.
To check if a voter has voted on a model, you can use voted_for?. You can check how the voter voted by using voted_as_when_voted_for.
I zeroed in on your problem because I was expecting to see a "?" after the deal.liked_by call, which would indicate a boolean result (by convention, not always the case).
So use this instead:
<% if current_account.voted_for? deal %>

Creating a fallback if current_user is nil for Devise

I have instances in my app that perform actions based on the current_user courtesy of Devise. For instance:
Controller
class PostsController < ApplicationController
def like
#post.liked_by current_user
end
end
View
...
<% if current_user.liked? post %>
<%= link_to "Unlike", unlike_post_path(current_user.to_param, post), method: :put, remote: true %>
<% else %>
<%= link_to "Like", like_post_path(current_user.to_param, post), method: :put, remote: true %>
<% end %>
My problem, cross-site is if there is no current_user then I'll get a NilClass error. I'm aware of the callback before_filter :authenticate_user! but was wondering what would be the best solution where the actions are still visible in the view for both logged in/non-logged in users.
Devise has a helper for this:
<% if user_signed_in? && current_user.liked?(post) %>
...
<% else %>
...
<% end %>
If they aren't signed in, the second part of the condition will never be evaluated so no Nil error.
You can also try the helper current_user like so:
<% if !current_user.nil? && current_user.liked?(post) %>
...
<% else %>
...
<% end %>
We are basically telling rails that if the current user is NOT nil and the current user has liked the post then we should see the unlike button, else we see the like button.

Using partial with paper trail

I'm trying to put some code in a partial from my show page in /events. The code works fine when I use it in my show page, but when i extract it to a partial I'm getting a No Method Error
undefined method `event' for #<ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_PaperTrail_Version:0x007f4368823a68>
versions/_version.html.erb
<% cache version do %>
<div class="feed-item">
<h4>
<%= link_to version.item.name, version.item %>
<small>
<%= version.event + "d" %> by <%= link_to User.find(version.whodunnit).username, User.find(version.whodunnit) %>
</small>
</h4>
<ul class="list-unstyled">
<% version.changeset.each do |data| %>
<li>
<strong><%= data[0].capitalize %>:</strong>
<% if data[1][0].present? %><p class="red">- <%= data[1][0] %></p><% end %>
<p class="green">+ <%= data[1][1] %></p>
</li>
<% end %>
</ul>
</div>
<% end %>
show.html.erb
<%= render :partial => '/versions/version', :object => #versions %>
events controller
def show
#versions = #event.versions
end
events model
has_paper_trail
Any idea how i could put my code in a partial instead of having it inside my show view? Thanks.
EDIT:
I still get a No Method Error
undefined method `item' for #<ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_PaperTrail_Version:0x007f43688ee1a0>
events_controller.rb
before_action :set_event, only: [:show, :update, :destroy]
def set_event
#event = Event.find(params[:id])
end
Do you have defined the #eventvariable in the eventscontroller?
The #event.versions method has to be called on an actual event object.
For example
def show
#event = Event.find(1)
#versions = #event.versions
end

Denying unauthorized users from editing a profile

I am trying to setup so that users will get a "not authorized" message if they click edit for a profile that is not theirs. This message should of course not appear for admins since admins can edit all profiles. I previously done this on Permission.rb, however I got rid of the file to go with a more basic user roles/authorization.
I don't see how I can implement what I had previously on Permission.rb for my current files. I have tried some solutions but they don't add up. If someone could point me in the right direction that will be great. Also I am doing this all from scratch, user authentication/authorization.
index.html.erb:
<% #users.each do |user| %>
<li>
<% if current_user.admin? || current_user == #user %>
<% end %>
<%= link_to "Edit #{user} profile", user %>
| <%= link_to "delete", user, method: :delete,
data: { confirm: "You sure?"} %>
</li>
<% end %>
Why are you giving the chance for users to edit other peoples profiles?
First, you should have a if statement in your view where you show the link for the edit page. I guess this is showing up on a profile of every user, so i suppose the code in your controller is something like this:
def show
#user = User.find(params[:id])
end
Then in your view you should have something like this:
<% if current_user.admin? || current_user == #user %>
<%= link_to 'Edit Profile' , edit_user_path(#user) %>
<% end %>
There is also a case if someone tries to 'force' their way in, just like trying to type a url www.yourapplication.com/users/6/edit you could write a before_filter method in your controller:
before_filter :check_privileges, only => [:edit, :update]
and then write a method in called check_privileges
def check_privileges
unless current_user.admin? || current_user.id == params[:id]
flash[:warning] = 'not authorized!'
redirect_to root_path
end
end
EDIT: After the questioner edited his code, i'm showing the mistake:
You are putting the end too soon:
<% #users.each do |user| %>
<li>
<%= link_to user.name, user %>
<% if current_user.admin? || current_user == #user %>
<%= link_to "Edit #{user} profile", user %>
| <%= link_to "delete", user, method: :delete,
data: { confirm: "You sure?"} %>
<% end %>
</li>
<% end %>

Resources