activerecord 4.2.11.3 → 5.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (229) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1029 -1349
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -7
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record.rb +7 -3
  7. data/lib/active_record/aggregations.rb +35 -25
  8. data/lib/active_record/association_relation.rb +2 -2
  9. data/lib/active_record/associations.rb +305 -204
  10. data/lib/active_record/associations/alias_tracker.rb +19 -16
  11. data/lib/active_record/associations/association.rb +10 -8
  12. data/lib/active_record/associations/association_scope.rb +73 -102
  13. data/lib/active_record/associations/belongs_to_association.rb +20 -32
  14. data/lib/active_record/associations/builder/association.rb +28 -34
  15. data/lib/active_record/associations/builder/belongs_to.rb +41 -18
  16. data/lib/active_record/associations/builder/collection_association.rb +8 -24
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +11 -11
  18. data/lib/active_record/associations/builder/has_many.rb +4 -4
  19. data/lib/active_record/associations/builder/has_one.rb +10 -5
  20. data/lib/active_record/associations/builder/singular_association.rb +2 -9
  21. data/lib/active_record/associations/collection_association.rb +40 -43
  22. data/lib/active_record/associations/collection_proxy.rb +55 -29
  23. data/lib/active_record/associations/foreign_association.rb +1 -1
  24. data/lib/active_record/associations/has_many_association.rb +20 -71
  25. data/lib/active_record/associations/has_many_through_association.rb +8 -52
  26. data/lib/active_record/associations/has_one_association.rb +12 -5
  27. data/lib/active_record/associations/join_dependency.rb +28 -18
  28. data/lib/active_record/associations/join_dependency/join_association.rb +13 -12
  29. data/lib/active_record/associations/preloader.rb +13 -4
  30. data/lib/active_record/associations/preloader/association.rb +45 -51
  31. data/lib/active_record/associations/preloader/collection_association.rb +0 -6
  32. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  33. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  34. data/lib/active_record/associations/preloader/through_association.rb +5 -4
  35. data/lib/active_record/associations/singular_association.rb +6 -0
  36. data/lib/active_record/associations/through_association.rb +11 -3
  37. data/lib/active_record/attribute.rb +61 -17
  38. data/lib/active_record/attribute/user_provided_default.rb +23 -0
  39. data/lib/active_record/attribute_assignment.rb +27 -140
  40. data/lib/active_record/attribute_decorators.rb +6 -5
  41. data/lib/active_record/attribute_methods.rb +79 -26
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
  43. data/lib/active_record/attribute_methods/dirty.rb +46 -86
  44. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  45. data/lib/active_record/attribute_methods/query.rb +2 -2
  46. data/lib/active_record/attribute_methods/read.rb +26 -42
  47. data/lib/active_record/attribute_methods/serialization.rb +13 -16
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +42 -9
  49. data/lib/active_record/attribute_methods/write.rb +13 -24
  50. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  51. data/lib/active_record/attribute_set.rb +30 -3
  52. data/lib/active_record/attribute_set/builder.rb +6 -4
  53. data/lib/active_record/attributes.rb +194 -81
  54. data/lib/active_record/autosave_association.rb +33 -15
  55. data/lib/active_record/base.rb +30 -18
  56. data/lib/active_record/callbacks.rb +36 -40
  57. data/lib/active_record/coders/yaml_column.rb +20 -8
  58. data/lib/active_record/collection_cache_key.rb +31 -0
  59. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +431 -122
  60. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
  61. data/lib/active_record/connection_adapters/abstract/database_statements.rb +40 -22
  62. data/lib/active_record/connection_adapters/abstract/quoting.rb +62 -8
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +46 -38
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +229 -185
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +52 -13
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +275 -115
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +32 -33
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +83 -32
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +384 -221
  70. data/lib/active_record/connection_adapters/column.rb +27 -41
  71. data/lib/active_record/connection_adapters/connection_specification.rb +2 -21
  72. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +57 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +69 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +59 -0
  76. data/lib/active_record/connection_adapters/mysql2_adapter.rb +22 -101
  77. data/lib/active_record/connection_adapters/postgresql/column.rb +6 -10
  78. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +3 -3
  79. data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
  80. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +23 -57
  81. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -2
  82. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -1
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
  84. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -22
  85. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +3 -3
  86. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
  87. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
  90. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +23 -16
  92. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +0 -4
  93. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
  94. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
  95. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
  96. data/lib/active_record/connection_adapters/postgresql/quoting.rb +18 -11
  97. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
  98. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
  99. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +54 -0
  100. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +174 -128
  101. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  102. data/lib/active_record/connection_adapters/postgresql_adapter.rb +184 -112
  103. data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
  104. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  105. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +15 -0
  106. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +134 -110
  107. data/lib/active_record/connection_adapters/statement_pool.rb +28 -11
  108. data/lib/active_record/connection_handling.rb +5 -5
  109. data/lib/active_record/core.rb +72 -104
  110. data/lib/active_record/counter_cache.rb +9 -20
  111. data/lib/active_record/dynamic_matchers.rb +1 -20
  112. data/lib/active_record/enum.rb +110 -76
  113. data/lib/active_record/errors.rb +72 -47
  114. data/lib/active_record/explain_registry.rb +1 -1
  115. data/lib/active_record/explain_subscriber.rb +1 -1
  116. data/lib/active_record/fixture_set/file.rb +19 -4
  117. data/lib/active_record/fixtures.rb +76 -40
  118. data/lib/active_record/gem_version.rb +4 -4
  119. data/lib/active_record/inheritance.rb +27 -40
  120. data/lib/active_record/integration.rb +4 -4
  121. data/lib/active_record/legacy_yaml_adapter.rb +18 -2
  122. data/lib/active_record/locale/en.yml +3 -2
  123. data/lib/active_record/locking/optimistic.rb +10 -14
  124. data/lib/active_record/locking/pessimistic.rb +1 -1
  125. data/lib/active_record/log_subscriber.rb +40 -22
  126. data/lib/active_record/migration.rb +304 -133
  127. data/lib/active_record/migration/command_recorder.rb +59 -18
  128. data/lib/active_record/migration/compatibility.rb +90 -0
  129. data/lib/active_record/model_schema.rb +92 -40
  130. data/lib/active_record/nested_attributes.rb +45 -34
  131. data/lib/active_record/null_relation.rb +15 -7
  132. data/lib/active_record/persistence.rb +112 -72
  133. data/lib/active_record/querying.rb +6 -5
  134. data/lib/active_record/railtie.rb +20 -13
  135. data/lib/active_record/railties/controller_runtime.rb +1 -1
  136. data/lib/active_record/railties/databases.rake +47 -38
  137. data/lib/active_record/readonly_attributes.rb +1 -1
  138. data/lib/active_record/reflection.rb +182 -57
  139. data/lib/active_record/relation.rb +152 -100
  140. data/lib/active_record/relation/batches.rb +133 -33
  141. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  142. data/lib/active_record/relation/calculations.rb +80 -101
  143. data/lib/active_record/relation/delegation.rb +6 -19
  144. data/lib/active_record/relation/finder_methods.rb +58 -46
  145. data/lib/active_record/relation/from_clause.rb +32 -0
  146. data/lib/active_record/relation/merger.rb +13 -42
  147. data/lib/active_record/relation/predicate_builder.rb +99 -105
  148. data/lib/active_record/relation/predicate_builder/array_handler.rb +11 -16
  149. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +78 -0
  150. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  151. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  152. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  153. data/lib/active_record/relation/predicate_builder/range_handler.rb +17 -0
  154. data/lib/active_record/relation/query_attribute.rb +19 -0
  155. data/lib/active_record/relation/query_methods.rb +274 -238
  156. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  157. data/lib/active_record/relation/spawn_methods.rb +3 -6
  158. data/lib/active_record/relation/where_clause.rb +173 -0
  159. data/lib/active_record/relation/where_clause_factory.rb +37 -0
  160. data/lib/active_record/result.rb +4 -3
  161. data/lib/active_record/runtime_registry.rb +1 -1
  162. data/lib/active_record/sanitization.rb +94 -65
  163. data/lib/active_record/schema.rb +23 -22
  164. data/lib/active_record/schema_dumper.rb +33 -22
  165. data/lib/active_record/schema_migration.rb +10 -4
  166. data/lib/active_record/scoping.rb +17 -6
  167. data/lib/active_record/scoping/default.rb +19 -6
  168. data/lib/active_record/scoping/named.rb +39 -28
  169. data/lib/active_record/secure_token.rb +38 -0
  170. data/lib/active_record/serialization.rb +2 -4
  171. data/lib/active_record/statement_cache.rb +15 -13
  172. data/lib/active_record/store.rb +8 -3
  173. data/lib/active_record/suppressor.rb +54 -0
  174. data/lib/active_record/table_metadata.rb +64 -0
  175. data/lib/active_record/tasks/database_tasks.rb +30 -40
  176. data/lib/active_record/tasks/mysql_database_tasks.rb +7 -15
  177. data/lib/active_record/tasks/postgresql_database_tasks.rb +11 -2
  178. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  179. data/lib/active_record/timestamp.rb +16 -9
  180. data/lib/active_record/touch_later.rb +58 -0
  181. data/lib/active_record/transactions.rb +138 -56
  182. data/lib/active_record/type.rb +66 -17
  183. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  184. data/lib/active_record/type/date.rb +2 -45
  185. data/lib/active_record/type/date_time.rb +2 -49
  186. data/lib/active_record/type/internal/abstract_json.rb +33 -0
  187. data/lib/active_record/type/internal/timezone.rb +15 -0
  188. data/lib/active_record/type/serialized.rb +9 -14
  189. data/lib/active_record/type/time.rb +3 -21
  190. data/lib/active_record/type/type_map.rb +4 -4
  191. data/lib/active_record/type_caster.rb +7 -0
  192. data/lib/active_record/type_caster/connection.rb +29 -0
  193. data/lib/active_record/type_caster/map.rb +19 -0
  194. data/lib/active_record/validations.rb +33 -32
  195. data/lib/active_record/validations/absence.rb +24 -0
  196. data/lib/active_record/validations/associated.rb +10 -3
  197. data/lib/active_record/validations/length.rb +36 -0
  198. data/lib/active_record/validations/presence.rb +12 -12
  199. data/lib/active_record/validations/uniqueness.rb +24 -21
  200. data/lib/rails/generators/active_record/migration.rb +7 -0
  201. data/lib/rails/generators/active_record/migration/migration_generator.rb +7 -4
  202. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -3
  203. data/lib/rails/generators/active_record/migration/templates/migration.rb +4 -1
  204. data/lib/rails/generators/active_record/model/model_generator.rb +21 -15
  205. data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
  206. metadata +50 -35
  207. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
  208. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  209. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
  210. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  211. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  212. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  213. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  214. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  215. data/lib/active_record/type/big_integer.rb +0 -13
  216. data/lib/active_record/type/binary.rb +0 -50
  217. data/lib/active_record/type/boolean.rb +0 -31
  218. data/lib/active_record/type/decimal.rb +0 -64
  219. data/lib/active_record/type/decimal_without_scale.rb +0 -11
  220. data/lib/active_record/type/decorator.rb +0 -14
  221. data/lib/active_record/type/float.rb +0 -19
  222. data/lib/active_record/type/integer.rb +0 -59
  223. data/lib/active_record/type/mutable.rb +0 -16
  224. data/lib/active_record/type/numeric.rb +0 -36
  225. data/lib/active_record/type/string.rb +0 -40
  226. data/lib/active_record/type/text.rb +0 -11
  227. data/lib/active_record/type/time_value.rb +0 -38
  228. data/lib/active_record/type/unsigned_integer.rb +0 -15
  229. data/lib/active_record/type/value.rb +0 -110
