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
Related
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.
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 %>
I want to delete something posted by a user. But it's not working the way it normally does despite the fact that I'm mirroring a setup I've used in other apps.
My html:
<div class="section-wrap">
<% #colors.each do |color| %>
<div class="swatch">
<div class="colorblock" style="background-color: <%= color.backgroundcolor %>">
</div>
<h2>PANTONE<sup>2</sup></h2>
<p><%= color.name %></p>
</div>
<div class="controls">
<% if current_user %>
<%= link_to 'Delete', #color, method: :delete %>
<% end %>
</div>
<% end %>
</div>
My controller action:
def destroy
#color.destroy
redirect_to root_path
end
My routes.rb (in which I should not have to specify the delete route):
resources :colors
delete 'colors', to: 'colors#destroy'
And then rake routes gives the route I'd expect:
DELETE /colors/:id(.:format) colors#destroy
But I get undefined method 'destroy' for nil:NilClass
It doesn't look like you've loaded #color, and instance variables are automatically initialized to nil (in contrast with local variables, which raise an exception if you use them without assigning them). Hence, Ruby complains that destroy is undefined on nil. Load #color first:
#color = Color.find(params[:id])
If you're using Color.find_by_id somewhere else in your controller (eg. a before_action), perhaps the ID just doesn't correspond to an existing Color. Unlike find, find_by_id will return nil when the record is missing.
2 issues:
#View
<% #colors.each do |color| %>
<%= link_to 'Delete', color, method: :delete %> #-> #color doesn't exist
<% end %>
#Controller
def destroy
#color = Color.find params[:id]
#color.destroy
redirect_to root_path
end
The above should work for you.
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.
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>