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
@@ -0,0 +1,103 @@
1
+ module ActiveRecord
2
+ class Migration
3
+ # ActiveRecord::Migration::CommandRecorder records commands done during
4
+ # a migration and knows how to reverse those commands. The CommandRecorder
5
+ # knows how to invert the following commands:
6
+ #
7
+ # * add_column
8
+ # * add_index
9
+ # * add_timestamp
10
+ # * create_table
11
+ # * remove_timestamps
12
+ # * rename_column
13
+ # * rename_index
14
+ # * rename_table
15
+ class CommandRecorder
16
+ attr_accessor :commands, :delegate
17
+
18
+ def initialize(delegate = nil)
19
+ @commands = []
20
+ @delegate = delegate
21
+ end
22
+
23
+ # record +command+. +command+ should be a method name and arguments.
24
+ # For example:
25
+ #
26
+ # recorder.record(:method_name, [:arg1, arg2])
27
+ def record(*command)
28
+ @commands << command
29
+ end
30
+
31
+ # Returns a list that represents commands that are the inverse of the
32
+ # commands stored in +commands+. For example:
33
+ #
34
+ # recorder.record(:rename_table, [:old, :new])
35
+ # recorder.inverse # => [:rename_table, [:new, :old]]
36
+ #
37
+ # This method will raise an IrreversibleMigration exception if it cannot
38
+ # invert the +commands+.
39
+ def inverse
40
+ @commands.reverse.map { |name, args|
41
+ method = :"invert_#{name}"
42
+ raise IrreversibleMigration unless respond_to?(method, true)
43
+ send(method, args)
44
+ }
45
+ end
46
+
47
+ def respond_to?(*args) # :nodoc:
48
+ super || delegate.respond_to?(*args)
49
+ end
50
+
51
+ [:create_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column, :change_column_default].each do |method|
52
+ class_eval <<-EOV, __FILE__, __LINE__ + 1
53
+ def #{method}(*args)
54
+ record(:"#{method}", args)
55
+ end
56
+ EOV
57
+ end
58
+
59
+ private
60
+
61
+ def invert_create_table(args)
62
+ [:drop_table, args]
63
+ end
64
+
65
+ def invert_rename_table(args)
66
+ [:rename_table, args.reverse]
67
+ end
68
+
69
+ def invert_add_column(args)
70
+ [:remove_column, args.first(2)]
71
+ end
72
+
73
+ def invert_rename_index(args)
74
+ [:rename_index, args.reverse]
75
+ end
76
+
77
+ def invert_rename_column(args)
78
+ [:rename_column, [args.first] + args.last(2).reverse]
79
+ end
80
+
81
+ def invert_add_index(args)
82
+ table, columns, _ = *args
83
+ [:remove_index, [table, {:column => columns}]]
84
+ end
85
+
86
+ def invert_remove_timestamps(args)
87
+ [:add_timestamps, args]
88
+ end
89
+
90
+ def invert_add_timestamps(args)
91
+ [:remove_timestamps, args]
92
+ end
93
+
94
+ # Forwards any missing method call to the \target.
95
+ def method_missing(method, *args, &block)
96
+ @delegate.send(method, *args, &block)
97
+ rescue NoMethodError => e
98
+ raise e, e.message.sub(/ for #<.*$/, " via proxy for #{@delegate}")
99
+ end
100
+
101
+ end
102
+ end
103
+ end
@@ -2,6 +2,7 @@ require 'active_support/core_ext/array'
2
2
  require 'active_support/core_ext/hash/except'
3
3
  require 'active_support/core_ext/kernel/singleton_class'
4
4
  require 'active_support/core_ext/object/blank'
5
+ require 'active_support/core_ext/class/attribute'
5
6
 
6
7
  module ActiveRecord
7
8
  # = Active Record Named \Scopes
@@ -29,14 +30,16 @@ module ActiveRecord
29
30
  if options
30
31
  scoped.apply_finder_options(options)
31
32
  else
32
- current_scoped_methods ? relation.merge(current_scoped_methods) : relation.clone
33
+ if current_scope
34
+ current_scope.clone
35
+ else
36
+ scope = relation.clone
37
+ scope.default_scoped = true
38
+ scope
39
+ end
33
40
  end
34
41
  end
35
42
 
36
- def scopes
37
- read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {})
38
- end
39
-
40
43
  # Adds a class method for retrieving and querying objects. A \scope represents a narrowing of a database query,