@@ -1,33 +1,44 @@
1
+ require 'active_record/attribute/user_provided_default'
2
+
1
3
  module ActiveRecord
2
- module Attributes # :nodoc:
4
+ # See ActiveRecord::Attributes::ClassMethods for documentation
5
+ module Attributes
3
6
  extend ActiveSupport::Concern
4
7
 
5
- Type = ActiveRecord::Type
6
-
7
8
  included do
8
- class_attribute :user_provided_columns, instance_accessor: false # :internal:
9
- class_attribute :user_provided_defaults, instance_accessor: false # :internal:
10
- self.user_provided_columns = {}
11
- self.user_provided_defaults = {}
12
-
13
- delegate :persistable_attribute_names, to: :class
9
+ class_attribute :attributes_to_define_after_schema_loads, instance_accessor: false # :internal:
10
+ self.attributes_to_define_after_schema_loads = {}
14
11
  end
15
12
 
16
- module ClassMethods # :nodoc:
17
- # Defines or overrides a attribute on this model. This allows customization of
18
- # Active Record's type casting behavior, as well as adding support for user defined
19
- # types.
13
+ module ClassMethods
14
+ # Defines an attribute with a type on this model. It will override the
15
+ # type of existing attributes if needed. This allows control over how
16
+ # values are converted to and from SQL when assigned to a model. It also
17
+ # changes the behavior of values passed to
18
+ # {ActiveRecord::Base.where}[rdoc-ref:QueryMethods#where]. This will let you use
19
+ # your domain objects across much of Active Record, without having to
20
+ # rely on implementation details or monkey patching.
20
21
  #
