activerecord 3.2.22.5 → 4.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 (162) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1024 -543
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +20 -29
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record.rb +55 -44
  7. data/lib/active_record/aggregations.rb +40 -34
  8. data/lib/active_record/associations.rb +204 -276
  9. data/lib/active_record/associations/alias_tracker.rb +1 -1
  10. data/lib/active_record/associations/association.rb +30 -35
  11. data/lib/active_record/associations/association_scope.rb +40 -40
  12. data/lib/active_record/associations/belongs_to_association.rb +15 -2
  13. data/lib/active_record/associations/builder/association.rb +81 -28
  14. data/lib/active_record/associations/builder/belongs_to.rb +35 -57
  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 +92 -88
  21. data/lib/active_record/associations/collection_proxy.rb +913 -63
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +12 -10
  23. data/lib/active_record/associations/has_many_association.rb +35 -9
  24. data/lib/active_record/associations/has_many_through_association.rb +24 -14
  25. data/lib/active_record/associations/has_one_association.rb +33 -13
  26. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency.rb +2 -2
  28. data/lib/active_record/associations/join_dependency/join_association.rb +17 -22
  29. data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
  30. data/lib/active_record/associations/join_helper.rb +1 -11
  31. data/lib/active_record/associations/preloader.rb +14 -17
  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 +1 -1
  35. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  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/singular_association.rb +11 -11
  39. data/lib/active_record/associations/through_association.rb +2 -2
  40. data/lib/active_record/attribute_assignment.rb +133 -153
  41. data/lib/active_record/attribute_methods.rb +196 -93
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
  43. data/lib/active_record/attribute_methods/dirty.rb +31 -28
  44. data/lib/active_record/attribute_methods/primary_key.rb +38 -30
  45. data/lib/active_record/attribute_methods/query.rb +5 -4
  46. data/lib/active_record/attribute_methods/read.rb +62 -91
  47. data/lib/active_record/attribute_methods/serialization.rb +97 -66
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -45
  49. data/lib/active_record/attribute_methods/write.rb +32 -39
  50. data/lib/active_record/autosave_association.rb +56 -70
  51. data/lib/active_record/base.rb +53 -450
  52. data/lib/active_record/callbacks.rb +53 -18
  53. data/lib/active_record/coders/yaml_column.rb +11 -9
  54. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +353 -197
  55. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  56. data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -131
  57. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -19
  58. data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -3
  59. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +101 -91
  60. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +59 -0
  61. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +225 -96
  62. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  63. data/lib/active_record/connection_adapters/abstract_adapter.rb +99 -46
  64. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +114 -36
  65. data/lib/active_record/connection_adapters/column.rb +46 -24
  66. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  67. data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
  68. data/lib/active_record/connection_adapters/mysql_adapter.rb +181 -64
  69. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  70. data/lib/active_record/connection_adapters/postgresql/cast.rb +132 -0
  71. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid.rb +347 -0
  73. data/lib/active_record/connection_adapters/postgresql/quoting.rb +158 -0
  74. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  75. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +448 -0
  76. data/lib/active_record/connection_adapters/postgresql_adapter.rb +454 -885
  77. data/lib/active_record/connection_adapters/schema_cache.rb +48 -16
  78. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +574 -13
  79. data/lib/active_record/connection_handling.rb +98 -0
  80. data/lib/active_record/core.rb +428 -0
  81. data/lib/active_record/counter_cache.rb +106 -108
  82. data/lib/active_record/dynamic_matchers.rb +110 -63
  83. data/lib/active_record/errors.rb +25 -8
  84. data/lib/active_record/explain.rb +8 -58
  85. data/lib/active_record/explain_subscriber.rb +6 -3
  86. data/lib/active_record/fixture_set/file.rb +56 -0
  87. data/lib/active_record/fixtures.rb +146 -148
  88. data/lib/active_record/inheritance.rb +77 -59
  89. data/lib/active_record/integration.rb +5 -5
  90. data/lib/active_record/locale/en.yml +8 -1
  91. data/lib/active_record/locking/optimistic.rb +38 -42
  92. data/lib/active_record/locking/pessimistic.rb +4 -4
  93. data/lib/active_record/log_subscriber.rb +19 -9
  94. data/lib/active_record/migration.rb +318 -153
  95. data/lib/active_record/migration/command_recorder.rb +90 -31
  96. data/lib/active_record/migration/join_table.rb +15 -0
  97. data/lib/active_record/model_schema.rb +69 -92
  98. data/lib/active_record/nested_attributes.rb +113 -148
  99. data/lib/active_record/null_relation.rb +65 -0
  100. data/lib/active_record/persistence.rb +188 -97
  101. data/lib/active_record/query_cache.rb +18 -36
  102. data/lib/active_record/querying.rb +19 -15
  103. data/lib/active_record/railtie.rb +91 -36
  104. data/lib/active_record/railties/console_sandbox.rb +0 -2
  105. data/lib/active_record/railties/controller_runtime.rb +2 -2
  106. data/lib/active_record/railties/databases.rake +90 -309
  107. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  108. data/lib/active_record/readonly_attributes.rb +7 -3
  109. data/lib/active_record/reflection.rb +72 -56
  110. data/lib/active_record/relation.rb +241 -157
  111. data/lib/active_record/relation/batches.rb +25 -22
  112. data/lib/active_record/relation/calculations.rb +143 -121
  113. data/lib/active_record/relation/delegation.rb +96 -18
  114. data/lib/active_record/relation/finder_methods.rb +117 -183
  115. data/lib/active_record/relation/merger.rb +133 -0
  116. data/lib/active_record/relation/predicate_builder.rb +90 -42
  117. data/lib/active_record/relation/query_methods.rb +666 -136
  118. data/lib/active_record/relation/spawn_methods.rb +43 -150
  119. data/lib/active_record/result.rb +33 -6
  120. data/lib/active_record/sanitization.rb +24 -50
  121. data/lib/active_record/schema.rb +19 -12
  122. data/lib/active_record/schema_dumper.rb +31 -39
  123. data/lib/active_record/schema_migration.rb +36 -0
  124. data/lib/active_record/scoping.rb +0 -124
  125. data/lib/active_record/scoping/default.rb +48 -45
  126. data/lib/active_record/scoping/named.rb +74 -103
  127. data/lib/active_record/serialization.rb +6 -2
  128. data/lib/active_record/serializers/xml_serializer.rb +9 -15
  129. data/lib/active_record/store.rb +119 -15
  130. data/lib/active_record/tasks/database_tasks.rb +158 -0
  131. data/lib/active_record/tasks/mysql_database_tasks.rb +138 -0
  132. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  133. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  134. data/lib/active_record/test_case.rb +61 -38
  135. data/lib/active_record/timestamp.rb +8 -9
  136. data/lib/active_record/transactions.rb +65 -51
  137. data/lib/active_record/validations.rb +17 -15
  138. data/lib/active_record/validations/associated.rb +20 -14
  139. data/lib/active_record/validations/presence.rb +65 -0
  140. data/lib/active_record/validations/uniqueness.rb +93 -52
  141. data/lib/active_record/version.rb +4 -4
  142. data/lib/rails/generators/active_record.rb +3 -5
  143. data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -7
  144. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  145. data/lib/rails/generators/active_record/model/model_generator.rb +4 -3
  146. data/lib/rails/generators/active_record/model/templates/model.rb +1 -6
  147. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  148. metadata +53 -46
  149. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  150. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  151. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  152. data/lib/active_record/dynamic_finder_match.rb +0 -68
  153. data/lib/active_record/dynamic_scope_match.rb +0 -23
  154. data/lib/active_record/fixtures/file.rb +0 -65
  155. data/lib/active_record/identity_map.rb +0 -162
  156. data/lib/active_record/observer.rb +0 -121
  157. data/lib/active_record/session_store.rb +0 -360
  158. data/lib/rails/generators/active_record/migration.rb +0 -15
  159. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  160. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  161. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  162. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,9 +1,8 @@
