0
votes

I'm brand new to rails, and am creating a simple glossary app using Rails 5 with gems Devise and Pundit. I generated an Admin model with Devise; that's the only role that needs to be logged in. I installed Pundit to create policies that would allow me to hide the "Edit," "Destroy," and "New" buttons unless logged in as admin. Glossary App Index

After adding the below policy code to my index.html.erb file to hide the 'Edit' and 'Destroy' buttons, I'm getting an "Undefined method 'current_user'" error.

<tbody>
      <% @terms.each do |term| %>
        <tr>
          <td><%= term.name %></td>
          <td><%= term.category %></td>
          <td><%= term.definition %></td>
          <td><%= link_to 'Show', term, class: 'btn btn-mini' %></td>
          <td>
            <% if policy(@term).edit? %>
              <%= link_to 'Edit', edit_term_path(term), class: 'btn btn-mini' %>
            <% end %>
          </td>
          <td>
            <% if policy(@term).destroy? %>
              <%= link_to 'Destroy', term, method: :delete, class: 'btn btn-mini', data: { confirm: 'Are you sure?' } %>
            <% end %>
          </td>
        </tr>
      <% end %>
    </tbody>

Since I didn't generate a "User" model with Devise, but generated an "Admin" model instead, it seemed logical that the error was referring to the word "user" in my new policies. So I replaced "user" with "admin" in my application_policy.rb and terms_policy.rb. Clearly I don't understand what "user" means in this error, as I'm still getting it.

I don't know what exactly you need to see, so here are my models, controllers, and policies:

application_record.rb

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
end

admin.rb

class Admin < ApplicationRecord
  has_many :terms
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :trackable, :timeoutable, :lockable
end

term.rb

class Term < ApplicationRecord
  belongs_to :admin

  def self.search(search)
    if search
      where(["name LIKE ?","%#{search}%"])
    else
      all
    end
  end

end

application_controller.rb

class ApplicationController < ActionController::Base
  include Pundit
  protect_from_forgery with: :exception
  before_action :set_current_user

  def set_current_user
    Term.current_user = current_user
  end
end

terms_controller.rb

class TermsController < ApplicationController
  before_action :set_term, only: [:show, :edit, :update, :destroy]
  before_action :authenticate_admin!, :only => [:new, :edit, :create, :destroy]

  # GET /terms
  # GET /terms.json
  def index
    @terms = Term.search(params[:search])
  end

  # GET /terms/1
  # GET /terms/1.json
  def show
  end

  # GET /terms/new
  def new
    @term = Term.new
  end

  # GET /terms/1/edit
  def edit
    @hide_edit_button = true
  end

  # POST /terms
  # POST /terms.json
  def create
    @term = Term.new(term_params)

    respond_to do |format|
      if @term.save
        format.html { redirect_to @term, notice: 'Term was successfully created.' }
        format.json { render :show, status: :created, location: @term }
      else
        format.html { render :new }
        format.json { render json: @term.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /terms/1
  # PATCH/PUT /terms/1.json
  def update
    respond_to do |format|
      if @term.update(term_params)
        format.html { redirect_to @term, notice: 'Term was successfully updated.' }
        format.json { render :show, status: :ok, location: @term }
      else
        format.html { render :edit }
        format.json { render json: @term.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /terms/1
  # DELETE /terms/1.json
  def destroy
    @term.destroy
    respond_to do |format|
      format.html { redirect_to terms_url, notice: 'Term was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_term
      @term = Term.find(params[:id])
    end

# Never trust parameters from the scary internet, only allow the white list through.
def term_params
  params.require(:term).permit(:name, :category, :definition)
end

def verify_is_admin
      (current_admin.nil?) ? redirect_to(root_path) : 
(redirect_to(root_path) unless current_admin.admin?)
    end
end

application_policy.rb

class ApplicationPolicy
  attr_reader :admin, :record

  def initialize(admin, record)
    @admin = admin
    @record = record
  end

  def index?
    false
  end

  def show?
    scope.where(:id => record.id).exists?
  end

  def create?
    false
  end

  def new?
    create?
  end

  def update?
    false
  end

  def edit?
    update?
  end

  def destroy?
    false
  end

  def scope
    Pundit.policy_scope!(admin, record.class)
  end

  class Scope
    attr_reader :admin, :scope

    def initialize(admin, scope)
      @admin = admin
      @scope = scope
    end

    def resolve
      scope
    end
  end
end

terms_policy.rb

class TermPolicy < ApplicationPolicy
  def index?
    true
  end

  def create?
    user.present?
  end

  def update?
    return true if user.present?
  end

  def edit?
    user.admin?
  end

  def destroy?
    user.admin?
  end 
end

I've tried implementing suggestions from Access to current_user from within a model in Ruby on Rails, undefined local variable or method `current_user' using devise & rails 3.2, https://code.tutsplus.com/tutorials/authorization-with-pundit--cms-28202, and a smattering of other sources. I'm sure these are all great resources, but at this stage I need something a little more targeted to my project and level of familiarity with Rails.

Let me know what else I can provide. Thanks for your help!

2
I think you have to use current_admin acording to the docs If your devise model is something other than User, replace "_user" with "_yourmodel"inye
Thanks inye. I'm guessing you're referring to the application_controller.rb, so I've replaced "_user" with "_admin". The error that I'm getting now is, "undefined method `set_current_admin' for #<TermsController:0x97d2d28> Did you mean? set_current_user" I'm not sure how to define this method in my terms_controller. If I were to guess, that would be a private method: def set_current_admin endVesper Annstas
you define the set_current_user in the aplication_controller.rb I think you have to change the nameinye

2 Answers

0
votes

Hi you can try to add some

  before_action :ensure_admin, except: [:show, :edit, :update]
  before_action :ensure_admin_or_user, only: [:edit, :update, :account]

in your controller

with method =>

  def ensure_admin
    if current_user.nil? || current_user.is_at_least?(:manager) == false
      flash[:notice] = I18n.t('must_be_admin')
      redirect_to root_path
      return true
    end
    false
  end

Hopes its helps you !

0
votes

Pundit looks to current_user in your controller to get the user record. You can configure this with the pundit_user method. Override it in application_controller.rb to return your Admin record.

def pundit_user
  Admin.find_however_you're_doing_that
end

For what it's worth, you're likely going about this a painful way if you intend to add additional roles (non-admin users). You likely want a single Devise model with roles defined as an attribute.