JSON 哈希值的布尔属性方法 - Rails 4.2,Postgres JSONB

Boolean Attribute Methods for JSON hash values - Rails 4.2, Postgres JSONB

我有一个带有 jsonb 类型列的模型 Foo,它存储属性的布尔对:

架构:

...
add_column :foos,  :roles, :jsonb, null: :false, default:
  '{
    "bar"   : true,  
    "baz"   : false,
    "fizz"  : false
  }'
...

型号:

class Foo < ActiveRecord::Base
  store_accessor :roles, :bar, :baz, :fizz 
end

有没有办法在不为每个属性编写方法的情况下将这些属性作为布尔值访问:

能够做到:

foo = Foo.new 
foo.bar? #<= should return 'true'

不加:

class Foo < ActiveRecord::Base
...
  def bar?
    bar == true 
  end
...
end

我认为最适合您的方法是动态定义此方法,使用 define_method:

class Foo < ActiveRecord::Base
...
  %w(baz bar fizz).each do |method|
    define_method("#{method}?") do
      send(method) == true
    end
  end
...
end

ActiveRecord::Store source code 概述了增强 getter 值的最常见方式是手动定义方法(您已经知道):

All stored values are automatically available through accessors on the Active Record object, but sometimes you want to specialize this behavior. This can be done by overwriting the default accessors (using the same name as the attribute) and calling super to actually change things.

不过,您使用问号方法的情况要简单一些。只要 jsonb 键基本上是字符串,您就可以将它们重构为在末尾带有问号(并具有相应的 "questioned" getter):

add_column :foos,  :roles, :jsonb, null: :false,
  default: '{ "bar?" : true, ... }'

class Foo < ActiveRecord::Base
  store_accessor :roles, :bar?

另一种选择,正如@Ilya 所建议的那样,是用一些元编程来定义这些方法(尽管将 store_accessor :roles, method 移动到循环内部只是为了将所有与商店相关的定义放在一个地方是很诱人的) .

最后但并非最不重要的一点是修补 ActiveRecord::Store :) 只要 store_accessor 基本上只是定义 getter 和 setter 的快捷方式方法,你可以给它传递一些额外的参数,并根据它的值定义 questioned? 方法。

这是一个实现目标的单独方法:

module ActiveRecord
  module Store
    module ClassMethods
      def store_accessor_with_question(store_attribute, *keys)
        store_accessor(store_attribute, keys)
        keys.flatten.each do |key|
          define_method("#{key}?") do
            send(key) == true
          end
        end
      end

将其加载到您的 config/initializers 文件夹中,您应该能够执行以下操作:

class Foo < ActiveRecord::Base
  store_accessor_with_question :roles, :bar, :baz
end

foo = Foo.new 
foo.bar  #<= true
foo.bar? #<= true