1
1
  require 'active_support/core_ext/enumerable'
2
- require 'active_support/deprecation'
3
2
 
4
3
  module ActiveRecord
5
4
  # = Active Record Attribute Methods
6
- module AttributeMethods #:nodoc:
5
+ module AttributeMethods
7
6
  extend ActiveSupport::Concern
8
7
  include ActiveModel::AttributeMethods
9
8
 
@@ -16,76 +15,45 @@ module ActiveRecord
16
15
  include TimeZoneConversion
17
16
  include Dirty
18
17
  include Serialization
19
- include DeprecatedUnderscoreRead
20
-
21
- # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
22
- # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
23
- # (Alias for the protected read_attribute method).
24
- def [](attr_name)
25
- read_attribute(attr_name)
26
- end
27
-
28
- # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
29
- # (Alias for the protected write_attribute method).
30
- def []=(attr_name, value)
31
- write_attribute(attr_name, value)
32
- end
33
18
  end
34
19
 
35
20
  module ClassMethods
36
21
  # Generates all the attribute related methods for columns in the database
37
22
  # accessors, mutators and query methods.
38
- def define_attribute_methods
39
- unless defined?(@attribute_methods_mutex)
40
- msg = "It looks like something (probably a gem/plugin) is overriding the " \
41
- "ActiveRecord::Base.inherited method. It is important that this hook executes so " \
42
- "that your models are set up correctly. A workaround has been added to stop this " \
43
- "causing an error in 3.2, but future versions will simply not work if the hook is " \
44
- "overridden. If you are using Kaminari, please upgrade as it is known to have had " \
45
- "this problem.\n\n"
46
- msg << "The following may help track down the problem:"
47
-
48
- meth = method(:inherited)
49
- if meth.respond_to?(:source_location)
50
- msg << " #{meth.source_location.inspect}"
51
- else
52
- msg << " #{meth.inspect}"
53
- end
54
- msg << "\n\n"
55
-
56
- ActiveSupport::Deprecation.warn(msg)
57
-
58
- @attribute_methods_mutex = Mutex.new
59
- end
60
-
23
+ def define_attribute_methods # :nodoc:
61
24
  # Use a mutex; we don't want two thread simaltaneously trying to define
