activerecord 3.0.0 → 4.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 (181) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2102 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +35 -44
  5. data/examples/performance.rb +110 -100
  6. data/lib/active_record/aggregations.rb +59 -75
  7. data/lib/active_record/associations/alias_tracker.rb +76 -0
  8. data/lib/active_record/associations/association.rb +248 -0
  9. data/lib/active_record/associations/association_scope.rb +135 -0
  10. data/lib/active_record/associations/belongs_to_association.rb +60 -59
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -59
  12. data/lib/active_record/associations/builder/association.rb +108 -0
  13. data/lib/active_record/associations/builder/belongs_to.rb +98 -0
  14. data/lib/active_record/associations/builder/collection_association.rb +89 -0
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
  16. data/lib/active_record/associations/builder/has_many.rb +15 -0
  17. data/lib/active_record/associations/builder/has_one.rb +25 -0
  18. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  19. data/lib/active_record/associations/collection_association.rb +608 -0
  20. data/lib/active_record/associations/collection_proxy.rb +986 -0
  21. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +40 -112
  22. data/lib/active_record/associations/has_many_association.rb +83 -76
  23. data/lib/active_record/associations/has_many_through_association.rb +147 -66
  24. data/lib/active_record/associations/has_one_association.rb +67 -108
  25. data/lib/active_record/associations/has_one_through_association.rb +21 -25
  26. data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
  27. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  28. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  29. data/lib/active_record/associations/join_dependency.rb +235 -0
  30. data/lib/active_record/associations/join_helper.rb +45 -0
  31. data/lib/active_record/associations/preloader/association.rb +121 -0
  32. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  33. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  35. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  36. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  37. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  38. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  39. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  40. data/lib/active_record/associations/preloader/through_association.rb +63 -0
  41. data/lib/active_record/associations/preloader.rb +178 -0
  42. data/lib/active_record/associations/singular_association.rb +64 -0
  43. data/lib/active_record/associations/through_association.rb +87 -0
  44. data/lib/active_record/associations.rb +512 -1224
  45. data/lib/active_record/attribute_assignment.rb +201 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +49 -12
  47. data/lib/active_record/attribute_methods/dirty.rb +51 -28
  48. data/lib/active_record/attribute_methods/primary_key.rb +94 -22
  49. data/lib/active_record/attribute_methods/query.rb +5 -4
  50. data/lib/active_record/attribute_methods/read.rb +63 -72
  51. data/lib/active_record/attribute_methods/serialization.rb +162 -0
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -41
  53. data/lib/active_record/attribute_methods/write.rb +39 -13
  54. data/lib/active_record/attribute_methods.rb +362 -29
  55. data/lib/active_record/autosave_association.rb +132 -75
  56. data/lib/active_record/base.rb +83 -1627
  57. data/lib/active_record/callbacks.rb +69 -47
  58. data/lib/active_record/coders/yaml_column.rb +38 -0
  59. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +411 -138
  60. data/lib/active_record/connection_adapters/abstract/database_limits.rb +21 -11
  61. data/lib/active_record/connection_adapters/abstract/database_statements.rb +234 -173
  62. data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -22
  63. data/lib/active_record/connection_adapters/abstract/quoting.rb +82 -25
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +176 -414
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +562 -232
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +281 -53
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
  70. data/lib/active_record/connection_adapters/column.rb +318 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  72. data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
  73. data/lib/active_record/connection_adapters/mysql_adapter.rb +365 -450
  74. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  75. data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
  76. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  80. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
  81. data/lib/active_record/connection_adapters/postgresql_adapter.rb +672 -752
  82. data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
  83. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +588 -17
  84. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  85. data/lib/active_record/connection_handling.rb +98 -0
  86. data/lib/active_record/core.rb +463 -0
  87. data/lib/active_record/counter_cache.rb +108 -101
  88. data/lib/active_record/dynamic_matchers.rb +131 -0
  89. data/lib/active_record/errors.rb +54 -13
  90. data/lib/active_record/explain.rb +38 -0
  91. data/lib/active_record/explain_registry.rb +30 -0
  92. data/lib/active_record/explain_subscriber.rb +29 -0
  93. data/lib/active_record/fixture_set/file.rb +55 -0
  94. data/lib/active_record/fixtures.rb +703 -785
  95. data/lib/active_record/inheritance.rb +200 -0
  96. data/lib/active_record/integration.rb +60 -0
  97. data/lib/active_record/locale/en.yml +8 -1
  98. data/lib/active_record/locking/optimistic.rb +69 -60
  99. data/lib/active_record/locking/pessimistic.rb +34 -12
  100. data/lib/active_record/log_subscriber.rb +40 -6
  101. data/lib/active_record/migration/command_recorder.rb +164 -0
  102. data/lib/active_record/migration/join_table.rb +15 -0
  103. data/lib/active_record/migration.rb +614 -216
  104. data/lib/active_record/model_schema.rb +345 -0
  105. data/lib/active_record/nested_attributes.rb +248 -119
  106. data/lib/active_record/null_relation.rb +65 -0
  107. data/lib/active_record/persistence.rb +275 -57
  108. data/lib/active_record/query_cache.rb +29 -9
  109. data/lib/active_record/querying.rb +62 -0
  110. data/lib/active_record/railtie.rb +135 -21
  111. data/lib/active_record/railties/console_sandbox.rb +5 -0
  112. data/lib/active_record/railties/controller_runtime.rb +17 -5
  113. data/lib/active_record/railties/databases.rake +249 -359
  114. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  115. data/lib/active_record/readonly_attributes.rb +30 -0
  116. data/lib/active_record/reflection.rb +283 -103
  117. data/lib/active_record/relation/batches.rb +38 -34
  118. data/lib/active_record/relation/calculations.rb +252 -139
  119. data/lib/active_record/relation/delegation.rb +125 -0
  120. data/lib/active_record/relation/finder_methods.rb +182 -188
  121. data/lib/active_record/relation/merger.rb +161 -0
  122. data/lib/active_record/relation/predicate_builder.rb +86 -21
  123. data/lib/active_record/relation/query_methods.rb +917 -134
  124. data/lib/active_record/relation/spawn_methods.rb +53 -92
  125. data/lib/active_record/relation.rb +405 -143
  126. data/lib/active_record/result.rb +67 -0
  127. data/lib/active_record/runtime_registry.rb +17 -0
  128. data/lib/active_record/sanitization.rb +168 -0
  129. data/lib/active_record/schema.rb +20 -14
  130. data/lib/active_record/schema_dumper.rb +55 -46
  131. data/lib/active_record/schema_migration.rb +39 -0
  132. data/lib/active_record/scoping/default.rb +146 -0
  133. data/lib/active_record/scoping/named.rb +175 -0
  134. data/lib/active_record/scoping.rb +82 -0
  135. data/lib/active_record/serialization.rb +8 -46
  136. data/lib/active_record/serializers/xml_serializer.rb +21 -68
  137. data/lib/active_record/statement_cache.rb +26 -0
  138. data/lib/active_record/store.rb +156 -0
  139. data/lib/active_record/tasks/database_tasks.rb +203 -0
  140. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  141. data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
  142. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  143. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  144. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  145. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  146. data/lib/active_record/test_case.rb +57 -28
  147. data/lib/active_record/timestamp.rb +49 -18
  148. data/lib/active_record/transactions.rb +106 -63
  149. data/lib/active_record/translation.rb +22 -0
  150. data/lib/active_record/validations/associated.rb +25 -24
  151. data/lib/active_record/validations/presence.rb +65 -0
  152. data/lib/active_record/validations/uniqueness.rb +123 -83
  153. data/lib/active_record/validations.rb +29 -29
  154. data/lib/active_record/version.rb +7 -5
  155. data/lib/active_record.rb +83 -34
  156. data/lib/rails/generators/active_record/migration/migration_generator.rb +46 -9
  157. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  158. data/lib/rails/generators/active_record/migration/templates/migration.rb +30 -8
  159. data/lib/rails/generators/active_record/model/model_generator.rb +15 -5
  160. data/lib/rails/generators/active_record/model/templates/model.rb +7 -2
  161. data/lib/rails/generators/active_record/model/templates/module.rb +3 -1
  162. data/lib/rails/generators/active_record.rb +4 -8
  163. metadata +163 -121
  164. data/CHANGELOG +0 -6023
  165. data/examples/associations.png +0 -0
  166. data/lib/active_record/association_preload.rb +0 -403
  167. data/lib/active_record/associations/association_collection.rb +0 -562
  168. data/lib/active_record/associations/association_proxy.rb +0 -295
  169. data/lib/active_record/associations/through_association_scope.rb +0 -154
  170. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -113
  171. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -401
  172. data/lib/active_record/dynamic_finder_match.rb +0 -53
  173. data/lib/active_record/dynamic_scope_match.rb +0 -32
  174. data/lib/active_record/named_scope.rb +0 -138
  175. data/lib/active_record/observer.rb +0 -140
  176. data/lib/active_record/session_store.rb +0 -340
  177. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -16
  178. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  179. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -2
  180. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -24
  181. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -0,0 +1,201 @@
