module ActiveRecord::Attributes::ClassMethods
Атрибуты Active Record
Открытые методы экземпляра
# File activerecord/lib/active_record/attributes.rb, line 14
Определяет атрибут с типом для этой модели. При необходимости он переопределит тип существующих атрибутов. Это позволяет контролировать, как значения преобразуются в SQL и обратно при присваивании модели. Это также изменяет поведение значений, передаваемых в ActiveRecord::Base.where. Благодаря этому вы сможете использовать объекты предметной области во многих частях Active Record, не полагаясь на детали реализации и не прибегая к monkey patching.
Параметры
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.
# 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
Этот API принимает только объекты типов и выполняет свою работу немедленно, не дожидаясь загрузки схемы. Этот метод предоставлен для авторов плагинов, однако в коде приложения, вероятно, следует использовать ClassMethods#attribute.
Параметры
name-
Имя определяемого атрибута. Ожидается, что это будет
String. cast_type-
Объект типа, используемый для этого атрибута.
default-
Значение по умолчанию, используемое, если значение не задано. Если этот параметр не передан, будет использоваться предыдущее значение по умолчанию (если оно есть). В противном случае значением по умолчанию будет
nil. Также можно передать proc, который будет вызываться каждый раз, когда требуется новое значение. - user_provided_default
-
Определяет, следует ли приводить значение по умолчанию с помощью
castилиdeserialize.
# File activerecord/lib/active_record/attributes.rb, line 269
См. ActiveModel::Attributes::ClassMethods#type_for_attribute.
При вызове этого метода будет выполнен доступ к базе данных и при необходимости загружена схема модели.
Защищённые методы экземпляра
# File activerecord/lib/active_record/attributes.rb, line 281 def reload_schema_from_cache(*) reset_default_attributes! super end
© 2004–2021 David Heinemeier Hansson
Licensed under the MIT License.