62
25
  # attribute methods.
63
26
  @attribute_methods_mutex.synchronize do
64
27
  return if attribute_methods_generated?
65
28
  superclass.define_attribute_methods unless self == base_class
66
29
  super(column_names)
67
- column_names.each { |name| define_external_attribute_method(name) }
68
30
  @attribute_methods_generated = true
69
31
  end
70
32
  end
71
33
 
72
- def attribute_methods_generated?
34
+ def attribute_methods_generated? # :nodoc:
73
35
  @attribute_methods_generated ||= false
74
36
  end
75
37
 
76
- # We will define the methods as instance methods, but will call them as singleton
77
- # methods. This allows us to use method_defined? to check if the method exists,
78
- # which is fast and won't give any false positives from the ancestors (because
79
- # there are no ancestors).
80
- def generated_external_attribute_methods
81
- @generated_external_attribute_methods ||= Module.new { extend self }
82
- end
83
-
84
- def undefine_attribute_methods
85
- super
38
+ def undefine_attribute_methods # :nodoc:
39
+ super if attribute_methods_generated?
86
40
  @attribute_methods_generated = false
87
41
  end
88
42
 
43
+ # Raises a <tt>ActiveRecord::DangerousAttributeError</tt> exception when an
44
+ # \Active \Record method is defined in the model, otherwise +false+.
45
+ #
46
+ # class Person < ActiveRecord::Base
47
+ # def save
48
+ # 'already defined by Active Record'
49
+ # end
50
+ # end
51
+ #
52
+ # Person.instance_method_already_implemented?(:save)
53
+ # # => ActiveRecord::DangerousAttributeError: save is defined by ActiveRecord
54
+ #
55
+ # Person.instance_method_already_implemented?(:name)
56
+ # # => false
89
57
  def instance_method_already_implemented?(method_name)
90
58
  if dangerous_attribute_method?(method_name)
91
59
  raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord"
@@ -102,11 +70,11 @@ module ActiveRecord
102
70
 
103
71
  # A method name is 'dangerous' if it is already defined by Active Record, but
104
72
  # not by any ancestors. (So 'puts' is not dangerous but 'save' is.)
105
- def dangerous_attribute_method?(name)
73
+ def dangerous_attribute_method?(name) # :nodoc:
106
74
  method_defined_within?(name, Base)
107
75
  end
108
76
 
109
- def method_defined_within?(name, klass, sup = klass.superclass)
77
+ def method_defined_within?(name, klass, sup = klass.superclass) # :nodoc:
110
78
  if klass.method_defined?(name) || klass.private_method_defined?(name)
111
79
  if sup.method_defined?(name) || sup.private_method_defined?(name)
112
80
  klass.instance_method(name).owner != sup.instance_method(name).owner