41
44
  # such as <tt>where(:color => :red).select('shirts.*').includes(:washing_instructions)</tt>.
42
45
  #
@@ -48,6 +51,14 @@ module ActiveRecord
48
51
  # The above calls to <tt>scope</tt> define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red,
49
52
  # in effect, represents the query <tt>Shirt.where(:color => 'red')</tt>.
50
53
  #
54
+ # Note that this is simply 'syntactic sugar' for defining an actual class method:
55
+ #
56
+ # class Shirt < ActiveRecord::Base
57
+ # def self.red
58
+ # where(:color => 'red')
59
+ # end
60
+ # end
61
+ #
51
62
  # Unlike <tt>Shirt.find(...)</tt>, however, the object returned by Shirt.red is not an Array; it
52
63
  # resembles the association object constructed by a <tt>has_many</tt> declaration. For instance,
53
64
  # you can invoke <tt>Shirt.red.first</tt>, <tt>Shirt.red.count</tt>, <tt>Shirt.red.where(:size => 'small')</tt>.
@@ -74,11 +85,31 @@ module ActiveRecord
74
85
  # Named \scopes can also be procedural:
75
86
  #
76
87
  # class Shirt < ActiveRecord::Base
77
- # scope :colored, lambda {|color| where(:color => color) }
88
+ # scope :colored, lambda { |color| where(:color => color) }
78
89
  # end
79
90
  #
80
91
  # In this example, <tt>Shirt.colored('puce')</tt> finds all puce shirts.
81
92
  #
93
+ # On Ruby 1.9 you can use the 'stabby lambda' syntax:
94
+ #
95
+ # scope :colored, ->(color) { where(:color => color) }
96
+ #
97
+ # Note that scopes defined with \scope will be evaluated when they are defined, rather than
98
+ # when they are used. For example, the following would be incorrect:
99
+ #
100
+ # class Post < ActiveRecord::Base
101
+ # scope :recent, where('published_at >= ?', Time.now - 1.week)
102
+ # end
103
+ #
104
+ # The example above would be 'frozen' to the <tt>Time.now</tt> value when the <tt>Post</tt>
105
+ # class was defined, and so the resultant SQL query would always be the same. The correct
106
+ # way to do this would be via a lambda, which will re-evaluate the scope each time
107
+ # it is called:
108
+ #
109
+ # class Post < ActiveRecord::Base
110
+ # scope :recent, lambda { where('published_at >= ?', Time.now - 1.week) }
111
+ # end
112
+ #
82
113
  # Named \scopes can also have extensions, just as with <tt>has_many</tt> declarations:
83
114
  #
84
115
  # class Shirt < ActiveRecord::Base
@@ -97,38 +128,50 @@ module ActiveRecord
97
128
  #
98
129
  # Article.published.new.published # => true
99
130
  # Article.published.create.published # => true
100
- def scope(name, scope_options = {}, &block)
131
+ #
132
+ # Class methods on your model are automatically available
133
+ # on scopes. Assuming the following setup:
134
+ #
135
+ # class Article < ActiveRecord::Base
136
+ # scope :published, where(:published => true)
137
+ # scope :featured, where(:featured => true)
138
+ #
139
+ # def self.latest_article
140
+ # order('published_at desc').first
141
+ # end
142
+ #
143
+ # def self.titles
144
+ # map(&:title)
145
+ # end
146
+ #
147
+ # end
148
+ #
149
+ # We are able to call the methods like this:
150
+ #
151
+ # Article.published.featured.latest_article
152
+ # Article.featured.titles
153
+
154
+ def scope(name, scope_options = {})
101
155
  name = name.to_sym
102
156
  valid_scope_name?(name)
157
+ extension = Module.new(&Proc.new) if block_given?
103
158
 
104
- extension = Module.new(&block) if block_given?
105
-
106
- scopes[name] = lambda do |*args|
107
- options = scope_options.is_a?(Proc) ? scope_options.call(*args) : scope_options
159
+ scope_proc = lambda do |*args|
160
+ options = scope_options.respond_to?(:call) ? scope_options.call(*args) : scope_options
161
+ options = scoped.apply_finder_options(options) if options.is_a?(Hash)
108
162
 
