活动记录属性
实例公共方法
attribute(name, cast_type = nil, default: NO_DEFAULT_PROVIDED, **options) 链接
在此模型上定义带有类型的属性。如果需要,它将覆盖现有属性的类型。这允许控制在分配给模型时如何将值转换为 SQL,以及如何从 SQL 转换值。它还更改了传递给 ActiveRecord::Base.where 的值的行为。这将允许你在大部分活动记录中使用你的领域对象,而无需依赖实现细节或猴子补丁。
name
为属性方法定义方法的名称,以及此方法将持续存在的列。
cast_type
一个符号,例如 :string
或 :integer
,或用于此属性的类型对象。有关提供自定义类型对象的更多信息,请参见以下示例。
选项
接受以下选项
default
当未提供值时要使用的默认值。如果未传递此选项,将使用以前的默认值(如果存在)。否则,默认值为 nil
。
array
(仅限 PostgreSQL)指定类型应为数组(请参见以下示例)。
range
(仅限 PostgreSQL)指定类型应为范围(请参见以下示例)。
当对 cast_type
使用符号时,额外的选项将转发到类型对象的构造函数。
示例
可以覆盖活动记录检测到的类型。
# 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
创建自定义类型
只要用户定义的自定义类型响应值类型上定义的方法,用户也可以定义自己的自定义类型。将对你的类型对象调用方法 deserialize
或 cast
,其中包含来自数据库或控制器中的原始输入。有关预期的 API,请参见 ActiveModel::Type::Value
。建议你的类型对象从现有类型或 ActiveRecord::Type::Value
继承
class MoneyType < 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(:money, MoneyType)
# app/models/store_listing.rb
class StoreListing < ActiveRecord::Base
attribute :price_in_cents, :money
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 MoneyType < 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(:money, MoneyType)
# app/models/product.rb
class Product < ActiveRecord::Base
currency_converter = ConversionRatesFromTheInternet.new
attribute :price_in_bitcoins, :money, 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
中这些方法的文档。
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/attributes.rb, line 208 def attribute(name, cast_type = nil, default: NO_DEFAULT_PROVIDED, **options) name = name.to_s name = attribute_aliases[name] || name reload_schema_from_cache case cast_type when Symbol cast_type = Type.lookup(cast_type, **options, adapter: Type.adapter_name_from(self)) when nil if (prev_cast_type, prev_default = attributes_to_define_after_schema_loads[name]) default = prev_default if default == NO_DEFAULT_PROVIDED else prev_cast_type = -> subtype { subtype } end cast_type = if block_given? -> subtype { yield Proc === prev_cast_type ? prev_cast_type[subtype] : prev_cast_type } else prev_cast_type end end self.attributes_to_define_after_schema_loads = attributes_to_define_after_schema_loads.merge(name => [cast_type, default]) end
define_attribute( name, cast_type, default: NO_DEFAULT_PROVIDED, user_provided_default: true ) 链接
这是位于 attribute
下面的底层 API。它只接受类型对象,并且会立即执行其工作,而不是等待架构加载。自动架构检测和 ClassMethods#attribute
都在幕后调用此方法。虽然提供了此方法以便插件作者可以使用它,但应用程序代码可能应该使用 ClassMethods#attribute
。
name
要定义的属性的名称。预期为 String
。
cast_type
要用于此属性的类型对象。
default
当未提供值时要使用的默认值。如果未传递此选项,将使用先前的默认值(如果有)。否则,默认值为 nil
。还可以传递一个 proc,并且每次需要新值时都会调用它一次。
user_provided_default
默认值是否应该使用 cast
或 deserialize
进行转换。
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/attributes.rb, line 253 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