activerecord 3.2.22.4 → 4.0.13

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 (173) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2799 -617
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +23 -32
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +40 -34
  7. data/lib/active_record/association_relation.rb +22 -0
  8. data/lib/active_record/associations/alias_tracker.rb +4 -2
  9. data/lib/active_record/associations/association.rb +60 -46
  10. data/lib/active_record/associations/association_scope.rb +46 -40
  11. data/lib/active_record/associations/belongs_to_association.rb +17 -4
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
  13. data/lib/active_record/associations/builder/association.rb +81 -28
  14. data/lib/active_record/associations/builder/belongs_to.rb +73 -56
  15. data/lib/active_record/associations/builder/collection_association.rb +54 -40
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
  17. data/lib/active_record/associations/builder/has_many.rb +8 -64
  18. data/lib/active_record/associations/builder/has_one.rb +13 -50
  19. data/lib/active_record/associations/builder/singular_association.rb +13 -13
  20. data/lib/active_record/associations/collection_association.rb +130 -96
  21. data/lib/active_record/associations/collection_proxy.rb +916 -63
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +15 -13
  23. data/lib/active_record/associations/has_many_association.rb +35 -8
  24. data/lib/active_record/associations/has_many_through_association.rb +37 -17
  25. data/lib/active_record/associations/has_one_association.rb +42 -19
  26. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency/join_association.rb +39 -22
  28. data/lib/active_record/associations/join_dependency/join_base.rb +2 -2
  29. data/lib/active_record/associations/join_dependency/join_part.rb +21 -8
  30. data/lib/active_record/associations/join_dependency.rb +30 -9
  31. data/lib/active_record/associations/join_helper.rb +1 -11
  32. data/lib/active_record/associations/preloader/association.rb +29 -33
  33. data/lib/active_record/associations/preloader/collection_association.rb +1 -1
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +2 -2
  35. data/lib/active_record/associations/preloader/has_many_through.rb +6 -2
  36. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  37. data/lib/active_record/associations/preloader/through_association.rb +13 -17
  38. data/lib/active_record/associations/preloader.rb +20 -43
  39. data/lib/active_record/associations/singular_association.rb +11 -11
  40. data/lib/active_record/associations/through_association.rb +3 -3
  41. data/lib/active_record/associations.rb +223 -282
  42. data/lib/active_record/attribute_assignment.rb +134 -154
  43. data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
  44. data/lib/active_record/attribute_methods/dirty.rb +36 -29
  45. data/lib/active_record/attribute_methods/primary_key.rb +45 -31
  46. data/lib/active_record/attribute_methods/query.rb +5 -4
  47. data/lib/active_record/attribute_methods/read.rb +67 -90
  48. data/lib/active_record/attribute_methods/serialization.rb +133 -70
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +51 -45
  50. data/lib/active_record/attribute_methods/write.rb +34 -39
  51. data/lib/active_record/attribute_methods.rb +268 -108
  52. data/lib/active_record/autosave_association.rb +80 -73
  53. data/lib/active_record/base.rb +54 -451
  54. data/lib/active_record/callbacks.rb +60 -22
  55. data/lib/active_record/coders/yaml_column.rb +18 -21
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +347 -197
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +146 -138
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -19
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +19 -3
  61. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +151 -142
  62. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +499 -217
  64. data/lib/active_record/connection_adapters/abstract/transaction.rb +208 -0
  65. data/lib/active_record/connection_adapters/abstract_adapter.rb +209 -44
  66. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +169 -61
  67. data/lib/active_record/connection_adapters/column.rb +67 -36
  68. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +28 -29
  70. data/lib/active_record/connection_adapters/mysql_adapter.rb +200 -73
  71. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +98 -0
  72. data/lib/active_record/connection_adapters/postgresql/cast.rb +160 -0
  73. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +240 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid.rb +374 -0
  75. data/lib/active_record/connection_adapters/postgresql/quoting.rb +183 -0
  76. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  77. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +508 -0
  78. data/lib/active_record/connection_adapters/postgresql_adapter.rb +544 -899
  79. data/lib/active_record/connection_adapters/schema_cache.rb +76 -16
  80. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +595 -16
  81. data/lib/active_record/connection_handling.rb +98 -0
  82. data/lib/active_record/core.rb +472 -0
  83. data/lib/active_record/counter_cache.rb +107 -108
  84. data/lib/active_record/dynamic_matchers.rb +115 -63
  85. data/lib/active_record/errors.rb +36 -18
  86. data/lib/active_record/explain.rb +15 -63
  87. data/lib/active_record/explain_registry.rb +30 -0
  88. data/lib/active_record/explain_subscriber.rb +8 -4
  89. data/lib/active_record/fixture_set/file.rb +55 -0
  90. data/lib/active_record/fixtures.rb +159 -155
  91. data/lib/active_record/inheritance.rb +93 -59
  92. data/lib/active_record/integration.rb +8 -8
  93. data/lib/active_record/locale/en.yml +8 -1
  94. data/lib/active_record/locking/optimistic.rb +39 -43
  95. data/lib/active_record/locking/pessimistic.rb +4 -4
  96. data/lib/active_record/log_subscriber.rb +19 -9
  97. data/lib/active_record/migration/command_recorder.rb +102 -33
  98. data/lib/active_record/migration/join_table.rb +15 -0
  99. data/lib/active_record/migration.rb +411 -173
  100. data/lib/active_record/model_schema.rb +81 -94
  101. data/lib/active_record/nested_attributes.rb +173 -131
  102. data/lib/active_record/null_relation.rb +67 -0
  103. data/lib/active_record/persistence.rb +254 -106
  104. data/lib/active_record/query_cache.rb +18 -36
  105. data/lib/active_record/querying.rb +19 -15
  106. data/lib/active_record/railtie.rb +113 -38
  107. data/lib/active_record/railties/console_sandbox.rb +3 -4
  108. data/lib/active_record/railties/controller_runtime.rb +4 -3
  109. data/lib/active_record/railties/databases.rake +115 -368
  110. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  111. data/lib/active_record/readonly_attributes.rb +7 -3
  112. data/lib/active_record/reflection.rb +110 -61
  113. data/lib/active_record/relation/batches.rb +29 -29
  114. data/lib/active_record/relation/calculations.rb +155 -125
  115. data/lib/active_record/relation/delegation.rb +94 -18
  116. data/lib/active_record/relation/finder_methods.rb +151 -203
  117. data/lib/active_record/relation/merger.rb +188 -0
  118. data/lib/active_record/relation/predicate_builder.rb +85 -42
  119. data/lib/active_record/relation/query_methods.rb +793 -146
  120. data/lib/active_record/relation/spawn_methods.rb +43 -150
  121. data/lib/active_record/relation.rb +293 -173
  122. data/lib/active_record/result.rb +48 -7
  123. data/lib/active_record/runtime_registry.rb +17 -0
  124. data/lib/active_record/sanitization.rb +41 -54
  125. data/lib/active_record/schema.rb +19 -12
  126. data/lib/active_record/schema_dumper.rb +41 -41
  127. data/lib/active_record/schema_migration.rb +46 -0
  128. data/lib/active_record/scoping/default.rb +56 -52
  129. data/lib/active_record/scoping/named.rb +78 -103
  130. data/lib/active_record/scoping.rb +54 -124
  131. data/lib/active_record/serialization.rb +6 -2
  132. data/lib/active_record/serializers/xml_serializer.rb +9 -15
  133. data/lib/active_record/statement_cache.rb +26 -0
  134. data/lib/active_record/store.rb +131 -15
  135. data/lib/active_record/tasks/database_tasks.rb +204 -0
  136. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  137. data/lib/active_record/tasks/mysql_database_tasks.rb +144 -0
  138. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  139. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  140. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  141. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  142. data/lib/active_record/test_case.rb +67 -38
  143. data/lib/active_record/timestamp.rb +16 -11
  144. data/lib/active_record/transactions.rb +73 -51
  145. data/lib/active_record/validations/associated.rb +19 -13
  146. data/lib/active_record/validations/presence.rb +65 -0
  147. data/lib/active_record/validations/uniqueness.rb +110 -57
  148. data/lib/active_record/validations.rb +18 -17
  149. data/lib/active_record/version.rb +7 -6
  150. data/lib/active_record.rb +63 -45
  151. data/lib/rails/generators/active_record/migration/migration_generator.rb +45 -8
  152. data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +4 -0
  153. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  154. data/lib/rails/generators/active_record/model/model_generator.rb +5 -4
  155. data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
  156. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  157. data/lib/rails/generators/active_record.rb +3 -5
  158. metadata +43 -29
  159. data/examples/associations.png +0 -0
  160. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  161. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  162. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  163. data/lib/active_record/dynamic_finder_match.rb +0 -68
  164. data/lib/active_record/dynamic_scope_match.rb +0 -23
  165. data/lib/active_record/fixtures/file.rb +0 -65
  166. data/lib/active_record/identity_map.rb +0 -162
  167. data/lib/active_record/observer.rb +0 -121
  168. data/lib/active_record/session_store.rb +0 -360
  169. data/lib/rails/generators/active_record/migration.rb +0 -15
  170. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  171. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  172. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  173. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,206 +1,91 @@
