如何避免未定义的方法错误

时间:2015-12-07 20:40:38

标签: ruby-on-rails ruby postgresql devise

我想检查当前对象的user-id是否与当前用户的id相同,因此我只允许对登录用户进行一些操作。我正在使用Devise gem来帮助我进行身份验证。

那就是说,我想提出一个范围更广的问题。我有构建关联,至少我是这么认为的,但是当我在浏览器中打开相应的页面时,我得到错误:

undefined method 'user' for nil:NilClass

我知道当数据库中的特定对象未实例化或没有条目时,通常会发生此错误,但我使用控制台和PostgreSQL GUI工具来检查数据是否存在。

这是截屏https://www.evernote.com/shard/s233/sh/305c5194-87e0-4019-9eba-9a7f5d7a2839/7c89b4842cc6efc1/res/b7879832-7829-4fe3-b81a-386b6f81cc11/skitch.png?resizeSmall&width=832

首先要澄清我理解正确,这里有些事情是这样做的:

  1. 如果您在控制器内定义一个方法(def x),那么私人"这意味着数据仅在您的控制器中可用?
  2. 使用回调(before_action),您可以使用私有方法的数据填充应用程序的REST方法,它可能要使用?
  3. 现在我有一个图像模型:

    class Image < ActiveRecord::Base
      mount_uploader :image, ImageUploader
      belongs_to :user
      belongs_to :game, inverse_of: :images
    end
    

    用户模型如下所示:

    class User < ActiveRecord::Base
      ...
      has_many :images
      has_many :games
      validates :first_name, :last_name, presence: true
    end
    

    在相应的图像控制器中,我使用:

    class ImagesController < ApplicationController
      before_action :set_image, only: [:show, :edit, :update, :destroy]
      before_action :set_game
      before_action :authenticate_user!
      before_action :check_user
      ...
      private
    
      def set_image
        @image = Image.find(params[:id])
      end
    
      def set_game
        @game = Game.all
      end
    
      def check_user
        unless (@image.user == current_user) || (current_user.admin?)
          redirect_to root_url, alert: "Sorry but you are not allowed to visit this page."
        end
      end
    
      def image_params
        params.require(:image).permit(:title, :alt, :desc, :image, :category)
      end
    end
    

    使用@image.user方法中的check_user,我会尝试获取用户的ID。如果我只使用current_user.admin?它可行,但显然不是预期的。

    正如您在上面的屏幕截图中看到的那样,user_id字段已填充,因此我不知道为什么会出现此错误。也许我忘记了什么?

2 个答案:

答案 0 :(得分:0)

根据您的错误消息,问题出现在@image.user方法的check_user上。在此,@imagenil。你应该检查那里是否有@image.nil?

可能会将其更改为:

@image = Image.find(params[:id])
unless !@image.nil? && ((@image.user == current_user) || (current_user.admin?))

顺便说一下,你应该只检查:show, :edit, :update, :destroy中的用户:

before_action :check_user, only: [:show, :edit, :update, :destroy]

答案 1 :(得分:0)

你问的是authorization

  
      
  • 身份验证 - 用户是否存在?
  •   
  • 授权 - 用户是否有权限?
  •   

Devise提供身份验证,而授权没有Rails的“标准”流程。

您要问的是基于Rails的应用程序中授权的基线要求。解决此问题的方法是使用其中一个授权gem,即CanCanCanPundit,以确保用户可以更改所需的对象。

我个人设置授权如下:

#Gemfile
gem 'cancancan'

#app/models/ability.rb
class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= User.new # guest user (not logged in)
    can :read, Image, user_id: user.id
  end
end

这样您只需拨打can? :read, @image即可验证用户的授权。

<强>修正

您遇到的真正问题是,您尝试在不存在的变量上调用.user

for nil:NilClass

当您看到上述错误时,表示您正在对未声明的变量调用方法。

与其他编程语言不同,Ruby不会将变量视为未声明,而是nil - 使许多开发人员感到困惑。简而言之,错误意味着您试图在没有方法的变量上调用.user;解决方案是确保声明@image

-

错误似乎是由此引起的:

@image.user #-> @image does not exist

因此,您必须检查为什么 @image尚未声明。

我猜测错误是由routes引起的。您需要确保正确调用images控制器:

#config/routes.rb
resources :images

#app/controllers/images_controller.rb
class ImagesController < ApplicationController
   def show
      @image = Image.find params[:id]
      authorize! :read, @image
   end
end

这应该只允许拥有该图像的 用户查看它。您不必担心身份验证,因为这将由Devise处理。