@@ -118,13 +86,27 @@ module ActiveRecord
118
86
  end
119
87
  end
120
88
 
89
+ # Returns +true+ if +attribute+ is an attribute method and table exists,
90
+ # +false+ otherwise.
91
+ #
92
+ # class Person < ActiveRecord::Base
93
+ # end
94
+ #
95
+ # Person.attribute_method?('name') # => true
96
+ # Person.attribute_method?(:age=) # => true
97
+ # Person.attribute_method?(:nothing) # => false
121
98
  def attribute_method?(attribute)
122
99
  super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, '')))
123
100
  end
124
101
 
125
- # Returns an array of column names as strings if it's not
126
- # an abstract class and table exists.
127
- # Otherwise it returns an empty array.
102
+ # Returns an array of column names as strings if it's not an abstract class and
103
+ # table exists. Otherwise it returns an empty array.
104
+ #
105
+ # class Person < ActiveRecord::Base
106
+ # end
107
+ #
108
+ # Person.attribute_names
109
+ # # => ["id", "created_at", "updated_at", "name", "age"]
128
110
  def attribute_names
129
111
  @attribute_names ||= if !abstract_class? && table_exists?
130
112
  column_names
@@ -136,7 +118,7 @@ module ActiveRecord
136
118
 
137
119
  # If we haven't generated any methods yet, generate them, then
138
120
  # see if we've created the method we're looking for.
139
- def method_missing(method, *args, &block)
121
+ def method_missing(method, *args, &block) # :nodoc:
140
122
  unless self.class.attribute_methods_generated?
141
123
  self.class.define_attribute_methods
142
124
 
@@ -150,7 +132,7 @@ module ActiveRecord
150
132
  end
151
133
  end
152
134
 
153
- def attribute_missing(match, *args, &block)
135
+ def attribute_missing(match, *args, &block) # :nodoc:
154
136
  if self.class.columns_hash[match.attr_name]
