activerecord 3.0.20 → 3.1.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 (122) hide show
  1. data/CHANGELOG +220 -91
  2. data/README.rdoc +3 -3
  3. data/examples/performance.rb +88 -109
  4. data/lib/active_record.rb +6 -2
  5. data/lib/active_record/aggregations.rb +22 -45
  6. data/lib/active_record/associations.rb +264 -991
  7. data/lib/active_record/associations/alias_tracker.rb +85 -0
  8. data/lib/active_record/associations/association.rb +231 -0
  9. data/lib/active_record/associations/association_scope.rb +120 -0
  10. data/lib/active_record/associations/belongs_to_association.rb +40 -60
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +15 -63
  12. data/lib/active_record/associations/builder/association.rb +53 -0
  13. data/lib/active_record/associations/builder/belongs_to.rb +85 -0
  14. data/lib/active_record/associations/builder/collection_association.rb +75 -0
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +63 -0
  16. data/lib/active_record/associations/builder/has_many.rb +65 -0
  17. data/lib/active_record/associations/builder/has_one.rb +63 -0
  18. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  19. data/lib/active_record/associations/collection_association.rb +524 -0
  20. data/lib/active_record/associations/collection_proxy.rb +125 -0
  21. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +27 -118
  22. data/lib/active_record/associations/has_many_association.rb +50 -79
  23. data/lib/active_record/associations/has_many_through_association.rb +98 -67
  24. data/lib/active_record/associations/has_one_association.rb +45 -115
  25. data/lib/active_record/associations/has_one_through_association.rb +21 -25
  26. data/lib/active_record/associations/join_dependency.rb +215 -0
  27. data/lib/active_record/associations/join_dependency/join_association.rb +150 -0
  28. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  29. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  30. data/lib/active_record/associations/join_helper.rb +56 -0
  31. data/lib/active_record/associations/preloader.rb +177 -0
  32. data/lib/active_record/associations/preloader/association.rb +126 -0
  33. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  34. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  35. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  36. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  37. data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
  38. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  39. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  40. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  41. data/lib/active_record/associations/preloader/through_association.rb +67 -0
  42. data/lib/active_record/associations/singular_association.rb +55 -0
  43. data/lib/active_record/associations/through_association.rb +80 -0
  44. data/lib/active_record/attribute_methods.rb +19 -5
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +9 -8
  46. data/lib/active_record/attribute_methods/dirty.rb +8 -2
  47. data/lib/active_record/attribute_methods/primary_key.rb +33 -13
  48. data/lib/active_record/attribute_methods/read.rb +17 -17
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -4
  50. data/lib/active_record/attribute_methods/write.rb +2 -1
  51. data/lib/active_record/autosave_association.rb +66 -45
  52. data/lib/active_record/base.rb +445 -273
  53. data/lib/active_record/callbacks.rb +24 -33
  54. data/lib/active_record/coders/yaml_column.rb +41 -0
  55. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +106 -13
  56. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +16 -2
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +12 -11
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +83 -12
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +16 -16
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +61 -22
  61. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +16 -273
  62. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +80 -42
  63. data/lib/active_record/connection_adapters/abstract_adapter.rb +44 -25
  64. data/lib/active_record/connection_adapters/column.rb +268 -0
  65. data/lib/active_record/connection_adapters/mysql2_adapter.rb +686 -0
  66. data/lib/active_record/connection_adapters/mysql_adapter.rb +331 -88
  67. data/lib/active_record/connection_adapters/postgresql_adapter.rb +295 -267
  68. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +3 -7
  69. data/lib/active_record/connection_adapters/sqlite_adapter.rb +108 -26
  70. data/lib/active_record/counter_cache.rb +7 -4
  71. data/lib/active_record/fixtures.rb +174 -192
  72. data/lib/active_record/identity_map.rb +131 -0
  73. data/lib/active_record/locking/optimistic.rb +20 -14
  74. data/lib/active_record/locking/pessimistic.rb +4 -4
  75. data/lib/active_record/log_subscriber.rb +24 -4
  76. data/lib/active_record/migration.rb +265 -144
  77. data/lib/active_record/migration/command_recorder.rb +103 -0
  78. data/lib/active_record/named_scope.rb +68 -25
  79. data/lib/active_record/nested_attributes.rb +58 -15
  80. data/lib/active_record/observer.rb +3 -7
  81. data/lib/active_record/persistence.rb +58 -38
  82. data/lib/active_record/query_cache.rb +25 -3
  83. data/lib/active_record/railtie.rb +21 -12
  84. data/lib/active_record/railties/console_sandbox.rb +6 -0
  85. data/lib/active_record/railties/databases.rake +147 -116
  86. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  87. data/lib/active_record/reflection.rb +176 -44
  88. data/lib/active_record/relation.rb +125 -49
  89. data/lib/active_record/relation/batches.rb +7 -5
  90. data/lib/active_record/relation/calculations.rb +50 -18
  91. data/lib/active_record/relation/finder_methods.rb +47 -26
  92. data/lib/active_record/relation/predicate_builder.rb +24 -21
  93. data/lib/active_record/relation/query_methods.rb +117 -101
  94. data/lib/active_record/relation/spawn_methods.rb +27 -20
  95. data/lib/active_record/result.rb +34 -0
  96. data/lib/active_record/schema.rb +5 -6
  97. data/lib/active_record/schema_dumper.rb +11 -13
  98. data/lib/active_record/serialization.rb +2 -2
  99. data/lib/active_record/serializers/xml_serializer.rb +10 -10
  100. data/lib/active_record/session_store.rb +8 -2
  101. data/lib/active_record/test_case.rb +9 -20
  102. data/lib/active_record/timestamp.rb +21 -9
  103. data/lib/active_record/transactions.rb +16 -15
  104. data/lib/active_record/validations.rb +21 -22
  105. data/lib/active_record/validations/associated.rb +3 -1
  106. data/lib/active_record/validations/uniqueness.rb +48 -58
  107. data/lib/active_record/version.rb +3 -3
  108. data/lib/rails/generators/active_record.rb +6 -0
  109. data/lib/rails/generators/active_record/migration/templates/migration.rb +10 -2
  110. data/lib/rails/generators/active_record/model/model_generator.rb +2 -1
  111. data/lib/rails/generators/active_record/model/templates/migration.rb +6 -5
  112. data/lib/rails/generators/active_record/model/templates/model.rb +2 -0
  113. data/lib/rails/generators/active_record/model/templates/module.rb +2 -0
  114. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  115. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +2 -1
  116. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +2 -2
  117. metadata +106 -77
  118. checksums.yaml +0 -7
  119. data/lib/active_record/association_preload.rb +0 -431
  120. data/lib/active_record/associations/association_collection.rb +0 -572
  121. data/lib/active_record/associations/association_proxy.rb +0 -304
  122. data/lib/active_record/associations/through_association_scope.rb +0 -160
@@ -20,14 +20,13 @@ module ActiveRecord
20
20
  # be cached. Usually caching only pays off for attributes with expensive conversion
21
21
  # methods, like time related columns (e.g. +created_at+, +updated_at+).
22
22
  def cache_attributes(*attribute_names)
23
- attribute_names.each {|attr| cached_attributes << attr.to_s}
23
+ cached_attributes.merge attribute_names.map { |attr| attr.to_s }
24
24
  end
25
25
 
26
26
  # Returns the attributes which are cached. By default time related columns
27
27
  # with datatype <tt>:datetime, :timestamp, :time, :date</tt> are cached.
28
28
  def cached_attributes
29
- @cached_attributes ||=
30
- columns.select{|c| attribute_types_cached_by_default.include?(c.type)}.map{|col| col.name}.to_set
29
+ @cached_attributes ||= columns.select { |c| cacheable_column?(c) }.map { |col| col.name }.to_set
31
30
  end
32
31
 
33
32
  # Returns +true+ if the provided attribute is being cached.
@@ -37,7 +36,7 @@ module ActiveRecord
37
36
 
38
37
  protected
39
38
  def define_method_attribute(attr_name)
40
- if self.serialized_attributes[attr_name]
39
+ if serialized_attributes.include?(attr_name)
41
40
  define_read_method_for_serialized_attribute(attr_name)
42
41
  else
43
42
  define_read_method(attr_name, attr_name, columns_hash[attr_name])
@@ -49,20 +48,25 @@ module ActiveRecord
49
48
  end
50
49
 
51
50
  private
51
+ def cacheable_column?(column)
52
+ serialized_attributes.include?(column.name) || attribute_types_cached_by_default.include?(column.type)
53
+ end
54
+
52
55
  # Define read method for serialized attribute.
53
56
  def define_read_method_for_serialized_attribute(attr_name)
54
- generated_attribute_methods.module_eval("def #{attr_name}; unserialize_attribute('#{attr_name}'); end", __FILE__, __LINE__)
57
+ access_code = "@attributes_cache['#{attr_name}'] ||= @attributes['#{attr_name}']"
58
+ generated_attribute_methods.module_eval("def _#{attr_name}; #{access_code}; end; alias #{attr_name} _#{attr_name}", __FILE__, __LINE__)
55
59
  end
56
60
 
57
61
  # Define an attribute reader method. Cope with nil column.
58
62
  # method_name is the same as attr_name except when a non-standard primary key is used,
59
63
  # we still define #id as an accessor for the key
60
64
  def define_read_method(method_name, attr_name, column)
61
- cast_code = column.type_cast_code('v') if column
62
- access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']"
65
+ cast_code = column.type_cast_code('v')
66
+ access_code = "(v=@attributes['#{attr_name}']) && #{cast_code}"
63
67
 
64
68
  unless attr_name.to_s == self.primary_key.to_s
65
- access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
69
+ access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
66
70
  end
67
71
 
68
72
  if cache_attribute?(attr_name)
@@ -75,7 +79,7 @@ module ActiveRecord
75
79
  #
76
80
  # The second, slower, branch is necessary to support instances where the database
77
81
  # returns columns with extra stuff in (like 'my_column(omg)').
