activerecord 4.2.6 → 5.0.0

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 (246) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1307 -1105
  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/aggregations.rb +37 -23
  8. data/lib/active_record/association_relation.rb +3 -3
  9. data/lib/active_record/associations/alias_tracker.rb +19 -16
  10. data/lib/active_record/associations/association.rb +11 -9
  11. data/lib/active_record/associations/association_scope.rb +73 -102
  12. data/lib/active_record/associations/belongs_to_association.rb +21 -32
  13. data/lib/active_record/associations/builder/association.rb +28 -34
  14. data/lib/active_record/associations/builder/belongs_to.rb +43 -18
  15. data/lib/active_record/associations/builder/collection_association.rb +7 -19
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +14 -11
  17. data/lib/active_record/associations/builder/has_many.rb +4 -4
  18. data/lib/active_record/associations/builder/has_one.rb +11 -6
  19. data/lib/active_record/associations/builder/singular_association.rb +3 -10
  20. data/lib/active_record/associations/collection_association.rb +50 -31
  21. data/lib/active_record/associations/collection_proxy.rb +69 -29
  22. data/lib/active_record/associations/foreign_association.rb +1 -1
  23. data/lib/active_record/associations/has_many_association.rb +20 -71
  24. data/lib/active_record/associations/has_many_through_association.rb +8 -47
  25. data/lib/active_record/associations/has_one_association.rb +12 -5
  26. data/lib/active_record/associations/join_dependency/join_association.rb +20 -8
  27. data/lib/active_record/associations/join_dependency.rb +29 -19
  28. data/lib/active_record/associations/preloader/association.rb +46 -52
  29. data/lib/active_record/associations/preloader/collection_association.rb +0 -6
  30. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  31. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  32. data/lib/active_record/associations/preloader/through_association.rb +27 -14
  33. data/lib/active_record/associations/preloader.rb +14 -4
  34. data/lib/active_record/associations/singular_association.rb +7 -1
  35. data/lib/active_record/associations/through_association.rb +11 -3
  36. data/lib/active_record/associations.rb +317 -209
  37. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  38. data/lib/active_record/attribute.rb +68 -18
  39. data/lib/active_record/attribute_assignment.rb +20 -141
  40. data/lib/active_record/attribute_decorators.rb +6 -5
  41. data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
  42. data/lib/active_record/attribute_methods/dirty.rb +46 -86
  43. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  44. data/lib/active_record/attribute_methods/query.rb +2 -2
  45. data/lib/active_record/attribute_methods/read.rb +31 -59
  46. data/lib/active_record/attribute_methods/serialization.rb +13 -16
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -14
  48. data/lib/active_record/attribute_methods/write.rb +14 -38
  49. data/lib/active_record/attribute_methods.rb +70 -45
  50. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  51. data/lib/active_record/attribute_set/builder.rb +6 -4
  52. data/lib/active_record/attribute_set.rb +30 -3
  53. data/lib/active_record/attributes.rb +199 -80
  54. data/lib/active_record/autosave_association.rb +49 -16
  55. data/lib/active_record/base.rb +32 -23
  56. data/lib/active_record/callbacks.rb +39 -43
  57. data/lib/active_record/coders/json.rb +1 -1
  58. data/lib/active_record/coders/yaml_column.rb +20 -8
  59. data/lib/active_record/collection_cache_key.rb +40 -0
  60. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -182
  61. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
  62. data/lib/active_record/connection_adapters/abstract/database_statements.rb +65 -61
  63. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -2
  64. data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -9
  65. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  66. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +61 -39
  67. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -185
  68. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +72 -17
  69. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +378 -140
  70. data/lib/active_record/connection_adapters/abstract/transaction.rb +51 -34
  71. data/lib/active_record/connection_adapters/abstract_adapter.rb +153 -59
  72. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +405 -362
  73. data/lib/active_record/connection_adapters/column.rb +28 -43
  74. data/lib/active_record/connection_adapters/connection_specification.rb +15 -27
  75. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  76. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  77. data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
  78. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  79. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  81. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  82. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  83. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  84. data/lib/active_record/connection_adapters/mysql2_adapter.rb +25 -176
  85. data/lib/active_record/connection_adapters/postgresql/column.rb +5 -10
  86. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +10 -72
  87. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +27 -56
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -1
  91. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -1
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -22
  93. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +3 -3
  94. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
  95. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
  96. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -4
  97. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
  98. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +31 -17
  100. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +0 -4
  101. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
  102. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
  103. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
  104. data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
  105. data/lib/active_record/connection_adapters/postgresql/quoting.rb +26 -18
  106. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
  107. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
  108. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  109. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +234 -148
  110. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  111. data/lib/active_record/connection_adapters/postgresql_adapter.rb +248 -160
  112. data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
  113. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  114. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  115. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  116. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  117. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +148 -203
  118. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  119. data/lib/active_record/connection_handling.rb +37 -14
  120. data/lib/active_record/core.rb +89 -107
  121. data/lib/active_record/counter_cache.rb +13 -24
  122. data/lib/active_record/dynamic_matchers.rb +1 -20
  123. data/lib/active_record/enum.rb +113 -76
  124. data/lib/active_record/errors.rb +87 -48
  125. data/lib/active_record/explain_registry.rb +1 -1
  126. data/lib/active_record/explain_subscriber.rb +1 -1
  127. data/lib/active_record/fixture_set/file.rb +26 -5
  128. data/lib/active_record/fixtures.rb +76 -40
  129. data/lib/active_record/gem_version.rb +3 -3
  130. data/lib/active_record/inheritance.rb +32 -40
  131. data/lib/active_record/integration.rb +4 -4
  132. data/lib/active_record/internal_metadata.rb +56 -0
  133. data/lib/active_record/legacy_yaml_adapter.rb +18 -2
  134. data/lib/active_record/locale/en.yml +3 -2
  135. data/lib/active_record/locking/optimistic.rb +15 -15
  136. data/lib/active_record/locking/pessimistic.rb +1 -1
  137. data/lib/active_record/log_subscriber.rb +43 -21
  138. data/lib/active_record/migration/command_recorder.rb +59 -18
  139. data/lib/active_record/migration/compatibility.rb +126 -0
  140. data/lib/active_record/migration.rb +364 -109
  141. data/lib/active_record/model_schema.rb +128 -38
  142. data/lib/active_record/nested_attributes.rb +58 -29
  143. data/lib/active_record/null_relation.rb +16 -8
  144. data/lib/active_record/persistence.rb +121 -80
  145. data/lib/active_record/query_cache.rb +15 -18
  146. data/lib/active_record/querying.rb +10 -9
  147. data/lib/active_record/railtie.rb +27 -18
  148. data/lib/active_record/railties/controller_runtime.rb +1 -1
  149. data/lib/active_record/railties/databases.rake +58 -45
  150. data/lib/active_record/readonly_attributes.rb +1 -1
  151. data/lib/active_record/reflection.rb +282 -115
  152. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  153. data/lib/active_record/relation/batches.rb +139 -34
  154. data/lib/active_record/relation/calculations.rb +80 -102
  155. data/lib/active_record/relation/delegation.rb +7 -20
  156. data/lib/active_record/relation/finder_methods.rb +163 -81
  157. data/lib/active_record/relation/from_clause.rb +32 -0
  158. data/lib/active_record/relation/merger.rb +16 -42
  159. data/lib/active_record/relation/predicate_builder/array_handler.rb +11 -15
  160. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  161. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  162. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  163. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  164. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  165. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  166. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  167. data/lib/active_record/relation/predicate_builder.rb +120 -107
  168. data/lib/active_record/relation/query_attribute.rb +19 -0
  169. data/lib/active_record/relation/query_methods.rb +308 -244
  170. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  171. data/lib/active_record/relation/spawn_methods.rb +4 -7
  172. data/lib/active_record/relation/where_clause.rb +174 -0
  173. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  174. data/lib/active_record/relation.rb +176 -116
  175. data/lib/active_record/result.rb +4 -3
  176. data/lib/active_record/runtime_registry.rb +1 -1
  177. data/lib/active_record/sanitization.rb +95 -66
  178. data/lib/active_record/schema.rb +26 -22
  179. data/lib/active_record/schema_dumper.rb +62 -38
  180. data/lib/active_record/schema_migration.rb +11 -17
  181. data/lib/active_record/scoping/default.rb +23 -9
  182. data/lib/active_record/scoping/named.rb +49 -28
  183. data/lib/active_record/scoping.rb +32 -15
  184. data/lib/active_record/secure_token.rb +38 -0
  185. data/lib/active_record/serialization.rb +2 -4
  186. data/lib/active_record/statement_cache.rb +16 -14
  187. data/lib/active_record/store.rb +8 -3
  188. data/lib/active_record/suppressor.rb +58 -0
  189. data/lib/active_record/table_metadata.rb +68 -0
  190. data/lib/active_record/tasks/database_tasks.rb +58 -41
  191. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -20
  192. data/lib/active_record/tasks/postgresql_database_tasks.rb +11 -2
  193. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  194. data/lib/active_record/timestamp.rb +20 -9
  195. data/lib/active_record/touch_later.rb +58 -0
  196. data/lib/active_record/transactions.rb +138 -56
  197. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  198. data/lib/active_record/type/date.rb +2 -41
  199. data/lib/active_record/type/date_time.rb +2 -49
  200. data/lib/active_record/type/internal/abstract_json.rb +29 -0
  201. data/lib/active_record/type/internal/timezone.rb +15 -0
  202. data/lib/active_record/type/serialized.rb +15 -14
  203. data/lib/active_record/type/time.rb +10 -16
  204. data/lib/active_record/type/type_map.rb +4 -4
  205. data/lib/active_record/type.rb +66 -17
  206. data/lib/active_record/type_caster/connection.rb +29 -0
  207. data/lib/active_record/type_caster/map.rb +19 -0
  208. data/lib/active_record/type_caster.rb +7 -0
  209. data/lib/active_record/validations/absence.rb +23 -0
  210. data/lib/active_record/validations/associated.rb +10 -3
  211. data/lib/active_record/validations/length.rb +24 -0
  212. data/lib/active_record/validations/presence.rb +11 -12
  213. data/lib/active_record/validations/uniqueness.rb +30 -29
  214. data/lib/active_record/validations.rb +33 -32
  215. data/lib/active_record.rb +7 -2
  216. data/lib/rails/generators/active_record/migration/migration_generator.rb +7 -4
  217. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -3
  218. data/lib/rails/generators/active_record/migration/templates/migration.rb +8 -1
  219. data/lib/rails/generators/active_record/migration.rb +7 -0
  220. data/lib/rails/generators/active_record/model/model_generator.rb +32 -15
  221. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  222. data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
  223. metadata +58 -34
  224. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  225. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  226. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
  227. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  228. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  229. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  230. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  231. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  232. data/lib/active_record/type/big_integer.rb +0 -13
  233. data/lib/active_record/type/binary.rb +0 -50
  234. data/lib/active_record/type/boolean.rb +0 -31
  235. data/lib/active_record/type/decimal.rb +0 -50
  236. data/lib/active_record/type/decimal_without_scale.rb +0 -11
  237. data/lib/active_record/type/decorator.rb +0 -14
  238. data/lib/active_record/type/float.rb +0 -19
  239. data/lib/active_record/type/integer.rb +0 -59
  240. data/lib/active_record/type/mutable.rb +0 -16
  241. data/lib/active_record/type/numeric.rb +0 -36
  242. data/lib/active_record/type/string.rb +0 -40
  243. data/lib/active_record/type/text.rb +0 -11
  244. data/lib/active_record/type/time_value.rb +0 -38
  245. data/lib/active_record/type/unsigned_integer.rb +0 -15
  246. data/lib/active_record/type/value.rb +0 -105
