activerecord 4.2.11.3 → 5.0.7.2

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 (251) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1638 -1132
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +7 -8
  5. data/examples/performance.rb +2 -3
  6. data/examples/simple.rb +0 -1
  7. data/lib/active_record.rb +7 -2
  8. data/lib/active_record/aggregations.rb +34 -21
  9. data/lib/active_record/association_relation.rb +7 -4
  10. data/lib/active_record/associations.rb +347 -218
  11. data/lib/active_record/associations/alias_tracker.rb +19 -16
  12. data/lib/active_record/associations/association.rb +22 -10
  13. data/lib/active_record/associations/association_scope.rb +75 -104
  14. data/lib/active_record/associations/belongs_to_association.rb +21 -32
  15. data/lib/active_record/associations/builder/association.rb +28 -34
  16. data/lib/active_record/associations/builder/belongs_to.rb +43 -18
  17. data/lib/active_record/associations/builder/collection_association.rb +7 -19
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +16 -11
  19. data/lib/active_record/associations/builder/has_many.rb +4 -4
  20. data/lib/active_record/associations/builder/has_one.rb +11 -6
  21. data/lib/active_record/associations/builder/singular_association.rb +13 -11
  22. data/lib/active_record/associations/collection_association.rb +85 -69
  23. data/lib/active_record/associations/collection_proxy.rb +104 -46
  24. data/lib/active_record/associations/foreign_association.rb +1 -1
  25. data/lib/active_record/associations/has_many_association.rb +21 -78
  26. data/lib/active_record/associations/has_many_through_association.rb +6 -47
  27. data/lib/active_record/associations/has_one_association.rb +12 -5
  28. data/lib/active_record/associations/join_dependency.rb +38 -22
  29. data/lib/active_record/associations/join_dependency/join_association.rb +15 -14
  30. data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
  31. data/lib/active_record/associations/preloader.rb +14 -4
  32. data/lib/active_record/associations/preloader/association.rb +52 -71
  33. data/lib/active_record/associations/preloader/collection_association.rb +0 -7
  34. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  35. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  36. data/lib/active_record/associations/preloader/singular_association.rb +0 -1
  37. data/lib/active_record/associations/preloader/through_association.rb +36 -17
  38. data/lib/active_record/associations/singular_association.rb +13 -1
  39. data/lib/active_record/associations/through_association.rb +12 -4
  40. data/lib/active_record/attribute.rb +69 -19
  41. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  42. data/lib/active_record/attribute_assignment.rb +19 -140
  43. data/lib/active_record/attribute_decorators.rb +6 -5
  44. data/lib/active_record/attribute_methods.rb +69 -44
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
  46. data/lib/active_record/attribute_methods/dirty.rb +46 -86
  47. data/lib/active_record/attribute_methods/primary_key.rb +16 -3
  48. data/lib/active_record/attribute_methods/query.rb +2 -2
  49. data/lib/active_record/attribute_methods/read.rb +31 -59
  50. data/lib/active_record/attribute_methods/serialization.rb +13 -16
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -14
  52. data/lib/active_record/attribute_methods/write.rb +13 -37
  53. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  54. data/lib/active_record/attribute_set.rb +32 -3
  55. data/lib/active_record/attribute_set/builder.rb +42 -16
  56. data/lib/active_record/attributes.rb +199 -81
  57. data/lib/active_record/autosave_association.rb +54 -17
  58. data/lib/active_record/base.rb +32 -23
  59. data/lib/active_record/callbacks.rb +39 -43
  60. data/lib/active_record/coders/json.rb +1 -1
  61. data/lib/active_record/coders/yaml_column.rb +20 -8
  62. data/lib/active_record/collection_cache_key.rb +50 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +467 -189
  64. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
  65. data/lib/active_record/connection_adapters/abstract/database_statements.rb +66 -62
  66. data/lib/active_record/connection_adapters/abstract/query_cache.rb +39 -4
  67. data/lib/active_record/connection_adapters/abstract/quoting.rb +86 -13
  68. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  69. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +61 -39
  70. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -188
  71. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +72 -17
  72. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +407 -156
  73. data/lib/active_record/connection_adapters/abstract/transaction.rb +51 -34
  74. data/lib/active_record/connection_adapters/abstract_adapter.rb +177 -71
  75. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +433 -399
  76. data/lib/active_record/connection_adapters/column.rb +28 -43
  77. data/lib/active_record/connection_adapters/connection_specification.rb +15 -27
  78. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  79. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  80. data/lib/active_record/connection_adapters/mysql/database_statements.rb +108 -0
  81. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  82. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  83. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  84. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  85. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  86. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  87. data/lib/active_record/connection_adapters/mysql2_adapter.rb +25 -166
  88. data/lib/active_record/connection_adapters/postgresql/column.rb +33 -11
  89. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -72
  90. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
  92. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +37 -57
  93. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +3 -3
  94. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -2
  95. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -1
  96. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -22
  97. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +13 -3
  98. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
  99. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
  100. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -4
  101. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
  102. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +31 -17
  104. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +0 -4
  105. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
  106. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +56 -19
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
  111. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +250 -154
  113. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  114. data/lib/active_record/connection_adapters/postgresql/utils.rb +2 -2
  115. data/lib/active_record/connection_adapters/postgresql_adapter.rb +264 -170
  116. data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
  117. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  118. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  119. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  120. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  121. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +151 -194
  122. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  123. data/lib/active_record/connection_handling.rb +37 -14
  124. data/lib/active_record/core.rb +92 -108
  125. data/lib/active_record/counter_cache.rb +13 -24
  126. data/lib/active_record/dynamic_matchers.rb +1 -20
  127. data/lib/active_record/enum.rb +116 -76
  128. data/lib/active_record/errors.rb +87 -48
  129. data/lib/active_record/explain.rb +20 -9
  130. data/lib/active_record/explain_registry.rb +1 -1
  131. data/lib/active_record/explain_subscriber.rb +1 -1
  132. data/lib/active_record/fixture_set/file.rb +26 -5
  133. data/lib/active_record/fixtures.rb +77 -41
  134. data/lib/active_record/gem_version.rb +4 -4
  135. data/lib/active_record/inheritance.rb +32 -40
  136. data/lib/active_record/integration.rb +17 -14
  137. data/lib/active_record/internal_metadata.rb +56 -0
  138. data/lib/active_record/legacy_yaml_adapter.rb +18 -2
  139. data/lib/active_record/locale/en.yml +3 -2
  140. data/lib/active_record/locking/optimistic.rb +15 -15
  141. data/lib/active_record/locking/pessimistic.rb +1 -1
  142. data/lib/active_record/log_subscriber.rb +48 -24
  143. data/lib/active_record/migration.rb +362 -111
  144. data/lib/active_record/migration/command_recorder.rb +59 -18
  145. data/lib/active_record/migration/compatibility.rb +126 -0
  146. data/lib/active_record/model_schema.rb +270 -73
  147. data/lib/active_record/nested_attributes.rb +58 -29
  148. data/lib/active_record/no_touching.rb +4 -0
  149. data/lib/active_record/null_relation.rb +16 -8
  150. data/lib/active_record/persistence.rb +152 -90
  151. data/lib/active_record/query_cache.rb +18 -23
  152. data/lib/active_record/querying.rb +12 -11
  153. data/lib/active_record/railtie.rb +23 -16
  154. data/lib/active_record/railties/controller_runtime.rb +1 -1
  155. data/lib/active_record/railties/databases.rake +52 -41
  156. data/lib/active_record/readonly_attributes.rb +1 -1
  157. data/lib/active_record/reflection.rb +302 -115
  158. data/lib/active_record/relation.rb +187 -120
  159. data/lib/active_record/relation/batches.rb +141 -36
  160. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  161. data/lib/active_record/relation/calculations.rb +92 -117
  162. data/lib/active_record/relation/delegation.rb +8 -20
  163. data/lib/active_record/relation/finder_methods.rb +173 -89
  164. data/lib/active_record/relation/from_clause.rb +32 -0
  165. data/lib/active_record/relation/merger.rb +16 -42
  166. data/lib/active_record/relation/predicate_builder.rb +120 -107
  167. data/lib/active_record/relation/predicate_builder/array_handler.rb +11 -16
  168. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  169. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  170. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  171. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  172. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  173. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  174. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  175. data/lib/active_record/relation/query_attribute.rb +19 -0
  176. data/lib/active_record/relation/query_methods.rb +308 -244
  177. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  178. data/lib/active_record/relation/spawn_methods.rb +4 -7
  179. data/lib/active_record/relation/where_clause.rb +174 -0
  180. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  181. data/lib/active_record/result.rb +11 -4
  182. data/lib/active_record/runtime_registry.rb +1 -1
  183. data/lib/active_record/sanitization.rb +105 -66
  184. data/lib/active_record/schema.rb +26 -22
  185. data/lib/active_record/schema_dumper.rb +54 -37
  186. data/lib/active_record/schema_migration.rb +11 -14
  187. data/lib/active_record/scoping.rb +34 -16
  188. data/lib/active_record/scoping/default.rb +28 -10
  189. data/lib/active_record/scoping/named.rb +59 -26
  190. data/lib/active_record/secure_token.rb +38 -0
  191. data/lib/active_record/serialization.rb +3 -5
  192. data/lib/active_record/statement_cache.rb +17 -15
  193. data/lib/active_record/store.rb +8 -3
  194. data/lib/active_record/suppressor.rb +58 -0
  195. data/lib/active_record/table_metadata.rb +69 -0
  196. data/lib/active_record/tasks/database_tasks.rb +66 -49
  197. data/lib/active_record/tasks/mysql_database_tasks.rb +6 -14
  198. data/lib/active_record/tasks/postgresql_database_tasks.rb +12 -3
  199. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  200. data/lib/active_record/timestamp.rb +20 -9
  201. data/lib/active_record/touch_later.rb +63 -0
  202. data/lib/active_record/transactions.rb +139 -57
  203. data/lib/active_record/type.rb +66 -17
  204. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  205. data/lib/active_record/type/date.rb +2 -45
  206. data/lib/active_record/type/date_time.rb +2 -49
  207. data/lib/active_record/type/internal/abstract_json.rb +33 -0
  208. data/lib/active_record/type/internal/timezone.rb +15 -0
  209. data/lib/active_record/type/serialized.rb +15 -14
  210. data/lib/active_record/type/time.rb +10 -16
  211. data/lib/active_record/type/type_map.rb +4 -4
  212. data/lib/active_record/type_caster.rb +7 -0
  213. data/lib/active_record/type_caster/connection.rb +29 -0
  214. data/lib/active_record/type_caster/map.rb +19 -0
  215. data/lib/active_record/validations.rb +33 -32
  216. data/lib/active_record/validations/absence.rb +23 -0
  217. data/lib/active_record/validations/associated.rb +10 -3
  218. data/lib/active_record/validations/length.rb +24 -0
  219. data/lib/active_record/validations/presence.rb +11 -12
  220. data/lib/active_record/validations/uniqueness.rb +33 -33
  221. data/lib/rails/generators/active_record/migration.rb +15 -0
  222. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -5
  223. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -3
  224. data/lib/rails/generators/active_record/migration/templates/migration.rb +8 -1
  225. data/lib/rails/generators/active_record/model/model_generator.rb +33 -16
  226. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  227. data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
  228. metadata +58 -34
  229. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
  230. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  231. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
  232. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  233. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  234. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  235. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  236. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  237. data/lib/active_record/type/big_integer.rb +0 -13
  238. data/lib/active_record/type/binary.rb +0 -50
  239. data/lib/active_record/type/boolean.rb +0 -31
  240. data/lib/active_record/type/decimal.rb +0 -64
  241. data/lib/active_record/type/decimal_without_scale.rb +0 -11
  242. data/lib/active_record/type/decorator.rb +0 -14
  243. data/lib/active_record/type/float.rb +0 -19
  244. data/lib/active_record/type/integer.rb +0 -59
  245. data/lib/active_record/type/mutable.rb +0 -16
  246. data/lib/active_record/type/numeric.rb +0 -36
  247. data/lib/active_record/type/string.rb +0 -40
  248. data/lib/active_record/type/text.rb +0 -11
  249. data/lib/active_record/type/time_value.rb +0 -38
  250. data/lib/active_record/type/unsigned_integer.rb +0 -15
  251. data/lib/active_record/type/value.rb +0 -110
