mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Docs pass for the attributes API
This commit is contained in:
parent
b71e08f8ba
commit
8c752c7ac7
2 changed files with 153 additions and 38 deletions
|
@ -1,5 +1,5 @@
|
|||
module ActiveRecord
|
||||
module Attributes # :nodoc:
|
||||
module Attributes
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
Type = ActiveRecord::Type
|
||||
|
@ -9,21 +9,31 @@ module ActiveRecord
|
|||
self.attributes_to_define_after_schema_loads = {}
|
||||
end
|
||||
|
||||
module ClassMethods # :nodoc:
|
||||
# Defines or overrides an attribute on this model. This allows customization of
|
||||
# Active Record's type casting behavior, as well as adding support for user defined
|
||||
# types.
|
||||
module ClassMethods
|
||||
# Defines an attribute with a type on this model. It will override the
|
||||
# type of existing attributes if needed. This allows control over how
|
||||
# values are converted to and from SQL when assigned to a model. It also
|
||||
# changes the behavior of values passed to
|
||||
# +ActiveRecord::Relation::QueryMethods#where+. This will let you use
|
||||
# your domain objects across much of Active Record, without having to
|
||||
# rely on implementation details or monkey patching.
|
||||
#
|
||||
# +name+ The name of the methods to define attribute methods for, and the column which
|
||||
# this will persist to.
|
||||
# +name+ The name of the methods to define attribute methods for, and the
|
||||
# column which this will persist to.
|
||||
#
|
||||
# +cast_type+ A type object that contains information about how to type cast the value.
|
||||
# See the examples section for more information.
|
||||
#
|
||||
# ==== Options
|
||||
# The options hash accepts the following options:
|
||||
# The following options are accepted:
|
||||
#
|
||||
# +default+ is the default value that the column should use on a new record.
|
||||
# +default+ The default value to use when no value is provided. If this option
|
||||
# is not passed, the previous default value (if any) will be used.
|
||||
# Otherwise, the default will be +nil+.
|
||||
#
|
||||
# +array+ (PG only) specifies that the type should be an array (see the examples below)
|
||||
#
|
||||
# +range+ (PG only) specifies that the type should be a range (see the examples below)
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
|
@ -50,11 +60,35 @@ module ActiveRecord
|
|||
# # after
|
||||
# store_listing.price_in_cents # => 10
|
||||
#
|
||||
# Users may also define their own custom types, as long as they respond to the methods
|
||||
# defined on the value type. The +type_cast+ method on your type object will be called
|
||||
# with values both from the database, and from your controllers. See
|
||||
# +ActiveRecord::Attributes::Type::Value+ for the expected API. It is recommended that your
|
||||
# type objects inherit from an existing type, or the base value type.
|
||||
# Attributes do not need to be backed by a database column.
|
||||
#
|
||||
# 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
|
||||
# }
|
||||
#
|
||||
# ==== Creating Custom Types
|
||||
#
|
||||
# Users may also define their own custom types, as long as they respond
|
||||
# to the methods defined on the value type. The +type_cast+ method on
|
||||
# your type object will be called with values both from the database, and
|
||||
# from your controllers. See +ActiveRecord::Attributes::Type::Value+ for
|
||||
# the expected API. It is recommended that your type objects inherit from
|
||||
# an existing type, or the base value type.
|
||||
#
|
||||
# class MoneyType < ActiveRecord::Type::Integer
|
||||
# def type_cast(value)
|
||||
|
@ -73,6 +107,51 @@ module ActiveRecord
|
|||
#
|
||||
# store_listing = StoreListing.new(price_in_cents: '$10.00')
|
||||
# store_listing.price_in_cents # => 1000
|
||||
#
|
||||
# For more details on creating custom types, see the documentation for
|
||||
# +ActiveRecord::Type::Value+
|
||||
#
|
||||
# ==== Querying
|
||||
#
|
||||
# When +ActiveRecord::Relation::QueryMethods#where+ is called, it will
|
||||
# use the type defined by the model class to convert the value to SQL,
|
||||
# calling +type_cast_for_database+ on your type object. For example:
|
||||
#
|
||||
# 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 +type_cast_from_database+ or
|
||||
# # +type_cast_from_user+. Assumed to be in instance of +Money+ in
|
||||
# # this case.
|
||||
# def type_cast_for_database(value)
|
||||
# value_in_bitcoins = currency_converter.convert_to_bitcoins(value)
|
||||
# value_in_bitcoins.amount
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# class Product < ActiveRecord::Base
|
||||
# currency_converter = ConversionRatesFromTheInternet.new
|
||||
# attribute :price_in_bitcoins, MoneyType.new(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
|
||||
#
|
||||
# ==== Dirty Tracking
|
||||
#
|
||||
# The type of an attribute is given the opportunity to change how dirty
|
||||
# tracking is performed. The methods +changed?+ and +changed_in_place?+
|
||||
# will be called from +ActiveRecord::AttributeMethods::Dirty+. See the
|
||||
# documentation for those methods in +ActiveRecord::Type::Value+ for more
|
||||
# details.
|
||||
def attribute(name, cast_type, **options)
|
||||
name = name.to_s
|
||||
reload_schema_from_cache
|
||||
|
@ -83,6 +162,23 @@ module ActiveRecord
|
|||
)
|
||||
end
|
||||
|
||||
# This is the low level API which sits beneath +attribute+. It only
|
||||
# accepts type objects, and will do its work immediately instead of
|
||||
# waiting for the schema to load. Automatic schema detection and
|
||||
# +attribute+ both call this under the hood. While this method is
|
||||
# provided so it can be used by plugin authors, application code should
|
||||
# probably use +attribute+.
|
||||
#
|
||||
# +name+ The name of the attribute being defined. Expected to be a +String+.
|
||||
#
|
||||
# +cast_type+ The type object to use for this attribute
|
||||
#
|
||||
# +default+ The default value to use when no value is provided. If this option
|
||||
# is not passed, the previous default value (if any) will be used.
|
||||
# Otherwise, the default will be +nil+.
|
||||
#
|
||||
# +user_provided_default+ Whether the default value should be cast using
|
||||
# +type_cast_from_user+ or +type_cast_from_database+
|
||||
def define_attribute(
|
||||
name,
|
||||
cast_type,
|
||||
|
@ -93,7 +189,7 @@ module ActiveRecord
|
|||
define_default_attribute(name, default, cast_type, from_user: user_provided_default)
|
||||
end
|
||||
|
||||
def load_schema!
|
||||
def load_schema! # :nodoc:
|
||||
super
|
||||
attributes_to_define_after_schema_loads.each do |name, (type, options)|
|
||||
if type.is_a?(Symbol)
|
||||
|
|
|
@ -1,34 +1,36 @@
|
|||
module ActiveRecord
|
||||
module Type
|
||||
class Value # :nodoc:
|
||||
class Value
|
||||
attr_reader :precision, :scale, :limit
|
||||
|
||||
# Valid options are +precision+, +scale+, and +limit+.
|
||||
def initialize(options = {})
|
||||
options.assert_valid_keys(:precision, :scale, :limit)
|
||||
@precision = options[:precision]
|
||||
@scale = options[:scale]
|
||||
@limit = options[:limit]
|
||||
def initialize(precision: nil, limit: nil, scale: nil)
|
||||
@precision = precision
|
||||
@scale = scale
|
||||
@limit = limit
|
||||
end
|
||||
|
||||
# The simplified type that this object represents. Returns a symbol such
|
||||
# as +:string+ or +:integer+
|
||||
def type; end
|
||||
def type; end # :nodoc:
|
||||
|
||||
# Type casts a string from the database into the appropriate ruby type.
|
||||
# Classes which do not need separate type casting behavior for database
|
||||
# and user provided values should override +cast_value+ instead.
|
||||
# Convert a value from database input to the appropriate ruby type. The
|
||||
# return value of this method will be returned from
|
||||
# +ActiveRecord::AttributeMethods::Read#read_attribute+. See also
|
||||
# +type_cast+ and +cast_value+
|
||||
#
|
||||
# +value+ The raw input, as provided from the database
|
||||
def type_cast_from_database(value)
|
||||
type_cast(value)
|
||||
end
|
||||
|
||||
# Type casts a value from user input (e.g. from a setter). This value may
|
||||
# be a string from the form builder, or an already type cast value
|
||||
# provided manually to a setter.
|
||||
# be a string from the form builder, or a ruby object passed to a setter.
|
||||
# There is currently no way to differentiate between which source it came
|
||||
# from.
|
||||
#
|
||||
# Classes which do not need separate type casting behavior for database
|
||||
# and user provided values should override +type_cast+ or +cast_value+
|
||||
# instead.
|
||||
# The return value of this method will be returned from
|
||||
# +ActiveRecord::AttributeMethods::Read#read_attribute+. See also:
|
||||
# +type_cast+ and +cast_value+
|
||||
#
|
||||
# +value+ The raw input, as provided to the attribute setter.
|
||||
def type_cast_from_user(value)
|
||||
type_cast(value)
|
||||
end
|
||||
|
@ -72,10 +74,23 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
# Determines whether the mutable value has been modified since it was
|
||||
# read. Returns +false+ by default. This method should not be overridden
|
||||
# directly. Types which return a mutable value should include
|
||||
# +Type::Mutable+, which will define this method.
|
||||
def changed_in_place?(*)
|
||||
# read. Returns +false+ by default. If your type returns an object
|
||||
# which could be mutated, you should override this method. You will need
|
||||
# to either:
|
||||
#
|
||||
# - pass +new_value+ to +type_cast_for_database+ and compare it to
|
||||
# +raw_old_value+
|
||||
#
|
||||
# or
|
||||
#
|
||||
# - pass +raw_old_value+ to +type_cast_from_database+ and compare it to
|
||||
# +new_value+
|
||||
#
|
||||
# +raw_old_value+ The original value, before being passed to
|
||||
# +type_cast_from_database+.
|
||||
#
|
||||
# +new_value+ The current value, after type casting.
|
||||
def changed_in_place?(raw_old_value, new_value)
|
||||
false
|
||||
end
|
||||
|
||||
|
@ -88,7 +103,11 @@ module ActiveRecord
|
|||
|
||||
private
|
||||
|
||||
def type_cast(value)
|
||||
# Convenience method. If you don't need separate behavior for
|
||||
# +type_cast_from_database+ and +type_cast_from_user+, you can override
|
||||
# this method instead. The default behavior of both methods is to call
|
||||
# this one. See also +cast_value+
|
||||
def type_cast(value) # :doc:
|
||||
cast_value(value) unless value.nil?
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in a new issue