@@ -0,0 +1,28 @@
1
+ require 'active_record/attribute'
2
+
3
+ module ActiveRecord
4
+ class Attribute # :nodoc:
5
+ class UserProvidedDefault < FromUser # :nodoc:
6
+ def initialize(name, value, type, database_default)
7
+ @user_provided_value = value
8
+ super(name, value, type, database_default)
9
+ end
10
+
11
+ def value_before_type_cast
12
+ if user_provided_value.is_a?(Proc)
13
+ @memoized_value_before_type_cast ||= user_provided_value.call
14
+ else
15
+ @user_provided_value
16
+ end
17
+ end
18
+
19
+ def with_type(type)
20
+ self.class.new(name, user_provided_value, type, original_attribute)
21
+ end
22
+
23
+ protected
24
+
25
+ attr_reader :user_provided_value
26
+ end
27
+ end
28
+ end
@@ -5,8 +5,8 @@ module ActiveRecord
5
5
  FromDatabase.new(name, value, type)
6
6
  end
7
7
 
8
- def from_user(name, value, type)
9
- FromUser.new(name, value, type)
8
+ def from_user(name, value, type, original_attribute = nil)
9
+ FromUser.new(name, value, type, original_attribute)
10
10
  end
11
11
 
12
12
  def with_cast_value(name, value, type)