@@ -3,33 +3,30 @@ require 'active_record/attribute'
3
3
  module ActiveRecord
4
4
  class AttributeSet # :nodoc:
5
5
  class Builder # :nodoc:
6
- attr_reader :types, :always_initialized
6
+ attr_reader :types, :default_attributes
7
7
 
8
- def initialize(types, always_initialized = nil)
8
+ def initialize(types, default_attributes = {})
9
9
  @types = types
10
- @always_initialized = always_initialized
10
+ @default_attributes = default_attributes
11
11
  end
12
12
 
13
13
  def build_from_database(values = {}, additional_types = {})
14
- if always_initialized && !values.key?(always_initialized)
15
- values[always_initialized] = nil
16
- end
17
-
18
- attributes = LazyAttributeHash.new(types, values, additional_types)
14
+ attributes = LazyAttributeHash.new(types, values, additional_types, default_attributes)
19
15
  AttributeSet.new(attributes)
20
16
  end
21
17
  end
22
18
  end
23
19
 
24
20
  class LazyAttributeHash # :nodoc:
25
- delegate :transform_values, to: :materialize
21
+ delegate :transform_values, :each_key, :fetch, :except, to: :materialize
26
22
 
27
- def initialize(types, values, additional_types)
23
+ def initialize(types, values, additional_types, default_attributes, delegate_hash = {})
28
24
  @types = types