78
- if method_name =~ /^[a-zA-Z_]\w*[!?=]?$/
82
+ if method_name =~ ActiveModel::AttributeMethods::COMPILABLE_REGEXP
79
83
  generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__
80
84
  def _#{method_name}
81
85
  #{access_code}
@@ -121,19 +125,15 @@ module ActiveRecord
121
125
 
122
126
  # Returns true if the attribute is of a text column and marked for serialization.
123
127
  def unserializable_attribute?(attr_name, column)
124
- column.text? && self.class.serialized_attributes[attr_name]
128
+ column.text? && self.class.serialized_attributes.include?(attr_name)
125
129
  end
126
130
 
127
131
  # Returns the unserialized object of the attribute.
128
132
  def unserialize_attribute(attr_name)
129
- unserialized_object = object_from_yaml(@attributes[attr_name])
133
+ coder = self.class.serialized_attributes[attr_name]
134
+ unserialized_object = coder.load(@attributes[attr_name])
130
135
 
131
- if unserialized_object.is_a?(self.class.serialized_attributes[attr_name]) || unserialized_object.nil?
132
- @attributes.frozen? ? unserialized_object : @attributes[attr_name] = unserialized_object
133
- else
134
- raise SerializationTypeMismatch,
135
- "#{attr_name} was supposed to be a #{self.class.serialized_attributes[attr_name]}, but was a #{unserialized_object.class.to_s}"
136
- end
136
+ @attributes.frozen? ? unserialized_object : @attributes[attr_name] = unserialized_object
137
137
  end
138
138
 
139
139
  private
@@ -1,3 +1,6 @@
1
+ require 'active_support/core_ext/class/attribute'
2
+ require 'active_support/core_ext/object/inclusion'
3
+
1
4
  module ActiveRecord
2
5
  module AttributeMethods
3
6
  module TimeZoneConversion
@@ -7,7 +10,7 @@ module ActiveRecord
7
10
  cattr_accessor :time_zone_aware_attributes, :instance_writer => false
8
11
  self.time_zone_aware_attributes = false
9
12
 
10
- class_inheritable_accessor :skip_time_zone_conversion_for_attributes, :instance_writer => false
13
+ class_attribute :skip_time_zone_conversion_for_attributes, :instance_writer => false
11
14
  self.skip_time_zone_conversion_for_attributes = []
12
15
  end
13
16
 
@@ -19,9 +22,9 @@ module ActiveRecord
19
22
  def define_method_attribute(attr_name)
20
23
  if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
21
24
  method_body, line = <<-EOV, __LINE__ + 1
22
- def _#{attr_name}(reload = false)
25
+ def _#{attr_name}
23
26
  cached = @attributes_cache['#{attr_name}']
24
- return cached if cached && !reload
27
+ return cached if cached
25
28
  time = _read_attribute('#{attr_name}')
26
29
  @attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time
27
30
  end
@@ -56,7 +59,7 @@ module ActiveRecord
56
59
 
57
60
  private
58
61
  def create_time_zone_conversion_attribute?(name, column)
59
- time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(name.to_sym) && [:datetime, :timestamp].include?(column.type)
62
+ time_zone_aware_attributes && !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) && column.type.in?([:datetime, :timestamp])
60
63
  end
61
64
  end
62
65
  end
@@ -10,7 +10,7 @@ module ActiveRecord
10
10
  module ClassMethods
11
11
  protected
12
12
  def define_method_attribute=(attr_name)
13
- if attr_name =~ /^[a-zA-Z_]\w*[!?=]?$/
13
+ if attr_name =~ ActiveModel::AttributeMethods::COMPILABLE_REGEXP
14
14
  generated_attribute_methods.module_eval("def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", __FILE__, __LINE__)
15
15
  else
16
16
  generated_attribute_methods.send(:define_method, "#{attr_name}=") do |new_value|
@@ -32,6 +32,7 @@ module ActiveRecord
32
32
  @attributes[attr_name] = value
33
33
  end
34
34
  end
35
+ alias_method :raw_write_attribute, :write_attribute
35
36
 
36
37
  private
37
38
  # Handle *= for method_missing.
@@ -4,7 +4,7 @@ module ActiveRecord
4
4
  # = Active Record Autosave Association
5
5
  #
6
6
  # +AutosaveAssociation+ is a module that takes care of automatically saving
7
- # associacted records when their parent is saved. In addition to saving, it
7
+ # associated records when their parent is saved. In addition to saving, it
8
8
  # also destroys any associated records that were marked for destruction.
9
9
  # (See +mark_for_destruction+ and <tt>marked_for_destruction?</tt>).
10
10
  #
@@ -116,30 +116,29 @@ module ActiveRecord
116
116
  module AutosaveAssociation
117
117
  extend ActiveSupport::Concern
118
118
 
119
- ASSOCIATION_TYPES = %w{ has_one belongs_to has_many has_and_belongs_to_many }
119
+ ASSOCIATION_TYPES = %w{ HasOne HasMany BelongsTo HasAndBelongsToMany }
120
+
121
+ module AssociationBuilderExtension #:nodoc:
122
+ def self.included(base)
123
+ base.valid_options << :autosave
124
+ end
125
+
126
+ def build
127
+ reflection = super
128
+ model.send(:add_autosave_association_callbacks, reflection)
129
+ reflection
130
+ end
131
+ end
120
132
 
121
133
  included do
122
134
  ASSOCIATION_TYPES.each do |type|
123
- send("valid_keys_for_#{type}_association") << :autosave
135
+ Associations::Builder.const_get(type).send(:include, AssociationBuilderExtension)
124
136
  end
125
137
  end
126
138
 
127
139
  module ClassMethods
128
140
  private
129
141
 
130
- # def belongs_to(name, options = {})
131
- # super
132
- # add_autosave_association_callbacks(reflect_on_association(name))
133
- # end
134
- ASSOCIATION_TYPES.each do |type|
135
- module_eval <<-CODE, __FILE__, __LINE__ + 1
136
- def #{type}(name, options = {})
137
- super
138
- add_autosave_association_callbacks(reflect_on_association(name))
139
- end
140
- CODE
141
- end
142
-
143
142
  def define_non_cyclic_method(name, reflection, &block)
144
143
  define_method(name) do |*args|
145
144
  result = true; @_already_called ||= {}
@@ -177,14 +176,23 @@ module ActiveRecord
177
176
  if collection
178
177
  before_save :before_save_collection_association
179
178
 
180
- define_method(save_method) { save_collection_association(reflection) }
179
+ define_non_cyclic_method(save_method, reflection) { save_collection_association(reflection) }
181
180
  # Doesn't use after_save as that would save associations added in after_create/after_update twice
182
181
  after_create save_method
183
182
  after_update save_method
184
183
  else
185
184
  if reflection.macro == :has_one
186
185
  define_method(save_method) { save_has_one_association(reflection) }
187
- after_save save_method
186
+ # Configures two callbacks instead of a single after_save so that
187
+ # the model may rely on their execution order relative to its
188
+ # own callbacks.
189
+ #
190
+ # For example, given that after_creates run before after_saves, if
191
+ # we configured instead an after_save there would be no way to fire
192
+ # a custom after_create callback after the child association gets
193
+ # created.
194
+ after_create save_method
195
+ after_update save_method
188
196
  else
189
197
  define_non_cyclic_method(save_method, reflection) { save_belongs_to_association(reflection) }
190
198
  before_save save_method
@@ -194,7 +202,7 @@ module ActiveRecord
194
202
 
195
203
  if reflection.validate? && !method_defined?(validation_method)
196
204
  method = (collection ? :validate_collection_association : :validate_single_association)
197
- define_method(validation_method) { send(method, reflection) }
205
+ define_non_cyclic_method(validation_method, reflection) { send(method, reflection) }
198
206
  validate validation_method
199
207
  end
200
208
  end
@@ -235,7 +243,7 @@ module ActiveRecord
235
243
  # unless the parent is/was a new record itself.
236
244
  def associated_records_to_validate_or_save(association, new_record, autosave)
237
245
  if new_record
238
- association
246
+ association && association.target
239
247
  elsif autosave
240
248
  association.target.find_all { |record| record.changed_for_autosave? }
241
249
  else
@@ -255,9 +263,9 @@ module ActiveRecord
255
263
  # Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
256
264
  # turned on for the association.
257
265
  def validate_single_association(reflection)
258
- if (association = association_instance_get(reflection.name)) && !association.target.nil?
259
- association_valid?(reflection, association)
260
- end
266
+ association = association_instance_get(reflection.name)
267
+ record = association && association.target
268
+ association_valid?(reflection, record) if record
261
269
  end
262
270
 
263
271
  # Validate the associated records if <tt>:validate</tt> or
@@ -274,12 +282,12 @@ module ActiveRecord
274
282
  # Returns whether or not the association is valid and applies any errors to
275
283
  # the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
276
284
  # enabled records if they're marked_for_destruction? or destroyed.
277
- def association_valid?(reflection, association)
278
- return true if association.destroyed? || association.marked_for_destruction?
285
+ def association_valid?(reflection, record)
286
+ return true if record.destroyed? || record.marked_for_destruction?
279
287
 
280
- unless valid = association.valid?
288
+ unless valid = record.valid?
281
289
  if reflection.options[:autosave]
282
- association.errors.each do |attribute, message|
290
+ record.errors.each do |attribute, message|
283
291
  attribute = "#{reflection.name}.#{attribute}"
284
292
  errors[attribute] << message
285
293
  errors[attribute].uniq!
@@ -311,27 +319,35 @@ module ActiveRecord
311
319
  autosave = reflection.options[:autosave]
312
320
 
313
321
  if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
322
+ begin
314
323
  records.each do |record|
315
324
  next if record.destroyed?
316
325
 
326
+ saved = true
327
+
317
328
  if autosave && record.marked_for_destruction?
318
- association.destroy(record)
329
+ association.proxy.destroy(record)
319
330
  elsif autosave != false && (@new_record_before_save || record.new_record?)
320
331
  if autosave
321
- saved = association.send(:insert_record, record, false, false)
332
+ saved = association.insert_record(record, false)
322
333
  else