@@ -26,36 +26,46 @@ module ActiveRecord
26
26
 
27
27
  # This method should not be called directly.
28
28
  # Use #from_database or #from_user
29
- def initialize(name, value_before_type_cast, type)
29
+ def initialize(name, value_before_type_cast, type, original_attribute = nil)
30
30
  @name = name
31
31
  @value_before_type_cast = value_before_type_cast
32
32
  @type = type
33
+ @original_attribute = original_attribute
33
34
  end
34
35
 
35
36
  def value
36
37
  # `defined?` is cheaper than `||=` when we get back falsy values
37
- @value = original_value unless defined?(@value)
38
+ @value = type_cast(value_before_type_cast) unless defined?(@value)
38
39
  @value
39
40
  end
40
41
 
41
42
  def original_value
42
- type_cast(value_before_type_cast)
43
+ if assigned?
44
+ original_attribute.original_value
45
+ else
46
+ type_cast(value_before_type_cast)
47
+ end
43
48
  end
44
49
 
45
50
  def value_for_database
46
- type.type_cast_for_database(value)
51
+ type.serialize(value)
52
+ end
53
+
54
+ def changed?
55
+ changed_from_assignment? || changed_in_place?
47
56
  end
48
57
 
49
- def changed_from?(old_value)
50
- type.changed?(old_value, value, value_before_type_cast)
58
+ def changed_in_place?
59
+ has_been_read? && type.changed_in_place?(original_value_for_database, value)
51
60
  end