29
25
  @values = values
30
26
  @additional_types = additional_types
31
27
  @materialized = false
32
- @delegate_hash = {}
28
+ @delegate_hash = delegate_hash
29
+ @default_attributes = default_attributes
33
30
  end
34
31
 
35
32
  def key?(key)
@@ -47,12 +44,14 @@ module ActiveRecord
47
44
  delegate_hash[key] = value
48
45
  end
49
46
 
50
- def initialized_keys
51
- delegate_hash.keys | values.keys
47
+ def deep_dup
48
+ dup.tap do |copy|
49
+ copy.instance_variable_set(:@delegate_hash, delegate_hash.transform_values(&:dup))
50
+ end
52
51
  end
53
52
 
54
53
  def initialize_dup(_)
55
- @delegate_hash = delegate_hash.transform_values(&:dup)
54
+ @delegate_hash = Hash[delegate_hash]
56
55
  super
57
56
  end
58
57
 
@@ -74,9 +73,31 @@ module ActiveRecord
74
73
  end
75
74
  end
76
75
 
76
+ def marshal_dump
77
+ [@types, @values, @additional_types, @default_attributes, @delegate_hash]
78
+ end
79
+
80
+ def marshal_load(values)
81
+ if values.is_a?(Hash)
82
+ empty_hash = {}.freeze
83
+ initialize(empty_hash, empty_hash, empty_hash, empty_hash, values)
84
+ @materialized = true
85
+ else
86
+ initialize(*values)
87
+ end
88
+ end
89
+
90
+ def encode_with(coder)
91
+ coder["delegate_hash"] = materialize
92
+ end
93
+
94
+ def init_with(coder)
95
+ marshal_load(coder["delegate_hash"])
96
+ end
97
+
77
98
  protected