1
+
2
+ module ActiveRecord
3
+ module AttributeAssignment
4
+ extend ActiveSupport::Concern
5
+ include ActiveModel::DeprecatedMassAssignmentSecurity
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
+ def assign_attributes(new_attributes)
15
+ return if new_attributes.blank?
16
+
17
+ attributes = new_attributes.stringify_keys
18
+ multi_parameter_attributes = []
19
+ nested_parameter_attributes = []
20
+
21
+ attributes = sanitize_for_mass_assignment(attributes)
22
+
23
+ attributes.each do |k, v|
24
+ if k.include?("(")
25
+ multi_parameter_attributes << [ k, v ]
26
+ elsif v.is_a?(Hash)
27
+ nested_parameter_attributes << [ k, v ]
28
+ else
29
+ _assign_attribute(k, v)
30
+ end
31
+ end
32
+
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?
35
+ end
36
+
37
+ alias attributes= assign_attributes
38
+
39
+ private
40
+
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
49
+ end
50
+
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
55
+
56
+ # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
57
+ # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
58
+ # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
59
+ # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
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+.
62
+ def assign_multiparameter_attributes(pairs)
63
+ execute_callstack_for_multiparameter_attributes(
64
+ extract_callstack_for_multiparameter_attributes(pairs)
65
+ )
66
+ end
67
+
68
+ def execute_callstack_for_multiparameter_attributes(callstack)
69
+ errors = []
70
+ callstack.each do |name, values_with_empty_parameters|
71
+ begin
72
+ send("#{name}=", MultiparameterAttribute.new(self, name, values_with_empty_parameters).read_value)
73
+ rescue => ex
74
+ errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
75
+ end
76
+ end
77
+ unless errors.empty?
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}]"
80
+ end
81
+ end
82
+
83
+ def extract_callstack_for_multiparameter_attributes(pairs)
84
+ attributes = {}
85
+
86
+ pairs.each do |(multiparameter_name, value)|
87
+ attribute_name = multiparameter_name.split("(").first
88
+ attributes[attribute_name] ||= {}
89
+
90
+ parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
91
+ attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
92
+ end
93
+
94
+ attributes
95
+ end
96
+
97
+ def type_cast_attribute_value(multiparameter_name, value)
98
+ multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
99
+ end
100
+
101
+ def find_parameter_position(multiparameter_name)
102
+ multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
103
+ end
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
200
+ end
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,27 +30,41 @@ 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
- self.attribute_names.inject({}) do |attrs, name|
17
- attrs[name] = read_attribute_before_type_cast(name)
18
- attrs
19
- end
59
+ @attributes
20
60
  end