155
137
  ActiveSupport::Deprecation.warn(
156
138
  "The method `#{match.method_name}', matching the attribute `#{match.attr_name}' has " \
@@ -164,26 +146,64 @@ module ActiveRecord
164
146
  super
165
147
  end
166
148
 
149
+ # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
150
+ # <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
151
+ # which will all return +true+. It also define the attribute methods if they have
152
+ # not been generated.
153
+ #
154
+ # class Person < ActiveRecord::Base
155
+ # end
156
+ #
157
+ # person = Person.new
158
+ # person.respond_to(:name) # => true
159
+ # person.respond_to(:name=) # => true
160
+ # person.respond_to(:name?) # => true
161
+ # person.respond_to('age') # => true
162
+ # person.respond_to('age=') # => true
163
+ # person.respond_to('age?') # => true
164
+ # person.respond_to(:nothing) # => false
167
165
  def respond_to?(name, include_private = false)
168
166
  self.class.define_attribute_methods unless self.class.attribute_methods_generated?
169
167
  super
170
168
  end
171
169
 
172
- # Returns true if the given attribute is in the attributes hash
170
+ # Returns +true+ if the given attribute is in the attributes hash, otherwise +false+.
171
+ #
172
+ # class Person < ActiveRecord::Base
173
+ # end
174
+ #
175
+ # person = Person.new
176
+ # person.has_attribute?(:name) # => true
177
+ # person.has_attribute?('age') # => true
178
+ # person.has_attribute?(:nothing) # => false
173
179
  def has_attribute?(attr_name)
174
180
  @attributes.has_key?(attr_name.to_s)
175
181
  end
176
182
 
177
183
  # Returns an array of names for the attributes available on this object.
184
+ #
185
+ # class Person < ActiveRecord::Base
186
+ # end
187
+ #
188
+ # person = Person.new
189
+ # person.attribute_names
190
+ # # => ["id", "created_at", "updated_at", "name", "age"]
178
191
  def attribute_names
179
192
  @attributes.keys
180
193
  end
181
194
 
182
195
  # Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
196
+ #
197
+ # class Person < ActiveRecord::Base
198
+ # end
199
+ #
200
+ # person = Person.create(name: 'Francesco', age: 22)
201
+ # person.attributes
202
+ # # => {"id"=>3, "created_at"=>Sun, 21 Oct 2012 04:53:04, "updated_at"=>Sun, 21 Oct 2012 04:53:04, "name"=>"Francesco", "age"=>22}
183
203
  def attributes
184
- attrs = {}
185
- attribute_names.each { |name| attrs[name] = read_attribute(name) }
186
- attrs
204
+ attribute_names.each_with_object({}) { |name, attrs|
205
+ attrs[name] = read_attribute(name)
206
+ }
187
207
  end
188
208
 
189
209
  # Returns an <tt>#inspect</tt>-like string for the value of the
@@ -192,13 +212,13 @@ module ActiveRecord
192
212
  # <tt>:db</tt> format. Other attributes return the value of
193
213
  # <tt>#inspect</tt> without modification.
194
214
  #
195
- # person = Person.create!(:name => "David Heinemeier Hansson " * 3)
215
+ # person = Person.create!(name: 'David Heinemeier Hansson ' * 3)
196
216
  #
197
217
  # person.attribute_for_inspect(:name)
198
- # # => '"David Heinemeier Hansson David Heinemeier Hansson D..."'
218
+ # # => "\"David Heinemeier Hansson David Heinemeier Hansson D...\""
199
219
  #
200
220
  # person.attribute_for_inspect(:created_at)
201
- # # => '"2009-01-12 04:48:57"'
221
+ # # => "\"2012-10-22 00:15:07\""
202
222
  def attribute_for_inspect(attr_name)
203
223
  value = read_attribute(attr_name)
204
224
 
@@ -211,65 +231,148 @@ module ActiveRecord
211
231
  end
212
232
  end
213
233
 
214
- # Returns true if the specified +attribute+ has been set by the user or by a database load and is neither
215
- # nil nor empty? (the latter only applies to objects that respond to empty?, most notably Strings).
234
+ # Returns +true+ if the specified +attribute+ has been set by the user or by a
235
+ # database load and is neither +nil+ nor <tt>empty?</tt> (the latter only applies
236
+ # to objects that respond to <tt>empty?</tt>, most notably Strings). Otherwise, +false+.
237
+ # Note that it always returns +true+ with boolean attributes.
238
+ #
239
+ # class Task < ActiveRecord::Base
240
+ # end
241
+ #
242
+ # person = Task.new(title: '', is_done: false)
243
+ # person.attribute_present?(:title) # => false
244
+ # person.attribute_present?(:is_done) # => true
245
+ # person.name = 'Francesco'
246
+ # person.is_done = true
247
+ # person.attribute_present?(:title) # => true
248
+ # person.attribute_present?(:is_done) # => true
216
249
  def attribute_present?(attribute)
217
250
  value = read_attribute(attribute)
218
251
  !value.nil? && !(value.respond_to?(:empty?) && value.empty?)
219
252
  end
220
253
 
221
- # Returns the column object for the named attribute.
254
+ # Returns the column object for the named attribute. Returns +nil+ if the
255
+ # named attribute not exists.
256
+ #
257
+ # class Person < ActiveRecord::Base
258
+ # end
259
+ #
260
+ # person = Person.new
261
+ # person.column_for_attribute(:name) # the result depends on the ConnectionAdapter
262
+ # # => #<ActiveRecord::ConnectionAdapters::SQLite3Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
263
+ #
264
+ # person.column_for_attribute(:nothing)
265
+ # # => nil
222
266
  def column_for_attribute(name)
267
+ # FIXME: should this return a null object for columns that don't exist?
223
268
  self.class.columns_hash[name.to_s]
224
269
  end
225
270
 
271
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
272
+ # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)). It raises
273
+ # <tt>ActiveModel::MissingAttributeError</tt> if the identified attribute is missing.
274
+ #
275
+ # Alias for the <tt>read_attribute</tt> method.
276
+ #
277
+ # class Person < ActiveRecord::Base
278
+ # belongs_to :organization
279
+ # end
280
+ #
281
+ # person = Person.new(name: 'Francesco', age: '22')
282
+ # person[:name] # => "Francesco"
283
+ # person[:age] # => 22
284
+ #
285
+ # person = Person.select('id').first
286
+ # person[:name] # => ActiveModel::MissingAttributeError: missing attribute: name
287
+ # person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute: organization_id
288
+ def [](attr_name)
289
+ read_attribute(attr_name) { |n| missing_attribute(n, caller) }
290
+ end
291
+
292
+ # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
293
+ # (Alias for the protected <tt>write_attribute</tt> method).
294
+ #
295
+ # class Person < ActiveRecord::Base
296
+ # end
297
+ #
298
+ # person = Person.new
299
+ # person[:age] = '22'
300
+ # person[:age] # => 22
301
+ # person[:age] # => Fixnum
302
+ def []=(attr_name, value)
303
+ write_attribute(attr_name, value)
304
+ end
305
+
226
306
  protected
227
307
 
228
- def clone_attributes(reader_method = :read_attribute, attributes = {})
308
+ def clone_attributes(reader_method = :read_attribute, attributes = {}) # :nodoc:
229
309
  attribute_names.each do |name|
230
310
  attributes[name] = clone_attribute_value(reader_method, name)
231
311
  end
232
312
  attributes
233
313
  end
234
314
 
235
- def clone_attribute_value(reader_method, attribute_name)
315
+ def clone_attribute_value(reader_method, attribute_name) # :nodoc:
236
316
  value = send(reader_method, attribute_name)
237
317
  value.duplicable? ? value.clone : value
238
318
  rescue TypeError, NoMethodError
239
319
  value
240
320
  end
241
321
 
242
- # Returns a copy of the attributes hash where all the values have been safely quoted for use in
243
- # an Arel insert/update method.
244
- def arel_attributes_values(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys)
245
- attrs = {}
246
- klass = self.class
247
- arel_table = klass.arel_table
322
+ def arel_attributes_with_values_for_create(attribute_names) # :nodoc:
323
+ arel_attributes_with_values(attributes_for_create(attribute_names))
324
+ end
248
325
 
249
- attribute_names.each do |name|
250
- if (column = column_for_attribute(name)) && (include_primary_key || !column.primary)
326
+ def arel_attributes_with_values_for_update(attribute_names) # :nodoc:
327
+ arel_attributes_with_values(attributes_for_update(attribute_names))
328
+ end
251
329
 
252
- if include_readonly_attributes || !self.class.readonly_attributes.include?(name)
330
+ def attribute_method?(attr_name) # :nodoc:
331
+ defined?(@attributes) && @attributes.include?(attr_name)
332
+ end
253
333
 
254
- value = if klass.serialized_attributes.include?(name)
255
- @attributes[name].serialized_value
256
- else
257
- # FIXME: we need @attributes to be used consistently.
258
- # If the values stored in @attributes were already type
259
- # casted, this code could be simplified
260
- read_attribute(name)
261
- end
334
+ private
262
335
 
263
- attrs[arel_table[name]] = value
264
- end
265
- end
266
- end
336
+ # Returns a Hash of the Arel::Attributes and attribute values that have been
337
+ # type casted for use in an Arel insert/update method.
338
+ def arel_attributes_with_values(attribute_names)
339
+ attrs = {}
340
+ arel_table = self.class.arel_table
267
341
 
342
+ attribute_names.each do |name|
343
+ attrs[arel_table[name]] = typecasted_attribute_value(name)
344
+ end
268
345
  attrs
269
346
  end
270
347
 
271
- def attribute_method?(attr_name)
272
- attr_name == 'id' || (defined?(@attributes) && @attributes.include?(attr_name))
348
+ # Filters the primary keys and readonly attributes from the attribute names.
349
+ def attributes_for_update(attribute_names)
350
+ attribute_names.select do |name|
351
+ column_for_attribute(name) && !pk_attribute?(name) && !readonly_attribute?(name)
352
+ end
353
+ end
354
+
355
+ # Filters out the primary keys, from the attribute names, when the primary
356
+ # key is to be generated (e.g. the id attribute has no value).
357
+ def attributes_for_create(attribute_names)
358
+ attribute_names.select do |name|
359
+ column_for_attribute(name) && !(pk_attribute?(name) && id.nil?)
360
+ end
361
+ end
362
+
363
+ def readonly_attribute?(name)
364
+ self.class.readonly_attributes.include?(name)
365
+ end
366
+
367
+ def pk_attribute?(name)
368
+ column_for_attribute(name).primary
369
+ end
370
+
371
+ def typecasted_attribute_value(name)
372
+ # FIXME: we need @attributes to be used consistently.
373
+ # If the values stored in @attributes were already typecasted, this code
374
+ # could be simplified
375
+ read_attribute(name)
273
376
  end
274
377
  end
275
378
  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