activerecord 4.2.11.3 → 5.0.0.beta1

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

Potentially problematic release.


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

Files changed (229) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1029 -1349
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -7
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record.rb +7 -3
  7. data/lib/active_record/aggregations.rb +35 -25
  8. data/lib/active_record/association_relation.rb +2 -2
  9. data/lib/active_record/associations.rb +305 -204
  10. data/lib/active_record/associations/alias_tracker.rb +19 -16
  11. data/lib/active_record/associations/association.rb +10 -8
  12. data/lib/active_record/associations/association_scope.rb +73 -102
  13. data/lib/active_record/associations/belongs_to_association.rb +20 -32
  14. data/lib/active_record/associations/builder/association.rb +28 -34
  15. data/lib/active_record/associations/builder/belongs_to.rb +41 -18
  16. data/lib/active_record/associations/builder/collection_association.rb +8 -24
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +11 -11
  18. data/lib/active_record/associations/builder/has_many.rb +4 -4
  19. data/lib/active_record/associations/builder/has_one.rb +10 -5
  20. data/lib/active_record/associations/builder/singular_association.rb +2 -9
  21. data/lib/active_record/associations/collection_association.rb +40 -43
  22. data/lib/active_record/associations/collection_proxy.rb +55 -29
  23. data/lib/active_record/associations/foreign_association.rb +1 -1
  24. data/lib/active_record/associations/has_many_association.rb +20 -71
  25. data/lib/active_record/associations/has_many_through_association.rb +8 -52
  26. data/lib/active_record/associations/has_one_association.rb +12 -5
  27. data/lib/active_record/associations/join_dependency.rb +28 -18
  28. data/lib/active_record/associations/join_dependency/join_association.rb +13 -12
  29. data/lib/active_record/associations/preloader.rb +13 -4
  30. data/lib/active_record/associations/preloader/association.rb +45 -51
  31. data/lib/active_record/associations/preloader/collection_association.rb +0 -6
  32. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  33. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  34. data/lib/active_record/associations/preloader/through_association.rb +5 -4
  35. data/lib/active_record/associations/singular_association.rb +6 -0
  36. data/lib/active_record/associations/through_association.rb +11 -3
  37. data/lib/active_record/attribute.rb +61 -17
  38. data/lib/active_record/attribute/user_provided_default.rb +23 -0
  39. data/lib/active_record/attribute_assignment.rb +27 -140
  40. data/lib/active_record/attribute_decorators.rb +6 -5
  41. data/lib/active_record/attribute_methods.rb +79 -26
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
  43. data/lib/active_record/attribute_methods/dirty.rb +46 -86
  44. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  45. data/lib/active_record/attribute_methods/query.rb +2 -2
  46. data/lib/active_record/attribute_methods/read.rb +26 -42
  47. data/lib/active_record/attribute_methods/serialization.rb +13 -16
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +42 -9
  49. data/lib/active_record/attribute_methods/write.rb +13 -24
  50. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  51. data/lib/active_record/attribute_set.rb +30 -3
  52. data/lib/active_record/attribute_set/builder.rb +6 -4
  53. data/lib/active_record/attributes.rb +194 -81
  54. data/lib/active_record/autosave_association.rb +33 -15
  55. data/lib/active_record/base.rb +30 -18
  56. data/lib/active_record/callbacks.rb +36 -40
  57. data/lib/active_record/coders/yaml_column.rb +20 -8
  58. data/lib/active_record/collection_cache_key.rb +31 -0
  59. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +431 -122
  60. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
  61. data/lib/active_record/connection_adapters/abstract/database_statements.rb +40 -22
  62. data/lib/active_record/connection_adapters/abstract/quoting.rb +62 -8
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +46 -38
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +229 -185
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +52 -13
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +275 -115
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +32 -33
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +83 -32
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +384 -221
  70. data/lib/active_record/connection_adapters/column.rb +27 -41
  71. data/lib/active_record/connection_adapters/connection_specification.rb +2 -21
  72. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +57 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +69 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +59 -0
  76. data/lib/active_record/connection_adapters/mysql2_adapter.rb +22 -101
  77. data/lib/active_record/connection_adapters/postgresql/column.rb +6 -10
  78. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +3 -3
  79. data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
  80. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +23 -57
  81. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -2
  82. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -1
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
  84. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -22
  85. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +3 -3
  86. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
  87. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
  90. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +23 -16
  92. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +0 -4
  93. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
  94. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
  95. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
  96. data/lib/active_record/connection_adapters/postgresql/quoting.rb +18 -11
  97. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
  98. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
  99. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +54 -0
  100. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +174 -128
  101. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  102. data/lib/active_record/connection_adapters/postgresql_adapter.rb +184 -112
  103. data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
  104. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  105. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +15 -0
  106. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +134 -110
  107. data/lib/active_record/connection_adapters/statement_pool.rb +28 -11
  108. data/lib/active_record/connection_handling.rb +5 -5
  109. data/lib/active_record/core.rb +72 -104
  110. data/lib/active_record/counter_cache.rb +9 -20
  111. data/lib/active_record/dynamic_matchers.rb +1 -20
  112. data/lib/active_record/enum.rb +110 -76
  113. data/lib/active_record/errors.rb +72 -47
  114. data/lib/active_record/explain_registry.rb +1 -1
  115. data/lib/active_record/explain_subscriber.rb +1 -1
  116. data/lib/active_record/fixture_set/file.rb +19 -4
  117. data/lib/active_record/fixtures.rb +76 -40
  118. data/lib/active_record/gem_version.rb +4 -4
  119. data/lib/active_record/inheritance.rb +27 -40
  120. data/lib/active_record/integration.rb +4 -4
  121. data/lib/active_record/legacy_yaml_adapter.rb +18 -2
  122. data/lib/active_record/locale/en.yml +3 -2
  123. data/lib/active_record/locking/optimistic.rb +10 -14
  124. data/lib/active_record/locking/pessimistic.rb +1 -1
  125. data/lib/active_record/log_subscriber.rb +40 -22
  126. data/lib/active_record/migration.rb +304 -133
  127. data/lib/active_record/migration/command_recorder.rb +59 -18
  128. data/lib/active_record/migration/compatibility.rb +90 -0
  129. data/lib/active_record/model_schema.rb +92 -40
  130. data/lib/active_record/nested_attributes.rb +45 -34
  131. data/lib/active_record/null_relation.rb +15 -7
  132. data/lib/active_record/persistence.rb +112 -72
  133. data/lib/active_record/querying.rb +6 -5
  134. data/lib/active_record/railtie.rb +20 -13
  135. data/lib/active_record/railties/controller_runtime.rb +1 -1
  136. data/lib/active_record/railties/databases.rake +47 -38
  137. data/lib/active_record/readonly_attributes.rb +1 -1
  138. data/lib/active_record/reflection.rb +182 -57
  139. data/lib/active_record/relation.rb +152 -100
  140. data/lib/active_record/relation/batches.rb +133 -33
  141. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  142. data/lib/active_record/relation/calculations.rb +80 -101
  143. data/lib/active_record/relation/delegation.rb +6 -19
  144. data/lib/active_record/relation/finder_methods.rb +58 -46
  145. data/lib/active_record/relation/from_clause.rb +32 -0
  146. data/lib/active_record/relation/merger.rb +13 -42
  147. data/lib/active_record/relation/predicate_builder.rb +99 -105
  148. data/lib/active_record/relation/predicate_builder/array_handler.rb +11 -16
  149. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +78 -0
  150. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  151. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  152. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  153. data/lib/active_record/relation/predicate_builder/range_handler.rb +17 -0
  154. data/lib/active_record/relation/query_attribute.rb +19 -0
  155. data/lib/active_record/relation/query_methods.rb +274 -238
  156. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  157. data/lib/active_record/relation/spawn_methods.rb +3 -6
  158. data/lib/active_record/relation/where_clause.rb +173 -0
  159. data/lib/active_record/relation/where_clause_factory.rb +37 -0
  160. data/lib/active_record/result.rb +4 -3
  161. data/lib/active_record/runtime_registry.rb +1 -1
  162. data/lib/active_record/sanitization.rb +94 -65
  163. data/lib/active_record/schema.rb +23 -22
  164. data/lib/active_record/schema_dumper.rb +33 -22
  165. data/lib/active_record/schema_migration.rb +10 -4
  166. data/lib/active_record/scoping.rb +17 -6
  167. data/lib/active_record/scoping/default.rb +19 -6
  168. data/lib/active_record/scoping/named.rb +39 -28
  169. data/lib/active_record/secure_token.rb +38 -0
  170. data/lib/active_record/serialization.rb +2 -4
  171. data/lib/active_record/statement_cache.rb +15 -13
  172. data/lib/active_record/store.rb +8 -3
  173. data/lib/active_record/suppressor.rb +54 -0
  174. data/lib/active_record/table_metadata.rb +64 -0
  175. data/lib/active_record/tasks/database_tasks.rb +30 -40
  176. data/lib/active_record/tasks/mysql_database_tasks.rb +7 -15
  177. data/lib/active_record/tasks/postgresql_database_tasks.rb +11 -2
  178. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  179. data/lib/active_record/timestamp.rb +16 -9
  180. data/lib/active_record/touch_later.rb +58 -0
  181. data/lib/active_record/transactions.rb +138 -56
  182. data/lib/active_record/type.rb +66 -17
  183. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  184. data/lib/active_record/type/date.rb +2 -45
  185. data/lib/active_record/type/date_time.rb +2 -49
  186. data/lib/active_record/type/internal/abstract_json.rb +33 -0
  187. data/lib/active_record/type/internal/timezone.rb +15 -0
  188. data/lib/active_record/type/serialized.rb +9 -14
  189. data/lib/active_record/type/time.rb +3 -21
  190. data/lib/active_record/type/type_map.rb +4 -4
  191. data/lib/active_record/type_caster.rb +7 -0
  192. data/lib/active_record/type_caster/connection.rb +29 -0
  193. data/lib/active_record/type_caster/map.rb +19 -0
  194. data/lib/active_record/validations.rb +33 -32
  195. data/lib/active_record/validations/absence.rb +24 -0
  196. data/lib/active_record/validations/associated.rb +10 -3
  197. data/lib/active_record/validations/length.rb +36 -0
  198. data/lib/active_record/validations/presence.rb +12 -12
  199. data/lib/active_record/validations/uniqueness.rb +24 -21
  200. data/lib/rails/generators/active_record/migration.rb +7 -0
  201. data/lib/rails/generators/active_record/migration/migration_generator.rb +7 -4
  202. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -3
  203. data/lib/rails/generators/active_record/migration/templates/migration.rb +4 -1
  204. data/lib/rails/generators/active_record/model/model_generator.rb +21 -15
  205. data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
  206. metadata +50 -35
  207. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
  208. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  209. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
  210. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  211. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  212. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  213. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  214. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  215. data/lib/active_record/type/big_integer.rb +0 -13
  216. data/lib/active_record/type/binary.rb +0 -50
  217. data/lib/active_record/type/boolean.rb +0 -31
  218. data/lib/active_record/type/decimal.rb +0 -64
  219. data/lib/active_record/type/decimal_without_scale.rb +0 -11
  220. data/lib/active_record/type/decorator.rb +0 -14
  221. data/lib/active_record/type/float.rb +0 -19
  222. data/lib/active_record/type/integer.rb +0 -59
  223. data/lib/active_record/type/mutable.rb +0 -16
  224. data/lib/active_record/type/numeric.rb +0 -36
  225. data/lib/active_record/type/string.rb +0 -40
  226. data/lib/active_record/type/text.rb +0 -11
  227. data/lib/active_record/type/time_value.rb +0 -38
  228. data/lib/active_record/type/unsigned_integer.rb +0 -15
  229. data/lib/active_record/type/value.rb +0 -110