323
- association.send(:insert_record, record)
334
+ association.insert_record(record)
324
335
  end
325
336
  elsif autosave
326
337
  saved = record.save(:validate => false)
327
338
  end
328
339
 
329
- raise ActiveRecord::Rollback if saved == false
340
+ raise ActiveRecord::Rollback unless saved
341
+ end
342
+ rescue
343
+ records.each {|x| IdentityMap.remove(x) } if IdentityMap.enabled?
344
+ raise
330
345
  end
346
+
331
347
  end
332
348
 
333
- # reconstruct the SQL queries now that we know the owner's id
334
- association.send(:construct_sql) if association.respond_to?(:construct_sql)
349
+ # reconstruct the scope now that we know the owner's id
350
+ association.send(:construct_scope) if association.respond_to?(:construct_scope)
335
351
  end
336
352
  end
337
353
 
@@ -344,16 +360,18 @@ module ActiveRecord
344
360
  # This all happens inside a transaction, _if_ the Transactions module is included into
345
361
  # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
346
362
  def save_has_one_association(reflection)
347
- if (association = association_instance_get(reflection.name)) && !association.target.nil? && !association.destroyed?
363
+ association = association_instance_get(reflection.name)
364
+ record = association && association.load_target
365
+ if record && !record.destroyed?
348
366
  autosave = reflection.options[:autosave]
349
367
 
350
- if autosave && association.marked_for_destruction?
351
- association.destroy
368
+ if autosave && record.marked_for_destruction?
369
+ record.destroy
352
370
  else
353
371
  key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
354
- if autosave != false && (new_record? || association.new_record? || association[reflection.primary_key_name] != key || autosave)
355
- association[reflection.primary_key_name] = key
356
- saved = association.save(:validate => !autosave)
372
+ if autosave != false && (new_record? || record.new_record? || record[reflection.foreign_key] != key || autosave)
373
+ record[reflection.foreign_key] = key
374
+ saved = record.save(:validate => !autosave)
357
375
  raise ActiveRecord::Rollback if !saved && autosave
358
376
  saved
359
377
  end
@@ -365,17 +383,20 @@ module ActiveRecord
365
383
  #
366
384
  # In addition, it will destroy the association if it was marked for destruction.
367
385
  def save_belongs_to_association(reflection)
368
- if (association = association_instance_get(reflection.name)) && !association.destroyed?
386
+ association = association_instance_get(reflection.name)
387
+ record = association && association.load_target
388
+ if record && !record.destroyed?
369
389
  autosave = reflection.options[:autosave]
370
390
 
371
- if autosave && association.marked_for_destruction?
372
- association.destroy
391
+ if autosave && record.marked_for_destruction?
392
+ record.destroy
373
393
  elsif autosave != false
374
- saved = association.save(:validate => !autosave) if association.new_record? || autosave
394
+ saved = record.save(:validate => !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
375
395
 
376
396
  if association.updated?
377
- association_id = association.send(reflection.options[:primary_key] || :id)
378
- self[reflection.primary_key_name] = association_id
397
+ association_id = record.send(reflection.options[:primary_key] || :id)
398
+ self[reflection.foreign_key] = association_id
399
+ association.loaded!
379
400
  end
380
401
 
381
402
  saved if autosave
@@ -12,7 +12,7 @@ require 'active_support/time'
12
12
  require 'active_support/core_ext/class/attribute'
13
13
  require 'active_support/core_ext/class/attribute_accessors'
14
14
  require 'active_support/core_ext/class/delegating_attributes'
15
- require 'active_support/core_ext/class/inheritable_attributes'
15
+ require 'active_support/core_ext/class/attribute'
16
16
  require 'active_support/core_ext/array/extract_options'
17
17
  require 'active_support/core_ext/hash/deep_merge'
18
18
  require 'active_support/core_ext/hash/indifferent_access'
@@ -20,7 +20,6 @@ require 'active_support/core_ext/hash/slice'
20
20
  require 'active_support/core_ext/string/behavior'
21
21
  require 'active_support/core_ext/kernel/singleton_class'
22
22
  require 'active_support/core_ext/module/delegation'
23
- require 'active_support/core_ext/module/deprecation'
24
23
  require 'active_support/core_ext/module/introspection'
25
24
  require 'active_support/core_ext/object/duplicable'
26
25
  require 'active_support/core_ext/object/blank'
@@ -84,7 +83,7 @@ module ActiveRecord #:nodoc:
84
83
  #
85
84
  # The <tt>authenticate_unsafely</tt> method inserts the parameters directly into the query
86
85
  # and is thus susceptible to SQL-injection attacks if the <tt>user_name</tt> and +password+
87
- # parameters come directly from an HTTP request. The <tt>authenticate_safely</tt> and
86
+ # parameters come directly from an HTTP request. The <tt>authenticate_safely</tt> and
88
87
  # <tt>authenticate_safely_simply</tt> both will sanitize the <tt>user_name</tt> and +password+
89
88
  # before inserting them in the query, which will ensure that an attacker can't escape the
90
89
  # query and fake the login (or worse).
@@ -181,10 +180,7 @@ module ActiveRecord #:nodoc:
181
180
  # It's also possible to use multiple attributes in the same find by separating them with "_and_".
182
181
  #
183
182
  # Person.where(:user_name => user_name, :password => password).first
184
- # Person.find_by_user_name_and_password #with dynamic finder
185
- #
186
- # Person.where(:user_name => user_name, :password => password, :gender => 'male').first
187
- # Payment.find_by_user_name_and_password_and_gender
183
+ # Person.find_by_user_name_and_password(user_name, password) # with dynamic finder
188
184
  #
189
185
  # It's even possible to call these dynamic finder methods on relations and named scopes.
190
186
  #
@@ -210,7 +206,7 @@ module ActiveRecord #:nodoc:
210
206
  #
211
207
  # # No 'Winter' tag exists
212
208
  # winter = Tag.find_or_initialize_by_name("Winter")
213
- # winter.new_record? # true
209
+ # winter.persisted? # false
214
210
  #
215
211
  # To find by a subset of the attributes to be used for instantiating a new object, pass a hash instead of
216
212
  # a list of parameters.
@@ -250,6 +246,17 @@ module ActiveRecord #:nodoc:
250
246
  # user = User.create(:preferences => %w( one two three ))
251
247
  # User.find(user.id).preferences # raises SerializationTypeMismatch
252
248
  #
249
+ # When you specify a class option, the default value for that attribute will be a new
250
+ # instance of that class.
251
+ #
252
+ # class User < ActiveRecord::Base
253
+ # serialize :preferences, OpenStruct
254
+ # end
255
+ #
256
+ # user = User.new
257
+ # user.preferences.theme_color = "red"
258
+ #
259
+ #
253
260
  # == Single table inheritance
254
261
  #
255
262
  # Active Record allows inheritance by storing the name of the class in a column that by
@@ -321,18 +328,6 @@ module ActiveRecord #:nodoc:
321
328
  # a class and instance level by calling +logger+.
322
329
  cattr_accessor :logger, :instance_writer => false
323
330
 
324
- class << self
325
- def reset_subclasses #:nodoc:
326
- ActiveSupport::Deprecation.warn 'ActiveRecord::Base.reset_subclasses no longer does anything in Rails 3. It will be removed in the final release; please update your apps and plugins.', caller
327
- end
328
-
329
- def subclasses
330
- descendants
331
- end
332
-
333
- deprecate :subclasses => :descendants
334
- end
335
-
336
331
  ##
337
332
  # :singleton-method:
338
333
  # Contains the database configuration - as is typically stored in config/database.yml -
@@ -411,10 +406,10 @@ module ActiveRecord #:nodoc:
411
406
  ##
412
407
  # :singleton-method:
413
408
  # Specifies the format to use when dumping the database schema with Rails'
414
- # Rakefile. If :sql, the schema is dumped as (potentially database-
415
- # specific) SQL statements. If :ruby, the schema is dumped as an
409
+ # Rakefile. If :sql, the schema is dumped as (potentially database-
410
+ # specific) SQL statements. If :ruby, the schema is dumped as an
416
411
  # ActiveRecord::Schema file which can be loaded into any database that
417
- # supports migrations. Use :ruby if you want to have different database
412
+ # supports migrations. Use :ruby if you want to have different database
418
413
  # adapters for, e.g., your development and test environments.
419
414
  cattr_accessor :schema_format , :instance_writer => false
420
415
  @@schema_format = :ruby
@@ -426,38 +421,39 @@ module ActiveRecord #:nodoc:
426
421
  @@timestamped_migrations = true
427
422
 
428
423
  # Determine whether to store the full constant name including namespace when using STI
429
- superclass_delegating_accessor :store_full_sti_class
424
+ class_attribute :store_full_sti_class
430
425
  self.store_full_sti_class = true
431
426
 
432
427
  # Stores the default scope for the class
433
- class_inheritable_accessor :default_scoping, :instance_writer => false
434
- self.default_scoping = []
428
+ class_attribute :default_scopes, :instance_writer => false
429
+ self.default_scopes = []
435
430
 
436
- class << self # Class methods
437
- def colorize_logging(*args)
438
- ActiveSupport::Deprecation.warn "ActiveRecord::Base.colorize_logging and " <<
439
- "config.active_record.colorize_logging are deprecated. Please use " <<
440
- "Rails::LogSubscriber.colorize_logging or config.colorize_logging instead", caller
441
- end
442
- alias :colorize_logging= :colorize_logging
431
+ # Returns a hash of all the attributes that have been specified for serialization as
432
+ # keys and their class restriction as values.
433
+ class_attribute :serialized_attributes
434
+ self.serialized_attributes = {}
435
+
436
+ class_attribute :_attr_readonly, :instance_writer => false
437
+ self._attr_readonly = []
443
438
 
444
- delegate :find, :first, :last, :all, :exists?, :any?, :many?, :to => :scoped
439
+ class << self # Class methods
440
+ delegate :find, :first, :first!, :last, :last!, :all, :exists?, :any?, :many?, :to => :scoped
445
441
  delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :scoped
446
442
  delegate :find_each, :find_in_batches, :to => :scoped
447
- delegate :select, :group, :order, :reorder, :except, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
443
+ delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
448
444
  delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped
449
445
 
450
- # Executes a custom SQL query against your database and returns all the results. The results will
446
+ # Executes a custom SQL query against your database and returns all the results. The results will
451
447
  # be returned as an array with columns requested encapsulated as attributes of the model you call
452
- # this method from. If you call <tt>Product.find_by_sql</tt> then the results will be returned in
448
+ # this method from. If you call <tt>Product.find_by_sql</tt> then the results will be returned in
453
449
  # a Product object with the attributes you specified in the SQL query.
454
450
  #
455
451
  # If you call a complicated SQL query which spans multiple tables the columns specified by the
456
452
  # SELECT will be attributes of the model, whether or not they are columns of the corresponding
457
453
  # table.
458
454
  #
459
- # The +sql+ parameter is a full SQL query as a string. It will be called as is, there will be
460
- # no database agnostic conversions performed. This should be a last resort because using, for example,
455
+ # The +sql+ parameter is a full SQL query as a string. It will be called as is, there will be
456
+ # no database agnostic conversions performed. This should be a last resort because using, for example,
461
457
  # MySQL specific terms will lock you to using that particular database engine or require you to
462
458
  # change your call if you switch engines.
463
459
  #
@@ -468,21 +464,30 @@ module ActiveRecord #:nodoc:
468
464
  #
469
465
  # # You can use the same string replacement techniques as you can with ActiveRecord#find
470
466
  # Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
471
- # > [#<Post:0x36bff9c @attributes={"first_name"=>"The Cheap Man Buys Twice"}>, ...]
472
- def find_by_sql(sql)
473
- connection.select_all(sanitize_sql(sql), "#{name} Load").collect! { |record| instantiate(record) }
467
+ # > [#<Post:0x36bff9c @attributes={"title"=>"The Cheap Man Buys Twice"}>, ...]
468
+ def find_by_sql(sql, binds = [])
469
+ connection.select_all(sanitize_sql(sql), "#{name} Load", binds).collect! { |record| instantiate(record) }
474
470
  end
475
471
 
476
472
  # Creates an object (or multiple objects) and saves it to the database, if validations pass.
477
473
  # The resulting object is returned whether the object was saved successfully to the database or not.
478
474
  #
479
- # The +attributes+ parameter can be either be a Hash or an Array of Hashes. These Hashes describe the
475
+ # The +attributes+ parameter can be either be a Hash or an Array of Hashes. These Hashes describe the
480
476
  # attributes on the objects that are to be created.
481
477
  #
478
+ # +create+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options
479
+ # in the +options+ parameter.
480
+ #
482
481
  # ==== Examples
483
482
  # # Create a single new object
484
483
  # User.create(:first_name => 'Jamie')
485
484
  #
485
+ # # Create a single new object using the :admin mass-assignment security scope
486
+ # User.create({ :first_name => 'Jamie', :is_admin => true }, :as => :admin)
487
+ #
488
+ # # Create a single new object bypassing mass-assignment security
489
+ # User.create({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true)
490
+ #
486
491
  # # Create an Array of new objects
487
492
  # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }])
488
493
  #
@@ -495,11 +500,11 @@ module ActiveRecord #:nodoc:
495
500
  # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }]) do |u|
496
501
  # u.is_admin = false
497
502
  # end
498
- def create(attributes = nil, &block)
503
+ def create(attributes = nil, options = {}, &block)
499
504
  if attributes.is_a?(Array)
500
- attributes.collect { |attr| create(attr, &block) }
505
+ attributes.collect { |attr| create(attr, options, &block) }
501
506
  else
502
- object = new(attributes)
507
+ object = new(attributes, options)
503
508
  yield(object) if block_given?
504
509
  object.save
505
510
  object
@@ -508,7 +513,7 @@ module ActiveRecord #:nodoc:
508
513
 
509
514
  # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
510
515
  # The use of this method should be restricted to complicated SQL queries that can't be executed
511
- # using the ActiveRecord::Calculations class methods. Look into those before using this.
516
+ # using the ActiveRecord::Calculations class methods. Look into those before using this.
512
517
  #
513
518
  # ==== Parameters
514
519
  #
@@ -525,12 +530,12 @@ module ActiveRecord #:nodoc:
525
530
  # Attributes listed as readonly will be used to create a new record but update operations will
526
531
  # ignore these fields.
527
532
  def attr_readonly(*attributes)
528
- write_inheritable_attribute(:attr_readonly, Set.new(attributes.map { |a| a.to_s }) + (readonly_attributes || []))
533
+ self._attr_readonly = Set.new(attributes.map { |a| a.to_s }) + (self._attr_readonly || [])
529
534
  end
530
535
 
531
536
  # Returns an array of all the attributes that have been specified as readonly.
532
537
  def readonly_attributes
533
- read_inheritable_attribute(:attr_readonly) || []
538
+ self._attr_readonly
534
539
  end
535
540
 
536
541
  # If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
@@ -545,17 +550,19 @@ module ActiveRecord #:nodoc:
545
550
  #
546
551
  # ==== Example
547
552
  # # Serialize a preferences attribute
548
- # class User
553
+ # class User < ActiveRecord::Base
549
554
  # serialize :preferences
550
555
  # end
551
556
  def serialize(attr_name, class_name = Object)
552
- serialized_attributes[attr_name.to_s] = class_name
553
- end
557
+ coder = if [:load, :dump].all? { |x| class_name.respond_to?(x) }
558
+ class_name
559
+ else
560
+ Coders::YAMLColumn.new(class_name)
561
+ end
554
562
 
555
- # Returns a hash of all the attributes that have been specified for serialization as
556
- # keys and their class restriction as values.
557
- def serialized_attributes
558
- read_inheritable_attribute(:attr_serialized) or write_inheritable_attribute(:attr_serialized, {})
563
+ # merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy
564
+ # has its own hash of own serialized attributes
565
+ self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder)
559
566
  end