109
- relation = if options.is_a?(Hash)
110
- scoped.apply_finder_options(options)
111
- elsif options
112
- scoped.merge(options)
113
- else
114
- scoped
115
- end
163
+ relation = scoped.merge(options)
116
164
 
117
165
  extension ? relation.extending(extension) : relation
118
166
  end
119
167
 
120
- singleton_class.send(:redefine_method, name, &scopes[name])
121
- end
122
-
123
- def named_scope(*args, &block)
124
- ActiveSupport::Deprecation.warn("Base.named_scope has been deprecated, please use Base.scope instead", caller)
125
- scope(*args, &block)
168
+ singleton_class.send(:redefine_method, name, &scope_proc)
126
169
  end
127
170
 
128
171
  protected
129
172
 
130
173
  def valid_scope_name?(name)
131
- if !scopes[name] && respond_to?(name, true)
174
+ if respond_to?(name, true)
132
175
  logger.warn "Creating scope :#{name}. " \
133
176
  "Overwriting existing method #{self.name}.#{name}."
134
177
  end
@@ -2,6 +2,7 @@ require 'active_support/core_ext/hash/except'
2
2
  require 'active_support/core_ext/object/try'
3
3
  require 'active_support/core_ext/object/blank'
4
4
  require 'active_support/core_ext/hash/indifferent_access'
5
+ require 'active_support/core_ext/class/attribute'
5
6
 
6
7
  module ActiveRecord
7
8
  module NestedAttributes #:nodoc:
@@ -11,7 +12,7 @@ module ActiveRecord
11
12
  extend ActiveSupport::Concern
12
13
 
13
14
  included do
14
- class_inheritable_accessor :nested_attributes_options, :instance_writer => false
15
+ class_attribute :nested_attributes_options, :instance_writer => false
15
16
  self.nested_attributes_options = {}
16
17
  end
17
18
 
@@ -190,6 +191,34 @@ module ActiveRecord
190
191
  # destruction, are saved and destroyed automatically and atomically when
191
192
  # the parent model is saved. This happens inside the transaction initiated
192
193
  # by the parents save method. See ActiveRecord::AutosaveAssociation.
194
+ #
195
+ # === Using with attr_accessible
196
+ #
197
+ # The use of <tt>attr_accessible</tt> can interfere with nested attributes
198
+ # if you're not careful. For example, if the <tt>Member</tt> model above
199
+ # was using <tt>attr_accessible</tt> like this:
200
+ #
201
+ # attr_accessible :name
202
+ #
203
+ # You would need to modify it to look like this:
204
+ #
205
+ # attr_accessible :name, :posts_attributes
206
+ #
207
+ # === Validating the presence of a parent model
208
+ #
209
+ # If you want to validate that a child record is associated with a parent
210
+ # record, you can use <tt>validates_presence_of</tt> and
211
+ # <tt>inverse_of</tt> as this example illustrates:
212
+ #
213
+ # class Member < ActiveRecord::Base
214
+ # has_many :posts, :inverse_of => :member
215
+ # accepts_nested_attributes_for :posts
216
+ # end
217
+ #
218
+ # class Post < ActiveRecord::Base
219
+ # belongs_to :member, :inverse_of => :posts
220
+ # validates_presence_of :member
221
+ # end
193
222
  module ClassMethods
194
223
  REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |_, value| value.blank? } }
195
224
 
@@ -240,7 +269,11 @@ module ActiveRecord
240
269
  if reflection = reflect_on_association(association_name)
241
270
  reflection.options[:autosave] = true
242
271
  add_autosave_association_callbacks(reflection)
272
+
273
+ nested_attributes_options = self.nested_attributes_options.dup
243
274
  nested_attributes_options[association_name.to_sym] = options
275
+ self.nested_attributes_options = nested_attributes_options
276
+
244
277
  type = (reflection.collection? ? :collection : :one_to_one)
245
278
 
246
279
  # def pirate_attributes=(attributes)
@@ -287,15 +320,14 @@ module ActiveRecord
287
320
  # update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
288
321
  # then the existing record will be marked for destruction.
