跳至内容 跳至搜索

Active Record 属性

方法
A
D
R
T

实例公共方法

attribute(name, cast_type = nil, **options)

在模型上定义一个带类型的属性。如果需要,它将覆盖现有属性的类型。这允许控制在将值分配给模型时如何将值转换为 SQL 和从 SQL 转换。它还会改变传递给 ActiveRecord::Base.where 的值的行为。这将使您能够在 Active Record 的大部分情况下使用您的域对象,而不必依赖于实现细节或猴子补丁。

name 要为其定义属性方法的方法名称,以及将持久化的列。

cast_type 一个符号,例如 :string:integer,或者用于此属性的类型对象。如果未传递此参数,将使用先前定义的类型(如果有)。否则,类型将是 ActiveModel::Type::Value。有关提供自定义类型对象的更多信息,请参见下面的示例。

选项

接受以下选项

default 当未提供值时要使用的默认值。如果未传递此选项,将使用超类或模式中先前定义的默认值(如果有)。否则,默认值为 nil

array(仅限 PostgreSQL)指定类型应为数组(请参见下面的示例)。

range(仅限 PostgreSQL)指定类型应为范围(请参见下面的示例)。

当使用符号作为 cast_type 时,额外的选项将转发到类型对象的构造函数。

示例

可以覆盖 Active Record 检测到的类型。

# db/schema.rb
create_table :store_listings, force: true do |t|
  t.decimal :price_in_cents
end

# app/models/store_listing.rb
class StoreListing < ActiveRecord::Base
end

store_listing = StoreListing.new(price_in_cents: '10.1')

# before
store_listing.price_in_cents # => BigDecimal(10.1)

class StoreListing < ActiveRecord::Base
  attribute :price_in_cents, :integer
end

# after
store_listing.price_in_cents # => 10

还可以提供默认值。

# db/schema.rb
create_table :store_listings, force: true do |t|
  t.string :my_string, default: "original default"
end

StoreListing.new.my_string # => "original default"

# app/models/store_listing.rb
class StoreListing < ActiveRecord::Base
  attribute :my_string, :string, default: "new default"
end

StoreListing.new.my_string # => "new default"

class Product < ActiveRecord::Base
  attribute :my_default_proc, :datetime, default: -> { Time.now }
end

Product.new.my_default_proc # => 2015-05-30 11:04:48 -0600
sleep 1
Product.new.my_default_proc # => 2015-05-30 11:04:49 -0600

属性不需要由数据库列支持。

# app/models/my_model.rb
class MyModel < ActiveRecord::Base
  attribute :my_string, :string
  attribute :my_int_array, :integer, array: true
  attribute :my_float_range, :float, range: true
end

model = MyModel.new(
  my_string: "string",
  my_int_array: ["1", "2", "3"],
  my_float_range: "[1,3.5]",
)
model.attributes
# =>
  {
    my_string: "string",
    my_int_array: [1, 2, 3],
    my_float_range: 1.0..3.5
  }

将选项传递给类型构造函数

# app/models/my_model.rb
class MyModel < ActiveRecord::Base
  attribute :small_int, :integer, limit: 2
end

MyModel.create(small_int: 65537)
# => Error: 65537 is out of range for the limit of two bytes

创建自定义类型

用户还可以定义自己的自定义类型,只要它们响应值类型上定义的方法即可。deserializecast 方法将在您的类型对象上调用,并带有来自数据库或来自控制器的原始输入。有关预期 API 的信息,请参见 ActiveModel::Type::Value。建议您的类型对象从现有类型或从 ActiveRecord::Type::Value 继承。

class PriceType < ActiveRecord::Type::Integer
  def cast(value)
    if !value.kind_of?(Numeric) && value.include?('$')
      price_in_dollars = value.gsub(/\$/, '').to_f
      super(price_in_dollars * 100)
    else
      super
    end
  end
end

# config/initializers/types.rb
ActiveRecord::Type.register(:price, PriceType)

# app/models/store_listing.rb
class StoreListing < ActiveRecord::Base
  attribute :price_in_cents, :price
end

store_listing = StoreListing.new(price_in_cents: '$10.00')
store_listing.price_in_cents # => 1000

有关创建自定义类型的更多详细信息,请参见 ActiveModel::Type::Value 的文档。有关将您的类型注册为符号引用的更多详细信息,请参见 ActiveRecord::Type.register。您还可以直接传递类型对象,而不是符号。

查询

当调用 ActiveRecord::Base.where 时,它将使用模型类定义的类型将值转换为 SQL,并在您的类型对象上调用 serialize。例如

class Money < Struct.new(:amount, :currency)
end

class PriceType < ActiveRecord::Type::Value
  def initialize(currency_converter:)
    @currency_converter = currency_converter
  end

  # value will be the result of +deserialize+ or
  # +cast+. Assumed to be an instance of +Money+ in
  # this case.
  def serialize(value)
    value_in_bitcoins = @currency_converter.convert_to_bitcoins(value)
    value_in_bitcoins.amount
  end
end

# config/initializers/types.rb
ActiveRecord::Type.register(:price, PriceType)

# app/models/product.rb
class Product < ActiveRecord::Base
  currency_converter = ConversionRatesFromTheInternet.new
  attribute :price_in_bitcoins, :price, currency_converter: currency_converter
end

Product.where(price_in_bitcoins: Money.new(5, "USD"))
# SELECT * FROM products WHERE price_in_bitcoins = 0.02230

Product.where(price_in_bitcoins: Money.new(5, "GBP"))
# SELECT * FROM products WHERE price_in_bitcoins = 0.03412

脏跟踪

属性的类型有机会更改脏跟踪的执行方式。changed?changed_in_place? 方法将从 ActiveModel::Dirty 调用。有关 ActiveModel::Type::Value 中这些方法的更多详细信息,请参见其文档。

# File activerecord/lib/active_record/attributes.rb, line 13
      

define_attribute( name, cast_type, default: NO_DEFAULT_PROVIDED, user_provided_default: true )

此 API 仅接受类型对象,并且会立即执行其工作,而不是等待模式加载。虽然提供此方法以便插件作者可以使用它,但应用程序代码可能应该使用 ClassMethods#attribute

name 正在定义的属性的名称。预计为 String

cast_type 用于此属性的类型对象。

default 当未提供值时要使用的默认值。如果未传递此选项,将使用先前的默认值(如果有)。否则,默认值为 nil。也可以传递一个 proc,它将在每次需要新值时调用一次。

user_provided_default 默认值是否应该使用 castdeserialize 转换。

# File activerecord/lib/active_record/attributes.rb, line 231
def define_attribute(
  name,
  cast_type,
  default: NO_DEFAULT_PROVIDED,
  user_provided_default: true
)
  attribute_types[name] = cast_type
  define_default_attribute(name, default, cast_type, from_user: user_provided_default)
end

type_for_attribute(attribute_name, &block)

参见 ActiveModel::Attributes::ClassMethods#type_for_attribute

此方法将访问数据库并在必要时加载模型的模式。

# File activerecord/lib/active_record/attributes.rb, line 256
      

实例受保护方法

reload_schema_from_cache(*)

# File activerecord/lib/active_record/attributes.rb, line 268
def reload_schema_from_cache(*)
  reset_default_attributes!
  super
end