560
567
 
561
568
  # Guesses the table name (in forced lower-case) based on the name of the class in the
@@ -583,7 +590,7 @@ module ActiveRecord #:nodoc:
583
590
  # invoice/lineitem.rb Invoice::Lineitem lineitems
584
591
  #
585
592
  # Additionally, the class-level +table_name_prefix+ is prepended and the
586
- # +table_name_suffix+ is appended. So if you have "myapp_" as a prefix,
593
+ # +table_name_suffix+ is appended. So if you have "myapp_" as a prefix,
587
594
  # the table name guess for an Invoice class becomes "myapp_invoices".
588
595
  # Invoice::Lineitem becomes "myapp_invoice_lineitems".
589
596
  #
@@ -617,7 +624,7 @@ module ActiveRecord #:nodoc:
617
624
  @inheritance_column ||= "type"
618
625
  end
619
626
 
620
- # Lazy-set the sequence name to the connection's default. This method
627
+ # Lazy-set the sequence name to the connection's default. This method
621
628
  # is only ever called once since set_sequence_name overrides it.
622
629
  def sequence_name #:nodoc:
623
630
  reset_sequence_name
@@ -629,7 +636,7 @@ module ActiveRecord #:nodoc:
629
636
  default
630
637
  end
631
638
 
632
- # Sets the table name. If the value is nil or false then the value returned by the given
639
+ # Sets the table name. If the value is nil or false then the value returned by the given
633
640
  # block is used.
634
641
  #
635
642
  # class Project < ActiveRecord::Base
@@ -638,6 +645,9 @@ module ActiveRecord #:nodoc:
638
645
  def set_table_name(value = nil, &block)
639
646
  @quoted_table_name = nil
640
647
  define_attr_method :table_name, value, &block
648
+
649
+ @arel_table = Arel::Table.new(table_name, arel_engine)
650
+ @relation = Relation.new(self, arel_table)
641
651
  end
642
652
  alias :table_name= :set_table_name
643
653
 
@@ -681,16 +691,12 @@ module ActiveRecord #:nodoc:
681
691
 
682
692
  # Returns an array of column objects for the table associated with this class.
683
693
  def columns
684
- unless defined?(@columns) && @columns
685
- @columns = connection.columns(table_name, "#{name} Columns")
686
- @columns.each { |column| column.primary = column.name == primary_key }
687
- end
688
- @columns
694
+ connection_pool.columns[table_name]
689
695
  end
690
696
 
691
697
  # Returns a hash of column objects for the table associated with this class.
692
698
  def columns_hash
693
- @columns_hash ||= Hash[columns.map { |column| [column.name, column] }]
699
+ connection_pool.columns_hash[table_name]
694
700
  end
695
701
 
696
702
  # Returns an array of column names as strings.
@@ -745,13 +751,16 @@ module ActiveRecord #:nodoc:
745
751
  # end
746
752
  # end
747
753
  def reset_column_information
754
+ connection.clear_cache!
748
755
  undefine_attribute_methods
749
- @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil
750
- @arel_engine = @relation = @arel_table = nil
756
+ connection_pool.clear_table_cache!(table_name) if table_exists?
757
+
758
+ @column_names = @content_columns = @dynamic_methods_hash = @inheritance_column = nil
759
+ @arel_engine = @relation = nil
751
760
  end
752
761
 
753
- def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc:
754
- descendants.each { |klass| klass.reset_inheritable_attributes; klass.reset_column_information }
762
+ def clear_cache! # :nodoc:
763
+ connection_pool.clear_cache!
755
764
  end
756
765
 
757
766
  def attribute_method?(attribute)
@@ -762,15 +771,12 @@ module ActiveRecord #:nodoc:
762
771
  def lookup_ancestors #:nodoc:
763
772
  klass = self
764
773
  classes = [klass]
774
+ return classes if klass == ActiveRecord::Base
775
+
765
776
  while klass != klass.base_class
766
777
  classes << klass = klass.superclass
767
778
  end
768
779
  classes