21
61
 
22
62
  private
23
- # Handle *_before_type_cast for method_missing.
24
- def attribute_before_type_cast(attribute_name)
25
- if attribute_name == 'id'
26
- read_attribute_before_type_cast(self.class.primary_key)
27
- else
28
- read_attribute_before_type_cast(attribute_name)
29
- end
30
- end
63
+
64
+ # Handle *_before_type_cast for method_missing.
65
+ def attribute_before_type_cast(attribute_name)
66
+ read_attribute_before_type_cast(attribute_name)
67
+ end
31
68
  end
32
69
  end
33
70
  end
@@ -1,23 +1,34 @@
1
- require 'active_support/core_ext/object/blank'
1
+ require 'active_support/core_ext/module/attribute_accessors'
2
2
 
3
3
  module ActiveRecord
4
4
  module AttributeMethods
5
- module Dirty
5
+ module Dirty # :nodoc:
6
6
  extend ActiveSupport::Concern
7
+
7
8
  include ActiveModel::Dirty
8
- include AttributeMethods::Write
9
9
 
10
10
  included do
11
11
  if self < ::ActiveRecord::Timestamp
12
12
  raise "You cannot include Dirty after Timestamp"
13
13
  end
14
14
 
15
- superclass_delegating_accessor :partial_updates
16
- 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
+ )
17
28
  end
18
29
 
19
30
  # Attempts to +save+ the record and clears changed attributes if successful.
20
- def save(*) #:nodoc:
31
+ def save(*)
21
32
  if status = super
