activerecord 4.2.11.1 → 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 +5 -5
  2. data/CHANGELOG.md +1282 -1195
  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 +8 -4
  8. data/lib/active_record/aggregations.rb +35 -24
  9. data/lib/active_record/association_relation.rb +3 -3
  10. data/lib/active_record/associations.rb +317 -209
  11. data/lib/active_record/associations/alias_tracker.rb +19 -16
  12. data/lib/active_record/associations/association.rb +11 -9
  13. data/lib/active_record/associations/association_scope.rb +73 -102
  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 +14 -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 +3 -10
  22. data/lib/active_record/associations/collection_association.rb +49 -41
  23. data/lib/active_record/associations/collection_proxy.rb +67 -27
  24. data/lib/active_record/associations/foreign_association.rb +1 -1
  25. data/lib/active_record/associations/has_many_association.rb +20 -71
  26. data/lib/active_record/associations/has_many_through_association.rb +8 -47
  27. data/lib/active_record/associations/has_one_association.rb +12 -5
  28. data/lib/active_record/associations/join_dependency.rb +29 -19
  29. data/lib/active_record/associations/join_dependency/join_association.rb +16 -10
  30. data/lib/active_record/associations/preloader.rb +14 -4
  31. data/lib/active_record/associations/preloader/association.rb +46 -52
  32. data/lib/active_record/associations/preloader/collection_association.rb +0 -6
  33. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  34. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  35. data/lib/active_record/associations/preloader/through_association.rb +27 -14
  36. data/lib/active_record/associations/singular_association.rb +7 -1
  37. data/lib/active_record/associations/through_association.rb +11 -3
  38. data/lib/active_record/attribute.rb +68 -18
  39. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  40. data/lib/active_record/attribute_assignment.rb +19 -140
  41. data/lib/active_record/attribute_decorators.rb +6 -5
  42. data/lib/active_record/attribute_methods.rb +76 -47
  43. data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
  44. data/lib/active_record/attribute_methods/dirty.rb +46 -86
  45. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  46. data/lib/active_record/attribute_methods/query.rb +2 -2
  47. data/lib/active_record/attribute_methods/read.rb +31 -59
  48. data/lib/active_record/attribute_methods/serialization.rb +13 -16
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -14
  50. data/lib/active_record/attribute_methods/write.rb +13 -37
  51. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  52. data/lib/active_record/attribute_set.rb +30 -3
  53. data/lib/active_record/attribute_set/builder.rb +6 -4
  54. data/lib/active_record/attributes.rb +199 -81
  55. data/lib/active_record/autosave_association.rb +49 -16
  56. data/lib/active_record/base.rb +32 -23
  57. data/lib/active_record/callbacks.rb +39 -43
  58. data/lib/active_record/coders/json.rb +1 -1
  59. data/lib/active_record/coders/yaml_column.rb +20 -8
  60. data/lib/active_record/collection_cache_key.rb +40 -0
  61. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -182
  62. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
  63. data/lib/active_record/connection_adapters/abstract/database_statements.rb +65 -61
  64. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -2
  65. data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -10
  66. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  67. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +61 -39
  68. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -185
  69. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +72 -17
  70. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +380 -141
  71. data/lib/active_record/connection_adapters/abstract/transaction.rb +51 -34
  72. data/lib/active_record/connection_adapters/abstract_adapter.rb +141 -59
  73. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +401 -370
  74. data/lib/active_record/connection_adapters/column.rb +28 -43
  75. data/lib/active_record/connection_adapters/connection_specification.rb +15 -27
  76. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  77. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  78. data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
  79. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  80. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  81. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  82. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  83. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  84. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  85. data/lib/active_record/connection_adapters/mysql2_adapter.rb +29 -166
  86. data/lib/active_record/connection_adapters/postgresql/column.rb +5 -10
  87. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +10 -72
  88. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
  90. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +27 -57
  91. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -2
  92. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -1
  94. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -22
  95. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +3 -3
  96. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -4
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
  100. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +31 -17
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +0 -4
  103. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
  104. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
  105. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
  106. data/lib/active_record/connection_adapters/postgresql/quoting.rb +26 -18
  107. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
  108. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
  109. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +234 -148
  111. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  112. data/lib/active_record/connection_adapters/postgresql_adapter.rb +248 -160
  113. data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
  114. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  115. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  116. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  117. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  118. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +149 -192
  119. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  120. data/lib/active_record/connection_handling.rb +37 -14
  121. data/lib/active_record/core.rb +89 -107
  122. data/lib/active_record/counter_cache.rb +13 -24
  123. data/lib/active_record/dynamic_matchers.rb +1 -20
  124. data/lib/active_record/enum.rb +113 -76
  125. data/lib/active_record/errors.rb +87 -48
  126. data/lib/active_record/explain_registry.rb +1 -1
  127. data/lib/active_record/explain_subscriber.rb +1 -1
  128. data/lib/active_record/fixture_set/file.rb +26 -5
  129. data/lib/active_record/fixtures.rb +76 -40
  130. data/lib/active_record/gem_version.rb +4 -4
  131. data/lib/active_record/inheritance.rb +32 -40
  132. data/lib/active_record/integration.rb +4 -4
  133. data/lib/active_record/internal_metadata.rb +56 -0
  134. data/lib/active_record/legacy_yaml_adapter.rb +18 -2
  135. data/lib/active_record/locale/en.yml +3 -2
  136. data/lib/active_record/locking/optimistic.rb +15 -15
  137. data/lib/active_record/locking/pessimistic.rb +1 -1
  138. data/lib/active_record/log_subscriber.rb +43 -21
  139. data/lib/active_record/migration.rb +363 -133
  140. data/lib/active_record/migration/command_recorder.rb +59 -18
  141. data/lib/active_record/migration/compatibility.rb +126 -0
  142. data/lib/active_record/model_schema.rb +129 -41
  143. data/lib/active_record/nested_attributes.rb +58 -29
  144. data/lib/active_record/null_relation.rb +16 -8
  145. data/lib/active_record/persistence.rb +121 -80
  146. data/lib/active_record/query_cache.rb +15 -18
  147. data/lib/active_record/querying.rb +10 -9
  148. data/lib/active_record/railtie.rb +23 -16
  149. data/lib/active_record/railties/controller_runtime.rb +1 -1
  150. data/lib/active_record/railties/databases.rake +69 -46
  151. data/lib/active_record/readonly_attributes.rb +1 -1
  152. data/lib/active_record/reflection.rb +282 -115
  153. data/lib/active_record/relation.rb +176 -116
  154. data/lib/active_record/relation/batches.rb +139 -34
  155. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  156. data/lib/active_record/relation/calculations.rb +79 -108
  157. data/lib/active_record/relation/delegation.rb +7 -20
  158. data/lib/active_record/relation/finder_methods.rb +163 -81
  159. data/lib/active_record/relation/from_clause.rb +32 -0
  160. data/lib/active_record/relation/merger.rb +16 -42
  161. data/lib/active_record/relation/predicate_builder.rb +120 -107
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +11 -16
  163. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  164. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  165. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  166. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  167. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  168. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  169. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  170. data/lib/active_record/relation/query_attribute.rb +19 -0
  171. data/lib/active_record/relation/query_methods.rb +308 -244
  172. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  173. data/lib/active_record/relation/spawn_methods.rb +4 -7
  174. data/lib/active_record/relation/where_clause.rb +174 -0
  175. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  176. data/lib/active_record/result.rb +4 -3
  177. data/lib/active_record/runtime_registry.rb +1 -1
  178. data/lib/active_record/sanitization.rb +95 -66
  179. data/lib/active_record/schema.rb +26 -22
  180. data/lib/active_record/schema_dumper.rb +62 -38
  181. data/lib/active_record/schema_migration.rb +11 -14
  182. data/lib/active_record/scoping.rb +32 -15
  183. data/lib/active_record/scoping/default.rb +23 -9
  184. data/lib/active_record/scoping/named.rb +49 -28
  185. data/lib/active_record/secure_token.rb +38 -0
  186. data/lib/active_record/serialization.rb +2 -4
  187. data/lib/active_record/statement_cache.rb +16 -14
  188. data/lib/active_record/store.rb +8 -3
  189. data/lib/active_record/suppressor.rb +58 -0
  190. data/lib/active_record/table_metadata.rb +68 -0
  191. data/lib/active_record/tasks/database_tasks.rb +57 -43
  192. data/lib/active_record/tasks/mysql_database_tasks.rb +6 -14
  193. data/lib/active_record/tasks/postgresql_database_tasks.rb +11 -2
  194. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  195. data/lib/active_record/timestamp.rb +20 -9
  196. data/lib/active_record/touch_later.rb +58 -0
  197. data/lib/active_record/transactions.rb +138 -56
  198. data/lib/active_record/type.rb +66 -17
  199. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  200. data/lib/active_record/type/date.rb +2 -45
  201. data/lib/active_record/type/date_time.rb +2 -49
  202. data/lib/active_record/type/internal/abstract_json.rb +29 -0
  203. data/lib/active_record/type/internal/timezone.rb +15 -0
  204. data/lib/active_record/type/serialized.rb +15 -14
  205. data/lib/active_record/type/time.rb +10 -16
  206. data/lib/active_record/type/type_map.rb +4 -4
  207. data/lib/active_record/type_caster.rb +7 -0
  208. data/lib/active_record/type_caster/connection.rb +29 -0
  209. data/lib/active_record/type_caster/map.rb +19 -0
  210. data/lib/active_record/validations.rb +33 -32
  211. data/lib/active_record/validations/absence.rb +23 -0
  212. data/lib/active_record/validations/associated.rb +10 -3
  213. data/lib/active_record/validations/length.rb +24 -0
  214. data/lib/active_record/validations/presence.rb +11 -12
  215. data/lib/active_record/validations/uniqueness.rb +30 -29
  216. data/lib/rails/generators/active_record/migration.rb +7 -0
  217. data/lib/rails/generators/active_record/migration/migration_generator.rb +7 -4
  218. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -3
  219. data/lib/rails/generators/active_record/migration/templates/migration.rb +8 -1
  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 +59 -34
  224. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
  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 -64
  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 -110
