модуль ActiveRecord::Attributes::ClassMethods
Открытые методы экземпляров
# File activerecord/lib/active_record/attributes.rb, line 208
def attribute(name, cast_type = nil, default: NO_DEFAULT_PROVIDED, **options)
name = name.to_s
name = attribute_aliases[name] || name
reload_schema_from_cache
case cast_type
when Symbol
cast_type = Type.lookup(cast_type, **options, adapter: Type.adapter_name_from(self))
when nil
if (prev_cast_type, prev_default = attributes_to_define_after_schema_loads[name])
default = prev_default if default == NO_DEFAULT_PROVIDED
else
prev_cast_type = -> subtype { subtype }
end
cast_type = if block_given?
-> subtype { yield Proc === prev_cast_type ? prev_cast_type[subtype] : prev_cast_type }
else
prev_cast_type
end
end
self.attributes_to_define_after_schema_loads =
attributes_to_define_after_schema_loads.merge(name => [cast_type, default])
end Определяет атрибут с типом для данной модели. Он переопределит тип существующих атрибутов при необходимости. Это позволяет контролировать, как значения преобразуются в SQL и из SQL при присвоении модели. Также меняет поведение значений, передаваемых в ActiveRecord::Base.where. Это позволит использовать ваши доменные объекты во многих частях Active Record без необходимости полагаться на детали реализации или модификацию класса.
name Имя методов для определения методов атрибутов и столбец, в который это будет сохранено.
cast_type Символ, такой как :string или :integer, или объект типа, используемый для этого атрибута. См. примеры ниже для получения дополнительной информации о предоставлении пользовательских объектов типа.
Параметры
Принимаются следующие параметры:
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 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 < 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(: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 253 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 оба вызывают это под капотом. Хотя этот метод предоставляется для использования разработчикам плагинов, код приложения, вероятно, должен использовать ClassMethods#attribute.
name Имя определяемого атрибута. Ожидается, что это String.
cast_type Объект типа, используемый для этого атрибута.
default Значение по умолчанию, используемое, когда значение не указано. Если этот параметр не передан, используется предыдущее значение по умолчанию (если таковое есть). В противном случае значением по умолчанию будет nil. Также можно передать процедуру, которая будет вызываться каждый раз при необходимости нового значения.
user_provided_default Указывает, должно ли значение по умолчанию преобразовываться с помощью cast или deserialize.
© 2004–2021 David Heinemeier Hansson
Licensed under the MIT License.