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,13 +1,14 @@
1
1
  require 'active_support/core_ext/enumerable'
2
- require 'active_support/deprecation'
2
+ require 'mutex_m'
3
3
 
4
4
  module ActiveRecord
5
5
  # = Active Record Attribute Methods
6
- module AttributeMethods #:nodoc:
6
+ module AttributeMethods
7
7
  extend ActiveSupport::Concern
8
8
  include ActiveModel::AttributeMethods
9
9
 
10
10
  included do
11
+ initialize_generated_modules
11
12
  include Read
12
13
  include Write
13
14
  include BeforeTypeCast
@@ -16,79 +17,69 @@ module ActiveRecord
16
17
  include TimeZoneConversion
17
18
  include Dirty
18
19
  include Serialization
19
- include DeprecatedUnderscoreRead
20
+ end
20
21
 
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)
22
+ module ClassMethods
23
+ def inherited(child_class) #:nodoc:
24
+ child_class.initialize_generated_modules
25
+ super
26
26
  end
27
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)
28
+ def initialize_generated_modules # :nodoc:
29
+ @generated_attribute_methods = Module.new {
30
+ extend Mutex_m
31
+
32
+ const_set :AttrNames, Module.new {
33
+ def self.set_name_cache(name, value)
34
+ const_name = "ATTR_#{name}"
35
+ unless const_defined? const_name
36
+ const_set const_name, value.dup.freeze
37
+ end
38
+ end
39
+ }
40
+ }
41
+ @attribute_methods_generated = false
42
+ include @generated_attribute_methods
32
43
  end
33
- end
34
44
 
35
- module ClassMethods
36
45
  # Generates all the attribute related methods for columns in the database
37
46
  # 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
-
61
- # Use a mutex; we don't want two thread simaltaneously trying to define
47
+ def define_attribute_methods # :nodoc:
48
+ # Use a mutex; we don't want two thread simultaneously trying to define
62
49
  # attribute methods.
63
- @attribute_methods_mutex.synchronize do
64
- return if attribute_methods_generated?
50
+ generated_attribute_methods.synchronize do
51
+ return false if @attribute_methods_generated
65
52
  superclass.define_attribute_methods unless self == base_class
66
53
  super(column_names)
67
- column_names.each { |name| define_external_attribute_method(name) }
68
54
  @attribute_methods_generated = true
69
55
  end
56
+ true
70
57
  end
71
58
 
72
- def attribute_methods_generated?
73
- @attribute_methods_generated ||= false
74
- end
75
-
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
86
- @attribute_methods_generated = false
59
+ def undefine_attribute_methods # :nodoc:
60
+ generated_attribute_methods.synchronize do
61
+ super if @attribute_methods_generated
62
+ @attribute_methods_generated = false
63
+ end
87
64
  end
88
65
 
66
+ # Raises a <tt>ActiveRecord::DangerousAttributeError</tt> exception when an
67
+ # \Active \Record method is defined in the model, otherwise +false+.
68
+ #
69
+ # class Person < ActiveRecord::Base
70
+ # def save
71
+ # 'already defined by Active Record'
72
+ # end
73
+ # end
74
+ #
75
+ # Person.instance_method_already_implemented?(:save)
76
+ # # => ActiveRecord::DangerousAttributeError: save is defined by ActiveRecord
77
+ #
78
+ # Person.instance_method_already_implemented?(:name)
79
+ # # => false
89
80
  def instance_method_already_implemented?(method_name)
90
81
  if dangerous_attribute_method?(method_name)
91
- raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord"
82
+ raise DangerousAttributeError, "#{method_name} is defined by Active Record"
92
83
  end
93
84
 
94
85
  if superclass == Base
@@ -96,17 +87,18 @@ module ActiveRecord
96
87
  else
97
88
  # If B < A and A defines its own attribute method, then we don't want to overwrite that.
98
89
  defined = method_defined_within?(method_name, superclass, superclass.generated_attribute_methods)
99
- defined && !ActiveRecord::Base.method_defined?(method_name) || super
90
+ base_defined = Base.method_defined?(method_name) || Base.private_method_defined?(method_name)
91
+ defined && !base_defined || super
100
92
  end
101
93
  end
102
94
 
103
95
  # A method name is 'dangerous' if it is already defined by Active Record, but
104
96
  # not by any ancestors. (So 'puts' is not dangerous but 'save' is.)
105
- def dangerous_attribute_method?(name)
97
+ def dangerous_attribute_method?(name) # :nodoc:
106
98
  method_defined_within?(name, Base)
107
99
  end
108
100
 