@@ -8,7 +8,7 @@ module ActiveRecord
8
8
  records_by_owner = super
9
9
 
10
10
  if reflection_scope.distinct_value
11
- records_by_owner.each_value { |records| records.uniq! }
11
+ records_by_owner.each_value(&:uniq!)
12
12
  end
13
13
 
14
14
  records_by_owner
@@ -2,7 +2,6 @@ module ActiveRecord
2
2
  module Associations
3
3
  class Preloader
4
4
  class HasOne < SingularAssociation #:nodoc:
5
-
6
5
  def association_key_name
7
6
  reflection.foreign_key
8
7
  end
@@ -10,13 +9,6 @@ module ActiveRecord
10
9
  def owner_key_name
11
10
  reflection.active_record_primary_key
12
11
  end
13
-
14
- private
15
-
16
- def build_scope
17
- super.order(preload_scope.values[:order] || reflection_scope.values[:order])
18
- end
19
-
20
12
  end
21
13
  end
22
14
  end
@@ -18,7 +18,8 @@ module ActiveRecord
18
18
  through_records = owners.map do |owner|
19
19
  association = owner.association through_reflection.name
20
20
 
21
- [owner, Array(association.reader)]
21
+ center = target_records_from_association(association)
22
+ [owner, Array(center)]
22
23
  end