78
99
 
79
- attr_reader :types, :values, :additional_types, :delegate_hash
100
+ attr_reader :types, :values, :additional_types, :delegate_hash, :default_attributes
80
101
 
81
102
  def materialize
82
103
  unless @materialized
@@ -99,7 +120,12 @@ module ActiveRecord
99
120
  if value_present
100
121
  delegate_hash[name] = Attribute.from_database(name, value, type)
101
122
  elsif types.key?(name)
102
- delegate_hash[name] = Attribute.uninitialized(name, type)
123
+ attr = default_attributes[name]
124
+ if attr
125
+ delegate_hash[name] = attr.dup
126
+ else
127
+ delegate_hash[name] = Attribute.uninitialized(name, type)
128
+ end
103
129
  end
104
130
  end
105
131
  end
@@ -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+ (PostgreSQL only) specifies that the type should be an array (see the
38
+ # examples below).
39
+ #
40
+ # +range+ (PostgreSQL only) specifies that the type should be a range (see the
41
+ # examples below).
31
42
  #
32
43
  # ==== Examples
33
44
  #
@@ -48,99 +59,206 @@ 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
+ # # db/schema.rb
71
+ # create_table :store_listings, force: true do |t|
72
+ # t.string :my_string, default: "original default"
73
+ # end
74
+ #
75
+ # StoreListing.new.my_string # => "original default"
76
+ #
77
+ # # app/models/store_listing.rb
78
+ # class StoreListing < ActiveRecord::Base
79
+ # attribute :my_string, :string, default: "new default"
80
+ # end
81
+ #
82
+ # StoreListing.new.my_string # => "new default"
83
+ #
84
+ # class Product < ActiveRecord::Base
85
+ # attribute :my_default_proc, :datetime, default: -> { Time.now }
86
+ # end
87
+ #
88
+ # Product.new.my_default_proc # => 2015-05-30 11:04:48 -0600
89
+ # sleep 1
90
+ # Product.new.my_default_proc # => 2015-05-30 11:04:49 -0600
91
+ #
92
+ # \Attributes do not need to be backed by a database column.
93
+ #
94
+ # # app/models/my_model.rb
95
+ # class MyModel < ActiveRecord::Base
96
+ # attribute :my_string, :string
97
+ # attribute :my_int_array, :integer, array: true
98
+ # attribute :my_float_range, :float, range: true
99
+ # end
100
+ #
101
+ # model = MyModel.new(
102
+ # my_string: "string",
103
+ # my_int_array: ["1", "2", "3"],
104
+ # my_float_range: "[1,3.5]",
105
+ # )
106
+ # model.attributes
107
+ # # =>
108
+ # {
109
+ # my_string: "string",
110
+ # my_int_array: [1, 2, 3],
111
+ # my_float_range: 1.0..3.5
112
+ # }
113
+ #
114
+ # ==== Creating Custom Types
115
+ #
116
+ # Users may also define their own custom types, as long as they respond
117
+ # to the methods defined on the value type. The method +deserialize+ or
118
+ # +cast+ will be called on your type object, with raw input from the
119
+ # database or from your controllers. See ActiveRecord::Type::Value for the
120
+ # expected API. It is recommended that your type objects inherit from an
121
+ # existing type, or from ActiveRecord::Type::Value
62
122
  #
63
123
  # class MoneyType < ActiveRecord::Type::Integer
64
- # def type_cast(value)
65
- # if value.include?('$')
124
+ # def cast(value)
125
+ # if !value.kind_of?(Numeric) && value.include?('$')
66
126
  # price_in_dollars = value.gsub(/\$/, '').to_f
67
- # price_in_dollars * 100
127
+ # super(price_in_dollars * 100)
68
128
  # else
69
- # value.to_i
129
+ # super
70
130
  # end
71
131
  # end
72
132
  # end
73
133
  #
134
+ # # config/initializers/types.rb
135
+ # ActiveRecord::Type.register(:money, MoneyType)
136
+ #
137
+ # # app/models/store_listing.rb
74
138
  # class StoreListing < ActiveRecord::Base
75
- # attribute :price_in_cents, MoneyType.new
139
+ # attribute :price_in_cents, :money
76
140
  # end
77
141
  #
78
142
  # store_listing = StoreListing.new(price_in_cents: '$10.00')
79
143
  # store_listing.price_in_cents # => 1000
80
- def attribute(name, cast_type, options = {})
144
+ #
145
+ # For more details on creating custom types, see the documentation for
146
+ # ActiveRecord::Type::Value. For more details on registering your types
147
+ # to be referenced by a symbol, see ActiveRecord::Type.register. You can
148
+ # also pass a type object directly, in place of a symbol.
149
+ #
150
+ # ==== \Querying
151
+ #
152
+ # When {ActiveRecord::Base.where}[rdoc-ref:QueryMethods#where] is called, it will
153
+ # use the type defined by the model class to convert the value to SQL,
154
+ # calling +serialize+ on your type object. For example:
155
+ #
156
+ # class Money < Struct.new(:amount, :currency)
157
+ # end
158
+ #
159
+ # class MoneyType < Type::Value
160
+ # def initialize(currency_converter:)
161
+ # @currency_converter = currency_converter
162
+ # end
163
+ #
164
+ # # value will be the result of +deserialize+ or
165
+ # # +cast+. Assumed to be an instance of +Money+ in
166
+ # # this case.
167
+ # def serialize(value)
168
+ # value_in_bitcoins = @currency_converter.convert_to_bitcoins(value)
169
+ # value_in_bitcoins.amount
170
+ # end
171
+ # end
172
+ #
173
+ # # config/initializers/types.rb
174
+ # ActiveRecord::Type.register(:money, MoneyType)
175
+ #
176
+ # # app/models/product.rb
177
+ # class Product < ActiveRecord::Base
178
+ # currency_converter = ConversionRatesFromTheInternet.new
179
+ # attribute :price_in_bitcoins, :money, currency_converter: currency_converter
180
+ # end
181
+ #
182
+ # Product.where(price_in_bitcoins: Money.new(5, "USD"))
183
+ # # => SELECT * FROM products WHERE price_in_bitcoins = 0.02230
184
+ #
185
+ # Product.where(price_in_bitcoins: Money.new(5, "GBP"))
186
+ # # => SELECT * FROM products WHERE price_in_bitcoins = 0.03412
187
+ #
188
+ # ==== Dirty Tracking
189
+ #
190
+ # The type of an attribute is given the opportunity to change how dirty
191
+ # tracking is performed. The methods +changed?+ and +changed_in_place?+
192
+ # will be called from ActiveModel::Dirty. See the documentation for those
193
+ # methods in ActiveRecord::Type::Value for more details.
194
+ def attribute(name, cast_type, **options)
81
195
  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)