52
61
 
53
- def changed_in_place_from?(old_value)
54
- has_been_read? && type.changed_in_place?(old_value, value)
62
+ def forgetting_assignment
63
+ with_value_from_database(value_for_database)
55
64
  end
56
65
 
57
66
  def with_value_from_user(value)
58
- self.class.from_user(name, value, type)
67
+ type.assert_valid_value(value)
68
+ self.class.from_user(name, value, type, self)
59
69
  end
60
70
 
61
71
  def with_value_from_database(value)
@@ -66,6 +76,10 @@ module ActiveRecord
66
76
  self.class.with_cast_value(name, value, type)
67
77
  end
68
78
 
79
+ def with_type(type)
80
+ self.class.new(name, value_before_type_cast, type, original_attribute)
81
+ end
82
+
69
83
  def type_cast(*)
70
84
  raise NotImplementedError
71
85
  end
@@ -78,36 +92,62 @@ module ActiveRecord
78
92
  false
79
93
  end
80
94
 
95
+ def has_been_read?
96
+ defined?(@value)
97
+ end
98
+
81
99
  def ==(other)
82
100
  self.class == other.class &&
83
101
  name == other.name &&
84
102
  value_before_type_cast == other.value_before_type_cast &&
85
103
  type == other.type
86
104
  end
105
+ alias eql? ==
106
+
107
+ def hash
108
+ [self.class, name, value_before_type_cast, type].hash
109
+ end
87
110
 
88
111
  protected
89
112
 
113
+ attr_reader :original_attribute
114
+ alias_method :assigned?, :original_attribute
115
+
90
116
  def initialize_dup(other)
91
117
  if defined?(@value) && @value.duplicable?
92
118
  @value = @value.dup
93
119
  end
94
120
  end
95
121
 
96
- private
122
+ def changed_from_assignment?
123
+ assigned? && type.changed?(original_value, value, value_before_type_cast)
124
+ end
125
+
126
+ def original_value_for_database
127
+ if assigned?
128
+ original_attribute.original_value_for_database
129
+ else
130
+ _original_value_for_database
131
+ end
132
+ end
97
133
 
98
- def has_been_read?
99
- defined?(@value)
134
+ def _original_value_for_database
135
+ value_for_database
100
136
  end
101
137
 
102
138
  class FromDatabase < Attribute # :nodoc:
103
139
  def type_cast(value)
104
- type.type_cast_from_database(value)
140
+ type.deserialize(value)
141
+ end
142
+
143
+ def _original_value_for_database
144
+ value_before_type_cast
105
145
  end
106
146
  end
107
147
 
108
148
  class FromUser < Attribute # :nodoc:
109
149
  def type_cast(value)
110
- type.type_cast_from_user(value)
150
+ type.cast(value)
111
151
  end
112
152
 
113
153
  def came_from_user?
@@ -130,10 +170,14 @@ module ActiveRecord
130
170
  super(name, nil, Type::Value.new)
131
171
  end
132
172
 
133
- def value
173
+ def type_cast(*)
134
174
  nil
135
175
  end
136
176
 
177
+ def with_type(type)
178
+ self.class.with_cast_value(name, nil, type)
179
+ end
180
+
137
181
  def with_value_from_database(value)