1
- require 'active_support/concern'
2
1
 
3
2
  module ActiveRecord
4
3
  module AttributeAssignment
5
4
  extend ActiveSupport::Concern
6
- include ActiveModel::MassAssignmentSecurity
5
+ include ActiveModel::DeprecatedMassAssignmentSecurity
6
+ include ActiveModel::ForbiddenAttributesProtection
7
7
 
8
- module ClassMethods
9
- private
10
-
11
- # The primary key and inheritance column can never be set by mass-assignment for security reasons.
12
- def attributes_protected_by_default
13
- default = [ primary_key, inheritance_column ]
14
- default << 'id' unless primary_key.eql? 'id'
15
- default
16
- end
17
- end
18
-
19
- # Allows you to set all the attributes at once by passing in a hash with keys
20
- # matching the attribute names (which again matches the column names).
21
- #
22
- # If any attributes are protected by either +attr_protected+ or
23
- # +attr_accessible+ then only settable attributes will be assigned.
24
- #
25
- # class User < ActiveRecord::Base
26
- # attr_protected :is_admin
27
- # end
28
- #
29
- # user = User.new
30
- # user.attributes = { :username => 'Phusion', :is_admin => true }
31
- # user.username # => "Phusion"
32
- # user.is_admin? # => false
33
- def attributes=(new_attributes)
34
- return unless new_attributes.is_a?(Hash)
35
-
36
- assign_attributes(new_attributes)
37
- end
38
-
39
- # Allows you to set all the attributes for a particular mass-assignment
40
- # security role by passing in a hash of attributes with keys matching
41
- # the attribute names (which again matches the column names) and the role
42
- # name using the :as option.
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).
43
10
  #
