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