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