196
+ reload_schema_from_cache
85
197
 
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))
198
+ self.attributes_to_define_after_schema_loads =
199
+ attributes_to_define_after_schema_loads.merge(
200
+ name => [cast_type, options]
201
+ )
94
202
  end
95
203
 
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] }]
99
- end
100
-
101
- def persistable_attribute_names # :nodoc:
102
- @persistable_attribute_names ||= connection.schema_cache.columns_hash(table_name).keys
204
+ # This is the low level API which sits beneath +attribute+. It only
205
+ # accepts type objects, and will do its work immediately instead of
206
+ # waiting for the schema to load. Automatic schema detection and
207
+ # ClassMethods#attribute both call this under the hood. While this method
208
+ # is provided so it can be used by plugin authors, application code
209
+ # should probably use ClassMethods#attribute.
210
+ #
211
+ # +name+ The name of the attribute being defined. Expected to be a +String+.
212
+ #
213
+ # +cast_type+ The type object to use for this attribute.
214
+ #
215
+ # +default+ The default value to use when no value is provided. If this option
216
+ # is not passed, the previous default value (if any) will be used.
217
+ # Otherwise, the default will be +nil+. A proc can also be passed, and
218
+ # will be called once each time a new value is needed.
219
+ #
220
+ # +user_provided_default+ Whether the default value should be cast using
221
+ # +cast+ or +deserialize+.
222
+ def define_attribute(
223
+ name,
224
+ cast_type,
225
+ default: NO_DEFAULT_PROVIDED,
226
+ user_provided_default: true
227
+ )
228
+ attribute_types[name] = cast_type
229
+ define_default_attribute(name, default, cast_type, from_user: user_provided_default)
103
230
  end