289
322
  def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
290
- options = nested_attributes_options[association_name]
323
+ options = self.nested_attributes_options[association_name]
291
324
  attributes = attributes.with_indifferent_access
292
- check_existing_record = (options[:update_only] || !attributes['id'].blank?)
293
325
 
294
- if check_existing_record && (record = send(association_name)) &&
326
+ if (options[:update_only] || !attributes['id'].blank?) && (record = send(association_name)) &&
295
327
  (options[:update_only] || record.id.to_s == attributes['id'].to_s)
296
328
  assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes)
297
329
 
298
- elsif !attributes['id'].blank?
330
+ elsif attributes['id'].present?
299
331
  raise_nested_attributes_record_not_found(association_name, attributes['id'])
300
332
 
301
333
  elsif !reject_new_record?(association_name, attributes)
@@ -336,7 +368,7 @@ module ActiveRecord
336
368
  # { :id => '2', :_destroy => true }
337
369
  # ])
338
370
  def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
339
- options = nested_attributes_options[association_name]
371
+ options = self.nested_attributes_options[association_name]
340
372
 
341
373
  unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
342
374
  raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
@@ -355,13 +387,13 @@ module ActiveRecord
355
387
  end
356
388
  end
357
389
 
358
- association = send(association_name)
390
+ association = association(association_name)
359
391
 
360
392
  existing_records = if association.loaded?
361
- association.to_a
393
+ association.target
362
394
  else
363
395
  attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact
364
- attribute_ids.present? ? association.all(:conditions => {association.primary_key => attribute_ids}) : []
396
+ attribute_ids.empty? ? [] : association.scoped.where(association.klass.primary_key => attribute_ids)
365
397
  end
366
398
 
367
399
  attributes_collection.each do |attributes|
@@ -371,11 +403,23 @@ module ActiveRecord
371
403
  unless reject_new_record?(association_name, attributes)
372
404
  association.build(attributes.except(*UNASSIGNABLE_KEYS))
373
405
  end
374
-
375
406
  elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
376
- association.send(:add_record_to_target_with_callbacks, existing_record) if !association.loaded? && !call_reject_if(association_name, attributes)
377
- assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
407
+ unless association.loaded? || call_reject_if(association_name, attributes)
408
+ # Make sure we are operating on the actual object which is in the association's
409
+ # proxy_target array (either by finding it, or adding it if not found)
410
+ target_record = association.target.detect { |record| record == existing_record }
411
+
412
+ if target_record
413
+ existing_record = target_record
414
+ else
415
+ association.add_to_target(existing_record)
416
+ end
378
417
 
418
+ end
419
+
420
+ if !call_reject_if(association_name, attributes)
421
+ assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
422
+ end
379
423
  else
380
424
  raise_nested_attributes_record_not_found(association_name, attributes['id'])
381
425
  end
@@ -403,7 +447,7 @@ module ActiveRecord
403
447
 
404
448
  def call_reject_if(association_name, attributes)
405
449
  return false if has_destroy_flag?(attributes)
406
- case callback = nested_attributes_options[association_name][:reject_if]
450
+ case callback = self.nested_attributes_options[association_name][:reject_if]
407
451
  when Symbol
408
452
  method(callback).arity == 0 ? send(callback) : send(callback, attributes)
409
453
  when Proc
@@ -412,8 +456,7 @@ module ActiveRecord
412
456
  end
413
457
 
414
458
  def raise_nested_attributes_record_not_found(association_name, record_id)
415
- reflection = self.class.reflect_on_association(association_name)
416
- raise RecordNotFound, "Couldn't find #{reflection.klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
459
+ raise RecordNotFound, "Couldn't find #{self.class.reflect_on_association(association_name).klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
417
460
  end
418
461
  end
419
462
  end
@@ -90,15 +90,11 @@ module ActiveRecord
90
90
  #
91
91
  class Observer < ActiveModel::Observer
92
92
 
93
- def initialize
94
- super
95
- observed_descendants.each { |klass| add_observer!(klass) }
96
- end
97
-
98
93
  protected
99
94
 
100
- def observed_descendants
101
- observed_classes.sum([]) { |klass| klass.descendants }
95
+ def observed_classes
96
+ klasses = super
97
+ klasses + klasses.map { |klass| klass.descendants }.flatten
102
98
  end