44
- # To bypass mass-assignment security you can use the :without_protection => true
45
- # option.
46
- #
47
- # class User < ActiveRecord::Base
48
- # attr_accessible :name
49
- # attr_accessible :name, :is_admin, :as => :admin
50
- # end
51
- #
52
- # user = User.new
53
- # user.assign_attributes({ :name => 'Josh', :is_admin => true })
54
- # user.name # => "Josh"
55
- # user.is_admin? # => false
56
- #
57
- # user = User.new
58
- # user.assign_attributes({ :name => 'Josh', :is_admin => true }, :as => :admin)
59
- # user.name # => "Josh"
60
- # user.is_admin? # => true
61
- #
62
- # user = User.new
63
- # user.assign_attributes({ :name => 'Josh', :is_admin => true }, :without_protection => true)
64
- # user.name # => "Josh"
65
- # user.is_admin? # => true
66
- def assign_attributes(new_attributes, options = {})
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
+ def assign_attributes(new_attributes)
67
15
  return if new_attributes.blank?
68
16
 
69
- attributes = new_attributes.stringify_keys
70
- multi_parameter_attributes = []
17
+ attributes = new_attributes.stringify_keys
18
+ multi_parameter_attributes = []
71
19
  nested_parameter_attributes = []
72
- @mass_assignment_options = options
73
20
 
74
- unless options[:without_protection]
75
- attributes = sanitize_for_mass_assignment(attributes, mass_assignment_role)
76
- end
21
+ attributes = sanitize_for_mass_assignment(attributes)
77
22
 
78
23
  attributes.each do |k, v|
79
24
  if k.include?("(")
80
25
  multi_parameter_attributes << [ k, v ]
81
- elsif respond_to?("#{k}=")
82
- if v.is_a?(Hash)
83
- nested_parameter_attributes << [ k, v ]
84
- else
85
- send("#{k}=", v)
86
- end
26
+ elsif v.is_a?(Hash)
27
+ nested_parameter_attributes << [ k, v ]
87
28
  else
