跳至内容 跳至搜索

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

脏跟踪

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

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

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 243
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 269
      

实例保护方法

reload_schema_from_cache(*)

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