769
- rescue
770
- # OPTIMIZE this rescue is to fix this test: ./test/cases/reflection_test.rb:56:in `test_human_name_for_column'
771
- # Apparently the method base_class causes some trouble.
772
- # It now works for sure.
773
- [self]
774
780
  end
775
781
 
776
782
  # Set the i18n scope to overwrite ActiveModel.
@@ -792,7 +798,7 @@ module ActiveRecord #:nodoc:
792
798
  :true == (@finder_needs_type_condition ||= descends_from_active_record? ? :false : :true)
793
799
  end
794
800
 
795
- # Returns a string like 'Post id:integer, title:string, body:text'
801
+ # Returns a string like 'Post(id:integer, title:string, body:text)'
796
802
  def inspect
797
803
  if self == Base
798
804
  super
@@ -820,6 +826,10 @@ module ActiveRecord #:nodoc:
820
826
  object.is_a?(self)
821
827
  end
822
828
 
829
+ def symbolized_base_class
830
+ @symbolized_base_class ||= base_class.to_s.to_sym
831
+ end
832
+
823
833
  # Returns the base AR subclass that this class descends from. If A
824
834
  # extends AR::Base, A.base_class will return A. If B descends from A
825
835
  # through some arbitrarily deep hierarchy, B.base_class will return A.
@@ -853,13 +863,13 @@ module ActiveRecord #:nodoc:
853
863
  end
854
864
 
855
865
  def arel_table
856
- @arel_table ||= Arel::Table.new(table_name, arel_engine)
866
+ Arel::Table.new(table_name, arel_engine)
857
867
  end
858
868
 
859
869
  def arel_engine
860
870
  @arel_engine ||= begin
861
871
  if self == ActiveRecord::Base
862
- Arel::Table.engine
872
+ ActiveRecord::Base
863
873
  else
864
874
  connection_handler.connection_pools[name] ? self : superclass.arel_engine
865
875
  end
@@ -869,7 +879,9 @@ module ActiveRecord #:nodoc:
869
879
  # Returns a scope for this class without taking into account the default_scope.
870
880
  #
871
881
  # class Post < ActiveRecord::Base
872
- # default_scope :published => true
882
+ # def self.default_scope
883
+ # where :published => true
884
+ # end
873
885
  # end
874
886
  #
875
887
  # Post.all # Fires "SELECT * FROM posts WHERE published = true"
@@ -882,8 +894,8 @@ module ActiveRecord #:nodoc:
882
894
  # limit(10) # Fires "SELECT * FROM posts LIMIT 10"
883
895
  # }
884
896
  #
885
- # It is recommended to use block form of unscoped because chaining unscoped with <tt>named_scope</tt>
886
- # does not work. Assuming that <tt>published</tt> is a <tt>named_scope</tt> following two statements are same.
897
+ # It is recommended to use block form of unscoped because chaining unscoped with <tt>scope</tt>
898
+ # does not work. Assuming that <tt>published</tt> is a <tt>scope</tt> following two statements are same.
887
899
  #
888
900
  # Post.unscoped.published
889
901
  # Post.published
@@ -891,29 +903,55 @@ module ActiveRecord #:nodoc:
891
903
  block_given? ? relation.scoping { yield } : relation
892
904
  end
893
905
 
894
- def scoped_methods #:nodoc:
895
- key = :"#{self}_scoped_methods"
896
- Thread.current[key] = Thread.current[key].presence || self.default_scoping.dup
906
+ def before_remove_const #:nodoc:
907
+ self.current_scope = nil
908
+ end
909
+
910
+ # Specifies how the record is loaded by +Marshal+.
911
+ #
912
+ # +_load+ sets an instance variable for each key in the hash it takes as input.
913
+ # Override this method if you require more complex marshalling.
914
+ def _load(data)
915
+ record = allocate
916
+ record.init_with(Marshal.load(data))
917
+ record
897
918
  end
898
919
 
899
- def before_remove_const #:nodoc:
900
- reset_scoped_methods
920
+
921
+ # Finder methods must instantiate through this method to work with the
922
+ # single-table inheritance model that makes it possible to create
923
+ # objects of different types from the same table.
924
+ def instantiate(record)
925
+ sti_class = find_sti_class(record[inheritance_column])
926
+ record_id = sti_class.primary_key && record[sti_class.primary_key]
927
+
928
+ if ActiveRecord::IdentityMap.enabled? && record_id
929
+ if (column = sti_class.columns_hash[sti_class.primary_key]) && column.number?
930
+ record_id = record_id.to_i
931
+ end
932
+ if instance = IdentityMap.get(sti_class, record_id)
933
+ instance.reinit_with('attributes' => record)
934
+ else
935
+ instance = sti_class.allocate.init_with('attributes' => record)
936
+ IdentityMap.add(instance)
937
+ end
938
+ else
939
+ instance = sti_class.allocate.init_with('attributes' => record)
940
+ end
941
+
942
+ instance
901
943
  end
902
944
 
903
945
  private
904
946
 
905
947
  def relation #:nodoc:
906
948
  @relation ||= Relation.new(self, arel_table)
907
- finder_needs_type_condition? ? @relation.where(type_condition) : @relation
908
- end
909
949
 
910
- # Finder methods must instantiate through this method to work with the
911
- # single-table inheritance model that makes it possible to create
912
- # objects of different types from the same table.
913
- def instantiate(record)
914
- model = find_sti_class(record[inheritance_column]).allocate
915
- model.init_with('attributes' => record)
916
- model
950
+ if finder_needs_type_condition?
951
+ @relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name)
952
+ else
953
+ @relation
954
+ end
917
955
  end
918
956
 
919
957
  def find_sti_class(type_name)
@@ -942,12 +980,11 @@ module ActiveRecord #:nodoc:
942
980
  relation
943
981
  end
944
982
 
945
- def type_condition
946
- sti_column = arel_table[inheritance_column]
947
- condition = sti_column.eq(sti_name)
948
- descendants.each { |subclass| condition = condition.or(sti_column.eq(subclass.sti_name)) }
983
+ def type_condition(table = arel_table)
984
+ sti_column = table[inheritance_column.to_sym]
985
+ sti_names = ([self] + descendants).map { |model| model.sti_name }
949
986
 
950
- condition
987
+ sti_column.in(sti_names)
951
988
  end
952
989
 
953
990
  # Guesses the table name, but does not decorate it with prefix and suffix information.
@@ -988,12 +1025,8 @@ module ActiveRecord #:nodoc:
988
1025
  attribute_names = match.attribute_names
989
1026
  super unless all_attributes_exists?(attribute_names)
990
1027
  if match.finder?
991
- options = if arguments.length > attribute_names.size
992
- arguments.extract_options!
993
- else
994
- {}
995
- end
996
- relation = options.any? ? construct_finder_arel(options, current_scoped_methods) : scoped
1028
+ options = arguments.extract_options!
1029
+ relation = options.any? ? scoped(options) : scoped
997
1030
  relation.send :find_by_attributes, match, attribute_names, *arguments
998
1031
  elsif match.instantiator?
999
1032
  scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block
@@ -1003,14 +1036,11 @@ module ActiveRecord #:nodoc:
1003
1036
  super unless all_attributes_exists?(attribute_names)
1004
1037
  if match.scope?
1005
1038
  self.class_eval <<-METHOD, __FILE__, __LINE__ + 1
