Creating a fallback if current_user is nil for Devise - ruby-on-rails

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.

Related

Acts as Votable errors after user logs out

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.

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 %>

Check if user voted acts as votable

I want to make some styling changes if user has voted for a photo, and I used this code (acts_as_votable docs):
<% if current_user.voted_for? #photo %>
<%= link_to like_photo_path(#photo), method: :put do %>
<button>
¡Liked!
</button>
<% end %>
<% else %>
You dont like it yet
<% end %>
But this wont work because it will show "Liked" all the time, even if I didn't click the Like button.
photos controller
def upvote
#photo = Photo.friendly.find(params[:id])
#photo.liked_by current_user
redirect_to user_photo_path(#photo.user,#photo)
end
What can it be wrong?
Add an additional condition in your if statement
<% if current_user.voted_for? #photo && #photo.liked_by current_user %>
# different text
<% elsif current_user.voted_for? #photo %>
<%= link_to like_photo_path(#photo), method: :put do %>
<button>
¡Liked!
</button>
<% end %>
<% else %>
You dont like it yet
<% end %>
This is a pretty common design pattern of basically falling through to the next logical default.
Note that if you find yourself nesting "if" statements, like so
if condition_one
if condition_two
if condition_three
# do something
else
# do something else
end
This is the same as
if condition_one && condition_two && condition_three
# do something
else
# do something else
end
If you find yourself falling into the nested ifs pattern then rethink what you're doing. You may need to decompose the code into a helper method, etc.

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 %>

what is the devise_mapping variable and how can I include it?

I'm trying to implement authentication with Devise in my Rails application (Rails 2.3.8, Devise 1.0.7, mongrel running on Windows Vista). But I'm getting the following error:
undefined local variable or method `devise_mapping' for #<ActionView::Base:0x6d63890>
This is when I use the auto-generated partial _devise_links.html.
<%- if controller_name != 'sessions' %>
<%= link_to t('devise.sessions.link'), new_session_path(resource_name) %><br />
<% end -%>
<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
<%= link_to t('devise.registrations.link'), new_registration_path(resource_name) %><br />
<% end -%>
<%- if devise_mapping.recoverable? && controller_name != 'passwords' %>
<%= link_to t('devise.passwords.link'), new_password_path(resource_name) %><br />
<% end -%>
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
<%= link_to t('devise.confirmations.link'), new_confirmation_path(resource_name) %><br />
<% end -%>
<%- if devise_mapping.lockable? && controller_name != 'unlocks' %>
<%= link_to t('devise.unlocks.link'), new_unlock_path(resource_name) %><br />
<% end -%>
Any ideas on how to fix this? I'm assuming the devise_mapping variable is not getting included in my views, but what do I do about it?
You can add helper methods to ApplicationHelper. Make sure to use the proper model name (in my case it's :user representing the User model).
def devise_mapping
Devise.mappings[:user]
end
def resource_name
devise_mapping.name
end
def resource_class
devise_mapping.to
end
Update 1/28/2014
The master branch of Devise shows that devise_mapping is now stored in the request:
# Attempt to find the mapped route for devise based on request path
def devise_mapping
#devise_mapping ||= request.env["devise.mapping"]
end
And resource_name is aliased as scope_name as well. See devise_controller.rb for more info.
I realize this question is kind of old, but I think I figured out why you can't just render that partial. The partial you're trying to render is the partial for the links that show up below the sign_in/sign_up form.
If you'd like to add those links to your application, this page on the Devise Wiki will show you how to do it, and it involves creating your own partial(s).
EDIT (2019-04-01): Copying the information from the Devise wiki page here for persistence.
How To: Add sign_in, sign_out, and sign_up links to your layout template
First add sign_in/out links, so the appropriate one will show up depending on whether the user is _already_ signed in:
# views/devise/menu/_login_items.html.erb
<% if user_signed_in? %>
<li>
<%= link_to('Logout', destroy_user_session_path, method: :delete) %>
</li>
<% else %>
<li>
<%= link_to('Login', new_user_session_path) %>
</li>
<% end %>
The method: :delete part is required if you use the default HTTP method. To change it, you will need to tell Devise this:
# config/initializers/devise.rb
# The default HTTP method used to sign out a resource. Default is :delete.
config.sign_out_via = :get
You can then omit method: :delete on all your sign_out links.
Next come the sign_up links. Again, these can be substituted with something else useful if the user is already signed in:
# views/devise/menu/_registration_items.html.erb
<% if user_signed_in? %>
<li>
<%= link_to('Edit registration', edit_user_registration_path) %>
</li>
<% else %>
<li>
<%= link_to('Register', new_user_registration_path) %>
</li>
<% end %>
Then use these templates in your layouts/application.html.erb, like this:
# layouts/application.html.erb
<ul class="hmenu">
<%= render 'devise/menu/registration_items' %>
<%= render 'devise/menu/login_items' %>
</ul>
<%= yield %>
Add some menu styling to the CSS (here for a horizontal menu):
ul.hmenu {
list-style: none;
margin: 0 0 2em;
padding: 0;
}
ul.hmenu li {
display: inline;
}
Instead of using devise_mapping, you can use Devise.mappings[:user], given that the user class in question is User.
Do you have the devise_for call in your routes.rb file?
For instance, if you are using it for your User class, then the route would be:
devise_for :users
for more info, see https://github.com/plataformatec/devise

Resources