109
- def method_defined_within?(name, klass, sup = klass.superclass)
101
+ def method_defined_within?(name, klass, sup = klass.superclass) # :nodoc:
110
102
  if klass.method_defined?(name) || klass.private_method_defined?(name)
111
103
  if sup.method_defined?(name) || sup.private_method_defined?(name)
112
104
  klass.instance_method(name).owner != sup.instance_method(name).owner
@@ -118,13 +110,37 @@ module ActiveRecord
118
110
  end
119
111
  end
120
112
 
113
+ def find_generated_attribute_method(method_name) # :nodoc:
114
+ klass = self
115
+ until klass == Base
116
+ gen_methods = klass.generated_attribute_methods
117
+ return gen_methods.instance_method(method_name) if method_defined_within?(method_name, gen_methods, Object)
118
+ klass = klass.superclass
119
+ end
120
+ nil
121
+ end
122
+
123
+ # Returns +true+ if +attribute+ is an attribute method and table exists,
124
+ # +false+ otherwise.
125
+ #
126
+ # class Person < ActiveRecord::Base
127
+ # end
128
+ #
129
+ # Person.attribute_method?('name') # => true
130
+ # Person.attribute_method?(:age=) # => true
131
+ # Person.attribute_method?(:nothing) # => false
121
132
  def attribute_method?(attribute)
122
133
  super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, '')))
123
134
  end
124
135
 
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.
136
+ # Returns an array of column names as strings if it's not an abstract class and
137
+ # table exists. Otherwise it returns an empty array.
138
+ #
139
+ # class Person < ActiveRecord::Base
140
+ # end
141
+ #
142
+ # Person.attribute_names
143
+ # # => ["id", "created_at", "updated_at", "name", "age"]
128
144
  def attribute_names
129
145
  @attribute_names ||= if !abstract_class? && table_exists?
130
146
  column_names
@@ -136,21 +152,24 @@ module ActiveRecord
136
152
 
137
153
  # If we haven't generated any methods yet, generate them, then
138
154
  # see if we've created the method we're looking for.
139
- def method_missing(method, *args, &block)
140
- unless self.class.attribute_methods_generated?
141
- self.class.define_attribute_methods
142
-
143
- if respond_to_without_attributes?(method)
144
- send(method, *args, &block)
155
+ def method_missing(method, *args, &block) # :nodoc:
156
+ self.class.define_attribute_methods
157
+ if respond_to_without_attributes?(method)
158
+ # make sure to invoke the correct attribute method, as we might have gotten here via a `super`
159
+ # call in a overwritten attribute method
160
+ if attribute_method = self.class.find_generated_attribute_method(method)
161
+ # this is probably horribly slow, but should only happen at most once for a given AR class
162
+ attribute_method.bind(self).call(*args, &block)
145
163
  else
146
- super
164
+ return super unless respond_to_missing?(method, true)
165
+ send(method, *args, &block)
147
166
  end
148
167
  else
149
168
  super
150
169
  end
151
170
  end
152
171
 
153
- def attribute_missing(match, *args, &block)
172
+ def attribute_missing(match, *args, &block) # :nodoc:
154
173
  if self.class.columns_hash[match.attr_name]