21
- # +name+ The name of the methods to define attribute methods for, and the column which
22
- # this will persist to.
22
+ # +name+ The name of the methods to define attribute methods for, and the
23
+ # column which this will persist to.
23
24
  #
24
- # +cast_type+ A type object that contains information about how to type cast the value.
25
- # See the examples section for more information.
25
+ # +cast_type+ A symbol such as +:string+ or +:integer+, or a type object
26
+ # to be used for this attribute. See the examples below for more
27
+ # information about providing custom type objects.
26
28
  #
27
29
  # ==== Options
28
- # The options hash accepts the following options:
29
30
  #
30
- # +default+ is the default value that the column should use on a new record.
31
+ # The following options are accepted:
32
+ #
33
+ # +default+ The default value to use when no value is provided. If this option
34
+ # is not passed, the previous default value (if any) will be used.
35
+ # Otherwise, the default will be +nil+.
36
+ #
37
+ # +array+ (PG only) specifies that the type should be an array (see the
38
+ # examples below).
39
+ #
40
+ # +range+ (PG only) specifies that the type should be a range (see the
41
+ # examples below).
31
42
  #
32
43
  # ==== Examples
33
44
  #
@@ -48,99 +59,201 @@ module ActiveRecord
48
59
  # store_listing.price_in_cents # => BigDecimal.new(10.1)