@@ -78,14 +78,15 @@ module ActiveRecord
78
78
  if options[:source_type]
79
79
  scope.where! reflection.foreign_type => options[:source_type]
80
80
  else
81
- unless reflection_scope.where_values.empty?
81
+ unless reflection_scope.where_clause.empty?
82
82
  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
83
+ scope.where_clause = reflection_scope.where_clause
85
84
  end
86
85
 
87
86
  scope.references! reflection_scope.values[:references]
88
- scope = scope.order reflection_scope.values[:order] if scope.eager_loading?
87
+ if scope.eager_loading? && order_values = reflection_scope.values[:order]
88
+ scope = scope.order(order_values)
89
+ end
89
90
  end
90
91
 
91
92
  scope
@@ -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
@@ -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
97
125
 
98
- def has_been_read?
99
- defined?(@value)
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
133
+
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?
@@ -134,6 +174,10 @@ module ActiveRecord
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
@@ -158,6 +202,6 @@ module ActiveRecord
158
202
  false
159
203
  end
160
204
  end
161
- private_constant :FromDatabase, :FromUser, :Null, :Uninitialized
205
+ private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue
162
206
  end
163
207
  end
@@ -0,0 +1,23 @@
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
+ super(name, value, type, database_default)
8
+ end
9
+
10
+ def type_cast(value)
11
+ if value.is_a?(Proc)
12
+ super(value.call)
13
+ else
14
+ super
15
+ end
16
+ end
17
+
18
+ def with_type(type)
19
+ self.class.new(name, value_before_type_cast, type, original_attribute)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -3,61 +3,38 @@ require 'active_model/forbidden_attributes_protection'
3
3
  module ActiveRecord