155
174
  ActiveSupport::Deprecation.warn(
156
175
  "The method `#{match.method_name}', matching the attribute `#{match.attr_name}' has " \
@@ -164,26 +183,83 @@ module ActiveRecord
164
183
  super
165
184
  end
166
185
 
186
+ # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
187
+ # <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
188
+ # which will all return +true+. It also define the attribute methods if they have
189
+ # not been generated.
190
+ #
191
+ # class Person < ActiveRecord::Base
192
+ # end
193
+ #
194
+ # person = Person.new
195
+ # person.respond_to(:name) # => true
196
+ # person.respond_to(:name=) # => true
197
+ # person.respond_to(:name?) # => true
198
+ # person.respond_to('age') # => true
199
+ # person.respond_to('age=') # => true
200
+ # person.respond_to('age?') # => true
201
+ # person.respond_to(:nothing) # => false
167
202
  def respond_to?(name, include_private = false)
168
- self.class.define_attribute_methods unless self.class.attribute_methods_generated?
169
- super
203
+ name = name.to_s
204
+ self.class.define_attribute_methods
205
+ result = super
206
+
207
+ # If the result is false the answer is false.
208
+ return false unless result
209
+
210
+ # If the result is true then check for the select case.
211
+ # For queries selecting a subset of columns, return false for unselected columns.
212
+ # We check defined?(@attributes) not to issue warnings if called on objects that
213
+ # have been allocated but not yet initialized.
214
+ if defined?(@attributes) && @attributes.present? && self.class.column_names.include?(name)
215
+ return has_attribute?(name)
216
+ end
217
+
218
+ return true
170
219
  end
171
220
 
172
- # Returns true if the given attribute is in the attributes hash
221
+ # Returns +true+ if the given attribute is in the attributes hash, otherwise +false+.
222
+ #
223
+ # class Person < ActiveRecord::Base
224
+ # end
225
+ #
226
+ # person = Person.new
227
+ # person.has_attribute?(:name) # => true
228
+ # person.has_attribute?('age') # => true
229
+ # person.has_attribute?(:nothing) # => false
173
230
  def has_attribute?(attr_name)
174
231
  @attributes.has_key?(attr_name.to_s)
175
232
  end
176
233
 
177
234
  # Returns an array of names for the attributes available on this object.
235
+ #
236
+ # class Person < ActiveRecord::Base
237
+ # end
238
+ #
239
+ # person = Person.new
240
+ # person.attribute_names
241
+ # # => ["id", "created_at", "updated_at", "name", "age"]
178
242
  def attribute_names
179
243
  @attributes.keys
180
244
  end
181
245
 
182
246
  # Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
247
+ #
248
+ # class Person < ActiveRecord::Base
249
+ # end
250
+ #
251
+ # person = Person.create(name: 'Francesco', age: 22)
252
+ # person.attributes
253
+ # # => {"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
254
  def attributes
184
- attrs = {}
185
- attribute_names.each { |name| attrs[name] = read_attribute(name) }
186
- attrs
255
+ attribute_names.each_with_object({}) { |name, attrs|
256
+ attrs[name] = read_attribute(name)
257
+ }
258
+ end
259
+
260
+ # Placeholder so it can be overriden when needed by serialization
261
+ def attributes_for_coder # :nodoc:
262
+ attributes
187
263
  end
188
264
 
189
265
  # Returns an <tt>#inspect</tt>-like string for the value of the
@@ -192,13 +268,13 @@ module ActiveRecord
192
268
  # <tt>:db</tt> format. Other attributes return the value of
193
269
  # <tt>#inspect</tt> without modification.
194
270
  #
195
- # person = Person.create!(:name => "David Heinemeier Hansson " * 3)
271
+ # person = Person.create!(name: 'David Heinemeier Hansson ' * 3)
196
272
  #
197
273
  # person.attribute_for_inspect(:name)
198
- # # => '"David Heinemeier Hansson David Heinemeier Hansson D..."'
274
+ # # => "\"David Heinemeier Hansson David Heinemeier Hansson D...\""
199
275
  #
200
276
  # person.attribute_for_inspect(:created_at)
201
- # # => '"2009-01-12 04:48:57"'
277
+ # # => "\"2012-10-22 00:15:07\""
202
278
  def attribute_for_inspect(attr_name)
203
279
  value = read_attribute(attr_name)
204
280
 
@@ -211,65 +287,149 @@ module ActiveRecord
211
287
  end
212
288
  end
213
289
 
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).
290
+ # Returns +true+ if the specified +attribute+ has been set by the user or by a
291
+ # database load and is neither +nil+ nor <tt>empty?</tt> (the latter only applies
292
+ # to objects that respond to <tt>empty?</tt>, most notably Strings). Otherwise, +false+.
293
+ # Note that it always returns +true+ with boolean attributes.
294
+ #
295
+ # class Task < ActiveRecord::Base
296
+ # end
297
+ #
298
+ # person = Task.new(title: '', is_done: false)
299
+ # person.attribute_present?(:title) # => false
300
+ # person.attribute_present?(:is_done) # => true
301
+ # person.name = 'Francesco'
302
+ # person.is_done = true
303
+ # person.attribute_present?(:title) # => true
304
+ # person.attribute_present?(:is_done) # => true
216
305
  def attribute_present?(attribute)
217
306
  value = read_attribute(attribute)
218
307
  !value.nil? && !(value.respond_to?(:empty?) && value.empty?)
219
308
  end
220
309
 
221
- # Returns the column object for the named attribute.
310
+ # Returns the column object for the named attribute. Returns +nil+ if the
311
+ # named attribute not exists.
312
+ #
313
+ # class Person < ActiveRecord::Base
314
+ # end
315
+ #
316
+ # person = Person.new
317
+ # person.column_for_attribute(:name) # the result depends on the ConnectionAdapter
318
+ # # => #<ActiveRecord::ConnectionAdapters::SQLite3Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
319
+ #
320
+ # person.column_for_attribute(:nothing)
321
+ # # => nil
222
322
  def column_for_attribute(name)
323
+ # FIXME: should this return a null object for columns that don't exist?
223
324
  self.class.columns_hash[name.to_s]
224
325
  end
225
326
 
