Ruby on Rails-Pundit gem-未定义的方法

时间:2018-07-27 14:02:11

标签: ruby-on-rails ruby

不知道为什么会在这里发生

PostsController#update中的NoMethodError nil:NilClass的未定义方法“用户”

我的用户具有admin:true,并且我无法更新其他users.posts。

我想让所有用户看到内容,但是只有注册用户才能创建内容。如果用户是管理员或该内容的创建者,他也可以对其进行编辑/更新/销毁。

post.rb

class Post < ApplicationRecord
  belongs_to :user
  has_many :comments, dependent: :destroy

  validates :title, presence: true, length: { minimum: 5 }
  validates :body, presence: true, length: { minimum: 240 }


end

user.rb

class User < ApplicationRecord
  include Encryptable

  has_many :posts, dependent: :destroy
  has_many :comments, dependent: :destroy

  has_attached_file :avatar
  validates_attachment_content_type :avatar, content_type: /\Aimage\/.*\z/
  validates :level, numericality: { less_than_or_equal_to: 100,  only_integer: true }, allow_blank: true

  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable

  before_validation :downcase_email #, :populate_iv_fields #if you need/want iv to change more often
  before_create :create_encryption_key
  after_create :save_encryption_key
  after_create :build_user_consents
  attr_encrypted :email, key: :encryption_key
  has_many :user_consents, dependent: :destroy

  #entry point for exporting user's personal information
  def self.export_personal_information(user_id)
    return nil unless User.exists?(user_id)
    descendants = ApplicationRecord.descendants.reject{|model| !model.has_personal_information?}
    result = Hash.new
    descendants.each do |descendant|
      result[descendant.name] = descendant.export_personal_information_from_model(user_id)
    end
    return result
  end
  #simplest example, we just export to json
  def self.export_personal_information_from_model(user_id)
    return User.find(user_id).to_json
  end
  #overwrite this to true for methods that you will want to be included in export_personal_information
  def self.has_personal_information?
    true
  end

  #helper method if you are creating a user from console and want them to have all consents set
  def fill_consents
    hash = Hash.new
    ConsentCategory.all.map(&:id).each do |id|
      hash[id]='on'
    end
    self.registration_consents=hash
  end

  #unfortunately not having an email field that you can just "write to" breaks
  #Devise. Below some necessary workarounds

  def email_changed?
    encrypted_email_changed?
  end

  def downcase_email
    self.email = self.email.downcase
  end

  def registration_consents=(consents)
    @consents = consents
  end

  def registration_consents
    @consents
  end

  validate                  :validate_consents_completeness
  validates_presence_of     :email, if: :email_required?
  validates_uniqueness_of   :username, allow_blank: false, if: :username_changed?
  validates_length_of       :username, within: 6..20, allow_blank: true

  validate                  :validate_email_uniqueness #validates_uniqueness_of :email, allow_blank: true, if: :email_changed?
  validates_format_of       :email, with: Devise.email_regexp, allow_blank: true, if: :email_changed?
  validates_presence_of     :password, if: :password_required?
  validates_confirmation_of :password, if: :password_required?
  validates_length_of       :password, within: Devise.password_length, allow_blank: true

  def password_required?
    !persisted? || !password.nil? || !password_confirmation.nil?
  end
  #end original devise

  def email_changed?
    self.encrypted_email_changed?
  end

  def email_required?
    true
  end

  def email_unique?
    records = Array(self.class.find_by_email(self.email))
    records.reject{|u| self.persisted? && u.id == self.id}.empty?
  end
  #unfortunately, this is an O(n) operation now that has to go through ALL the users to see if an email is unique. Sorry!
  #if you need it to ne O(1) then consider adding email_hash field instead
  def self.find_by_email(email)
    users = User.all
    users.each do |user|
      return user if user.email.downcase == email.downcase
    end
    return nil
  end

  protected

  def validate_email_uniqueness
    errors.add(:email, :taken) unless email_unique?
  end

  def validate_consents_completeness
    return if self.id #we assume that already created user has all consents
    errors.add(:registration_consents, 'Sie müssen allen erforderlichen Bedingungen zustimmen um fortzufahren.') and return unless self.registration_consents
    consents = ConsentCategory.where(mandatory: true).map(&:id)
    ids = self.registration_consents.keys.map(&:to_i) #we are relying on a fact that checkboxes that are not checked are not sent to Rails back-end at all
    consents.each do |consent_type|
      errors.add(:registration_consents, 'Sie müssen allen erforderlichen Bedingungen zustimmen um fortzufahren.') and return unless ids.include?(consent_type)
    end
  end

  def build_user_consents
    ids = registration_consents.keys
    categories = ConsentCategory.where(id: ids)
    raise 'Die vom Benutzer eingereichte Zustimmungsliste enthält Objekte, die nicht in der Datenbank vorhanden sind!' if categories.count != ids.count
    categories.each do |category|
      consent = UserConsent.new
      consent.consent_category = category
      consent.user = self
      consent.requires_revalidation = false
      consent.agreed_at = self.created_at
      consent.save!
    end
  end