23
24
 
24
25
  reset_association owners, through_reflection.name
@@ -37,28 +38,35 @@ module ActiveRecord
37
38
  }
38
39
  end
39
40
 
40
- record_offset = {}
41
- @preloaded_records.each_with_index do |record,i|
42
- record_offset[record] = i
43
- end
44
-
45
- through_records.each_with_object({}) { |(lhs,center),records_by_owner|
41
+ through_records.each_with_object({}) do |(lhs,center), records_by_owner|
46
42
  pl_to_middle = center.group_by { |record| middle_to_pl[record] }
47
43
 
48
44
  records_by_owner[lhs] = pl_to_middle.flat_map do |pl, middles|
49
45
  rhs_records = middles.flat_map { |r|
50
46
  association = r.association source_reflection.name
51
47
 
52
- association.reader
48
+ target_records_from_association(association)
53
49
  }.compact
54
50
 
55
- rhs_records.sort_by { |rhs| record_offset[rhs] }
51
+ # Respect the order on `reflection_scope` if it exists, else use the natural order.
52
+ if reflection_scope.values[:order].present?
53
+ @id_map ||= id_to_index_map @preloaded_records
54
+ rhs_records.sort_by { |rhs| @id_map[rhs] }
55
+ else
56
+ rhs_records
57
+ end
56
58
  end
57
- }
59
+ end
58
60
  end