104
231
 
105
- def reset_column_information # :nodoc:
232
+ def load_schema! # :nodoc:
106
233
  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
234
+ attributes_to_define_after_schema_loads.each do |name, (type, options)|
235
+ if type.is_a?(Symbol)
236
+ type = ActiveRecord::Type.lookup(type, **options.except(:default))
119
237
  end
120
- end
121
238
 
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)
239
+ define_attribute(name, type, **options.slice(:default))
125
240
  end
126
-
127
- existing_columns + new_columns
128
241
  end
129
242
 
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
243
+ private
244
+
245
+ NO_DEFAULT_PROVIDED = Object.new # :nodoc:
246
+ private_constant :NO_DEFAULT_PROVIDED
141
247
 
142
- def raw_default_values
143
- super.merge(user_provided_defaults)
248
+ def define_default_attribute(name, value, type, from_user:)
249
+ if value == NO_DEFAULT_PROVIDED
250
+ default_attribute = _default_attributes[name].with_type(type)
251
+ elsif from_user
252
+ default_attribute = Attribute::UserProvidedDefault.new(
253
+ name,
254
+ value,
255
+ type,
256
+ _default_attributes.fetch(name.to_s) { nil },
257
+ )
258
+ else
259
+ default_attribute = Attribute.from_database(name, value, type)
260
+ end
261
+ _default_attributes[name] = default_attribute
144
262
  end