end

post_policy.rb

       class PostPolicy < ApplicationPolicy
  attr_reader :user, :post

  def initialize(user, post)
    @user = user
    @post = post
  end

  def index?
    true
  end

  def create?
    user.present?
  end

  def new?
    user.present?
  end

  def update?
    return true if post.user_id == user.id || user == user.admin?
  end

  def destroy?
    return true if post.user_id == user.id || user == user.admin?
  end

  private

  def post
    record
  end
end

application_policy.rb

    class ApplicationPolicy
  attr_reader :user, :record

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

  def index?
    false
  end

  def show?
    false
  end

  def create?
    false
  end

  def new?
    create?
  end

  def update?
    user.admin?
  end

  def edit?
    user.admin?
  end

  def destroy?
    user.admin?
  end

  class Scope
    attr_reader :user, :scope

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

    def resolve
      scope.all
    end
  end
end

post_controller

    class PostsController < ApplicationController
  before_action :find_post, only: %i[destroy edit update comment_owner upvote downvote]
  after_action :verify_authorized, except: [:index, :show]

  layout '_app_nav'

  def index
    return redirect_to post_path(params[:post_id]) if params[:post_id]
    return redirect_to user_path(params[:user_id]) if params[:user_id]
    @post = Post.all.order('created_at DESC')
    @posts = Post.all.order('created_at DESC')
    @user = User.all
    @posts = if params[:suche]

             else
               Post.all.order('created_at DESC')

             end
    @comments = Comment.all

  end

  def new
    @post = Post.new

  end

  def create
    @post = current_user.posts.build(post_params)
    authorize @post
    if @post.save!
      redirect_to @post
    else
      render 'new'
    end
  end

  def show
    @post = Post.find(params[:id])
    @user = @post.user
    @comments = Comment.where(post_id: @post).order('created_at DESC').paginate(:page => params[:page], :per_page => 5)
  end

  def edit
    authorize @post
    @post = Post.find(params[:id])

  end

  def update
    @user = User.find(params[:id])
    @post = Post.find(params[:id])
    authorize @post
    @post.update(title: params[:title], body: params[:post_params])
    redirect_to post_path(@post)
  end

  def destroy
    @post = Post.find(params[:id])
    @post.destroy
    authorize @post

    redirect_to posts_path
  end

=begin
  def upvote
    @post.upvote_from current_user
    redirect_to post_path(@post)
  end

  def downvote
    @post.downvote_from current_user
    redirect_to post_path(@post)
  end
=end

  private

  def post_params
    params.require(:post).permit(:title, :body, :user_id)
  end

  def find_post
    @post = Post.find(params[:id])
  end


end

application_controller

class ApplicationController < ActionController::Base
  include Pundit
  protect_from_forgery with: :exception
  before_action :authenticate_user!
  before_action :configure_permitted_parameters, if: :devise_controller?

  rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up) do |user_params|
      user_params.permit(:username, :email, :password, :password_confirmation, registration_consents: {})
    end
  end

  private

  def user_not_authorized
    flash[:alert] = 'test'
    redirect_to(root_path)
  end

end

registrations_controller:

class RegistrationsController < Devise::RegistrationsController


  private

  def account_update_params
    params.require(:user).permit(:email, :username, :avatar, :current_password, :password, :password_confirmation)
  end
end

编辑:

使用@post(例如return true if user.present? && user == @post.user || user == user.admin?)更新我的post_policy.rb可以解决-> PostsController#update中的Pundit :: NotAuthorizedError 不允许更新?

2 个答案:

答案 0 :(得分:0)

如果您使用的是devise,则可以在application_controller.rb中使用此回调来验证您是否已登录用户。

  

before_action:authenticate_user!

避免这样做

  

user.present?

您的post_policy.rb应该看起来像这样

  class PostPolicy < ApplicationPolicy
  attr_reader :user, :post

  def initialize(user, post)
    @user = user
    @post = post
  end

  def index?
    true
  end

  def create?
    user.present?
  end

  def new?
    user.present?
  end

  def update?
    return true if record.user_id == user.id || user == user.admin?
  end

  def destroy?
    return true if record.user_id == user.id || user == user.admin?
  end

  private

  def post
    record
  end
end

此外,为避免用户可以在浏览器中输入链接,您可以对控制器执行额外的步骤,即添加以下回调和方法

class PostsController < ApplicationControl
    before_action :authorization

    private

    def authorization
      authorize(Post)
    end
end

编辑:

确保您的ApplicationController看起来像这样,以防止出现Pundit :: NotAuthorizedError错误。

class ApplicationController < ActionController::Base
  include Pundit

  before_action :authenticate_user!

  rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized

  private

  def user_not_authorized
    flash[:alert] = 'You are not authorized to perform this action.'
    redirect_to(root_path)
  end
end

答案 1 :(得分:0)

在您的PostsController中,您需要在数组中包含update方法,您可以在其中指定在执行操作之前应该authenticate_user使用哪些方法:

  before_action :authenticate_user!, only: [:create, :destroy, :new, :edit, :update]