Active Record 属性
实例公共方法
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
创建自定义类型
用户还可以定义自己的自定义类型,只要它们响应值类型上定义的方法即可。deserialize
或 cast
方法将在您的类型对象上调用,并带有来自数据库或来自控制器的原始输入。有关预期 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
中这些方法的更多详细信息,请参见其文档。
来源:在 GitHub 上
# 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
默认值是否应该使用 cast
或 deserialize
转换。
来源:显示 | 在 GitHub 上
# 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
。
此方法将访问数据库并在必要时加载模型的模式。
来源:在 GitHub 上
# File activerecord/lib/active_record/attributes.rb, line 256
实例受保护方法
reload_schema_from_cache(*) 链接
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/attributes.rb, line 268 def reload_schema_from_cache(*) reset_default_attributes! super end