327
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
328
+ # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)). It raises
329
+ # <tt>ActiveModel::MissingAttributeError</tt> if the identified attribute is missing.
330
+ #
331
+ # Alias for the <tt>read_attribute</tt> method.
332
+ #
333
+ # class Person < ActiveRecord::Base
334
+ # belongs_to :organization
335
+ # end
336
+ #
337
+ # person = Person.new(name: 'Francesco', age: '22')
338
+ # person[:name] # => "Francesco"
339
+ # person[:age] # => 22
340
+ #
341
+ # person = Person.select('id').first
342
+ # person[:name] # => ActiveModel::MissingAttributeError: missing attribute: name
343
+ # person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute: organization_id
344
+ def [](attr_name)
345
+ read_attribute(attr_name) { |n| missing_attribute(n, caller) }
346
+ end
347
+
348
+ # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
349
+ # (Alias for the protected <tt>write_attribute</tt> method).
350
+ #
351
+ # class Person < ActiveRecord::Base
352
+ # end
353
+ #
354
+ # person = Person.new
355
+ # person[:age] = '22'
356
+ # person[:age] # => 22
357
+ # person[:age] # => Fixnum
358
+ def []=(attr_name, value)
359
+ write_attribute(attr_name, value)
360
+ end
361
+
226
362
  protected
227
363
 
228
- def clone_attributes(reader_method = :read_attribute, attributes = {})
364
+ def clone_attributes(reader_method = :read_attribute, attributes = {}) # :nodoc:
229
365
  attribute_names.each do |name|
230
366
  attributes[name] = clone_attribute_value(reader_method, name)
231
367
  end
232
368
  attributes
233
369
  end
234
370
 
235
- def clone_attribute_value(reader_method, attribute_name)
371
+ def clone_attribute_value(reader_method, attribute_name) # :nodoc:
236
372
  value = send(reader_method, attribute_name)
237
373
  value.duplicable? ? value.clone : value
238
374
  rescue TypeError, NoMethodError
239
375
  value
240
376
  end
241
377
 
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
378
+ def arel_attributes_with_values_for_create(attribute_names) # :nodoc:
379
+ arel_attributes_with_values(attributes_for_create(attribute_names))
380
+ end
381
+
382
+ def arel_attributes_with_values_for_update(attribute_names) # :nodoc:
383
+ arel_attributes_with_values(attributes_for_update(attribute_names))
384
+ end
248
385
 
249
- attribute_names.each do |name|
250
- if (column = column_for_attribute(name)) && (include_primary_key || !column.primary)
386
+ def attribute_method?(attr_name) # :nodoc:
387
+ # We check defined? because Syck calls respond_to? before actually calling initialize.
388
+ defined?(@attributes) && @attributes.include?(attr_name)
389
+ end
251
390
 
252
- if include_readonly_attributes || !self.class.readonly_attributes.include?(name)
391
+ private
253
392
 
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
393
+ # Returns a Hash of the Arel::Attributes and attribute values that have been
394
+ # typecasted for use in an Arel insert/update method.
395
+ def arel_attributes_with_values(attribute_names)
396
+ attrs = {}
397
+ arel_table = self.class.arel_table
262
398
 
263
- attrs[arel_table[name]] = value
264
- end
265
- end
399
+ attribute_names.each do |name|
400
+ attrs[arel_table[name]] = typecasted_attribute_value(name)
266
401
  end
267
-
268
402
  attrs
269
403
  end
270
404
 
271
- def attribute_method?(attr_name)
272
- attr_name == 'id' || (defined?(@attributes) && @attributes.include?(attr_name))
405
+ # Filters the primary keys and readonly attributes from the attribute names.
406
+ def attributes_for_update(attribute_names)
407
+ attribute_names.select do |name|
408
+ column_for_attribute(name) && !readonly_attribute?(name)
409
+ end
410
+ end
411
+
412
+ # Filters out the primary keys, from the attribute names, when the primary
413
+ # key is to be generated (e.g. the id attribute has no value).
414
+ def attributes_for_create(attribute_names)
415
+ attribute_names.select do |name|
416
+ column_for_attribute(name) && !(pk_attribute?(name) && id.nil?)
417
+ end
418
+ end
419
+
420
+ def readonly_attribute?(name)
421
+ self.class.readonly_attributes.include?(name)
422
+ end
423
+
424
+ def pk_attribute?(name)
425
+ column_for_attribute(name).primary
426
+ end
427
+
428
+ def typecasted_attribute_value(name)
429
+ # FIXME: we need @attributes to be used consistently.
430
+ # If the values stored in @attributes were already typecasted, this code
431
+ # could be simplified
432
+ read_attribute(name)
273
433
  end
274
434
  end
275
435
  end