11module ActiveRecord
2- module Attributes # :nodoc:
2+ module Attributes
33 extend ActiveSupport ::Concern
44
55 Type = ActiveRecord ::Type
@@ -9,21 +9,31 @@ module Attributes # :nodoc:
99 self . attributes_to_define_after_schema_loads = { }
1010 end
1111
12- module ClassMethods # :nodoc:
13- # Defines or overrides an attribute on this model. This allows customization of
14- # Active Record's type casting behavior, as well as adding support for user defined
15- # types.
16- #
17- # +name+ The name of the methods to define attribute methods for, and the column which
18- # this will persist to.
12+ module ClassMethods
13+ # Defines an attribute with a type on this model. It will override the
14+ # type of existing attributes if needed. This allows control over how
15+ # values are converted to and from SQL when assigned to a model. It also
16+ # changes the behavior of values passed to
17+ # +ActiveRecord::Relation::QueryMethods#where+. This will let you use
18+ # your domain objects across much of Active Record, without having to
19+ # rely on implementation details or monkey patching.
20+ #
21+ # +name+ The name of the methods to define attribute methods for, and the
22+ # column which this will persist to.
1923 #
2024 # +cast_type+ A type object that contains information about how to type cast the value.
2125 # See the examples section for more information.
2226 #
2327 # ==== Options
24- # The options hash accepts the following options:
28+ # The following options are accepted:
29+ #
30+ # +default+ The default value to use when no value is provided. If this option
31+ # is not passed, the previous default value (if any) will be used.
32+ # Otherwise, the default will be +nil+.
2533 #
26- # +default+ is the default value that the column should use on a new record.
34+ # +array+ (PG only) specifies that the type should be an array (see the examples below)
35+ #
36+ # +range+ (PG only) specifies that the type should be a range (see the examples below)
2737 #
2838 # ==== Examples
2939 #
@@ -50,11 +60,35 @@ module ClassMethods # :nodoc:
5060 # # after
5161 # store_listing.price_in_cents # => 10
5262 #
53- # Users may also define their own custom types, as long as they respond to the methods
54- # defined on the value type. The +type_cast+ method on your type object will be called
55- # with values both from the database, and from your controllers. See
56- # +ActiveRecord::Attributes::Type::Value+ for the expected API. It is recommended that your
57- # type objects inherit from an existing type, or the base value type.
63+ # Attributes do not need to be backed by a database column.
64+ #
65+ # class MyModel < ActiveRecord::Base
66+ # attribute :my_string, :string
67+ # attribute :my_int_array, :integer, array: true
68+ # attribute :my_float_range, :float, range: true
69+ # end
70+ #
71+ # model = MyModel.new(
72+ # my_string: "string",
73+ # my_int_array: ["1", "2", "3"],
74+ # my_float_range: "[1,3.5]",
75+ # )
76+ # model.attributes
77+ # # =>
78+ # {
79+ # my_string: "string",
80+ # my_int_array: [1, 2, 3],
81+ # my_float_range: 1.0..3.5
82+ # }
83+ #
84+ # ==== Creating Custom Types
85+ #
86+ # Users may also define their own custom types, as long as they respond
87+ # to the methods defined on the value type. The +type_cast+ method on
88+ # your type object will be called with values both from the database, and
89+ # from your controllers. See +ActiveRecord::Attributes::Type::Value+ for
90+ # the expected API. It is recommended that your type objects inherit from
91+ # an existing type, or the base value type.
5892 #
5993 # class MoneyType < ActiveRecord::Type::Integer
6094 # def type_cast(value)
@@ -73,6 +107,51 @@ module ClassMethods # :nodoc:
73107 #
74108 # store_listing = StoreListing.new(price_in_cents: '$10.00')
75109 # store_listing.price_in_cents # => 1000
110+ #
111+ # For more details on creating custom types, see the documentation for
112+ # +ActiveRecord::Type::Value+
113+ #
114+ # ==== Querying
115+ #
116+ # When +ActiveRecord::Relation::QueryMethods#where+ is called, it will
117+ # use the type defined by the model class to convert the value to SQL,
118+ # calling +type_cast_for_database+ on your type object. For example:
119+ #
120+ # class Money < Struct.new(:amount, :currency)
121+ # end
122+ #
123+ # class MoneyType < Type::Value
124+ # def initialize(currency_converter)
125+ # @currency_converter = currency_converter
126+ # end
127+ #
128+ # # value will be the result of +type_cast_from_database+ or
129+ # # +type_cast_from_user+. Assumed to be in instance of +Money+ in
130+ # # this case.
131+ # def type_cast_for_database(value)
132+ # value_in_bitcoins = currency_converter.convert_to_bitcoins(value)
133+ # value_in_bitcoins.amount
134+ # end
135+ # end
136+ #
137+ # class Product < ActiveRecord::Base
138+ # currency_converter = ConversionRatesFromTheInternet.new
139+ # attribute :price_in_bitcoins, MoneyType.new(currency_converter)
140+ # end
141+ #
142+ # Product.where(price_in_bitcoins: Money.new(5, "USD"))
143+ # # => SELECT * FROM products WHERE price_in_bitcoins = 0.02230
144+ #
145+ # Product.where(price_in_bitcoins: Money.new(5, "GBP"))
146+ # # => SELECT * FROM products WHERE price_in_bitcoins = 0.03412
147+ #
148+ # ==== Dirty Tracking
149+ #
150+ # The type of an attribute is given the opportunity to change how dirty
151+ # tracking is performed. The methods +changed?+ and +changed_in_place?+
152+ # will be called from +ActiveRecord::AttributeMethods::Dirty+. See the
153+ # documentation for those methods in +ActiveRecord::Type::Value+ for more
154+ # details.
76155 def attribute ( name , cast_type , **options )
77156 name = name . to_s
78157 reload_schema_from_cache
@@ -83,6 +162,23 @@ def attribute(name, cast_type, **options)
83162 )
84163 end
85164
165+ # This is the low level API which sits beneath +attribute+. It only
166+ # accepts type objects, and will do its work immediately instead of
167+ # waiting for the schema to load. Automatic schema detection and
168+ # +attribute+ both call this under the hood. While this method is
169+ # provided so it can be used by plugin authors, application code should
170+ # probably use +attribute+.
171+ #
172+ # +name+ The name of the attribute being defined. Expected to be a +String+.
173+ #
174+ # +cast_type+ The type object to use for this attribute
175+ #
176+ # +default+ The default value to use when no value is provided. If this option
177+ # is not passed, the previous default value (if any) will be used.
178+ # Otherwise, the default will be +nil+.
179+ #
180+ # +user_provided_default+ Whether the default value should be cast using
181+ # +type_cast_from_user+ or +type_cast_from_database+
86182 def define_attribute (
87183 name ,
88184 cast_type ,
@@ -93,7 +189,7 @@ def define_attribute(
93189 define_default_attribute ( name , default , cast_type , from_user : user_provided_default )
94190 end
95191
96- def load_schema!
192+ def load_schema! # :nodoc:
97193 super
98194 attributes_to_define_after_schema_loads . each do |name , ( type , options ) |
99195 if type . is_a? ( Symbol )
0 commit comments