49
60
  #
50
61
  # class StoreListing < ActiveRecord::Base
51
- # attribute :price_in_cents, Type::Integer.new
62
+ # attribute :price_in_cents, :integer
52
63
  # end
53
64
  #
54
65
  # # after
55
66
  # store_listing.price_in_cents # => 10
56
67
  #
57
- # Users may also define their own custom types, as long as they respond to the methods
58
- # defined on the value type. The `type_cast` method on your type object will be called
59
- # with values both from the database, and from your controllers. See
60
- # `ActiveRecord::Attributes::Type::Value` for the expected API. It is recommended that your
61
- # type objects inherit from an existing type, or the base value type.
68
+ # A default can also be provided.
69
+ #
70
+ # create_table :store_listings, force: true do |t|
71
+ # t.string :my_string, default: "original default"
72
+ # end
73
+ #
74
+ # StoreListing.new.my_string # => "original default"
75
+ #
76
+ # class StoreListing < ActiveRecord::Base
77
+ # attribute :my_string, :string, default: "new default"
78
+ # end
79
+ #
80
+ # StoreListing.new.my_string # => "new default"
81
+ #
82
+ # class Product < ActiveRecord::Base
83
+ # attribute :my_default_proc, :datetime, default: -> { Time.now }
84
+ # end
85
+ #
86
+ # Product.new.my_default_proc # => 2015-05-30 11:04:48 -0600
87
+ # sleep 1
88
+ # Product.new.my_default_proc # => 2015-05-30 11:04:49 -0600
89
+ #
90
+ # \Attributes do not need to be backed by a database column.
91
+ #
92
+ # class MyModel < ActiveRecord::Base
93
+ # attribute :my_string, :string
94
+ # attribute :my_int_array, :integer, array: true
95
+ # attribute :my_float_range, :float, range: true
96
+ # end
97
+ #
98
+ # model = MyModel.new(
99
+ # my_string: "string",
100
+ # my_int_array: ["1", "2", "3"],
101
+ # my_float_range: "[1,3.5]",
102
+ # )
103
+ # model.attributes
104
+ # # =>
105
+ # {
106
+ # my_string: "string",
107
+ # my_int_array: [1, 2, 3],
108
+ # my_float_range: 1.0..3.5
109
+ # }
110
+ #
111
+ # ==== Creating Custom Types
112
+ #
113
+ # Users may also define their own custom types, as long as they respond
114
+ # to the methods defined on the value type. The method +deserialize+ or
115
+ # +cast+ will be called on your type object, with raw input from the
116
+ # database or from your controllers. See ActiveRecord::Type::Value for the
117
+ # expected API. It is recommended that your type objects inherit from an
118
+ # existing type, or from ActiveRecord::Type::Value
62
119
  #
