Reputation: 1394
After two days, I've not been able to solve this problem on my own. It seems like it should be pretty simple, but I'm missing something. I'm creating a simple blog with Posts and Authors. Authors have a boolean admin column.
The line that is giving me an error right now is where I check permissions to show the edit button in a post.Current error is:
NoMethodError in Posts#show
Showing .../posts/show.html.erb where line #18 raised:
undefined method `stringify_keys' for #
posts/show.html.rb
<% if @author.can? :update, @post %>
<p><%= link_to 'Edit', edit_post_path(@post), :class => 'btn' %> <%= link_to 'Destroy', @post, confirm: 'Are you sure?', method: :delete %></p>
<% end %>
application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
rescue_from CanCan::AccessDenied do |exception|
redirect_to root_url, :alert => exception.message
end
helper_method :current_author
def current_user
@current_ability ||= Author.new(current_author)
end
end
ability.rb
class Ability
include CanCan::Ability
def initialize(author)
author ||= Author.new # guest user (not logged in)
if author.admin?
can :manage, :all
else
can :read, :all
end
end
end
Also, from what I can tell, CanCan is included in the gem file correctly.
Upvotes: 0
Views: 284
Reputation: 10423
Two things.
First, you need to have a current_user
method in your controller, on which cancan relies. If you don't have one, you may
current_user
to your current_whatever
method or @ability = Ability.new(current_whatever)
and call your can?
's on that generated ability in your views (like @ability.can? :edit, @post
).Second, your Ability
uses current_author
on both line 4 and 5, however you don't have a current_author
in your initialize
method. You have author
, though. If no Author
object is available/given to the ability's initializer, you use a non-persisted author instead (and not a AuthorAbility, unless your AuthorAbility is what current_user
returns or initialize
in your ability gets as argument). Something like this:
class Ability
include CanCan::Ability
def initialize(author)
author ||= Author.new # guest user (not logged in)
if author.admin?
can :manage, :all
else
can :read, :all
end
end
end
Edit based on the comments to keep it simpler:
Ideally you put a current_user
method inside your application controller and also make it available as helper in your views (because you may want to conditionally show/hide/change things in your view based on a logged-in user).
class ApplicationController < ActionController::Base
helper_method :current_user
def current_user
# return the currently logged in user record
end
end
If this is new to you, I suggest to have a look at an authentication gem. Devise also introduces this and authlogic describes this in its how-to and the example application. If you're doing authentication from scratch, you just need to return the user based on the session.
edit 2. You actually need to understand what you do, which IMHO is not the case at the moment. You're doing a bit of a mess here ;-)
Problem 1: current_user
needs to return the current author / user logged in (not an abilty nor fallback user nor something else) or nil
if no author is logged in. So you can e.g. do <% if current_user %>
in your view. @current_ability ||= Author.new(current_author)
is plain wrong. The fallback from the ability class needs to stay in the ability class because cancan's methods can only be applied to an object and not to nil
. So with author ||= Author.new
in your ability, you make sure that there is an object, even if no author is logged in (in which case current_author
returns nil
).
Problem 2: helper_method :current_author
actually does nothing because there is no current_author
method in your application controller. You need to somehow define current_author
.
Problem 3: In your view, you're calling can?
on an instance of Author
which is wrong. can?
is a method of Ability
. So you'd need to use @my_ability.can?
where @my_ability is an instance of e.g. Ability.new(Author.first)
. This is used if you need to work with multiple abilities or customized something, which is not the case here, so just use can?
directly without a receiver (like @author.can?
).
For testing purposes, I'd create the following:
class ApplicationController < ActionController::Base
helper_method :current_user # as defined below...
def current_user
# Return a static user here, for testing purposes,
# like @current_user = User.first or Author.first
end
end
So your current_user
returns a valid user (I hope, you need to at least have one in your database stored, though) and then can sort out the ability issues. If your ability works, you implement your authentication. As a beginner, I'd either stick to authlogic or devise.
Upvotes: 1