88
- raise(UnknownAttributeError, "unknown attribute: #{k}")
29
+ _assign_attribute(k, v)
89
30
  end
90
31
  end
91
32
 
92
- # assign any deferred nested attributes after the base attributes have been set
93
- nested_parameter_attributes.each do |k,v|
94
- send("#{k}=", v)
95
- end
96
-
97
- @mass_assignment_options = nil
98
- assign_multiparameter_attributes(multi_parameter_attributes)
33
+ assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
34
+ assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
99
35
  end
100
36
 
101
- protected
37
+ alias attributes= assign_attributes
102
38
 
103
- def mass_assignment_options
104
- @mass_assignment_options ||= {}
105
- end
39
+ private
106
40
 
107
- def mass_assignment_role
108
- mass_assignment_options[:as] || :default
41
+ def _assign_attribute(k, v)
42
+ public_send("#{k}=", v)
43
+ rescue NoMethodError
44
+ if respond_to?("#{k}=")
45
+ raise
46
+ else
47
+ raise UnknownAttributeError, "unknown attribute: #{k}"
48
+ end
109
49
  end
110
50
 
111
- private
51
+ # Assign any deferred nested attributes after the base attributes have been set.
52
+ def assign_nested_parameter_attributes(pairs)
53
+ pairs.each { |k, v| _assign_attribute(k, v) }
54
+ end
112
55
 
113
56
  # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
114
57
  # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
115
58
  # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
116
59
  # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
117
- # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum,
118
- # f for Float, s for String, and a for Array. If all the values for a given attribute are empty, the
119
- # attribute will be set to nil.
60
+ # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum and
61
+ # f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
120
62
  def assign_multiparameter_attributes(pairs)
121
63
  execute_callstack_for_multiparameter_attributes(
122
64
  extract_callstack_for_multiparameter_attributes(pairs)
123
65
  )
124
66
  end
125
67
 
126
- def instantiate_time_object(name, values)
127
- if self.class.send(:create_time_zone_conversion_attribute?, name, column_for_attribute(name))
128
- Time.zone.local(*values)
129
- else
130
- Time.time_with_datetime_fallback(self.class.default_timezone, *values)
131
- end
132
- end
133
-
134
68
  def execute_callstack_for_multiparameter_attributes(callstack)
135
69
  errors = []
136
70
  callstack.each do |name, values_with_empty_parameters|
137
71
  begin
138
- send(name + "=", read_value_from_parameter(name, values_with_empty_parameters))
72
+ send("#{name}=", MultiparameterAttribute.new(self, name, values_with_empty_parameters).read_value)
139
73
  rescue => ex
140
- errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name}", ex, name)
74
+ errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
141
75
  end
142
76
  end
143
77
  unless errors.empty?