1006
- def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args)
1007
- options = args.extract_options! # options = args.extract_options!
1008
- attributes = construct_attributes_from_arguments( # attributes = construct_attributes_from_arguments(
1009
- [:#{attribute_names.join(',:')}], args # [:user_name, :password], args
1010
- ) # )
1011
- #
1012
- scoped(:conditions => attributes) # scoped(:conditions => attributes)
1013
- end # end
1039
+ def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args)
1040
+ attributes = Hash[[:#{attribute_names.join(',:')}].zip(args)] # attributes = Hash[[:user_name, :password].zip(args)]
1041
+ #
1042
+ scoped(:conditions => attributes) # scoped(:conditions => attributes)
1043
+ end # end
1014
1044
  METHOD
1015
1045
  send(method_id, *arguments)
1016
1046
  end
@@ -1019,31 +1049,22 @@ module ActiveRecord #:nodoc:
1019
1049
  end
1020
1050
  end
1021
1051
 
1022
- def construct_attributes_from_arguments(attribute_names, arguments)
1023
- attributes = {}
1024
- attribute_names.each_with_index { |name, idx| attributes[name] = arguments[idx] }
1025
- attributes
1026
- end
1027
-
1028
1052
  # Similar in purpose to +expand_hash_conditions_for_aggregates+.
1029
1053
  def expand_attribute_names_for_aggregates(attribute_names)
1030
- expanded_attribute_names = []
1031
- attribute_names.each do |attribute_name|
1054
+ attribute_names.map { |attribute_name|
1032
1055
  unless (aggregation = reflect_on_aggregation(attribute_name.to_sym)).nil?
1033
- aggregate_mapping(aggregation).each do |field_attr, aggregate_attr|
1034
- expanded_attribute_names << field_attr
1056
+ aggregate_mapping(aggregation).map do |field_attr, _|
1057
+ field_attr.to_sym
1035
1058
  end
1036
1059
  else
1037
- expanded_attribute_names << attribute_name
1060
+ attribute_name.to_sym
1038
1061
  end
1039
- end
1040
- expanded_attribute_names
1062
+ }.flatten
1041
1063
  end
1042
1064
 
1043
1065
  def all_attributes_exists?(attribute_names)
1044
- expand_attribute_names_for_aggregates(attribute_names).all? { |name|
1045
- column_methods_hash.include?(name.to_sym)
1046
- }
1066
+ (expand_attribute_names_for_aggregates(attribute_names) -
1067
+ column_methods_hash.keys).empty?
1047
1068
  end
1048
1069
 
1049
1070
  protected
@@ -1065,7 +1086,7 @@ module ActiveRecord #:nodoc:
1065
1086
  # <tt>where</tt>, <tt>includes</tt>, and <tt>joins</tt> operations in <tt>Relation</tt>, which are merged.
1066
1087
  #
1067
1088
  # <tt>joins</tt> operations are uniqued so multiple scopes can join in the same table without table aliasing
1068
- # problems. If you need to join multiple tables, but still want one of the tables to be uniqued, use the
1089
+ # problems. If you need to join multiple tables, but still want one of the tables to be uniqued, use the
1069
1090
  # array of strings format for your joins.
1070
1091
  #
1071
1092
  # class Article < ActiveRecord::Base
@@ -1094,43 +1115,47 @@ module ActiveRecord #:nodoc:
1094
1115
  # end
1095
1116
  #
1096
1117
  # *Note*: the +:find+ scope also has effect on update and deletion methods, like +update_all+ and +delete_all+.
1097
- def with_scope(method_scoping = {}, action = :merge, &block)
1098
- method_scoping = method_scoping.method_scoping if method_scoping.respond_to?(:method_scoping)
1118
+ def with_scope(scope = {}, action = :merge, &block)
1119
+ # If another Active Record class has been passed in, get its current scope
1120
+ scope = scope.current_scope if !scope.is_a?(Relation) && scope.respond_to?(:current_scope)
1121
+
1122
+ previous_scope = self.current_scope
1099
1123
 
1100
- if method_scoping.is_a?(Hash)
1124
+ if scope.is_a?(Hash)
1101
1125
  # Dup first and second level of hash (method and params).
1102
- method_scoping = method_scoping.dup
1103
- method_scoping.each do |method, params|
1104
- method_scoping[method] = params.dup unless params == true
1126
+ scope = scope.dup
1127
+ scope.each do |method, params|
1128
+ scope[method] = params.dup unless params == true
1105
1129
  end
1106
1130
 
1107
- method_scoping.assert_valid_keys([ :find, :create ])
1108
- relation = construct_finder_arel(method_scoping[:find] || {})
1131
+ scope.assert_valid_keys([ :find, :create ])
1132
+ relation = construct_finder_arel(scope[:find] || {})
1133
+ relation.default_scoped = true unless action == :overwrite
1109
1134
 
1110
- if current_scoped_methods && current_scoped_methods.create_with_value && method_scoping[:create]
1135
+ if previous_scope && previous_scope.create_with_value && scope[:create]
1111
1136
  scope_for_create = if action == :merge
1112
- current_scoped_methods.create_with_value.merge(method_scoping[:create])
1137
+ previous_scope.create_with_value.merge(scope[:create])
1113
1138
  else
1114
- method_scoping[:create]
1139
+ scope[:create]
1115
1140
  end
1116
1141
 
1117
1142
  relation = relation.create_with(scope_for_create)
1118
1143
  else
1119
- scope_for_create = method_scoping[:create]
1120
- scope_for_create ||= current_scoped_methods.create_with_value if current_scoped_methods
1144
+ scope_for_create = scope[:create]
1145
+ scope_for_create ||= previous_scope.create_with_value if previous_scope
1121
1146
  relation = relation.create_with(scope_for_create) if scope_for_create
1122
1147
  end
1123
1148
 
1124
- method_scoping = relation
1149
+ scope = relation
1125
1150
  end
1126
1151
 
1127
- method_scoping = current_scoped_methods.merge(method_scoping) if current_scoped_methods && action == :merge
1152
+ scope = previous_scope.merge(scope) if previous_scope && action == :merge
1128
1153
 
1129
- self.scoped_methods << method_scoping
1154
+ self.current_scope = scope
1130
1155
  begin
1131
1156
  yield
1132
1157
  ensure
1133
- self.scoped_methods.pop
1158
+ self.current_scope = previous_scope
1134
1159
  end
1135
1160
  end
1136
1161
 
@@ -1153,33 +1178,80 @@ MSG
1153
1178
  with_scope(method_scoping, :overwrite, &block)
1154
1179
  end
1155
1180
 
1156
- # Sets the default options for the model. The format of the
1157
- # <tt>options</tt> argument is the same as in find.
1181
+ def current_scope #:nodoc:
1182
+ Thread.current[:"#{self}_current_scope"]
1183
+ end
1184
+
1185
+ def current_scope=(scope) #:nodoc:
1186
+ Thread.current[:"#{self}_current_scope"] = scope
1187
+ end
1188
+
1189
+ # Use this macro in your model to set a default scope for all operations on
1190
+ # the model.
1158
1191
  #
1159
- # class Person < ActiveRecord::Base
1160
- # default_scope order('last_name, first_name')
1192
+ # class Article < ActiveRecord::Base
1193
+ # default_scope where(:published => true)
1161
1194
  # end
1162
1195
  #
1163
- # <tt>default_scope</tt> is also applied while creating/building a record. It is not
1196
+ # Article.all # => SELECT * FROM articles WHERE published = true
1197
+ #
1198
+ # The <tt>default_scope</tt> is also applied while creating/building a record. It is not
1164
1199
  # applied while updating a record.
1165
1200
  #
1201
+ # Article.new.published # => true
1202
+ # Article.create.published # => true
1203
+ #
1204
+ # You can also use <tt>default_scope</tt> with a block, in order to have it lazily evaluated:
1205
+ #
1206
+ # class Article < ActiveRecord::Base
1207
+ # default_scope { where(:published_at => Time.now - 1.week) }
1208
+ # end
1209
+ #
1210
+ # (You can also pass any object which responds to <tt>call</tt> to the <tt>default_scope</tt>
1211
+ # macro, and it will be called when building the default scope.)
1212
+ #
1213
+ # If you use multiple <tt>default_scope</tt> declarations in your model then they will
1214
+ # be merged together:
1215
+ #
1166
1216
  # class Article < ActiveRecord::Base
1167
1217
  # default_scope where(:published => true)
1218
+ # default_scope where(:rating => 'G')
1168
1219
  # end
1169
1220
  #
1170
- # Article.new.published # => true
1171
- # Article.create.published # => true
1172
- def default_scope(options = {})
1173
- reset_scoped_methods
1174
- self.default_scoping << construct_finder_arel(options, default_scoping.pop)
1175
- end
1176
-
1177
- def current_scoped_methods #:nodoc:
1178
- scoped_methods.last
1221
+ # Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G'
1222
+ #
1223
+ # This is also the case with inheritance and module includes where the parent or module
1224
+ # defines a <tt>default_scope</tt> and the child or including class defines a second one.
1225
+ #
1226
+ # If you need to do more complex things with a default scope, you can alternatively
1227
+ # define it as a class method:
1228
+ #
1229
+ # class Article < ActiveRecord::Base
1230
+ # def self.default_scope
1231
+ # # Should return a scope, you can call 'super' here etc.
1232
+ # end
1233
+ # end
1234
+ def default_scope(scope = {})
1235
+ scope = Proc.new if block_given?
1236
+ self.default_scopes = default_scopes + [scope]
1179
1237
  end
1180
1238
 
1181
- def reset_scoped_methods #:nodoc:
1182
- Thread.current[:"#{self}_scoped_methods"] = nil
1239
+ def build_default_scope #:nodoc:
1240
+ if method(:default_scope).owner != Base.singleton_class
1241
+ # Use relation.scoping to ensure we ignore whatever the current value of
1242
+ # self.current_scope may be.
1243
+ relation.scoping { default_scope }
1244
+ elsif default_scopes.any?
1245
+ default_scopes.inject(relation) do |default_scope, scope|
1246
+ if scope.is_a?(Hash)
1247
+ default_scope.apply_finder_options(scope)
1248
+ elsif !scope.is_a?(Relation) && scope.respond_to?(:call)
1249
+ default_scope.merge(scope.call)
1250
+ else
1251
+ default_scope.merge(scope)
1252
+ end
1253
+ end
1254
+ end
1183
1255
  end
1184
1256
 
1185
1257
  # Returns the class type of the record using the current module as a prefix. So descendants of
@@ -1301,9 +1373,11 @@ MSG
1301
1373
  def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
1302
1374
  attrs = expand_hash_conditions_for_aggregates(attrs)
1303
1375
 
1304
- table = Arel::Table.new(self.table_name, :engine => arel_engine, :as => default_table_name)
1305
- builder = PredicateBuilder.new(arel_engine)
1306
- builder.build_from_hash(attrs, table).map{ |b| b.to_sql }.join(' AND ')
1376
+ table = Arel::Table.new(table_name).alias(default_table_name)
1377
+ viz = Arel::Visitors.for(arel_engine)
1378
+ PredicateBuilder.build_from_hash(arel_engine, attrs, table).map { |b|
1379
+ viz.accept b
1380
+ }.join(' AND ')
1307
1381
  end
1308
1382
  alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
1309
1383
 
@@ -1316,12 +1390,12 @@ MSG
1316
1390
  end.join(', ')
1317
1391
  end
1318
1392
 
1319
- # Accepts an array of conditions. The array has each value
1393
+ # Accepts an array of conditions. The array has each value
1320
1394
  # sanitized and interpolated into the SQL statement.
1321
1395
  # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
1322
1396
  def sanitize_sql_array(ary)
1323
1397
  statement, *values = ary
1324
- if values.first.is_a?(Hash) and statement =~ /:\w+/
1398
+ if values.first.is_a?(Hash) && statement =~ /:\w+/
1325
1399
  replace_named_bind_variables(statement, values.first)
1326
1400
  elsif statement.include?('?')
1327
1401
  replace_bind_variables(statement, values)
@@ -1400,8 +1474,23 @@ MSG
1400
1474
  # attributes but not yet saved (pass a hash with key names matching the associated table column names).
1401
1475
  # In both instances, valid attribute keys are determined by the column names of the associated table --
1402
1476
  # hence you can't have attributes that aren't part of the table columns.
1403
- def initialize(attributes = nil)
1477
+ #
1478
+ # +initialize+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options
1479
+ # in the +options+ parameter.
1480
+ #
1481
+ # ==== Examples
1482
+ # # Instantiates a single new object
1483
+ # User.new(:first_name => 'Jamie')
1484
+ #
1485
+ # # Instantiates a single new object using the :admin mass-assignment security scope
1486
+ # User.new({ :first_name => 'Jamie', :is_admin => true }, :as => :admin)
1487
+ #
1488
+ # # Instantiates a single new object bypassing mass-assignment security
1489
+ # User.new({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true)
1490
+ def initialize(attributes = nil, options = {})
1404
1491
  @attributes = attributes_from_column_definition
1492
+ @association_cache = {}
1493
+ @aggregation_cache = {}
1405
1494
  @attributes_cache = {}
1406
1495
  @new_record = true
1407
1496
  @readonly = false
@@ -1411,41 +1500,35 @@ MSG
1411
1500
  @changed_attributes = {}
1412
1501
 
1413
1502
  ensure_proper_type
1503
+ set_serialized_attributes
1414
1504
 
1415
1505
  populate_with_current_scope_attributes
1416
- self.attributes = attributes unless attributes.nil?
1506
+
1507
+ assign_attributes(attributes, options) if attributes
1417
1508
 
1418
1509
  result = yield self if block_given?
1419
- _run_initialize_callbacks
1510
+ run_callbacks :initialize
1420
1511
  result
1421
1512
  end
1422
1513
 
1423
- # Cloned objects have no id assigned and are treated as new records. Note that this is a "shallow" clone
1424
- # as it copies the object's attributes only, not its associations. The extent of a "deep" clone is
1425
- # application specific and is therefore left to the application to implement according to its need.
1426
- def initialize_copy(other)
1427
- _run_after_initialize_callbacks if respond_to?(:_run_after_initialize_callbacks)
1428
- cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
1429
- cloned_attributes.delete(self.class.primary_key)
1430
-
1431
- @attributes = cloned_attributes
1432
-
1433
- @changed_attributes = {}
1434
- attributes_from_column_definition.each do |attr, orig_value|
1435
- @changed_attributes[attr] = orig_value if field_changed?(attr, orig_value, @attributes[attr])
1436
- end
1437
-
1438
- clear_aggregation_cache
1439
- clear_association_cache
1440
- @attributes_cache = {}
1441
- @new_record = true
1442
- ensure_proper_type
1443
-
1444
- populate_with_current_scope_attributes
1514
+ # Populate +coder+ with attributes about this record that should be
1515
+ # serialized. The structure of +coder+ defined in this method is
1516
+ # guaranteed to match the structure of +coder+ passed to the +init_with+
1517
+ # method.
1518
+ #
1519
+ # Example:
1520
+ #
1521
+ # class Post < ActiveRecord::Base
1522
+ # end
1523
+ # coder = {}
1524
+ # Post.new.encode_with(coder)
1525
+ # coder # => { 'id' => nil, ... }
1526
+ def encode_with(coder)
1527
+ coder['attributes'] = attributes
1445
1528
  end
1446
1529
 
1447
- # Initialize an empty model object from +coder+. +coder+ must contain
1448
- # the attributes necessary for initializing an empty model object. For
1530
+ # Initialize an empty model object from +coder+. +coder+ must contain
1531
+ # the attributes necessary for initializing an empty model object. For
1449
1532
  # example:
1450
1533
  #
1451
1534
  # class Post < ActiveRecord::Base
@@ -1456,10 +1539,28 @@ MSG
1456
1539
  # post.title # => 'hello world'
1457
1540
  def init_with(coder)
1458
1541
  @attributes = coder['attributes']
1542
+
1543
+ set_serialized_attributes
1544
+
1459
1545
  @attributes_cache, @previously_changed, @changed_attributes = {}, {}, {}
1460
- @new_record = @readonly = @destroyed = @marked_for_destruction = false
1461
- _run_find_callbacks
1462
- _run_initialize_callbacks
1546
+ @association_cache = {}
1547
+ @aggregation_cache = {}
1548
+ @readonly = @destroyed = @marked_for_destruction = false
1549
+ @new_record = false
1550
+ run_callbacks :find
1551
+ run_callbacks :initialize
1552
+
1553
+ self
1554
+ end
1555
+
1556
+ # Specifies how the record is dumped by +Marshal+.
1557
+ #
1558
+ # +_dump+ emits a marshalled hash which has been passed to +encode_with+. Override this
1559
+ # method if you require more complex marshalling.
1560
+ def _dump(level)
1561
+ dump = {}
1562
+ encode_with(dump)
1563
+ Marshal.dump(dump)
1463
1564
  end
1464
1565
 
1465
1566
  # Returns a String, which Action Pack uses for constructing an URL to this
@@ -1501,8 +1602,7 @@ MSG
1501
1602
  when new_record?
1502
1603
  "#{self.class.model_name.cache_key}/new"
1503
1604
  when timestamp = self[:updated_at]
1504
- timestamp = timestamp.utc.to_s(:number)
1505
- "#{self.class.model_name.cache_key}/#{id}-#{timestamp}"
1605
+ "#{self.class.model_name.cache_key}/#{id}-#{timestamp.to_s(:number)}"
1506
1606
  else
1507
1607
  "#{self.class.model_name.cache_key}/#{id}"
1508
1608
  end
@@ -1517,32 +1617,19 @@ MSG
1517
1617
  @attributes.has_key?(attr_name.to_s)
1518
1618
  end
1519
1619
 
1520
- # Returns an array of names for the attributes available on this object sorted alphabetically.
1620
+ # Returns an array of names for the attributes available on this object.
1521
1621
  def attribute_names
1522
- @attributes.keys.sort
1523
- end
1524
-
1525
- # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
1526
- # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
1527
- # (Alias for the protected read_attribute method).
1528
- def [](attr_name)
1529
- read_attribute(attr_name)
1530
- end
1531
-
1532
- # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
1533
- # (Alias for the protected write_attribute method).
1534
- def []=(attr_name, value)
1535
- write_attribute(attr_name, value)
1622
+ @attributes.keys
1536
1623
  end
1537
1624
 
1538
1625
  # Allows you to set all the attributes at once by passing in a hash with keys
1539
1626
  # matching the attribute names (which again matches the column names).
1540
1627
  #
1541
- # If +guard_protected_attributes+ is true (the default), then sensitive
1542
- # attributes can be protected from this form of mass-assignment by using
1543
- # the +attr_protected+ macro. Or you can alternatively specify which
1544
- # attributes *can* be accessed with the +attr_accessible+ macro. Then all the
1545
- # attributes not included in that won't be allowed to be mass-assigned.
1628
+ # If any attributes are protected by either +attr_protected+ or
1629
+ # +attr_accessible+ then only settable attributes will be assigned.
1630
+ #
1631
+ # The +guard_protected_attributes+ argument is now deprecated, use
1632
+ # the +assign_attributes+ method if you want to bypass mass-assignment security.
1546
1633
  #
1547
1634
  # class User < ActiveRecord::Base
1548
1635
  # attr_protected :is_admin
@@ -1552,21 +1639,67 @@ MSG
1552
1639
  # user.attributes = { :username => 'Phusion', :is_admin => true }
1553
1640
  # user.username # => "Phusion"
1554
1641
  # user.is_admin? # => false
1642
+ def attributes=(new_attributes, guard_protected_attributes = nil)
1643
+ unless guard_protected_attributes.nil?
1644
+ message = "the use of 'guard_protected_attributes' will be removed from the next major release of rails, " +
1645
+ "if you want to bypass mass-assignment security then look into using assign_attributes"
1646
+ ActiveSupport::Deprecation.warn(message)
1647
+ end
1648
+
1649
+ return unless new_attributes.is_a?(Hash)
1650
+
1651
+ guard_protected_attributes ||= true
1652
+ if guard_protected_attributes
1653
+ assign_attributes(new_attributes)
1654
+ else
1655
+ assign_attributes(new_attributes, :without_protection => true)
1656
+ end
1657
+ end
1658
+
1659
+ # Allows you to set all the attributes for a particular mass-assignment
1660
+ # security scope by passing in a hash of attributes with keys matching
1661
+ # the attribute names (which again matches the column names) and the scope
1662
+ # name using the :as option.
1663
+ #
1664
+ # To bypass mass-assignment security you can use the :without_protection => true
1665
+ # option.
1666
+ #
1667
+ # class User < ActiveRecord::Base
1668
+ # attr_accessible :name
1669
+ # attr_accessible :name, :is_admin, :as => :admin
1670
+ # end
1671
+ #
1672
+ # user = User.new
1673
+ # user.assign_attributes({ :name => 'Josh', :is_admin => true })
1674
+ # user.name # => "Josh"
1675
+ # user.is_admin? # => false
1555
1676
  #
1556
- # user.send(:attributes=, { :username => 'Phusion', :is_admin => true }, false)
1677
+ # user = User.new
1678
+ # user.assign_attributes({ :name => 'Josh', :is_admin => true }, :as => :admin)
1679
+ # user.name # => "Josh"
1557
1680
  # user.is_admin? # => true
1558
- def attributes=(new_attributes, guard_protected_attributes = true)
1559
- return unless new_attributes.is_a?(Hash)
1681
+ #
1682
+ # user = User.new
1683
+ # user.assign_attributes({ :name => 'Josh', :is_admin => true }, :without_protection => true)
1684
+ # user.name # => "Josh"
1685
+ # user.is_admin? # => true
1686
+ def assign_attributes(new_attributes, options = {})
1560
1687
  attributes = new_attributes.stringify_keys
1688
+ scope = options[:as] || :default
1561
1689
 
1562
1690
  multi_parameter_attributes = []
1563
- attributes = sanitize_for_mass_assignment(attributes) if guard_protected_attributes
1691
+
1692
+ unless options[:without_protection]
1693
+ attributes = sanitize_for_mass_assignment(attributes, scope)
1694
+ end
1564
1695
 
1565
1696
  attributes.each do |k, v|
1566
1697
  if k.include?("(")
1567
1698
  multi_parameter_attributes << [ k, v ]
1699
+ elsif respond_to?("#{k}=")
1700
+ send("#{k}=", v)
1568
1701
  else
1569
- respond_to?(:"#{k}=") ? send(:"#{k}=", v) : raise(UnknownAttributeError, "unknown attribute: #{k}")
1702
+ raise(UnknownAttributeError, "unknown attribute: #{k}")
1570
1703
  end
1571
1704
  end
1572
1705
 
@@ -1575,9 +1708,7 @@ MSG
1575
1708
 
1576
1709
  # Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
1577
1710
  def attributes
1578
- attrs = {}
1579
- attribute_names.each { |name| attrs[name] = read_attribute(name) }
1580
- attrs
1711
+ Hash[@attributes.map { |name, _| [name, read_attribute(name)] }]
1581
1712
  end
1582
1713
 
1583
1714
  # Returns an <tt>#inspect</tt>-like string for the value of the
@@ -1608,8 +1739,7 @@ MSG
1608
1739
  # Returns true if the specified +attribute+ has been set by the user or by a database load and is neither
1609
1740
  # nil nor empty? (the latter only applies to objects that respond to empty?, most notably Strings).
1610
1741
  def attribute_present?(attribute)
1611
- value = read_attribute(attribute)
1612
- !value.blank?
1742
+ !_read_attribute(attribute).blank?
1613
1743
  end
1614
1744
 
1615
1745
  # Returns the column object for the named attribute.
@@ -1628,13 +1758,14 @@ MSG
1628
1758
  # models are still comparable.
1629
1759
  def ==(comparison_object)
1630
1760
  comparison_object.equal?(self) ||
1631
- (comparison_object.instance_of?(self.class) &&
1632
- comparison_object.id == id && !comparison_object.new_record?)
1761
+ comparison_object.instance_of?(self.class) &&
1762
+ id.present? &&
1763
+ comparison_object.id == id
1633
1764
  end
1634
1765
 
1635
1766
  # Delegates to ==
1636
1767
  def eql?(comparison_object)
1637
- self == (comparison_object)
1768
+ self == comparison_object
1638
1769
  end
1639
1770
 
1640
1771
  # Delegates to id in order to allow two records of the same type and id to work with something like:
@@ -1653,11 +1784,42 @@ MSG
1653
1784
  @attributes.frozen?
1654
1785
  end
1655
1786
 
1656
- # Returns duplicated record with unfreezed attributes.
1657
- def dup
1658
- obj = super
1659
- obj.instance_variable_set('@attributes', @attributes.dup)
1660
- obj
1787
+ # Backport dup from 1.9 so that initialize_dup() gets called
1788
+ unless Object.respond_to?(:initialize_dup)
1789
+ def dup # :nodoc:
1790
+ copy = super
1791
+ copy.initialize_dup(self)
1792
+ copy
1793
+ end
1794
+ end
1795
+
1796
+ # Duped objects have no id assigned and are treated as new records. Note
1797
+ # that this is a "shallow" copy as it copies the object's attributes
1798
+ # only, not its associations. The extent of a "deep" copy is application
1799
+ # specific and is therefore left to the application to implement according
1800
+ # to its need.
1801
+ # The dup method does not preserve the timestamps (created|updated)_(at|on).
1802
+ def initialize_dup(other)
1803
+ cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
1804
+ cloned_attributes.delete(self.class.primary_key)
1805
+
1806
+ @attributes = cloned_attributes
1807
+
1808
+ _run_after_initialize_callbacks if respond_to?(:_run_after_initialize_callbacks)
1809
+
1810
+ @changed_attributes = {}
1811
+ attributes_from_column_definition.each do |attr, orig_value|
1812
+ @changed_attributes[attr] = orig_value if field_changed?(attr, orig_value, @attributes[attr])
1813
+ end
1814
+
1815
+ @aggregation_cache = {}
1816
+ @association_cache = {}
1817
+ @attributes_cache = {}
1818
+ @new_record = true
1819
+
1820
+ ensure_proper_type
1821
+ populate_with_current_scope_attributes
1822
+ clear_timestamp_attributes
1661
1823
  end
1662
1824
 
1663
1825
  # Returns +true+ if the record is read only. Records loaded through joins with piggy-back
@@ -1674,7 +1836,7 @@ MSG
1674
1836
  # Returns the contents of the record as a nicely formatted string.
1675
1837
  def inspect
1676
1838
  attributes_as_nice_string = self.class.column_names.collect { |name|
1677
- if has_attribute?(name) || new_record?
1839
+ if has_attribute?(name)
1678
1840
  "#{name}: #{attribute_for_inspect(name)}"
1679
1841
  end
1680
1842
  }.compact.join(", ")
@@ -1698,6 +1860,13 @@ MSG
1698
1860
 
1699
1861
  private
1700
1862
 
1863
+ def set_serialized_attributes
1864
+ (@attributes.keys & self.class.serialized_attributes.keys).each do |key|
1865
+ coder = self.class.serialized_attributes[key]
1866
+ @attributes[key] = coder.load @attributes[key]
1867
+ end
1868
+ end
1869
+
1701
1870
  # Sets the attribute used for single table inheritance to this class name if this is not the
1702
1871
  # ActiveRecord::Base descendant.
1703
1872
  # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to
@@ -1719,17 +1888,25 @@ MSG
1719
1888
  # Returns a copy of the attributes hash where all the values have been safely quoted for use in
1720
1889
  # an Arel insert/update method.
1721
1890
  def arel_attributes_values(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys)
1722
- attrs = {}
1891
+ attrs = {}
1892
+ klass = self.class
1893
+ arel_table = klass.arel_table
1894
+
1723
1895
  attribute_names.each do |name|
1724
1896
  if (column = column_for_attribute(name)) && (include_primary_key || !column.primary)
1725
1897
 
1726
1898
  if include_readonly_attributes || (!include_readonly_attributes && !self.class.readonly_attributes.include?(name))
1727
- value = read_attribute(name)
1728
1899
 
1729
- if !value.nil? && self.class.serialized_attributes.key?(name)
1730
- value = YAML.dump value
1731
- end
1732
- attrs[self.class.arel_table[name]] = value
1900
+ value = if coder = klass.serialized_attributes[name]
1901
+ coder.dump @attributes[name]
1902
+ else
1903
+ # FIXME: we need @attributes to be used consistently.
1904
+ # If the values stored in @attributes were already type
1905
+ # casted, this code could be simplified
1906
+ read_attribute(name)
1907
+ end
1908
+
1909
+ attrs[arel_table[name]] = value
1733
1910
  end
1734
1911
  end
1735
1912
  end
@@ -1741,26 +1918,6 @@ MSG
1741
1918
  self.class.connection.quote(value, column)
1742
1919
  end
1743
1920
 
1744
- def interpolate_and_sanitize_sql(sql, record = nil, sanitize_klass = self.class)
1745
- sanitized = sanitize_klass.send(:sanitize_sql, sql)
1746
- interpolate_sanitized_sql(sanitized, record, sanitize_klass)
1747
- end
1748
-
1749
- def interpolate_sanitized_sql(sanitized, record = nil, sanitize_klass = self.class)
1750
- if sanitized =~ /\#\{.*\}/
1751
- ActiveSupport::Deprecation.warn(
1752
- 'String-based interpolation of association conditions is deprecated. Please use a ' \
1753
- 'proc instead. So, for example, has_many :older_friends, :conditions => \'age > #{age}\' ' \
1754
- 'should be changed to has_many :older_friends, :conditions => proc { "age > #{age}" }.'
1755
- )
1756
- instance_eval("%@#{sanitized.gsub('@', '\@')}@", __FILE__, __LINE__)
1757
- elsif sanitized.respond_to?(:to_proc)
1758
- sanitize_klass.send(:sanitize_sql, instance_exec(record, &sanitized))
1759
- else
1760
- sanitized
1761
- end
1762
- end
1763
-
1764
1921
  # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
1765
1922
  # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
1766
1923
  # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
@@ -1867,15 +2024,17 @@ MSG
1867
2024
  end
1868
2025
  end
1869
2026
 
1870
- def object_from_yaml(string)
1871
- return string unless string.is_a?(String) && string =~ /^---/
1872
- YAML::load(string) rescue string
2027
+ def populate_with_current_scope_attributes
2028
+ self.class.scoped.scope_for_create.each do |att,value|
2029
+ respond_to?("#{att}=") && send("#{att}=", value)
2030
+ end
1873
2031
  end
1874
2032
 
1875
- def populate_with_current_scope_attributes
1876
- if scope = self.class.send(:current_scoped_methods)
1877
- create_with = scope.scope_for_create
1878
- create_with.each { |att,value| self.respond_to?(:"#{att}=") && self.send("#{att}=", value) } if create_with
2033
+ # Clear attributes and changed_attributes
2034
+ def clear_timestamp_attributes
2035
+ all_timestamp_attributes_in_model.each do |attribute_name|
2036
+ self[attribute_name] = nil
2037
+ changed_attributes.delete(attribute_name)
1879
2038
  end
1880
2039
  end
1881
2040
  end
@@ -1898,7 +2057,9 @@ MSG
1898
2057
  include AttributeMethods::Dirty
1899
2058
  include ActiveModel::MassAssignmentSecurity
1900
2059
  include Callbacks, ActiveModel::Observing, Timestamp
1901
- include Associations, AssociationPreload, NamedScope
2060
+ include Associations, NamedScope
2061
+ include IdentityMap
2062
+ include ActiveModel::SecurePassword
1902
2063
 
1903
2064
  # AutosaveAssociation needs to be included before Transactions, because we want
1904
2065
  # #save_with_autosave_associations to be wrapped inside a transaction.
@@ -1906,6 +2067,17 @@ MSG
1906
2067
  include Aggregations, Transactions, Reflection, Serialization
1907
2068
 
1908
2069
  NilClass.add_whiner(self) if NilClass.respond_to?(:add_whiner)
2070
+
2071
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
2072
+ # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
2073
+ # (Alias for the protected read_attribute method).
2074
+ alias [] read_attribute
2075
+
2076
+ # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
2077
+ # (Alias for the protected write_attribute method).
2078
+ alias []= write_attribute
2079
+
2080
+ public :[], :[]=
1909
2081
  end
1910
2082
  end
1911
2083