145
263
  end
146
264
  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
@@ -22,7 +22,7 @@ module ActiveRecord
22
22
  #
23
23
  # == Validation
24
24
  #
25
- # Children records are validated unless <tt>:validate</tt> is +false+.
25
+ # Child records are validated unless <tt>:validate</tt> is +false+.
26
26
  #
27
27
  # == Callbacks
28
28
  #
@@ -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,17 +325,30 @@ 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)
332
+
324
333
  unless valid = record.valid?(validation_context)
325
334
  if reflection.options[:autosave]
335
+ indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveRecord::Base.index_nested_attribute_errors)
336
+
326
337
  record.errors.each do |attribute, message|
327
- attribute = "#{reflection.name}.#{attribute}"
338
+ attribute = normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
328
339
  errors[attribute] << message
329
340
  errors[attribute].uniq!
330
341
  end
342
+
343
+ record.errors.details.each_key do |attribute|
344
+ reflection_attribute =
345
+ normalize_reflection_attribute(indexed_attribute, reflection, index, attribute).to_sym
346
+
347
+ record.errors.details[attribute].each do |error|
348
+ errors.details[reflection_attribute] << error
349
+ errors.details[reflection_attribute].uniq!
350
+ end
351
+ end
331
352
  else
332
353
  errors.add(reflection.name)
333
354
  end
@@ -335,6 +356,14 @@ module ActiveRecord
335
356
  valid
336
357
  end
337
358
 
359
+ def normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
360
+ if indexed_attribute
361
+ "#{reflection.name}[#{index}].#{attribute}"
362
+ else
363
+ "#{reflection.name}.#{attribute}"
364
+ end
365
+ end
366
+
338
367
  # Is used as a before_save callback to check while saving a collection
339
368
  # association whether or not the parent was a new record before saving.
340
369
  def before_save_collection_association
@@ -346,7 +375,7 @@ module ActiveRecord
346
375
  # <tt>:autosave</tt> is enabled on the association.
347
376
  #
348
377
  # In addition, it destroys all children that were marked for destruction
349
- # with mark_for_destruction.
378
+ # with #mark_for_destruction.
350
379
  #
351
380
  # This all happens inside a transaction, _if_ the Transactions module is included into
352
381
  # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
@@ -389,7 +418,7 @@ module ActiveRecord
389
418
  # on the association.
390
419
  #
391
420
  # In addition, it will destroy the association if it was marked for
392
- # destruction with mark_for_destruction.
421
+ # destruction with #mark_for_destruction.
393
422
  #
394
423
  # This all happens inside a transaction, _if_ the Transactions module is included into
395
424
  # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
@@ -430,7 +459,9 @@ module ActiveRecord
430
459
  # In addition, it will destroy the association if it was marked for destruction.
431
460
  def save_belongs_to_association(reflection)
432
461
  association = association_instance_get(reflection.name)
433
- record = association && association.load_target
462
+ return unless association && association.loaded? && !association.stale_target?
463
+
464
+ record = association.load_target
434
465
  if record && !record.destroyed?
435
466
  autosave = reflection.options[:autosave]
436
467
 
@@ -450,5 +481,11 @@ module ActiveRecord
450
481
  end
451
482
  end
452
483
  end
484
+
485
+ def _ensure_no_duplicate_errors
486
+ errors.messages.each_key do |attribute|
487
+ errors[attribute].uniq!
488
+ end
489
+ end
453
490
  end
454
491
  end