22
33
  @previously_changed = changes
23
34
  @changed_attributes.clear
@@ -26,7 +37,7 @@ module ActiveRecord
26
37
  end
27
38
 
28
39
  # Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
29
- def save!(*) #:nodoc:
40
+ def save!(*)
30
41
  super.tap do
31
42
  @previously_changed = changes
32
43
  @changed_attributes.clear
@@ -34,7 +45,7 @@ module ActiveRecord
34
45
  end
35
46
 
36
47
  # <tt>reload</tt> the record and clears changed attributes.
37
- def reload(*) #:nodoc:
48
+ def reload(*)
38
49
  super.tap do
39
50
  @previously_changed.clear
40
51
  @changed_attributes.clear
@@ -49,35 +60,34 @@ module ActiveRecord
49
60
  # The attribute already has an unsaved change.
50
61
  if attribute_changed?(attr)
51
62
  old = @changed_attributes[attr]
52
- @changed_attributes.delete(attr) unless field_changed?(attr, old, value)
63
+ @changed_attributes.delete(attr) unless _field_changed?(attr, old, value)
53
64
  else
54
65
  old = clone_attribute_value(:read_attribute, attr)
55
- # Save Time objects as TimeWithZone if time_zone_aware_attributes == true
56
- old = old.in_time_zone if clone_with_time_zone_conversion_attribute?(attr, old)
57
- @changed_attributes[attr] = old if field_changed?(attr, old, value)
66
+ @changed_attributes[attr] = old if _field_changed?(attr, old, value)
58
67
  end
59
68
 
60
69
  # Carry on.
61
70
  super(attr, value)
62
71
  end
63
72
 
64
- def update(*)
65
- if partial_updates?
66
- # Serialized attributes should always be written in case they've been
67
- # changed in place.
68
- super(changed | (attributes.keys & self.class.serialized_attributes.keys))
69
- else
70
- super
71
- 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
72
79
  end
73
80
 
74
- def field_changed?(attr, old, value)
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)
85
+ end
86
+
87
+ def _field_changed?(attr, old, value)
75
88
  if column = column_for_attribute(attr)
76
- if column.number? && column.null && (old.nil? || old == 0) && value.blank?
77
- # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
78
- # Hence we don't record it as a change if the value changes from nil to ''.
79
- # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
80
- # be typecast back to 0 (''.to_i => 0)
89
+ if column.number? && (changes_from_nil_to_empty_string?(column, old, value) ||
90
+ changes_from_zero_to_string?(old, value))
81
91
  value = nil
82
92
  else
83
93
  value = column.type_cast(value)
@@ -87,8 +97,21 @@ module ActiveRecord
87
97
  old != value
88
98
  end
89
99
 
90
- def clone_with_time_zone_conversion_attribute?(attr, old)
91
- old.class.name == "Time" && time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(attr.to_sym)
100
+ def changes_from_nil_to_empty_string?(column, old, value)
101
+ # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
102
+ # Hence we don't record it as a change if the value changes from nil to ''.
103
+ # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
104
+ # be typecast back to 0 (''.to_i => 0)
105
+ column.null && (old.nil? || old == 0) && value.blank?
106
+ end
107
+
108
+ def changes_from_zero_to_string?(old, value)
109
+ # For columns with old 0 and value non-empty string
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/
92
115
  end
93
116
  end
94
117
  end
@@ -1,49 +1,121 @@
1
+ require 'set'
2
+
1
3
  module ActiveRecord
2
4
  module AttributeMethods
3
5
  module PrimaryKey
4
6
  extend ActiveSupport::Concern
5
7
 
6
- # Returns this record's primary key value wrapped in an Array
7
- # or nil if the record is a new_record?
8
+ # Returns this record's primary key value wrapped in an Array if one is
9
+ # available.
8
10
  def to_key
9
- new_record? ? nil : [ id ]
11
+ sync_with_transaction_state
12
+ key = self.id
13
+ [key] if key
14
+ end
15
+
16
+ # Returns the primary key value.
17
+ def id
18
+ sync_with_transaction_state
19
+ read_attribute(self.class.primary_key)
20
+ end
21
+
22
+ # Sets the primary key value.
23
+ def id=(value)
24
+ sync_with_transaction_state
25
+ write_attribute(self.class.primary_key, value) if self.class.primary_key
26
+ end
27
+
28
+ # Queries the primary key value.
29
+ def id?
30
+ sync_with_transaction_state
31
+ query_attribute(self.class.primary_key)
32
+ end
33
+
34
+ # Returns the primary key value before type cast.
35
+ def id_before_type_cast
36
+ sync_with_transaction_state
37
+ read_attribute_before_type_cast(self.class.primary_key)
38
+ end
39
+
40
+ protected
41
+
42
+ def attribute_method?(attr_name)
43
+ attr_name == 'id' || super
10
44
  end
