Class - Rails 4 中的元编程谓词方法

Metaprogramming predicate methods inside a Class - Rails 4

我有一个名为 User 的 class,has_many: roles, through: user_roles。我正在尝试为用户 class 中的关联角色对谓词方法进行元编程,如下所示:

class User < ActiverRecord::Base 
...      
  Role.all.pluck(:name).each do |role_name|
    define_method("#{role_name}?") do
      roles.map(&:name).include?(role_name)
    end
  end
...
end

虽然 Role.all.pluck(&:name) 执行 return 现有角色名称的数组,但 define_method 永远不会被调用,并且我的规范因未定义的方法而失败:

  ...
  subject.roles << create(:role, name: 'foo')
  expect(subject.foo?).to be true #<= undefined method `foo?' for #<User...>
  ...

您正在使用的元编程技术旨在利用这样一个事实,即当加载 class 时,仅位于 class 内的代码将被执行。因此,当 Rails 加载您的 User class、 正在执行定义谓词方法的逻辑,并且正在为任何内容创建方法Role.all returns 在加载 User 的那一刻

创建一个新角色,就像您在测试中所做的那样,因此不会影响在加载 class 和执行代码时创建的谓词方法。

您可以通过在任何目录中创建一个名为 count.rb 的文件来查看实际效果,其中包含以下代码:

$count += 1

然后,打开 irb 并输入:

irb(main):001:0> $count = 0
=> 0
irb(main):002:0> require './count'
=> true
irb(main):003:0> $count
=> 1

请注意 $count 在加载文件时递增 1。现在,如果您再次 require 该文件,则不会发生任何事情。您可以使用 load 而不是 require:

强制重新加载代码
# ...continued from above
irb(main):004:0> require './count'
=> false
irb(main):005:0> $count
=> 1
irb(main):006:0> load './foo.rb'
=> true
irb(main):007:0> $count
=> 2

因此,要让您的测试通过,您必须创建角色,然后强制重新加载 User class,然后进行断言。