Rails:隐藏 "Edit" 和 "Destroy" 除非以管理员身份登录

Rails: Hide "Edit" and "Destroy" unless logged in as Admin

我是 rails 的新手,我正在使用 Rails 5 以及 gems Devise 和 Pundit 创建一个简单的词汇表应用程序。我用 Devise 生成了一个 Admin 模型;那是唯一需要登录的角色。我安装了 Pundit 来创建允许我隐藏 "Edit," "Destroy," 和 "New" 按钮的策略,除非以管理员身份登录。 Glossary App Index

将以下策略代码添加到我的 index.html.erb 文件以隐藏 'Edit' 和 'Destroy' 按钮后,我收到 "Undefined method 'current_user'" 错误。

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

由于我没有使用 Devise 生成 "User" 模型,而是生成了 "Admin" 模型,因此错误指的是我的 "user" 一词似乎合乎逻辑新政策。所以我在 application_policy.rb 和 terms_policy.rb 中用 "admin" 替换了 "user"。显然我不明白 "user" 在此错误中的含义,因为我仍然明白。

我不知道你到底需要看什么,下面是我的模型、控制器和策略:

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

我已经尝试实施来自 Access to current_user from within a model in Ruby on Rails, , https://code.tutsplus.com/tutorials/authorization-with-pundit--cms-28202 的建议以及一些其他来源的建议。我确信这些都是很好的资源,但在这个阶段我需要一些更有针对性的项目和对 Rails.

的熟悉程度

让我知道我还能提供什么。感谢您的帮助!

您好,您可以尝试添加一些

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

在你的控制器中

使用方法=>

  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

希望对您有所帮助!

Pundit 在您的控制器中查找 current_user 以获取用户记录。您可以 configure 使用 pundit_user 方法。在 application_controller.rb 中将其覆盖到 return 您的管理员记录。

def pundit_user
  Admin.find_however_you're_doing_that
end

就其价值而言,如果您打算添加其他角色(非管理员用户),您可能会采用这种痛苦的方式。您可能需要一个将角色定义为属性的 Devise 模型。