11
45
 
12
46
  module ClassMethods
13
- # Defines the primary key field -- can be overridden in subclasses. Overwriting will negate any effect of the
14
- # primary_key_prefix_type setting, though.
47
+ def define_method_attribute(attr_name)
48
+ super
49
+
50
+ if attr_name == primary_key && attr_name != 'id'
51
+ generated_attribute_methods.send(:alias_method, :id, primary_key)
52
+ end
53
+ end
54
+
55
+ ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast).to_set
56
+
57
+ def dangerous_attribute_method?(method_name)
58
+ super && !ID_ATTRIBUTE_METHODS.include?(method_name)
59
+ end
60
+
61
+ # Defines the primary key field -- can be overridden in subclasses.
62
+ # Overwriting will negate any effect of the +primary_key_prefix_type+
63
+ # setting, though.
15
64
  def primary_key
16
- reset_primary_key
65
+ @primary_key = reset_primary_key unless defined? @primary_key
66
+ @primary_key
67
+ end
68
+
69
+ # Returns a quoted version of the primary key name, used to construct
70
+ # SQL statements.
71
+ def quoted_primary_key
72
+ @quoted_primary_key ||= connection.quote_column_name(primary_key)
17
73
  end
18
74
 
19
75
  def reset_primary_key #:nodoc:
20
- key = get_primary_key(base_class.name)
21
- set_primary_key(key)
22
- key
76
+ if self == base_class
77
+ self.primary_key = get_primary_key(base_class.name)
78
+ else
79
+ self.primary_key = base_class.primary_key
80
+ end
23
81
  end
24
82
 
25
83
  def get_primary_key(base_name) #:nodoc:
26
- key = 'id'
84
+ return 'id' if base_name.blank?
85
+
27
86
  case primary_key_prefix_type
28
- when :table_name
29
- key = base_name.to_s.foreign_key(false)
30
- when :table_name_with_underscore
31
- key = base_name.to_s.foreign_key
87
+ when :table_name
88
+ base_name.foreign_key(false)
89
+ when :table_name_with_underscore
90
+ base_name.foreign_key
91
+ else
92
+ if ActiveRecord::Base != self && table_exists?
93
+ connection.schema_cache.primary_keys(table_name)
94
+ else
95
+ 'id'
96
+ end
32
97
  end
33
- key
34
98
  end
35
99
 
36
- # Sets the name of the primary key column to use to the given value,
37
- # or (if the value is nil or false) to the value returned by the given
38
- # block.
100
+ # Sets the name of the primary key column.
39
101
  #
40
102
  # class Project < ActiveRecord::Base
41
- # set_primary_key "sysid"
103
+ # self.primary_key = 'sysid'
42
104
  # end
43
- def set_primary_key(value = nil, &block)
44
- define_attr_method :primary_key, value, &block
105
+ #
106
+ # You can also define the +primary_key+ method yourself:
107
+ #
108
+ # class Project < ActiveRecord::Base
109
+ # def self.primary_key
110
+ # 'foo_' + super
111
+ # end
112
+ # end
113
+ #
114
+ # Project.primary_key # => "foo_id"
115
+ def primary_key=(value)
116
+ @primary_key = value && value.to_s
117
+ @quoted_primary_key = nil
45
118
  end
46
- alias :primary_key= :set_primary_key
47
119
  end
48
120
  end
49
121
  end
@@ -1,5 +1,3 @@
1
- require 'active_support/core_ext/object/blank'
2
-
3
1
  module ActiveRecord
4
2
  module AttributeMethods
5
3
  module Query
@@ -10,8 +8,11 @@ module ActiveRecord
10
8
  end
11
9
 
12
10
  def query_attribute(attr_name)
13
- unless value = read_attribute(attr_name)
14
- false
11
+ value = read_attribute(attr_name) { |n| missing_attribute(n, caller) }
12
+
13
+ case value
14
+ when true then true
15
+ when false, nil then false
15
16
  else
16
17
  column = self.class.columns_hash[attr_name]
17
18
  if column.nil?