63
120
  # class MoneyType < ActiveRecord::Type::Integer
64
- # def type_cast(value)
65
- # if value.include?('$')
121
+ # def cast(value)
122
+ # if !value.kind_of(Numeric) && value.include?('$')
66
123
  # price_in_dollars = value.gsub(/\$/, '').to_f
67
- # price_in_dollars * 100
124
+ # super(price_in_dollars * 100)
68
125
  # else
69
- # value.to_i
126
+ # super
70
127
  # end
71
128
  # end
72
129
  # end
73
130
  #
131
+ # # config/initializers/types.rb
132
+ # ActiveRecord::Type.register(:money, MoneyType)
133
+ #
134
+ # # /app/models/store_listing.rb
74
135
  # class StoreListing < ActiveRecord::Base
75
- # attribute :price_in_cents, MoneyType.new
136
+ # attribute :price_in_cents, :money
76
137
  # end
77
138
  #
78
139
  # store_listing = StoreListing.new(price_in_cents: '$10.00')
79
140
  # store_listing.price_in_cents # => 1000
80
- def attribute(name, cast_type, options = {})
141
+ #
142
+ # For more details on creating custom types, see the documentation for
143
+ # ActiveRecord::Type::Value. For more details on registering your types
144
+ # to be referenced by a symbol, see ActiveRecord::Type.register. You can
145
+ # also pass a type object directly, in place of a symbol.
146
+ #
147
+ # ==== \Querying
148
+ #
149
+ # When {ActiveRecord::Base.where}[rdoc-ref:QueryMethods#where] is called, it will
150
+ # use the type defined by the model class to convert the value to SQL,
151
+ # calling +serialize+ on your type object. For example:
152
+ #
153
+ # class Money < Struct.new(:amount, :currency)
154
+ # end
155
+ #
156
+ # class MoneyType < Type::Value
157
+ # def initialize(currency_converter)
158
+ # @currency_converter = currency_converter
159
+ # end
160
+ #
161
+ # # value will be the result of +deserialize+ or
162
+ # # +cast+. Assumed to be an instance of +Money+ in
163
+ # # this case.
164
+ # def serialize(value)
165
+ # value_in_bitcoins = @currency_converter.convert_to_bitcoins(value)
166
+ # value_in_bitcoins.amount
167
+ # end
168
+ # end
169
+ #
170
+ # ActiveRecord::Type.register(:money, MoneyType)
171
+ #
172
+ # class Product < ActiveRecord::Base
173
+ # currency_converter = ConversionRatesFromTheInternet.new
174
+ # attribute :price_in_bitcoins, :money, currency_converter
175
+ # end
176
+ #
177
+ # Product.where(price_in_bitcoins: Money.new(5, "USD"))
178
+ # # => SELECT * FROM products WHERE price_in_bitcoins = 0.02230
179
+ #
180
+ # Product.where(price_in_bitcoins: Money.new(5, "GBP"))
181
+ # # => SELECT * FROM products WHERE price_in_bitcoins = 0.03412
182
+ #
183
+ # ==== Dirty Tracking
184
+ #
185
+ # The type of an attribute is given the opportunity to change how dirty
186
+ # tracking is performed. The methods +changed?+ and +changed_in_place?+
187
+ # will be called from ActiveModel::Dirty. See the documentation for those
188
+ # methods in ActiveRecord::Type::Value for more details.
189
+ def attribute(name, cast_type, **options)
81
190
  name = name.to_s
