модуль ActiveRecord::Attributes::ClassMethods
Открытые методы экземпляров
# File activerecord/lib/active_record/attributes.rb, line 194
def attribute(name, cast_type = Type::Value.new, **options)
name = name.to_s
reload_schema_from_cache
self.attributes_to_define_after_schema_loads =
attributes_to_define_after_schema_loads.merge(
name => [cast_type, options]
)
end Определяет атрибут с типом для этой модели. Он переопределит тип существующих атрибутов при необходимости. Это позволяет управлять тем, как значения преобразуются в SQL и из SQL при присвоении модели. Это также изменяет поведение значений, переданных в ActiveRecord::Base.where. Это позволит вам использовать ваши предметные области в большей части Active Record, не прибегая к деталям реализации или подмене методов.
name Имя методов для определения методов атрибутов и столбец, в который это будет сохранено.
cast_type Символ, например :string или :integer, или объект типа, который будет использоваться для этого атрибута. См. примеры ниже для получения дополнительной информации о предоставлении пользовательских объектов типа.
Параметры
Принимаются следующие параметры:
default Значение по умолчанию, которое используется, когда значение не предоставлено. Если этот параметр не передан, будет использоваться предыдущее значение по умолчанию (если таковое имеется). В противном случае значение по умолчанию будет nil.
array (только для PostgreSQL) указывает, что тип должен быть массивом (см. примеры ниже).
range (только для PostgreSQL) указывает, что тип должен быть диапазоном (см. примеры ниже).
Примеры
Тип, определяемый 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
}
Создание пользовательских типов
Пользователи также могут определять свои собственные типы, если они отвечают на методы, определенные для типа значения. Метод deserialize или cast будет вызываться на вашем объекте типа с необработанными данными из базы данных или из ваших контроллеров. См. ActiveModel::Type::Value для ожидаемой API. Рекомендуется, чтобы ваши объекты типа наследовали от существующего типа или от 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 < 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 для получения дополнительной информации.
# File activerecord/lib/active_record/attributes.rb, line 222 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. Он принимает только объекты типа и выполнит свою работу немедленно, вместо ожидания загрузки схемы. Автоматическое определение схемы и #attribute оба вызывают это под капотом. Хотя этот метод предоставляется для использования авторами плагинов, коду приложения, вероятно, следует использовать #attribute.
name Имя определенного атрибута. Ожидается, что это будет String.
cast_type Объект типа, который следует использовать для этого атрибута.
default Значение по умолчанию, которое используется, когда значение не предоставлено. Если этот параметр не передан, будет использоваться предыдущее значение по умолчанию (если таковое имеется). В противном случае значение по умолчанию будет nil. Также можно передать процедуру, которая будет вызвана один раз каждый раз, когда требуется новое значение.
user_provided_default Указывает, должно ли значение по умолчанию быть преобразовано с помощью cast или deserialize.
© 2004–2018 David Heinemeier Hansson
Licensed under the MIT License.