Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

undefined method `admin?' for nil:NilClass

I followed railscast #250 Authentication from Scratch & got everthing wworking fine. Now I'm trying to only display edit & destroy links on my index page to admin user's.

I've set up mu User database with a admin boolean field & tried putting a simple if statement in the view of another model (hikingtrails) to only display certain links to admin users but I get this error when I try it out, undefined method 'admin?' for nil:NilClass

Database Schema

  create_table "users", :force => true do |t|
    t.string   "email"
    t.string   "password_digest"
    t.boolean  "admin"
    t.datetime "created_at",      :null => false
    t.datetime "updated_at",      :null => false
  end

User Model

class User < ActiveRecord::Base
  attr_accessible :email, :password, :password_confirmation#, :admin

  validates :email, :uniqueness => true

  has_secure_password
end

Application Controller

class ApplicationController < ActionController::Base
  protect_from_forgery

  # fetch the currently logged-in user record to see if the user is currently logged in
  # putting this method in ApplicationController so that it’s available in all controllers
  private
  def current_user
    # checks for a User based on the session’s user id that was stored when they logged in, and stores result in an instance variable
    @current_user ||= User.find(session[:user_id]) if session[:user_id]
  end
  # to give access to this method from all the views, the helper_method makes it a helper method
  helper_method :current_user

  # basic authorization, user must be logged in!
  def authorize
    redirect_to login_url, alert: "You must be logged in to perform this action" if current_user.nil?
  end
end

views/hikingtrails/index.html.erb

  <% if current_user.admin? %>
    <%= link_to t('.edit', :default => t("helpers.links.edit")),
              edit_hikingtrail_path(hikingtrail), :class => 'btn btn-mini' %>
    <%= link_to t('.destroy', :default => t("helpers.links.destroy")),
              hikingtrail_path(hikingtrail),
              :method => :delete,
              :data => { :confirm => t('.confirm', :default => t("helpers.links.confirm", :default => 'Are you sure?')) },
              :class => 'btn btn-mini btn-danger' %>
  <% end %>
like image 702
Holly Avatar asked Apr 06 '13 19:04

Holly


3 Answers

current_user will be nil if a user is not logged in according to your code. So you need to do this:

<% if current_user && current_user.admin? %>

or using the try method Rails adds to all objects.

<% if current_user.try(:admin?) %>
like image 93
Dogbert Avatar answered Oct 29 '22 14:10

Dogbert


as Dogbert said, current_user will be nil if the user is not logged in.

I would suggest two other alternatives:

1) in the current_user method return a special type "guest" user instead of nil. Il will be useful in case you want to do something else with it later, for example in response to some user action.
As inspiration, look at how Ryan Bates explains the Ability class of his gem cancan: link. The first thing he does is creating an unitilized (and not persisted in DB) user. An that Ability class will be instantiated each time Rails will parse an ERB template with that kind of user verification.
So, you could do:

def current_user
  @current_user ||= ((User.find(session[:user_id]) if session[:user_id]) || User.new)
end

So, if (User.find(session[:user_id]) if session[:user_id]) returns nil, the @current_user will be set to an uninitialized User with no identity in DB.

2) define a new metod just to check if the user is an admin, for example:

# your unmodified current_user implementation
def current_user
  @current_user ||= User.find(session[:user_id]) if session[:user_id]
end

def is_an_admin?
  if current_user && current_user.admin?
end

So that you can use it in this way:

<% if is_an_admin? %>
  <div>
    <%= do stuff....%>

...It might be an extra method call, but it might also make your code more readable.

like image 5
tompave Avatar answered Oct 29 '22 13:10

tompave


I know this is old, but if someone is googling the error as I did, there is actually no error in Rails Tutorial, but they forgot to highlight one thing they added.

Listing 9.54

before_action :logged_in_user, only: [:index, :edit, :update, :destroy]

Note that they added :destroy action here, not added before, which makes sure that the user is logged to perform destroy action and just then checks if he's an admin

before_action :admin_user,     only: :destroy

Correction:

As of the time of this edit 12/14/2015, the rails tutorial now adds the :destroy action in Listing 9.53. If you miss that one as I did, you will get this error.

like image 3
qnsi Avatar answered Oct 29 '22 15:10

qnsi