103
99
 
104
100
  def add_observer!(klass)
@@ -18,9 +18,6 @@ module ActiveRecord
18
18
  !(new_record? || destroyed?)
19
19
  end
20
20
 
21
- # :call-seq:
22
- # save(options)
23
- #
24
21
  # Saves the model.
25
22
  #
26
23
  # If the model is new a record gets created in the database, otherwise
@@ -36,11 +33,7 @@ module ActiveRecord
36
33
  # +save+ returns +false+. See ActiveRecord::Callbacks for further
37
34
  # details.
38
35
  def save(*)
39
- begin
40
- create_or_update
41
- rescue ActiveRecord::RecordInvalid
42
- false
43
- end
36
+ create_or_update
44
37
  end
45
38
 
46
39
  # Saves the model.
@@ -71,7 +64,10 @@ module ActiveRecord
71
64
  # callbacks, Observer methods, or any <tt>:dependent</tt> association
72
65
  # options, use <tt>#destroy</tt>.
73
66
  def delete
74
- self.class.delete(id) if persisted?
67
+ if persisted?
68
+ self.class.delete(id)
69
+ IdentityMap.remove(self) if IdentityMap.enabled?
70
+ end
75
71
  @destroyed = true
76
72
  freeze
77
73
  end
@@ -79,10 +75,17 @@ module ActiveRecord
79
75
  # Deletes the record in the database and freezes this instance to reflect
80
76
  # that no changes should be made (since they can't be persisted).
81
77
  def destroy
82
- destroy_associations
83
-
84
78
  if persisted?
85
- self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).delete_all
79
+ IdentityMap.remove(self) if IdentityMap.enabled?
80
+ pk = self.class.primary_key
81
+ column = self.class.columns_hash[pk]
82
+ substitute = connection.substitute_at(column, 0)
83
+
84
+ relation = self.class.unscoped.where(
85
+ self.class.arel_table[pk].eq(substitute))
86
+
87
+ relation.bind_values = [[column, id]]
88
+ relation.delete_all
86
89
  end
87
90
 
88
91
  @destroyed = true
@@ -105,6 +108,7 @@ module ActiveRecord
105
108
  became.instance_variable_set("@attributes_cache", @attributes_cache)
106
109
  became.instance_variable_set("@new_record", new_record?)
107
110
  became.instance_variable_set("@destroyed", destroyed?)
111
+ became.type = klass.name unless self.class.descends_from_active_record?
108
112
  became
109
113
  end
110
114
 
@@ -123,25 +127,44 @@ module ActiveRecord
123
127
  save(:validate => false)
124
128
  end
125
129
 
130
+ # Updates a single attribute of an object, without calling save.
131
+ #
132
+ # * Validation is skipped.
133
+ # * Callbacks are skipped.
134
+ # * updated_at/updated_on column is not updated if that column is available.
135
+ #
136
+ def update_column(name, value)
137
+ name = name.to_s
138
+ raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
139
+ raise ActiveRecordError, "can not update on a new record object" unless persisted?
140
+ raw_write_attribute(name, value)
141
+ self.class.update_all({ name => value }, self.class.primary_key => id) == 1
142
+ end
143
+
126
144
  # Updates the attributes of the model from the passed-in hash and saves the
127
145
  # record, all wrapped in a transaction. If the object is invalid, the saving
128
146
  # will fail and false will be returned.
129
- def update_attributes(attributes)
147
+ #
148
+ # When updating model attributes, mass-assignment security protection is respected.
149
+ # If no +:as+ option is supplied then the +:default+ scope will be used.
150
+ # If you want to bypass the protection given by +attr_protected+ and
151
+ # +attr_accessible+ then you can do so using the +:without_protection+ option.
152
+ def update_attributes(attributes, options = {})
130
153
  # The following transaction covers any possible database side-effects of the
131
154
  # attributes assignment. For example, setting the IDs of a child collection.
132
155
  with_transaction_returning_status do
133
- self.attributes = attributes
156
+ self.assign_attributes(attributes, options)
134
157
  save
135
158
  end
136
159
  end
137
160
 
138
161
  # Updates its receiver just like +update_attributes+ but calls <tt>save!</tt> instead