4
4
  module AttributeAssignment
5
5
  extend ActiveSupport::Concern
6
- include ActiveModel::ForbiddenAttributesProtection
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?
6
+ include ActiveModel::AttributeAssignment
7
+
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
32
+ # Tries to assign given value to given attribute.
33
+ # In case of an error, re-raises with the ActiveRecord constant.
34
+ def _assign_attribute(k, v) # :nodoc:
35
+ super
36
+ rescue ActiveModel::UnknownAttributeError
37
+ raise UnknownAttributeError.new(self, k)
61
38
  end
62
39
 
63
40
  # Assign any deferred nested attributes after the base attributes have been set.
@@ -69,7 +46,7 @@ module ActiveRecord
69
46
  # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
70
47
  # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
71
48
  # 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 Integer and
49
+ # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum and
73
50
  # f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
74
51
  def assign_multiparameter_attributes(pairs)
75
52
  execute_callstack_for_multiparameter_attributes(
@@ -81,13 +58,18 @@ module ActiveRecord
81
58
  errors = []
82
59
  callstack.each do |name, values_with_empty_parameters|
83
60
  begin
84
- send("#{name}=", MultiparameterAttribute.new(self, name, values_with_empty_parameters).read_value)
61
+ if values_with_empty_parameters.each_value.all?(&:nil?)
62
+ values = nil
63
+ else
64
+ values = values_with_empty_parameters
65
+ end
66
+ send("#{name}=", values)
85
67
  rescue => ex
86
68
  errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
87
69
  end
88
70
  end
89
71
  unless errors.empty?
90
- error_descriptions = errors.map { |ex| ex.message }.join(",")
72
+ error_descriptions = errors.map(&:message).join(",")
91
73
  raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
92
74
  end
93
75
  end
@@ -113,100 +95,5 @@ module ActiveRecord
113
95
  def find_parameter_position(multiparameter_name)
114
96
  multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
115
97
  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
98
  end
212
99
  end