138
182
  raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`"
139
183
  end
@@ -141,6 +185,8 @@ module ActiveRecord
141
185
  end
142
186
 
143
187
  class Uninitialized < Attribute # :nodoc:
188
+ UNINITIALIZED_ORIGINAL_VALUE = Object.new
189
+
144
190
  def initialize(name, type)
145
191
  super(name, nil, type)
146
192
  end
@@ -151,6 +197,10 @@ module ActiveRecord
151
197
  end
152
198
  end
153
199
 
200
+ def original_value
201
+ UNINITIALIZED_ORIGINAL_VALUE
202
+ end
203
+
154
204
  def value_for_database
155
205
  end
156
206
 
@@ -158,6 +208,6 @@ module ActiveRecord
158
208
  false
159
209
  end
160
210
  end
161
- private_constant :FromDatabase, :FromUser, :Null, :Uninitialized
211
+ private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue
162
212
  end
163
213
  end
@@ -3,63 +3,32 @@ require 'active_model/forbidden_attributes_protection'
3
3
  module ActiveRecord
4
4
  module AttributeAssignment
5
5
  extend ActiveSupport::Concern
6
- include ActiveModel::ForbiddenAttributesProtection
6
+ include ActiveModel::AttributeAssignment
7
7
 
8
- # Allows you to set all the attributes by passing in a hash of attributes with
9
- # keys matching the attribute names (which again matches the column names).
10
- #
11
- # If the passed hash responds to <tt>permitted?</tt> method and the return value
12
- # of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
13
- # exception is raised.
14
- #
15
- # cat = Cat.new(name: "Gorby", status: "yawning")
16
- # cat.attributes # => { "name" => "Gorby", "status" => "yawning", "created_at" => nil, "updated_at" => nil}
17
- # cat.assign_attributes(status: "sleeping")
18
- # cat.attributes # => { "name" => "Gorby", "status" => "sleeping", "created_at" => nil, "updated_at" => nil }
19
- #
20
- # New attributes will be persisted in the database when the object is saved.
21
- #
22
- # Aliased to <tt>attributes=</tt>.
23
- def assign_attributes(new_attributes)
24
- if !new_attributes.respond_to?(:stringify_keys)
25
- raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
26
- end
27
- return if new_attributes.blank?
8
+ # Alias for ActiveModel::AttributeAssignment#assign_attributes. See ActiveModel::AttributeAssignment.
9
+ def attributes=(attributes)
10
+ assign_attributes(attributes)
11
+ end
28
12
 
29
- attributes = new_attributes.stringify_keys
30
- multi_parameter_attributes = []
31
- nested_parameter_attributes = []
13
+ private
32
14
 
33
- attributes = sanitize_for_mass_assignment(attributes)
15
+ def _assign_attributes(attributes) # :nodoc:
16
+ multi_parameter_attributes = {}
17
+ nested_parameter_attributes = {}
34
18
 
35
19
  attributes.each do |k, v|
36
20
  if k.include?("(")
37
- multi_parameter_attributes << [ k, v ]
21
+ multi_parameter_attributes[k] = attributes.delete(k)
38
22
  elsif v.is_a?(Hash)
39
- nested_parameter_attributes << [ k, v ]
40
- else
41
- _assign_attribute(k, v)
23
+ nested_parameter_attributes[k] = attributes.delete(k)
42
24
  end
43
25
  end
26
+ super(attributes)
44
27
 
45
28
  assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
46
29
  assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
47
30
  end
48
31
 
49
- alias attributes= assign_attributes
50
-
51
- private
52
-
53
- def _assign_attribute(k, v)
54
- public_send("#{k}=", v)
55
- rescue NoMethodError, NameError
56
- if respond_to?("#{k}=")
57
- raise
58
- else
59
- raise UnknownAttributeError.new(self, k)
60
- end
61
- end
62
-
63
32
  # Assign any deferred nested attributes after the base attributes have been set.
64
33
  def assign_nested_parameter_attributes(pairs)
65
34
  pairs.each { |k, v| _assign_attribute(k, v) }
@@ -69,7 +38,7 @@ module ActiveRecord
69
38
  # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
70
39
  # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
71
40
  # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
72
- # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum and
41
+ # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and
73
42
  # f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
74
43
  def assign_multiparameter_attributes(pairs)
75
44
  execute_callstack_for_multiparameter_attributes(
@@ -81,13 +50,18 @@ module ActiveRecord
81
50
  errors = []
82
51
  callstack.each do |name, values_with_empty_parameters|
83
52
  begin
84
- send("#{name}=", MultiparameterAttribute.new(self, name, values_with_empty_parameters).read_value)
53
+ if values_with_empty_parameters.each_value.all?(&:nil?)
54
+ values = nil
55
+ else
56
+ values = values_with_empty_parameters
57
+ end
58
+ send("#{name}=", values)
85
59
  rescue => ex
86
60
  errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
87
61
  end
88
62
  end
89
63
  unless errors.empty?
90
- error_descriptions = errors.map { |ex| ex.message }.join(",")
64
+ error_descriptions = errors.map(&:message).join(",")
91
65
  raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
92
66
  end
93
67
  end
@@ -113,100 +87,5 @@ module ActiveRecord
113
87
  def find_parameter_position(multiparameter_name)
114
88
  multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
115
89
  end
116
-
117
- class MultiparameterAttribute #:nodoc:
118
- attr_reader :object, :name, :values, :cast_type
119
-
120
- def initialize(object, name, values)
121
- @object = object
122
- @name = name
123
- @values = values
124
- end
125
-
126
- def read_value
127
- return if values.values.compact.empty?
128
-
129
- @cast_type = object.type_for_attribute(name)
130
- klass = cast_type.klass
131
-
132
- if klass == Time
133
- read_time
134
- elsif klass == Date
135
- read_date
136
- else
137
- read_other
138
- end
139
- end
140
-
141
- private
142
-
143
- def instantiate_time_object(set_values)
144
- if object.class.send(:create_time_zone_conversion_attribute?, name, cast_type)
145
- Time.zone.local(*set_values)
146
- else
147
- Time.send(object.class.default_timezone, *set_values)
148
- end
149
- end
150
-
151
- def read_time
152
- # If column is a :time (and not :date or :datetime) there is no need to validate if
153
- # there are year/month/day fields
154
- if cast_type.type == :time
155
- # if the column is a time set the values to their defaults as January 1, 1970, but only if they're nil
156
- { 1 => 1970, 2 => 1, 3 => 1 }.each do |key,value|
157
- values[key] ||= value
158
- end
159
- else
160
- # else column is a timestamp, so if Date bits were not provided, error
161
- validate_required_parameters!([1,2,3])
162
-
163
- # If Date bits were provided but blank, then return nil
164
- return if blank_date_parameter?
165
- end
166
-
167
- max_position = extract_max_param(6)
168
- set_values = values.values_at(*(1..max_position))
169
- # If Time bits are not there, then default to 0
170
- (3..5).each { |i| set_values[i] = set_values[i].presence || 0 }
171
- instantiate_time_object(set_values)
172
- end
173
-
174
- def read_date
175
- return if blank_date_parameter?
176
- set_values = values.values_at(1,2,3)
177
- begin
178
- Date.new(*set_values)
179
- rescue ArgumentError # if Date.new raises an exception on an invalid date
180
- instantiate_time_object(set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
181
- end
182
- end
183
-
184
- def read_other
185
- max_position = extract_max_param
186
- positions = (1..max_position)
187
- validate_required_parameters!(positions)
188
-
189
- values.slice(*positions)
190
- end
191
-
192
- # Checks whether some blank date parameter exists. Note that this is different
193
- # than the validate_required_parameters! method, since it just checks for blank
194
- # positions instead of missing ones, and does not raise in case one blank position
195
- # exists. The caller is responsible to handle the case of this returning true.
196
- def blank_date_parameter?
197
- (1..3).any? { |position| values[position].blank? }
198
- end
199
-
200
- # If some position is not provided, it errors out a missing parameter exception.
201
- def validate_required_parameters!(positions)
202
- if missing_parameter = positions.detect { |position| !values.key?(position) }
203
- raise ArgumentError.new("Missing Parameter - #{name}(#{missing_parameter})")
204
- end
205
- end
206
-
207
- def extract_max_param(upper_cap = 100)
208
- [values.keys.max, upper_cap].min
209
- end
210
- end
211
90
  end
212
91
  end
@@ -15,7 +15,7 @@ module ActiveRecord
15
15
  end
16
16
 
17
17
  def decorate_matching_attribute_types(matcher, decorator_name, &block)
18
- clear_caches_calculated_from_columns
18
+ reload_schema_from_cache
19
19
  decorator_name = decorator_name.to_s
20
20
 
21
21
  # Create new hashes so we don't modify parent classes
@@ -24,10 +24,11 @@ module ActiveRecord
24
24
 
25
25
  private
26
26
 
27
- def add_user_provided_columns(*)
28
- super.map do |column|
29
- decorated_type = attribute_type_decorations.apply(column.name, column.cast_type)
30
- column.with_type(decorated_type)
27
+ def load_schema!
28
+ super
29
+ attribute_types.each do |name, type|
30
+ decorated_type = attribute_type_decorations.apply(name, type)
31
+ define_attribute(name, decorated_type)
31
32
  end
32
33
  end
33
34
  end
@@ -2,7 +2,7 @@ module ActiveRecord
2
2
  module AttributeMethods
3
3
  # = Active Record Attribute Methods Before Type Cast
4
4
  #
5
- # <tt>ActiveRecord::AttributeMethods::BeforeTypeCast</tt> provides a way to
5
+ # ActiveRecord::AttributeMethods::BeforeTypeCast provides a way to
6
6
  # read the value of the attributes before typecasting and deserialization.
7
7
  #
8
8
  # class Task < ActiveRecord::Base
@@ -1,4 +1,5 @@
1
1
  require 'active_support/core_ext/module/attribute_accessors'
2
+ require 'active_record/attribute_mutation_tracker'
2
3
 
3
4
  module ActiveRecord
4
5
  module AttributeMethods
@@ -34,24 +35,43 @@ module ActiveRecord
34
35
  # <tt>reload</tt> the record and clears changed attributes.
35
36
  def reload(*)
36
37
  super.tap do
37
- clear_changes_information
38
+ @mutation_tracker = nil
39
+ @previous_mutation_tracker = nil
40
+ @changed_attributes = HashWithIndifferentAccess.new
38
41
  end
39
42
  end
40
43
 
41
44
  def initialize_dup(other) # :nodoc:
42
45
  super
43
- @original_raw_attributes = nil
44
- calculate_changes_from_defaults
46
+ @attributes = self.class._default_attributes.map do |attr|
47
+ attr.with_value_from_user(@attributes.fetch_value(attr.name))
48
+ end
49
+ @mutation_tracker = nil
45
50
  end
46
51
 
47
52
  def changes_applied
48
- super
49
- store_original_raw_attributes
53
+ @previous_mutation_tracker = mutation_tracker
54
+ @changed_attributes = HashWithIndifferentAccess.new
55
+ store_original_attributes
50
56
  end
51
57
 
52
58
  def clear_changes_information
59
+ @previous_mutation_tracker = nil
60
+ @changed_attributes = HashWithIndifferentAccess.new
61
+ store_original_attributes
62
+ end
63
+
64
+ def raw_write_attribute(attr_name, *)
65
+ result = super
66
+ clear_attribute_change(attr_name)
67
+ result
68
+ end
69
+
70
+ def clear_attribute_changes(attr_names)
53
71
  super
54
- original_raw_attributes.clear
72
+ attr_names.each do |attr_name|
73
+ clear_attribute_change(attr_name)
74
+ end
55
75
  end
56
76
 
57
77
  def changed_attributes
@@ -60,7 +80,7 @@ module ActiveRecord
60
80
  if defined?(@cached_changed_attributes)
61
81
  @cached_changed_attributes
62
82
  else
63
- super.reverse_merge(attributes_changed_in_place).freeze
83
+ super.reverse_merge(mutation_tracker.changed_values).freeze
64
84
  end
65
85
  end
66
86
 
@@ -70,59 +90,29 @@ module ActiveRecord
70
90
  end
71
91
  end
72
92
 
93
+ def previous_changes
94
+ previous_mutation_tracker.changes
95
+ end
96
+
73
97
  def attribute_changed_in_place?(attr_name)
74
- old_value = original_raw_attribute(attr_name)
75
- @attributes[attr_name].changed_in_place_from?(old_value)
98
+ mutation_tracker.changed_in_place?(attr_name)
76
99
  end
77
100
 
78
101
  private
79
102
 
80
- def changes_include?(attr_name)
81
- super || attribute_changed_in_place?(attr_name)
82
- end
83
-
84
- def calculate_changes_from_defaults
85
- @changed_attributes = nil
86
- self.class.column_defaults.each do |attr, orig_value|
87
- set_attribute_was(attr, orig_value) if _field_changed?(attr, orig_value)
103
+ def mutation_tracker
104
+ unless defined?(@mutation_tracker)
105
+ @mutation_tracker = nil
88
106
  end
107
+ @mutation_tracker ||= AttributeMutationTracker.new(@attributes)
89
108
  end
90
109
 
91
- # Wrap write_attribute to remember original attribute value.
92
- def write_attribute(attr, value)
93
- attr = attr.to_s
94
-
95
- old_value = old_attribute_value(attr)
96
-
97
- result = super
98
- store_original_raw_attribute(attr)
99
- save_changed_attribute(attr, old_value)
100
- result
101
- end
102
-
103
- def raw_write_attribute(attr, value)
104
- attr = attr.to_s
105
-
106
- result = super
107
- original_raw_attributes[attr] = value
108
- result
110
+ def changes_include?(attr_name)
111
+ super || mutation_tracker.changed?(attr_name)
109
112
  end
110
113
 
111
- def save_changed_attribute(attr, old_value)
112
- clear_changed_attributes_cache
113
- if attribute_changed_by_setter?(attr)
114
- clear_attribute_changes(attr) unless _field_changed?(attr, old_value)
115
- else
116
- set_attribute_was(attr, old_value) if _field_changed?(attr, old_value)
117
- end
118
- end
119
-
120
- def old_attribute_value(attr)
121
- if attribute_changed?(attr)
122
- changed_attributes[attr]
123
- else
124
- clone_attribute_value(:_read_attribute, attr)
125
- end
114
+ def clear_attribute_change(attr_name)
115
+ mutation_tracker.forget_change(attr_name)
126
116
  end
127
117
 
128
118
  def _update_record(*)
@@ -133,47 +123,17 @@ module ActiveRecord
133
123
  partial_writes? ? super(keys_for_partial_write) : super
134
124
  end
135
125
 
136
- # Serialized attributes should always be written in case they've been
137
- # changed in place.
138
126
  def keys_for_partial_write
139
- changed & persistable_attribute_names
140
- end
141
-
142
- def _field_changed?(attr, old_value)
143
- @attributes[attr].changed_from?(old_value)
144
- end
145
-
146
- def attributes_changed_in_place
147
- changed_in_place.each_with_object({}) do |attr_name, h|
148
- orig = @attributes[attr_name].original_value
149
- h[attr_name] = orig
150
- end
151
- end
152
-
153
- def changed_in_place
154
- self.class.attribute_names.select do |attr_name|
155
- attribute_changed_in_place?(attr_name)
156
- end
127
+ changed & self.class.column_names
157
128
  end
158
129
 
159
- def original_raw_attribute(attr_name)
160
- original_raw_attributes.fetch(attr_name) do
161
- read_attribute_before_type_cast(attr_name)
162
- end
130
+ def store_original_attributes
131
+ @attributes = @attributes.map(&:forgetting_assignment)
132
+ @mutation_tracker = nil
163
133
  end
164
134
 
165
- def original_raw_attributes
166
- @original_raw_attributes ||= {}
167
- end
168
-
169
- def store_original_raw_attribute(attr_name)
170
- original_raw_attributes[attr_name] = @attributes[attr_name].value_for_database rescue nil
171
- end
172
-
173
- def store_original_raw_attributes
174
- attribute_names.each do |attr|
175
- store_original_raw_attribute(attr)
176
- end
135
+ def previous_mutation_tracker
136
+ @previous_mutation_tracker ||= NullMutationTracker.instance
177
137
  end
178
138
 
179
139
  def cache_changed_attributes
@@ -5,7 +5,7 @@ module ActiveRecord
5
5
  module PrimaryKey
6
6
  extend ActiveSupport::Concern
7
7
 
8
- # Returns this record's primary key value wrapped in an Array if one is
8
+ # Returns this record's primary key value wrapped in an array if one is
9
9
  # available.
10
10
  def to_key
11
11
  sync_with_transaction_state
@@ -108,7 +108,7 @@ module ActiveRecord
108
108
  # self.primary_key = 'sysid'
109
109
  # end
110
110
  #
111
- # You can also define the +primary_key+ method yourself:
111
+ # You can also define the #primary_key method yourself:
112
112
  #
113
113
  # class Project < ActiveRecord::Base
114
114
  # def self.primary_key
@@ -19,10 +19,10 @@ module ActiveRecord
19
19
  if Numeric === value || value !~ /[^0-9]/
20
20
  !value.to_i.zero?
21
21
  else
22
- return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value)
22
+ return false if ActiveModel::Type::Boolean::FALSE_VALUES.include?(value)
23
23
  !value.blank?
24
24
  end
25
- elsif column.number?
25
+ elsif value.respond_to?(:zero?)
26
26
  !value.zero?
27
27
  else
28
28
  !value.blank?