139
162
  # of +save+, so an exception is raised if the record is invalid.
140
- def update_attributes!(attributes)
163
+ def update_attributes!(attributes, options = {})
141
164
  # The following transaction covers any possible database side-effects of the
142
165
  # attributes assignment. For example, setting the IDs of a child collection.
143
166
  with_transaction_returning_status do
144
- self.attributes = attributes
167
+ self.assign_attributes(attributes, options)
145
168
  save!
146
169
  end
147
170
  end
@@ -204,7 +227,12 @@ module ActiveRecord
204
227
  def reload(options = nil)
205
228
  clear_aggregation_cache
206
229
  clear_association_cache
207
- @attributes.update(self.class.unscoped { self.class.find(self.id, options) }.instance_variable_get('@attributes'))
230
+
231
+ IdentityMap.without do
232
+ fresh_object = self.class.unscoped { self.class.find(self.id, options) }
233
+ @attributes.update(fresh_object.instance_variable_get('@attributes'))
234
+ end
235
+
208
236
  @attributes_cache = {}
209
237
  self
210
238
  end
@@ -232,6 +260,7 @@ module ActiveRecord
232
260
  def touch(name = nil)
233
261
  attributes = timestamp_attributes_for_update_in_model
234
262
  attributes << name if name
263
+
235
264
  unless attributes.empty?
236
265
  current_time = current_time_from_proper_timezone
237
266
  changes = {}
@@ -240,18 +269,15 @@ module ActiveRecord
240
269
  changes[column.to_s] = write_attribute(column.to_s, current_time)
241
270
  end
242
271
 
272
+ changes[self.class.locking_column] = increment_lock if locking_enabled?
273
+
243
274
  @changed_attributes.except!(*changes.keys)
244
275
  primary_key = self.class.primary_key
245
- self.class.unscoped.update_all(changes, { primary_key => self[primary_key] }) == 1
276
+ self.class.update_all(changes, { primary_key => self[primary_key] }) == 1
246
277
  end
247
278
  end
248
279
 
249
280
  private
250
-
251
- # A hook to be overriden by association modules.
252
- def destroy_associations
253
- end
254
-
255
281
  def create_or_update
256
282
  raise ReadOnlyRecord if readonly?
257
283
  result = new_record? ? create : update
@@ -263,26 +289,21 @@ module ActiveRecord
263
289
  def update(attribute_names = @attributes.keys)
264
290
  attributes_with_values = arel_attributes_values(false, false, attribute_names)
265
291
  return 0 if attributes_with_values.empty?
266
- self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).arel.update(attributes_with_values)
292
+ klass = self.class
293
+ stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id)).arel.compile_update(attributes_with_values)
294
+ klass.connection.update stmt.to_sql
267
295
  end
268
296
 
269
297
  # Creates a record with values matching those of the instance attributes
270
298
  # and returns its id.
271
299
  def create
272
- if self.id.nil? && connection.prefetch_primary_key?(self.class.table_name)
273
- self.id = connection.next_sequence_value(self.class.sequence_name)
274
- end
300
+ attributes_values = arel_attributes_values(!id.nil?)
275
301
 
276
- attributes_values = arel_attributes_values
277
-
278
- new_id = if attributes_values.empty?
279
- self.class.unscoped.insert connection.empty_insert_statement_value
280
- else
281
- self.class.unscoped.insert attributes_values
282
- end
302
+ new_id = self.class.unscoped.insert attributes_values
283
303
 
284
304
  self.id ||= new_id
285
305
 
306
+ IdentityMap.add(self) if IdentityMap.enabled?
286
307
  @new_record = false
287
308
  id
288
309
  end
@@ -292,10 +313,9 @@ module ActiveRecord
292
313
  # that a new instance, or one populated from a passed-in Hash, still has all the attributes
293
314
  # that instances loaded from the database would.
294
315
  def attributes_from_column_definition
295
- self.class.columns.inject({}) do |attributes, column|
296
- attributes[column.name] = column.default unless column.name == self.class.primary_key
297
- attributes
298
- end
316
+ Hash[self.class.columns.map do |column|
317
+ [column.name, column.default]
318
+ end]
299
319
  end
300
320
  end
301
321
  end