82
- clear_caches_calculated_from_columns
83
- # Assign a new hash to ensure that subclasses do not share a hash
84
- self.user_provided_columns = user_provided_columns.merge(name => cast_type)
85
-
86
- if options.key?(:default)
87
- self.user_provided_defaults = user_provided_defaults.merge(name => options[:default])
88
- end
89
- end
90
-
91
- # Returns an array of column objects for the table associated with this class.
92
- def columns
93
- @columns ||= add_user_provided_columns(connection.schema_cache.columns(table_name))
94
- end
191
+ reload_schema_from_cache
95
192
 
96
- # Returns a hash of column objects for the table associated with this class.
97
- def columns_hash
98
- @columns_hash ||= Hash[columns.map { |c| [c.name, c] }]
193
+ self.attributes_to_define_after_schema_loads =
194
+ attributes_to_define_after_schema_loads.merge(
195
+ name => [cast_type, options]
196
+ )
99
197
  end
100
198
 
101
- def persistable_attribute_names # :nodoc:
102
- @persistable_attribute_names ||= connection.schema_cache.columns_hash(table_name).keys
199
+ # This is the low level API which sits beneath +attribute+. It only
200
+ # accepts type objects, and will do its work immediately instead of
201
+ # waiting for the schema to load. Automatic schema detection and
202
+ # ClassMethods#attribute both call this under the hood. While this method
203
+ # is provided so it can be used by plugin authors, application code
204
+ # should probably use ClassMethods#attribute.
205
+ #
206
+ # +name+ The name of the attribute being defined. Expected to be a +String+.
207
+ #
208
+ # +cast_type+ The type object to use for this attribute.
209
+ #
210
+ # +default+ The default value to use when no value is provided. If this option
211
+ # is not passed, the previous default value (if any) will be used.
212
+ # Otherwise, the default will be +nil+. A proc can also be passed, and
213
+ # will be called once each time a new value is needed.
214
+ #
215
+ # +user_provided_default+ Whether the default value should be cast using
216
+ # +cast+ or +deserialize+.
217
+ def define_attribute(
218
+ name,
219
+ cast_type,
220
+ default: NO_DEFAULT_PROVIDED,
221
+ user_provided_default: true
222
+ )
223
+ attribute_types[name] = cast_type
224
+ define_default_attribute(name, default, cast_type, from_user: user_provided_default)
103
225
  end
104
226
 
105
- def reset_column_information # :nodoc:
227
+ def load_schema! # :nodoc:
106
228
  super
107
- clear_caches_calculated_from_columns
108
- end
109
-
110
- private
111
-
112
- def add_user_provided_columns(schema_columns)
113
- existing_columns = schema_columns.map do |column|
114
- new_type = user_provided_columns[column.name]
115
- if new_type
116
- column.with_type(new_type)
117
- else
118
- column
229
+ attributes_to_define_after_schema_loads.each do |name, (type, options)|
230
+ if type.is_a?(Symbol)
231
+ type = ActiveRecord::Type.lookup(type, **options.except(:default))
119
232
  end