59
61
 
60
62
  private
61
63
 
64
+ def id_to_index_map(ids)
65
+ id_map = {}
66
+ ids.each_with_index { |id, index| id_map[id] = index }
67
+ id_map
68
+ end
69
+
62
70
  def reset_association(owners, association_name)
63
71
  should_reset = (through_scope != through_reflection.klass.unscoped) ||
64
72
  (reflection.options[:source_type] && through_reflection.collection?)
@@ -78,18 +86,23 @@ module ActiveRecord
78
86
  if options[:source_type]
79
87
  scope.where! reflection.foreign_type => options[:source_type]
80
88
  else
81
- unless reflection_scope.where_values.empty?
89
+ unless reflection_scope.where_clause.empty?
82
90
  scope.includes_values = Array(reflection_scope.values[:includes] || options[:source])
83
- scope.where_values = reflection_scope.values[:where]
84
- scope.bind_values = reflection_scope.bind_values
91
+ scope.where_clause = reflection_scope.where_clause
85
92
  end
86
93
 
87
94
  scope.references! reflection_scope.values[:references]
88
- scope = scope.order reflection_scope.values[:order] if scope.eager_loading?
95
+ if scope.eager_loading? && order_values = reflection_scope.values[:order]
96
+ scope = scope.order(order_values)
97
+ end
89
98
  end
90
99
 
91
100
  scope
92
101
  end
102
+
103
+ def target_records_from_association(association)
104
+ association.loaded? ? association.target : association.reader
105
+ end
93
106
  end
94
107
  end
95
108
  end
@@ -4,6 +4,12 @@ module ActiveRecord
4
4
  # Implements the reader method, e.g. foo.bar for Foo.has_one :bar
5
5
  def reader(force_reload = false)
6
6
  if force_reload && klass
7
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
8
+ Passing an argument to force an association to reload is now
9
+ deprecated and will be removed in Rails 5.1. Please call `reload`
10
+ on the parent object instead.
11
+ MSG
12
+
7
13
  klass.uncached { reload }
8
14
  elsif !loaded? || stale_target?
9
15
  reload
@@ -39,7 +45,7 @@ module ActiveRecord
39
45
  end
40
46
 
41
47
  def get_records
42
- return scope.limit(1).to_a if skip_statement_cache?
48
+ return scope.limit(1).records if skip_statement_cache?
43
49
 
44
50
  conn = klass.connection
45
51
  sc = reflection.association_scope_cache(conn, owner) do
@@ -27,7 +27,7 @@ module ActiveRecord
27
27
  # Construct attributes for :through pointing to owner and associate. This is used by the
28
28
  # methods which create and delete records on the association.
29
29
  #
30
- # We only support indirectly modifying through associations which has a belongs_to source.
30
+ # We only support indirectly modifying through associations which have a belongs_to source.
31
31
  # This is the "has_many :tags, through: :taggings" situation, where the join model
32
32
  # typically has a belongs_to on both side. In other words, associations which could also
33
33
  # be represented as has_and_belongs_to_many associations.
@@ -76,13 +76,21 @@ module ActiveRecord
76
76
 
77
77
  def ensure_mutable
78
78
  unless source_reflection.belongs_to?
79
- raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
79
+ if reflection.has_one?
80
+ raise HasOneThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
81
+ else
82
+ raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
83
+ end
80
84
  end
81
85
  end
82
86
 
83
87
  def ensure_not_nested
84
88
  if reflection.nested?
85
- raise HasManyThroughNestedAssociationsAreReadonly.new(owner, reflection)
89
+ if reflection.has_one?
90
+ raise HasOneThroughNestedAssociationsAreReadonly.new(owner, reflection)
91
+ else
92
+ raise HasManyThroughNestedAssociationsAreReadonly.new(owner, reflection)
93
+ end
86
94
  end
87
95
  end
88
96
 
@@ -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
@@ -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
@@ -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) }
@@ -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