144
- raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
145
- end
146
- end
147
-
148
- def read_value_from_parameter(name, values_hash_from_param)
149
- klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
150
- if values_hash_from_param.values.all?{|v|v.nil?}
151
- nil
152
- elsif klass == Time
153
- read_time_parameter_value(name, values_hash_from_param)
154
- elsif klass == Date
155
- read_date_parameter_value(name, values_hash_from_param)
156
- else
157
- read_other_parameter_value(klass, name, values_hash_from_param)
158
- end
159
- end
160
-
161
- def read_time_parameter_value(name, values_hash_from_param)
162
- # If Date bits were not provided, error
163
- raise "Missing Parameter" if [1,2,3].any?{|position| !values_hash_from_param.has_key?(position)}
164
- max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param, 6)
165
- # If Date bits were provided but blank, then return nil
166
- return nil if (1..3).any? {|position| values_hash_from_param[position].blank?}
167
-
168
- set_values = (1..max_position).collect{|position| values_hash_from_param[position] }
169
- # If Time bits are not there, then default to 0
170
- (3..5).each {|i| set_values[i] = set_values[i].blank? ? 0 : set_values[i]}
171
- instantiate_time_object(name, set_values)
172
- end
173
-
174
- def read_date_parameter_value(name, values_hash_from_param)
175
- return nil if (1..3).any? {|position| values_hash_from_param[position].blank?}
176
- set_values = [values_hash_from_param[1], values_hash_from_param[2], values_hash_from_param[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(name, 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_parameter_value(klass, name, values_hash_from_param)
185
- max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param)
186
- values = (1..max_position).collect do |position|
187
- raise "Missing Parameter" if !values_hash_from_param.has_key?(position)
188
- values_hash_from_param[position]
78
+ error_descriptions = errors.map { |ex| ex.message }.join(",")
79
+ raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
189
80
  end
190
- klass.new(*values)
191
- end
192
-
193
- def extract_max_param_for_multiparameter_attributes(values_hash_from_param, upper_cap = 100)
194
- [values_hash_from_param.keys.max,upper_cap].min
195
81
  end
196
82
 
197
83
  def extract_callstack_for_multiparameter_attributes(pairs)
198
- attributes = { }
84
+ attributes = {}
199
85
 
200
- pairs.each do |pair|
201
- multiparameter_name, value = pair
86
+ pairs.each do |(multiparameter_name, value)|
202
87
  attribute_name = multiparameter_name.split("(").first
203
- attributes[attribute_name] = {} unless attributes.include?(attribute_name)
88
+ attributes[attribute_name] ||= {}
204
89
 
205
90
  parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
206
91
  attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
@@ -217,5 +102,100 @@ module ActiveRecord
217
102
  multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
218
103
  end
219
104
 
105
+ class MultiparameterAttribute #:nodoc:
106
+ attr_reader :object, :name, :values, :column
107
+
108
+ def initialize(object, name, values)
109
+ @object = object
110
+ @name = name
111
+ @values = values
112
+ end
113
+
114
+ def read_value
115
+ return if values.values.compact.empty?
116
+
117
+ @column = object.class.reflect_on_aggregation(name.to_sym) || object.column_for_attribute(name)
118
+ klass = column.klass
119
+
120
+ if klass == Time
121
+ read_time
122
+ elsif klass == Date
123
+ read_date
124
+ else
125
+ read_other(klass)
126
+ end
127
+ end
128
+
129
+ private
130
+
131
+ def instantiate_time_object(set_values)
132
+ if object.class.send(:create_time_zone_conversion_attribute?, name, column)
133
+ Time.zone.local(*set_values)
134
+ else
135
+ Time.send(object.class.default_timezone, *set_values)
136
+ end
137
+ end
138
+
139
+ def read_time
140
+ # If column is a :time (and not :date or :timestamp) there is no need to validate if
141
+ # there are year/month/day fields
142
+ if column.type == :time
143
+ # if the column is a time set the values to their defaults as January 1, 1970, but only if they're nil
144
+ { 1 => 1970, 2 => 1, 3 => 1 }.each do |key,value|
145
+ values[key] ||= value
146
+ end
147
+ else
148
+ # else column is a timestamp, so if Date bits were not provided, error
149
+ validate_required_parameters!([1,2,3])
150
+
151
+ # If Date bits were provided but blank, then return nil
152
+ return if blank_date_parameter?
153
+ end
154
+
155
+ max_position = extract_max_param(6)
156
+ set_values = values.values_at(*(1..max_position))
157
+ # If Time bits are not there, then default to 0
158
+ (3..5).each { |i| set_values[i] = set_values[i].presence || 0 }
159
+ instantiate_time_object(set_values)
160
+ end
161
+
162
+ def read_date
163
+ return if blank_date_parameter?
164
+ set_values = values.values_at(1,2,3)
165
+ begin
166
+ Date.new(*set_values)
167
+ rescue ArgumentError # if Date.new raises an exception on an invalid date
168
+ 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
169
+ end
170
+ end
171
+
172
+ def read_other(klass)
173
+ max_position = extract_max_param
174
+ positions = (1..max_position)
175
+ validate_required_parameters!(positions)
176
+
177
+ set_values = values.values_at(*positions)
178
+ klass.new(*set_values)
179
+ end
180
+
181
+ # Checks whether some blank date parameter exists. Note that this is different
182
+ # than the validate_required_parameters! method, since it just checks for blank
183
+ # positions instead of missing ones, and does not raise in case one blank position
184
+ # exists. The caller is responsible to handle the case of this returning true.
185
+ def blank_date_parameter?
186
+ (1..3).any? { |position| values[position].blank? }
187
+ end
188
+
189
+ # If some position is not provided, it errors out a missing parameter exception.
190
+ def validate_required_parameters!(positions)
191
+ if missing_parameter = positions.detect { |position| !values.key?(position) }
192
+ raise ArgumentError.new("Missing Parameter - #{name}(#{missing_parameter})")
193
+ end
194
+ end
195
+
196
+ def extract_max_param(upper_cap = 100)
197
+ [values.keys.max, upper_cap].min
198
+ end
199
+ end
220
200
  end
221
201
  end
@@ -1,5 +1,28 @@
1
1
  module ActiveRecord
2
2
  module AttributeMethods
3
+ # = Active Record Attribute Methods Before Type Cast
4
+ #
5
+ # <tt>ActiveRecord::AttributeMethods::BeforeTypeCast</tt> provides a way to
6
+ # read the value of the attributes before typecasting and deserialization.
7
+ #
8
+ # class Task < ActiveRecord::Base
9
+ # end
10
+ #
11
+ # task = Task.new(id: '1', completed_on: '2012-10-21')
12
+ # task.id # => 1
13
+ # task.completed_on # => Sun, 21 Oct 2012
14
+ #
15
+ # task.attributes_before_type_cast
16
+ # # => {"id"=>"1", "completed_on"=>"2012-10-21", ... }
17
+ # task.read_attribute_before_type_cast('id') # => "1"
18
+ # task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
19
+ #
20
+ # In addition to #read_attribute_before_type_cast and #attributes_before_type_cast,
21
+ # it declares a method for all attributes with the <tt>*_before_type_cast</tt>
22
+ # suffix.
23
+ #
24
+ # task.id_before_type_cast # => "1"
25
+ # task.completed_on_before_type_cast # => "2012-10-21"
3
26
  module BeforeTypeCast
4
27
  extend ActiveSupport::Concern
5
28
 
@@ -7,11 +30,31 @@ module ActiveRecord
7
30
  attribute_method_suffix "_before_type_cast"
8
31
  end
9
32
 
33
+ # Returns the value of the attribute identified by +attr_name+ before
34
+ # typecasting and deserialization.
35
+ #
36
+ # class Task < ActiveRecord::Base
37
+ # end
38
+ #
39
+ # task = Task.new(id: '1', completed_on: '2012-10-21')
40
+ # task.read_attribute('id') # => 1
41
+ # task.read_attribute_before_type_cast('id') # => '1'
42
+ # task.read_attribute('completed_on') # => Sun, 21 Oct 2012
43
+ # task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
10
44
  def read_attribute_before_type_cast(attr_name)
11
45
  @attributes[attr_name]
12
46
  end
13
47
 
14
48
  # Returns a hash of attributes before typecasting and deserialization.
49
+ #
50
+ # class Task < ActiveRecord::Base
51
+ # end
52
+ #
53
+ # task = Task.new(title: nil, is_done: true, completed_on: '2012-10-21')
54
+ # task.attributes
55
+ # # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>Sun, 21 Oct 2012, "created_at"=>nil, "updated_at"=>nil}
56
+ # task.attributes_before_type_cast
57
+ # # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil}
15
58
  def attributes_before_type_cast
16
59
  @attributes
17
60
  end
@@ -20,11 +63,7 @@ module ActiveRecord
20
63
 
21
64
  # Handle *_before_type_cast for method_missing.
22
65
  def attribute_before_type_cast(attribute_name)
23
- if attribute_name == 'id'
24
- read_attribute_before_type_cast(self.class.primary_key)
25
- else
26
- read_attribute_before_type_cast(attribute_name)
27
- end
66
+ read_attribute_before_type_cast(attribute_name)
28
67
  end
29
68
  end
30
69
  end
@@ -1,46 +1,51 @@
1
- require 'active_support/core_ext/class/attribute'
2
- require 'active_support/core_ext/object/blank'
1
+ require 'active_support/core_ext/module/attribute_accessors'
3
2
 
4
3
  module ActiveRecord
5
4
  module AttributeMethods
6
- module Dirty
5
+ module Dirty # :nodoc:
7
6
  extend ActiveSupport::Concern
7
+
8
8
  include ActiveModel::Dirty
9
- include AttributeMethods::Write
10
9
 
11
10
  included do
12
11
  if self < ::ActiveRecord::Timestamp
13
12
  raise "You cannot include Dirty after Timestamp"
14
13
  end
15
14
 
16
- class_attribute :partial_updates
17
- self.partial_updates = true
15
+ class_attribute :partial_writes, instance_writer: false
16
+ self.partial_writes = true
17
+
18
+ def self.partial_updates=(v); self.partial_writes = v; end
19
+ def self.partial_updates?; partial_writes?; end
20
+ def self.partial_updates; partial_writes; end
21
+
22
+ ActiveSupport::Deprecation.deprecate_methods(
23
+ singleton_class,
24
+ :partial_updates= => :partial_writes=,
25
+ :partial_updates? => :partial_writes?,
26
+ :partial_updates => :partial_writes
27
+ )
18
28
  end
19
29
 
20
30
  # Attempts to +save+ the record and clears changed attributes if successful.
21
- def save(*) #:nodoc:
31
+ def save(*)
22
32
  if status = super
23
33
  @previously_changed = changes
24
34
  @changed_attributes.clear
25
- elsif IdentityMap.enabled?
26
- IdentityMap.remove(self)
27
35
  end
28
36
  status
29
37
  end
30
38
 
31
39
  # Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
32
- def save!(*) #:nodoc:
40
+ def save!(*)
33
41
  super.tap do
34
42
  @previously_changed = changes
35
43
  @changed_attributes.clear
36
44
  end
37
- rescue
38
- IdentityMap.remove(self) if IdentityMap.enabled?
39
- raise
40
45
  end
41
46
 
42
47
  # <tt>reload</tt> the record and clears changed attributes.
43
- def reload(*) #:nodoc:
48
+ def reload(*)
44
49
  super.tap do
45
50
  @previously_changed.clear
46
51
  @changed_attributes.clear
@@ -58,8 +63,6 @@ module ActiveRecord
58
63
  @changed_attributes.delete(attr) unless _field_changed?(attr, old, value)
59
64
  else
60
65
  old = clone_attribute_value(:read_attribute, attr)
61
- # Save Time objects as TimeWithZone if time_zone_aware_attributes == true
62
- old = old.in_time_zone if clone_with_time_zone_conversion_attribute?(attr, old)
63
66
  @changed_attributes[attr] = old if _field_changed?(attr, old, value)
64
67
  end
65
68
 
@@ -67,14 +70,18 @@ module ActiveRecord
67
70
  super(attr, value)
68
71
  end
69
72
 
70
- def update(*)
71
- if partial_updates?
72
- # Serialized attributes should always be written in case they've been
73
- # changed in place.
74
- super(changed | (attributes.keys & self.class.serialized_attributes.keys))
75
- else
76
- super
77
- end
73
+ def _update_record(*)
74
+ partial_writes? ? super(keys_for_partial_write) : super
75
+ end
76
+
77
+ def _create_record(*)
78
+ partial_writes? ? super(keys_for_partial_write) : super
79
+ end
80
+
81
+ # Serialized attributes should always be written in case they've been
82
+ # changed in place.
83
+ def keys_for_partial_write
84
+ changed | (attributes.keys & self.class.serialized_attributes.keys)
78
85
  end
79
86
 
80
87
  def _field_changed?(attr, old, value)
@@ -90,10 +97,6 @@ module ActiveRecord
90
97
  old != value
91
98
  end
92
99
 
93
- def clone_with_time_zone_conversion_attribute?(attr, old)
94
- old.class.name == "Time" && time_zone_aware_attributes && !self.skip_time_zone_conversion_for_attributes.include?(attr.to_sym)
95
- end
96
-
97
100
  def changes_from_nil_to_empty_string?(column, old, value)
98
101
  # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
99
102
  # Hence we don't record it as a change if the value changes from nil to ''.
@@ -104,7 +107,11 @@ module ActiveRecord
104
107
 
105
108
  def changes_from_zero_to_string?(old, value)
106
109
  # For columns with old 0 and value non-empty string
107
- old == 0 && value.is_a?(String) && value.present? && value != '0'
110
+ old == 0 && value.is_a?(String) && value.present? && non_zero?(value)
111
+ end
112
+
113
+ def non_zero?(value)
114
+ value !~ /\A0+(\.0+)?\z/
108
115
  end
109
116
  end
110
117
  end