120
- end
121
233
 
122
- existing_column_names = existing_columns.map(&:name)
123
- new_columns = user_provided_columns.except(*existing_column_names).map do |(name, type)|
124
- connection.new_column(name, nil, type)
234
+ define_attribute(name, type, **options.slice(:default))
125
235
  end
126
-
127
- existing_columns + new_columns
128
236
  end
129
237
 
130
- def clear_caches_calculated_from_columns
131
- @attributes_builder = nil
132
- @column_names = nil
133
- @column_types = nil
134
- @columns = nil
135
- @columns_hash = nil
136
- @content_columns = nil
137
- @default_attributes = nil
138
- @persistable_attribute_names = nil
139
- @attribute_names = nil
140
- end
238
+ private
239
+
240
+ NO_DEFAULT_PROVIDED = Object.new # :nodoc:
241
+ private_constant :NO_DEFAULT_PROVIDED
141
242
 
142
- def raw_default_values
143
- super.merge(user_provided_defaults)
243
+ def define_default_attribute(name, value, type, from_user:)
244
+ if value == NO_DEFAULT_PROVIDED
245
+ default_attribute = _default_attributes[name].with_type(type)
246
+ elsif from_user
247
+ default_attribute = Attribute::UserProvidedDefault.new(
248
+ name,
249
+ value,
250
+ type,
251
+ _default_attributes[name],
252
+ )
253
+ else
254
+ default_attribute = Attribute.from_database(name, value, type)
255
+ end
256
+ _default_attributes[name] = default_attribute
144
257
  end
145
258
  end
146
259
  end
@@ -1,10 +1,10 @@
1
1
  module ActiveRecord
2
2
  # = Active Record Autosave Association
3
3
  #
4
- # +AutosaveAssociation+ is a module that takes care of automatically saving
4
+ # AutosaveAssociation is a module that takes care of automatically saving
5
5
  # associated records when their parent is saved. In addition to saving, it
6
6
  # also destroys any associated records that were marked for destruction.
7
- # (See +mark_for_destruction+ and <tt>marked_for_destruction?</tt>).
7
+ # (See #mark_for_destruction and #marked_for_destruction?).
8
8
  #
9
9
  # Saving of the parent, its associations, and the destruction of marked
10
10
  # associations, all happen inside a transaction. This should never leave the
@@ -125,7 +125,6 @@ module ActiveRecord
125
125
  # Now it _is_ removed from the database:
126
126
  #
127
127
  # Comment.find_by(id: id).nil? # => true
128
-
129
128
  module AutosaveAssociation
130
129
  extend ActiveSupport::Concern
131
130
 
@@ -141,9 +140,11 @@ module ActiveRecord
141
140
 
142
141
  included do
143
142
  Associations::Builder::Association.extensions << AssociationBuilderExtension
143
+ mattr_accessor :index_nested_attribute_errors, instance_writer: false
144
+ self.index_nested_attribute_errors = false
144
145
  end
145
146
 
146
- module ClassMethods
147
+ module ClassMethods # :nodoc:
147
148
  private
148
149
 
149
150
  def define_non_cyclic_method(name, &block)
@@ -198,7 +199,7 @@ module ActiveRecord
198
199
  after_create save_method
199
200
  after_update save_method
200
201
  else
201
- define_non_cyclic_method(save_method) { save_belongs_to_association(reflection) }
202
+ define_non_cyclic_method(save_method) { throw(:abort) if save_belongs_to_association(reflection) == false }
202
203
  before_save save_method
203
204
  end
204
205
 
@@ -214,8 +215,15 @@ module ActiveRecord
214
215
  method = :validate_single_association
215
216
  end
216
217
 
217
- define_non_cyclic_method(validation_method) { send(method, reflection) }
218
+ define_non_cyclic_method(validation_method) do
219
+ send(method, reflection)
220
+ # TODO: remove the following line as soon as the return value of
221
+ # callbacks is ignored, that is, returning `false` does not
222
+ # display a deprecation warning or halts the callback chain.
223
+ true
224
+ end
218
225
  validate validation_method
226
+ after_validation :_ensure_no_duplicate_errors
219
227
  end
220
228
  end
221
229
  end
@@ -227,7 +235,7 @@ module ActiveRecord
227
235
  super
228
236
  end
229
237
 
230
- # Marks this record to be destroyed as part of the parents save transaction.
238
+ # Marks this record to be destroyed as part of the parent's save transaction.
231
239
  # This does _not_ actually destroy the record instantly, rather child record will be destroyed
232
240
  # when <tt>parent.save</tt> is called.
233
241
  #
@@ -236,7 +244,7 @@ module ActiveRecord
236
244
  @marked_for_destruction = true
237
245
  end
238
246
 
239
- # Returns whether or not this record will be destroyed as part of the parents save transaction.
247
+ # Returns whether or not this record will be destroyed as part of the parent's save transaction.
240
248
  #
241
249
  # Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
242
250
  def marked_for_destruction?
@@ -271,9 +279,9 @@ module ActiveRecord
271
279
  if new_record
272
280
  association && association.target
273
281
  elsif autosave
274
- association.target.find_all { |record| record.changed_for_autosave? }
282
+ association.target.find_all(&:changed_for_autosave?)
275
283
  else
276
- association.target.find_all { |record| record.new_record? }
284
+ association.target.find_all(&:new_record?)
277
285
  end
278
286
  end
279
287
 
@@ -309,7 +317,7 @@ module ActiveRecord
309
317
  def validate_collection_association(reflection)
310
318
  if association = association_instance_get(reflection.name)
311
319
  if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
312
- records.each { |record| association_valid?(reflection, record) }
320
+ records.each_with_index { |record, index| association_valid?(reflection, record, index) }
313
321
  end
314
322
  end
315
323
  end
@@ -317,14 +325,18 @@ module ActiveRecord
317
325
  # Returns whether or not the association is valid and applies any errors to
318
326
  # the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
319
327
  # enabled records if they're marked_for_destruction? or destroyed.
320
- def association_valid?(reflection, record)
328
+ def association_valid?(reflection, record, index=nil)
321
329
  return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
322
330
 
323
331
  validation_context = self.validation_context unless [:create, :update].include?(self.validation_context)
324
332
  unless valid = record.valid?(validation_context)
325
333
  if reflection.options[:autosave]
326
334
  record.errors.each do |attribute, message|
327
- attribute = "#{reflection.name}.#{attribute}"
335
+ if index.nil? || (!reflection.options[:index_errors] && !ActiveRecord::Base.index_nested_attribute_errors)
336
+ attribute = "#{reflection.name}.#{attribute}"
337
+ else
338
+ attribute = "#{reflection.name}[#{index}].#{attribute}"
339
+ end
328
340
  errors[attribute] << message
329
341
  errors[attribute].uniq!
330
342
  end
@@ -346,7 +358,7 @@ module ActiveRecord
346
358
  # <tt>:autosave</tt> is enabled on the association.
347
359
  #
348
360
  # In addition, it destroys all children that were marked for destruction
349
- # with mark_for_destruction.
361
+ # with #mark_for_destruction.
350
362
  #
351
363
  # This all happens inside a transaction, _if_ the Transactions module is included into
352
364
  # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
@@ -389,7 +401,7 @@ module ActiveRecord
389
401
  # on the association.
390
402
  #
391
403
  # In addition, it will destroy the association if it was marked for
392
- # destruction with mark_for_destruction.
404
+ # destruction with #mark_for_destruction.
393
405
  #
394
406
  # This all happens inside a transaction, _if_ the Transactions module is included into
395
407
  # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
@@ -450,5 +462,11 @@ module ActiveRecord
450
462
  end
451
463
  end
452
464
  end
465
+
466
+ def _ensure_no_duplicate_errors
467
+ errors.messages.each_key do |attribute|
468
+ errors[attribute].uniq!
469
+ end
470
+ end
453
471
  end
454
472
  end