activerecord 3.0.0.beta4 → 3.0.0.rc

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 (69) hide show
  1. data/CHANGELOG +267 -254
  2. data/README.rdoc +222 -0
  3. data/examples/performance.rb +9 -9
  4. data/lib/active_record/aggregations.rb +3 -4
  5. data/lib/active_record/association_preload.rb +15 -10
  6. data/lib/active_record/associations.rb +54 -37
  7. data/lib/active_record/associations/association_collection.rb +43 -17
  8. data/lib/active_record/associations/association_proxy.rb +2 -0
  9. data/lib/active_record/associations/belongs_to_association.rb +1 -0
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -0
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +22 -7
  12. data/lib/active_record/associations/has_many_association.rb +6 -1
  13. data/lib/active_record/associations/has_many_through_association.rb +1 -0
  14. data/lib/active_record/associations/has_one_association.rb +1 -0
  15. data/lib/active_record/associations/has_one_through_association.rb +1 -0
  16. data/lib/active_record/associations/through_association_scope.rb +3 -2
  17. data/lib/active_record/attribute_methods.rb +1 -0
  18. data/lib/active_record/autosave_association.rb +4 -6
  19. data/lib/active_record/base.rb +106 -240
  20. data/lib/active_record/callbacks.rb +4 -25
  21. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +22 -29
  22. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +2 -8
  23. data/lib/active_record/connection_adapters/abstract/database_statements.rb +2 -2
  24. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +10 -0
  25. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +56 -7
  26. data/lib/active_record/connection_adapters/abstract_adapter.rb +10 -18
  27. data/lib/active_record/connection_adapters/mysql_adapter.rb +2 -2
  28. data/lib/active_record/connection_adapters/postgresql_adapter.rb +65 -69
  29. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +19 -6
  30. data/lib/active_record/connection_adapters/sqlite_adapter.rb +20 -46
  31. data/lib/active_record/counter_cache.rb +14 -4
  32. data/lib/active_record/dynamic_finder_match.rb +9 -0
  33. data/lib/active_record/dynamic_scope_match.rb +7 -0
  34. data/lib/active_record/errors.rb +3 -0
  35. data/lib/active_record/fixtures.rb +5 -6
  36. data/lib/active_record/locale/en.yml +1 -1
  37. data/lib/active_record/locking/optimistic.rb +1 -0
  38. data/lib/active_record/log_subscriber.rb +48 -0
  39. data/lib/active_record/migration.rb +64 -37
  40. data/lib/active_record/named_scope.rb +33 -19
  41. data/lib/active_record/nested_attributes.rb +17 -13
  42. data/lib/active_record/observer.rb +13 -6
  43. data/lib/active_record/persistence.rb +55 -22
  44. data/lib/active_record/query_cache.rb +1 -0
  45. data/lib/active_record/railtie.rb +14 -8
  46. data/lib/active_record/railties/controller_runtime.rb +2 -2
  47. data/lib/active_record/railties/databases.rake +63 -33
  48. data/lib/active_record/reflection.rb +46 -28
  49. data/lib/active_record/relation.rb +38 -24
  50. data/lib/active_record/relation/finder_methods.rb +5 -5
  51. data/lib/active_record/relation/predicate_builder.rb +2 -4
  52. data/lib/active_record/relation/query_methods.rb +134 -115
  53. data/lib/active_record/relation/spawn_methods.rb +1 -1
  54. data/lib/active_record/schema.rb +2 -0
  55. data/lib/active_record/schema_dumper.rb +15 -12
  56. data/lib/active_record/serialization.rb +2 -0
  57. data/lib/active_record/session_store.rb +93 -79
  58. data/lib/active_record/test_case.rb +3 -0
  59. data/lib/active_record/timestamp.rb +49 -29
  60. data/lib/active_record/transactions.rb +5 -2
  61. data/lib/active_record/validations.rb +5 -2
  62. data/lib/active_record/validations/associated.rb +1 -1
  63. data/lib/active_record/validations/uniqueness.rb +1 -1
  64. data/lib/active_record/version.rb +1 -1
  65. data/lib/rails/generators/active_record/migration/templates/migration.rb +12 -6
  66. data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
  67. metadata +27 -14
  68. data/README +0 -351
  69. data/lib/active_record/railties/log_subscriber.rb +0 -32
@@ -3,6 +3,8 @@ require 'active_support/core_ext/array/wrap'
3
3
 
4
4
  module ActiveRecord
5
5
  module Associations
6
+ # = Active Record Association Collection
7
+ #
6
8
  # AssociationCollection is an abstract class that provides common stuff to
7
9
  # ease the implementation of association proxies that represent
8
10
  # collections. See the class hierarchy in AssociationProxy.
@@ -24,10 +26,10 @@ module ActiveRecord
24
26
 
25
27
  delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped
26
28
 
27
- def select(select = nil, &block)
29
+ def select(select = nil)
28
30
  if block_given?
29
31
  load_target
30
- @target.select(&block)
32
+ @target.select.each { |e| yield e }
31
33
  else
32
34
  scoped.select(select)
33
35
  end
@@ -121,7 +123,7 @@ module ActiveRecord
121
123
  end
122
124
  end
123
125
 
124
- # Add +records+ to this association. Returns +self+ so method calls may be chained.
126
+ # Add +records+ to this association. Returns +self+ so method calls may be chained.
125
127
  # Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically.
126
128
  def <<(*records)
127
129
  result = true
@@ -166,7 +168,7 @@ module ActiveRecord
166
168
  reset_target!
167
169
  reset_named_scopes_cache!
168
170
  end
169
-
171
+
170
172
  # Calculate sum using SQL, not Enumerable
171
173
  def sum(*args)
172
174
  if block_given?
@@ -181,10 +183,13 @@ module ActiveRecord
181
183
  # descendant's +construct_sql+ method will have set :counter_sql automatically.
182
184
  # Otherwise, construct options and pass them with scope to the target class's +count+.
183
185
  def count(column_name = nil, options = {})
184
- if @reflection.options[:counter_sql]
186
+ column_name, options = nil, column_name if column_name.is_a?(Hash)
187
+
188
+ if @reflection.options[:counter_sql] && !options.blank?
189
+ raise ArgumentError, "If finder_sql/counter_sql is used then options cannot be passed"
190
+ elsif @reflection.options[:counter_sql]
185
191
  @reflection.klass.count_by_sql(@counter_sql)
186
192
  else
187
- column_name, options = nil, column_name if column_name.is_a?(Hash)
188
193
 
189
194
  if @reflection.options[:uniq]
190
195
  # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
@@ -213,9 +218,9 @@ module ActiveRecord
213
218
  # are actually removed from the database, that depends precisely on
214
219
  # +delete_records+. They are in any case removed from the collection.
215
220
  def delete(*records)
216
- remove_records(records) do |records, old_records|
221
+ remove_records(records) do |_records, old_records|
217
222
  delete_records(old_records) if old_records.any?
218
- records.each { |record| @target.delete(record) }
223
+ _records.each { |record| @target.delete(record) }
219
224
  end
220
225
  end
221
226
 
@@ -226,7 +231,7 @@ module ActiveRecord
226
231
  # ignoring the +:dependent+ option.
227
232
  def destroy(*records)
228
233
  records = find(records) if records.any? {|record| record.kind_of?(Fixnum) || record.kind_of?(String)}
229
- remove_records(records) do |records, old_records|
234
+ remove_records(records) do |_records, old_records|
230
235
  old_records.each { |record| record.destroy }
231
236
  end
232
237
 
@@ -239,7 +244,7 @@ module ActiveRecord
239
244
 
240
245
  if @reflection.options[:dependent] && @reflection.options[:dependent] == :destroy
241
246
  destroy_all
242
- else
247
+ else
243
248
  delete_all
244
249
  end
245
250
 
@@ -251,9 +256,10 @@ module ActiveRecord
251
256
  # See destroy for more info.
252
257
  def destroy_all
253
258
  load_target
254
- destroy(@target)
255
- reset_target!
256
- reset_named_scopes_cache!
259
+ destroy(@target).tap do
260
+ reset_target!
261
+ reset_named_scopes_cache!
262
+ end
257
263
  end
258
264
 
259
265
  def create(attrs = {})
@@ -388,7 +394,17 @@ module ActiveRecord
388
394
  begin
389
395
  if !loaded?
390
396
  if @target.is_a?(Array) && @target.any?
391
- @target = find_target + @target.find_all {|t| t.new_record? }
397
+ @target = find_target.map do |f|
398
+ i = @target.index(f)
399
+ if i
400
+ @target.delete_at(i).tap do |t|
401
+ keys = ["id"] + t.changes.keys + (f.attribute_names - t.attribute_names)
402
+ t.attributes = f.attributes.except(*keys)
403
+ end
404
+ else
405
+ f
406
+ end
407
+ end + @target
392
408
  else
393
409
  @target = find_target
394
410
  end
@@ -403,6 +419,12 @@ module ActiveRecord
403
419
  end
404
420
 
405
421
  def method_missing(method, *args)
422
+ match = DynamicFinderMatch.match(method)
423
+ if match && match.creator?
424
+ attributes = match.attribute_names
425
+ return send(:"find_by_#{attributes.join('and')}", *args) || create(Hash[attributes.zip(args)])
426
+ end
427
+
406
428
  if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
407
429
  if block_given?
408
430
  super { |*block_args| yield(*block_args) }
@@ -456,7 +478,11 @@ module ActiveRecord
456
478
  callback(:before_add, record)
457
479
  yield(record) if block_given?
458
480
  @target ||= [] unless loaded?
459
- @target << record unless @reflection.options[:uniq] && @target.include?(record)
481
+ if index = @target.index(record)
482
+ @target[index] = record
483
+ else
484
+ @target << record
485
+ end
460
486
  callback(:after_add, record)
461
487
  set_inverse_instance(record, @owner)
462
488
  record
@@ -514,8 +540,8 @@ module ActiveRecord
514
540
  def callbacks_for(callback_name)
515
541
  full_callback_name = "#{callback_name}_for_#{@reflection.name}"
516
542
  @owner.class.read_inheritable_attribute(full_callback_name.to_sym) || []
517
- end
518
-
543
+ end
544
+
519
545
  def ensure_owner_is_not_new
520
546
  if @owner.new_record?
521
547
  raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
@@ -2,6 +2,8 @@ require 'active_support/core_ext/array/wrap'
2
2
 
3
3
  module ActiveRecord
4
4
  module Associations
5
+ # = Active Record Associations
6
+ #
5
7
  # This is the root class of all association proxies:
6
8
  #
7
9
  # AssociationProxy
@@ -1,4 +1,5 @@
1
1
  module ActiveRecord
2
+ # = Active Record Belongs To Associations
2
3
  module Associations
3
4
  class BelongsToAssociation < AssociationProxy #:nodoc:
4
5
  def create(attributes = {})
@@ -1,4 +1,5 @@
1
1
  module ActiveRecord
2
+ # = Active Record Belongs To Polymorphic Association
2
3
  module Associations
3
4
  class BelongsToPolymorphicAssociation < AssociationProxy #:nodoc:
4
5
  def replace(record)
@@ -1,4 +1,5 @@
1
1
  module ActiveRecord
2
+ # = Active Record Has And Belongs To Many Association
2
3
  module Associations
3
4
  class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
4
5
  def create(attributes = {})
@@ -44,17 +45,23 @@ module ActiveRecord
44
45
  if @reflection.options[:insert_sql]
45
46
  @owner.connection.insert(interpolate_sql(@reflection.options[:insert_sql], record))
46
47
  else
47
- relation = Arel::Table.new(@reflection.options[:join_table])
48
+ relation = Arel::Table.new(@reflection.options[:join_table])
49
+ timestamps = record_timestamp_columns(record)
50
+ timezone = record.send(:current_time_from_proper_timezone) if timestamps.any?
51
+
48
52
  attributes = columns.inject({}) do |attrs, column|
49
- case column.name.to_s
53
+ name = column.name
54
+ case name.to_s
50
55
  when @reflection.primary_key_name.to_s
51
- attrs[relation[column.name]] = owner_quoted_id
56
+ attrs[relation[name]] = @owner.id
52
57
  when @reflection.association_foreign_key.to_s
53
- attrs[relation[column.name]] = record.quoted_id
58
+ attrs[relation[name]] = record.id
59
+ when *timestamps
60
+ attrs[relation[name]] = timezone
54
61
  else
55
- if record.has_attribute?(column.name)
56
- value = @owner.send(:quote_value, record[column.name], column)
57
- attrs[relation[column.name]] = value unless value.nil?
62
+ if record.has_attribute?(name)
63
+ value = @owner.send(:quote_value, record[name], column)
64
+ attrs[relation[name]] = value unless value.nil?
58
65
  end
59
66
  end
60
67
  attrs
@@ -116,6 +123,14 @@ module ActiveRecord
116
123
  build_record(attributes, &block)
117
124
  end
118
125
  end
126
+
127
+ def record_timestamp_columns(record)
128
+ if record.record_timestamps
129
+ record.send(:all_timestamp_attributes).map(&:to_s)
130
+ else
131
+ []
132
+ end
133
+ end
119
134
  end
120
135
  end
121
136
  end
@@ -1,4 +1,5 @@
1
1
  module ActiveRecord
2
+ # = Active Record Has Many Association
2
3
  module Associations
3
4
  # This is the proxy that handles a has many association.
4
5
  #
@@ -109,7 +110,11 @@ module ActiveRecord
109
110
  create_scoping = {}
110
111
  set_belongs_to_association_for(create_scoping)
111
112
  {
112
- :find => { :conditions => @finder_sql, :readonly => false, :order => @reflection.options[:order], :limit => @reflection.options[:limit], :include => @reflection.options[:include]},
113
+ :find => { :conditions => @finder_sql,
114
+ :readonly => false,
115
+ :order => @reflection.options[:order],
116
+ :limit => @reflection.options[:limit],
117
+ :include => @reflection.options[:include]},
113
118
  :create => create_scoping
114
119
  }
115
120
  end
@@ -2,6 +2,7 @@ require "active_record/associations/through_association_scope"
2
2
  require 'active_support/core_ext/object/blank'
3
3
 
4
4
  module ActiveRecord
5
+ # = Active Record Has Many Through Association
5
6
  module Associations
6
7
  class HasManyThroughAssociation < HasManyAssociation #:nodoc:
7
8
  include ThroughAssociationScope
@@ -1,4 +1,5 @@
1
1
  module ActiveRecord
2
+ # = Active Record Belongs To Has One Association
2
3
  module Associations
3
4
  class HasOneAssociation < AssociationProxy #:nodoc:
4
5
  def initialize(owner, reflection)
@@ -1,6 +1,7 @@
1
1
  require "active_record/associations/through_association_scope"
2
2
 
3
3
  module ActiveRecord
4
+ # = Active Record Has One Through Association
4
5
  module Associations
5
6
  class HasOneThroughAssociation < HasOneAssociation
6
7
  include ThroughAssociationScope
@@ -1,4 +1,5 @@
1
1
  module ActiveRecord
2
+ # = Active Record Through Association Scope
2
3
  module Associations
3
4
  module ThroughAssociationScope
4
5
 
@@ -34,7 +35,7 @@ module ActiveRecord
34
35
  @owner.class.base_class.name.to_s,
35
36
  reflection.klass.columns_hash["#{as}_type"]) }
36
37
  elsif reflection.macro == :belongs_to
37
- { reflection.klass.primary_key => @owner[reflection.primary_key_name] }
38
+ { reflection.klass.primary_key => @owner.class.quote_value(@owner[reflection.primary_key_name]) }
38
39
  else
39
40
  { reflection.primary_key_name => owner_quoted_id }
40
41
  end
@@ -91,7 +92,7 @@ module ActiveRecord
91
92
 
92
93
  # Construct attributes for :through pointing to owner and associate.
93
94
  def construct_join_attributes(associate)
94
- # TODO: revist this to allow it for deletion, supposing dependent option is supported
95
+ # TODO: revisit this to allow it for deletion, supposing dependent option is supported
95
96
  raise ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(@owner, @reflection) if [:has_one, :has_many].include?(@reflection.source_reflection.macro)
96
97
 
97
98
  join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
@@ -1,6 +1,7 @@
1
1
  require 'active_support/core_ext/enumerable'
2
2
 
3
3
  module ActiveRecord
4
+ # = Active Record Attribute Methods
4
5
  module AttributeMethods #:nodoc:
5
6
  extend ActiveSupport::Concern
6
7
  include ActiveModel::AttributeMethods
@@ -1,6 +1,8 @@
1
1
  require 'active_support/core_ext/array/wrap'
2
2
 
3
3
  module ActiveRecord
4
+ # = Active Record Autosave Association
5
+ #
4
6
  # AutosaveAssociation is a module that takes care of automatically saving
5
7
  # your associations when the parent is saved. In addition to saving, it
6
8
  # also destroys any associations that were marked for destruction.
@@ -145,12 +147,12 @@ module ActiveRecord
145
147
  # add_autosave_association_callbacks(reflect_on_association(name))
146
148
  # end
147
149
  ASSOCIATION_TYPES.each do |type|
148
- module_eval %{
150
+ module_eval <<-CODE, __FILE__, __LINE__ + 1
149
151
  def #{type}(name, options = {})
150
152
  super
151
153
  add_autosave_association_callbacks(reflect_on_association(name))
152
154
  end
153
- }
155
+ CODE
154
156
  end
155
157
 
156
158
  # Adds a validate and save callback for the association as specified by
@@ -375,10 +377,6 @@ module ActiveRecord
375
377
  if association.updated?
376
378
  association_id = association.send(reflection.options[:primary_key] || :id)
377
379
  self[reflection.primary_key_name] = association_id
378
- # TODO: Removing this code doesn't seem to matter…
379
- if reflection.options[:polymorphic]
380
- self[reflection.options[:foreign_type]] = association.class.base_class.name.to_s
381
- end
382
380
  end
383
381
 
384
382
  saved if autosave
@@ -2,6 +2,7 @@ require 'yaml'
2
2
  require 'set'
3
3
  require 'active_support/benchmarkable'
4
4
  require 'active_support/dependencies'
5
+ require 'active_support/descendants_tracker'
5
6
  require 'active_support/time'
6
7
  require 'active_support/core_ext/class/attribute'
7
8
  require 'active_support/core_ext/class/attribute_accessors'
@@ -14,12 +15,17 @@ require 'active_support/core_ext/hash/slice'
14
15
  require 'active_support/core_ext/string/behavior'
15
16
  require 'active_support/core_ext/kernel/singleton_class'
16
17
  require 'active_support/core_ext/module/delegation'
18
+ require 'active_support/core_ext/module/deprecation'
19
+ require 'active_support/core_ext/module/introspection'
17
20
  require 'active_support/core_ext/object/duplicable'
18
21
  require 'active_support/core_ext/object/blank'
19
22
  require 'arel'
20
23
  require 'active_record/errors'
24
+ require 'active_record/log_subscriber'
21
25
 
22
26
  module ActiveRecord #:nodoc:
27
+ # = Active Record
28
+ #
23
29
  # Active Record objects don't specify their attributes directly, but rather infer them from the table definition with
24
30
  # which they're linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change
25
31
  # is instantly reflected in the Active Record objects. The mapping that binds a given Active Record class to a certain
@@ -273,27 +279,17 @@ module ActiveRecord #:nodoc:
273
279
  # on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+.
274
280
  cattr_accessor :logger, :instance_writer => false
275
281
 
276
- def self.inherited(child) #:nodoc:
277
- @@subclasses[self] ||= []
278
- @@subclasses[self] << child
279
- super
280
- end
282
+ class << self
283
+ def reset_subclasses #:nodoc:
284
+ 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
285
+ end
281
286
 
282
- def self.reset_subclasses #:nodoc:
283
- nonreloadables = []
284
- subclasses.each do |klass|
285
- unless ActiveSupport::Dependencies.autoloaded? klass
286
- nonreloadables << klass
287
- next
288
- end
289
- klass.instance_variables.each { |var| klass.send(:remove_instance_variable, var) }
290
- klass.instance_methods(false).each { |m| klass.send :undef_method, m }
287
+ def subclasses
288
+ descendants
291
289
  end
292
- @@subclasses = {}
293
- nonreloadables.each { |klass| (@@subclasses[klass.superclass] ||= []) << klass }
294
- end
295
290
 
296
- @@subclasses = {}
291
+ deprecate :subclasses => :descendants
292
+ end
297
293
 
298
294
  ##
299
295
  # :singleton-method:
@@ -402,7 +398,7 @@ module ActiveRecord #:nodoc:
402
398
 
403
399
  delegate :find, :first, :last, :all, :destroy, :destroy_all, :exists?, :delete, :delete_all, :update, :update_all, :to => :scoped
404
400
  delegate :find_each, :find_in_batches, :to => :scoped
405
- delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped
401
+ delegate :select, :group, :order, :reorder, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
406
402
  delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped
407
403
 
408
404
  # Executes a custom SQL query against your database and returns all the results. The results will
@@ -480,112 +476,16 @@ module ActiveRecord #:nodoc:
480
476
  connection.select_value(sql, "#{name} Count").to_i
481
477
  end
482
478
 
483
- # Attributes named in this macro are protected from mass-assignment,
484
- # such as <tt>new(attributes)</tt>,
485
- # <tt>update_attributes(attributes)</tt>, or
486
- # <tt>attributes=(attributes)</tt>.
487
- #
488
- # Mass-assignment to these attributes will simply be ignored, to assign
489
- # to them you can use direct writer methods. This is meant to protect
490
- # sensitive attributes from being overwritten by malicious users
491
- # tampering with URLs or forms.
492
- #
493
- # class Customer < ActiveRecord::Base
494
- # attr_protected :credit_rating
495
- # end
496
- #
497
- # customer = Customer.new("name" => David, "credit_rating" => "Excellent")
498
- # customer.credit_rating # => nil
499
- # customer.attributes = { "description" => "Jolly fellow", "credit_rating" => "Superb" }
500
- # customer.credit_rating # => nil
501
- #
502
- # customer.credit_rating = "Average"
503
- # customer.credit_rating # => "Average"
504
- #
505
- # To start from an all-closed default and enable attributes as needed,
506
- # have a look at +attr_accessible+.
507
- #
508
- # If the access logic of your application is richer you can use <tt>Hash#except</tt>
509
- # or <tt>Hash#slice</tt> to sanitize the hash of parameters before they are
510
- # passed to Active Record.
511
- #
512
- # For example, it could be the case that the list of protected attributes
513
- # for a given model depends on the role of the user:
514
- #
515
- # # Assumes plan_id is not protected because it depends on the role.
516
- # params[:account] = params[:account].except(:plan_id) unless admin?
517
- # @account.update_attributes(params[:account])
518
- #
519
- # Note that +attr_protected+ is still applied to the received hash. Thus,
520
- # with this technique you can at most _extend_ the list of protected
521
- # attributes for a particular mass-assignment call.
522
- def attr_protected(*attributes)
523
- write_inheritable_attribute(:attr_protected, Set.new(attributes.map {|a| a.to_s}) + (protected_attributes || []))
524
- end
525
-
526
- # Returns an array of all the attributes that have been protected from mass-assignment.
527
- def protected_attributes # :nodoc:
528
- read_inheritable_attribute(:attr_protected)
479
+ # Attributes listed as readonly can be set for a new record, but will be ignored in database updates afterwards.
480
+ def attr_readonly(*attributes)
481
+ write_inheritable_attribute(:attr_readonly, Set.new(attributes.map(&:to_s)) + (readonly_attributes || []))
529
482
  end
530
483
 
531
- # Specifies a white list of model attributes that can be set via
532
- # mass-assignment, such as <tt>new(attributes)</tt>,
533
- # <tt>update_attributes(attributes)</tt>, or
534
- # <tt>attributes=(attributes)</tt>
535
- #
536
- # This is the opposite of the +attr_protected+ macro: Mass-assignment
537
- # will only set attributes in this list, to assign to the rest of
538
- # attributes you can use direct writer methods. This is meant to protect
539
- # sensitive attributes from being overwritten by malicious users
540
- # tampering with URLs or forms. If you'd rather start from an all-open
541
- # default and restrict attributes as needed, have a look at
542
- # +attr_protected+.
543
- #
544
- # class Customer < ActiveRecord::Base
545
- # attr_accessible :name, :nickname
546
- # end
547
- #
548
- # customer = Customer.new(:name => "David", :nickname => "Dave", :credit_rating => "Excellent")
549
- # customer.credit_rating # => nil
550
- # customer.attributes = { :name => "Jolly fellow", :credit_rating => "Superb" }
551
- # customer.credit_rating # => nil
552
- #
553
- # customer.credit_rating = "Average"
554
- # customer.credit_rating # => "Average"
555
- #
556
- # If the access logic of your application is richer you can use <tt>Hash#except</tt>
557
- # or <tt>Hash#slice</tt> to sanitize the hash of parameters before they are
558
- # passed to Active Record.
559
- #
560
- # For example, it could be the case that the list of accessible attributes
561
- # for a given model depends on the role of the user:
562
- #
563
- # # Assumes plan_id is accessible because it depends on the role.
564
- # params[:account] = params[:account].except(:plan_id) unless admin?
565
- # @account.update_attributes(params[:account])
566
- #
567
- # Note that +attr_accessible+ is still applied to the received hash. Thus,
568
- # with this technique you can at most _narrow_ the list of accessible
569
- # attributes for a particular mass-assignment call.
570
- def attr_accessible(*attributes)
571
- write_inheritable_attribute(:attr_accessible, Set.new(attributes.map(&:to_s)) + (accessible_attributes || []))
484
+ # Returns an array of all the attributes that have been specified as readonly.
485
+ def readonly_attributes
486
+ read_inheritable_attribute(:attr_readonly) || []
572
487
  end
573
488
 
574
- # Returns an array of all the attributes that have been made accessible to mass-assignment.
575
- def accessible_attributes # :nodoc:
576
- read_inheritable_attribute(:attr_accessible)
577
- end
578
-
579
- # Attributes listed as readonly can be set for a new record, but will be ignored in database updates afterwards.
580
- def attr_readonly(*attributes)
581
- write_inheritable_attribute(:attr_readonly, Set.new(attributes.map(&:to_s)) + (readonly_attributes || []))
582
- end
583
-
584
- # Returns an array of all the attributes that have been specified as readonly.
585
- def readonly_attributes
586
- read_inheritable_attribute(:attr_readonly) || []
587
- end
588
-
589
489
  # If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
590
490
  # then specify the name of that attribute using this method and it will be handled automatically.
591
491
  # The serialization is done through YAML. If +class_name+ is specified, the serialized object must be of that
@@ -647,29 +547,14 @@ module ActiveRecord #:nodoc:
647
547
  reset_table_name
648
548
  end
649
549
 
550
+ # Returns a quoted version of the table name, used to construct SQL statements.
650
551
  def quoted_table_name
651
552
  @quoted_table_name ||= connection.quote_table_name(table_name)
652
553
  end
653
554
 
555
+ # Computes the table name, (re)sets it internally, and returns it.
654
556
  def reset_table_name #:nodoc:
655
- base = base_class
656
-
657
- name =
658
- # STI subclasses always use their superclass' table.
659
- unless self == base
660
- base.table_name
661
- else
662
- # Nested classes are prefixed with singular parent table name.
663
- if parent < ActiveRecord::Base && !parent.abstract_class?
664
- contained = parent.table_name
665
- contained = contained.singularize if parent.pluralize_table_names
666
- contained << '_'
667
- end
668
- name = "#{full_table_name_prefix}#{contained}#{undecorated_table_name(base.name)}#{table_name_suffix}"
669
- end
670
-
671
- set_table_name(name)
672
- name
557
+ self.table_name = compute_table_name
673
558
  end
674
559
 
675
560
  def full_table_name_prefix #:nodoc:
@@ -739,14 +624,6 @@ module ActiveRecord #:nodoc:
739
624
  end
740
625
  alias :sequence_name= :set_sequence_name
741
626
 
742
- # Turns the +table_name+ back into a class name following the reverse rules of +table_name+.
743
- def class_name(table_name = table_name) # :nodoc:
744
- # remove any prefix and/or suffix from the table name
745
- class_name = table_name[table_name_prefix.length..-(table_name_suffix.length + 1)].camelize
746
- class_name = class_name.singularize if pluralize_table_names
747
- class_name
748
- end
749
-
750
627
  # Indicates whether the table associated with this class exists
751
628
  def table_exists?
752
629
  connection.table_exists?(table_name)
@@ -820,11 +697,11 @@ module ActiveRecord #:nodoc:
820
697
  def reset_column_information
821
698
  undefine_attribute_methods
822
699
  @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil
823
- @arel_engine = @unscoped = @arel_table = nil
700
+ @arel_engine = @relation = @arel_table = nil
824
701
  end
825
702
 
826
703
  def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc:
827
- subclasses.each { |klass| klass.reset_inheritable_attributes; klass.reset_column_information }
704
+ descendants.each { |klass| klass.reset_inheritable_attributes; klass.reset_column_information }
828
705
  end
829
706
 
830
707
  def attribute_method?(attribute)
@@ -896,6 +773,9 @@ module ActiveRecord #:nodoc:
896
773
  # Returns the base AR subclass that this class descends from. If A
897
774
  # extends AR::Base, A.base_class will return A. If B descends from A
898
775
  # through some arbitrarily deep hierarchy, B.base_class will return A.
776
+ #
777
+ # If B < A and C < B and if A is an abstract_class then both B.base_class
778
+ # and C.base_class would return B as the answer since A is an abstract_class.
899
779
  def base_class
900
780
  class_of_active_record_descendant(self)
901
781
  end
@@ -903,8 +783,7 @@ module ActiveRecord #:nodoc:
903
783
  # Set this to true if this is an abstract class (see <tt>abstract_class?</tt>).
904
784
  attr_accessor :abstract_class
905
785
 
906
- # Returns whether this class is a base AR class. If A is a base class and
907
- # B descends from A, then B.base_class will return B.
786
+ # Returns whether this class is an abstract class or not.
908
787
  def abstract_class?
909
788
  defined?(@abstract_class) && @abstract_class == true
910
789
  end
@@ -923,11 +802,6 @@ module ActiveRecord #:nodoc:
923
802
  store_full_sti_class ? name : name.demodulize
924
803
  end
925
804
 
926
- def unscoped
927
- @unscoped ||= Relation.new(self, arel_table)
928
- finder_needs_type_condition? ? @unscoped.where(type_condition) : @unscoped
929
- end
930
-
931
805
  def arel_table
932
806
  @arel_table ||= Arel::Table.new(table_name, :engine => arel_engine)
933
807
  end
@@ -942,15 +816,46 @@ module ActiveRecord #:nodoc:
942
816
  end
943
817
  end
944
818
 
819
+ # Returns a scope for this class without taking into account the default_scope.
820
+ #
821
+ # class Post < ActiveRecord::Base
822
+ # default_scope :published => true
823
+ # end
824
+ #
825
+ # Post.all # Fires "SELECT * FROM posts WHERE published = true"
826
+ # Post.unscoped.all # Fires "SELECT * FROM posts"
827
+ #
828
+ # This method also accepts a block meaning that all queries inside the block will
829
+ # not use the default_scope:
830
+ #
831
+ # Post.unscoped {
832
+ # limit(10) # Fires "SELECT * FROM posts LIMIT 10"
833
+ # }
834
+ #
835
+ def unscoped
836
+ block_given? ? relation.scoping { yield } : relation
837
+ end
838
+
839
+ def scoped_methods #:nodoc:
840
+ key = :"#{self}_scoped_methods"
841
+ Thread.current[key] = Thread.current[key].presence || self.default_scoping.dup
842
+ end
843
+
945
844
  private
845
+
846
+ def relation #:nodoc:
847
+ @relation ||= Relation.new(self, arel_table)
848
+ finder_needs_type_condition? ? @relation.where(type_condition) : @relation
849
+ end
850
+
946
851
  # Finder methods must instantiate through this method to work with the
947
852
  # single-table inheritance model that makes it possible to create
948
853
  # objects of different types from the same table.
949
854
  def instantiate(record)
950
855
  object = find_sti_class(record[inheritance_column]).allocate
951
856
 
952
- object.instance_variable_set(:'@attributes', record)
953
- object.instance_variable_set(:'@attributes_cache', {})
857
+ object.instance_variable_set(:@attributes, record)
858
+ object.instance_variable_set(:@attributes_cache, {})
954
859
  object.instance_variable_set(:@new_record, false)
955
860
  object.instance_variable_set(:@readonly, false)
956
861
  object.instance_variable_set(:@destroyed, false)
@@ -989,7 +894,7 @@ module ActiveRecord #:nodoc:
989
894
  def type_condition
990
895
  sti_column = arel_table[inheritance_column]
991
896
  condition = sti_column.eq(sti_name)
992
- subclasses.each{|subclass| condition = condition.or(sti_column.eq(subclass.sti_name)) }
897
+ descendants.each { |subclass| condition = condition.or(sti_column.eq(subclass.sti_name)) }
993
898
 
994
899
  condition
995
900
  end
@@ -1001,6 +906,23 @@ module ActiveRecord #:nodoc:
1001
906
  table_name
1002
907
  end
1003
908
 
909
+ # Computes and returns a table name according to default conventions.
910
+ def compute_table_name
911
+ base = base_class
912
+ if self == base
913
+ # Nested classes are prefixed with singular parent table name.
914
+ if parent < ActiveRecord::Base && !parent.abstract_class?
915
+ contained = parent.table_name
916
+ contained = contained.singularize if parent.pluralize_table_names
917
+ contained << '_'
918
+ end
919
+ "#{full_table_name_prefix}#{contained}#{undecorated_table_name(name)}#{table_name_suffix}"
920
+ else
921
+ # STI subclasses always use their superclass' table.
922
+ base.table_name
923
+ end
924
+ end
925
+
1004
926
  # Enables dynamic finders like <tt>find_by_user_name(user_name)</tt> and <tt>find_by_user_name_and_password(user_name, password)</tt>
1005
927
  # that are turned into <tt>where(:user_name => user_name).first</tt> and <tt>where(:user_name => user_name, :password => :password).first</tt>
1006
928
  # respectively. Also works for <tt>all</tt> by using <tt>find_all_by_amount(50)</tt> that is turned into <tt>where(:amount => 50).all</tt>.
@@ -1068,19 +990,6 @@ module ActiveRecord #:nodoc:
1068
990
  attribute_names.all? { |name| column_methods_hash.include?(name.to_sym) }
1069
991
  end
1070
992
 
1071
- def attribute_condition(quoted_column_name, argument)
1072
- case argument
1073
- when nil then "#{quoted_column_name} IS ?"
1074
- when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope then "#{quoted_column_name} IN (?)"
1075
- when Range then if argument.exclude_end?
1076
- "#{quoted_column_name} >= ? AND #{quoted_column_name} < ?"
1077
- else
1078
- "#{quoted_column_name} BETWEEN ? AND ?"
1079
- end
1080
- else "#{quoted_column_name} = ?"
1081
- end
1082
- end
1083
-
1084
993
  protected
1085
994
  # Scope parameters to method calls within the block. Takes a hash of method_name => parameters hash.
1086
995
  # method_name may be <tt>:find</tt> or <tt>:create</tt>. <tt>:find</tt> parameter is <tt>Relation</tt> while
@@ -1121,7 +1030,7 @@ module ActiveRecord #:nodoc:
1121
1030
  # class Article < ActiveRecord::Base
1122
1031
  # def self.find_with_exclusive_scope
1123
1032
  # with_scope(:find => where(:blog_id => 1).limit(1)) do
1124
- # with_exclusive_scope(:find => limit(10))
1033
+ # with_exclusive_scope(:find => limit(10)) do
1125
1034
  # all # => SELECT * from articles LIMIT 10
1126
1035
  # end
1127
1036
  # end
@@ -1172,16 +1081,22 @@ module ActiveRecord #:nodoc:
1172
1081
 
1173
1082
  # Works like with_scope, but discards any nested properties.
1174
1083
  def with_exclusive_scope(method_scoping = {}, &block)
1175
- with_scope(method_scoping, :overwrite, &block)
1176
- end
1084
+ if method_scoping.values.any? { |e| e.is_a?(ActiveRecord::Relation) }
1085
+ raise ArgumentError, <<-MSG
1086
+ New finder API can not be used with_exclusive_scope. You can either call unscoped to get an anonymous scope not bound to the default_scope:
1177
1087
 
1178
- # Returns a list of all subclasses of this class, meaning all descendants.
1179
- def subclasses
1180
- @@subclasses[self] ||= []
1181
- @@subclasses[self] + @@subclasses[self].inject([]) {|list, subclass| list + subclass.subclasses }
1182
- end
1088
+ User.unscoped.where(:active => true)
1183
1089
 
1184
- public :subclasses
1090
+ Or call unscoped with a block:
1091
+
1092
+ User.unscoped do
1093
+ User.where(:active => true).all
1094
+ end
1095
+
1096
+ MSG
1097
+ end
1098
+ with_scope(method_scoping, :overwrite, &block)
1099
+ end
1185
1100
 
1186
1101
  # Sets the default options for the model. The format of the
1187
1102
  # <tt>options</tt> argument is the same as in find.
@@ -1193,11 +1108,6 @@ module ActiveRecord #:nodoc:
1193
1108
  self.default_scoping << construct_finder_arel(options, default_scoping.pop)
1194
1109
  end
1195
1110
 
1196
- def scoped_methods #:nodoc:
1197
- key = :"#{self}_scoped_methods"
1198
- Thread.current[key] = Thread.current[key].presence || self.default_scoping.dup
1199
- end
1200
-
1201
1111
  def current_scoped_methods #:nodoc:
1202
1112
  scoped_methods.last
1203
1113
  end
@@ -1242,11 +1152,6 @@ module ActiveRecord #:nodoc:
1242
1152
  end
1243
1153
  end
1244
1154
 
1245
- # Returns the name of the class descending directly from Active Record in the inheritance hierarchy.
1246
- def class_name_of_active_record_descendant(klass) #:nodoc:
1247
- klass.base_class.name
1248
- end
1249
-
1250
1155
  # Accepts an array, hash, or string of SQL conditions and sanitizes
1251
1156
  # them into a valid SQL fragment for a WHERE clause.
1252
1157
  # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
@@ -1350,6 +1255,8 @@ module ActiveRecord #:nodoc:
1350
1255
  replace_named_bind_variables(statement, values.first)
1351
1256
  elsif statement.include?('?')
1352
1257
  replace_bind_variables(statement, values)
1258
+ elsif statement.blank?
1259
+ statement
1353
1260
  else
1354
1261
  statement % values.collect { |value| connection.quote_string(value.to_s) }
1355
1262
  end
@@ -1450,14 +1357,6 @@ module ActiveRecord #:nodoc:
1450
1357
  # as it copies the object's attributes only, not its associations. The extent of a "deep" clone is
1451
1358
  # application specific and is therefore left to the application to implement according to its need.
1452
1359
  def initialize_copy(other)
1453
- # Think the assertion which fails if the after_initialize callback goes at the end of the method is wrong. The
1454
- # deleted clone method called new which therefore called the after_initialize callback. It then went on to copy
1455
- # over the attributes. But if it's copying the attributes afterwards then it hasn't finished initializing right?
1456
- # For example in the test suite the topic model's after_initialize method sets the author_email_address to
1457
- # test@test.com. I would have thought this would mean that all cloned models would have an author email address
1458
- # of test@test.com. However the test_clone test method seems to test that this is not the case. As a result the
1459
- # after_initialize callback has to be run *before* the copying of the atrributes rather than afterwards in order
1460
- # for all tests to pass. This makes no sense to me.
1461
1360
  callback(:after_initialize) if respond_to_without_attributes?(:after_initialize)
1462
1361
  cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
1463
1362
  cloned_attributes.delete(self.class.primary_key)
@@ -1470,6 +1369,7 @@ module ActiveRecord #:nodoc:
1470
1369
  end
1471
1370
 
1472
1371
  clear_aggregation_cache
1372
+ clear_association_cache
1473
1373
  @attributes_cache = {}
1474
1374
  @new_record = true
1475
1375
  ensure_proper_type
@@ -1573,11 +1473,11 @@ module ActiveRecord #:nodoc:
1573
1473
  # user.send(:attributes=, { :username => 'Phusion', :is_admin => true }, false)
1574
1474
  # user.is_admin? # => true
1575
1475
  def attributes=(new_attributes, guard_protected_attributes = true)
1576
- return if new_attributes.nil?
1476
+ return unless new_attributes.is_a?(Hash)
1577
1477
  attributes = new_attributes.stringify_keys
1578
1478
 
1579
1479
  multi_parameter_attributes = []
1580
- attributes = remove_attributes_protected_from_mass_assignment(attributes) if guard_protected_attributes
1480
+ attributes = sanitize_for_mass_assignment(attributes) if guard_protected_attributes
1581
1481
 
1582
1482
  attributes.each do |k, v|
1583
1483
  if k.include?("(")
@@ -1717,46 +1617,10 @@ module ActiveRecord #:nodoc:
1717
1617
  end
1718
1618
  end
1719
1619
 
1720
- def remove_attributes_protected_from_mass_assignment(attributes)
1721
- safe_attributes =
1722
- if self.class.accessible_attributes.nil? && self.class.protected_attributes.nil?
1723
- attributes.reject { |key, value| attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
1724
- elsif self.class.protected_attributes.nil?
1725
- attributes.reject { |key, value| !self.class.accessible_attributes.include?(key.gsub(/\(.+/, "")) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
1726
- elsif self.class.accessible_attributes.nil?
1727
- attributes.reject { |key, value| self.class.protected_attributes.include?(key.gsub(/\(.+/,"")) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
1728
- else
1729
- raise "Declare either attr_protected or attr_accessible for #{self.class}, but not both."
1730
- end
1731
-
1732
- removed_attributes = attributes.keys - safe_attributes.keys
1733
-
1734
- if removed_attributes.any?
1735
- log_protected_attribute_removal(removed_attributes)
1736
- end
1737
-
1738
- safe_attributes
1739
- end
1740
-
1741
- # Removes attributes which have been marked as readonly.
1742
- def remove_readonly_attributes(attributes)
1743
- unless self.class.readonly_attributes.nil?
1744
- attributes.delete_if { |key, value| self.class.readonly_attributes.include?(key.gsub(/\(.+/,"")) }
1745
- else
1746
- attributes
1747
- end
1748
- end
1749
-
1750
- def log_protected_attribute_removal(*attributes)
1751
- if logger
1752
- logger.debug "WARNING: Can't mass-assign these protected attributes: #{attributes.join(', ')}"
1753
- end
1754
- end
1755
-
1756
1620
  # The primary key and inheritance column can never be set by mass-assignment for security reasons.
1757
- def attributes_protected_by_default
1758
- default = [ self.class.primary_key, self.class.inheritance_column ]
1759
- default << 'id' unless self.class.primary_key.eql? 'id'
1621
+ def self.attributes_protected_by_default
1622
+ default = [ primary_key, inheritance_column ]
1623
+ default << 'id' unless primary_key.eql? 'id'
1760
1624
  default
1761
1625
  end
1762
1626
 
@@ -1910,6 +1774,7 @@ module ActiveRecord #:nodoc:
1910
1774
  extend ActiveModel::Naming
1911
1775
  extend QueryCache::ClassMethods
1912
1776
  extend ActiveSupport::Benchmarkable
1777
+ extend ActiveSupport::DescendantsTracker
1913
1778
 
1914
1779
  include ActiveModel::Conversion
1915
1780
  include Validations
@@ -1920,6 +1785,7 @@ module ActiveRecord #:nodoc:
1920
1785
  include AttributeMethods::PrimaryKey
1921
1786
  include AttributeMethods::TimeZoneConversion
1922
1787
  include AttributeMethods::Dirty
1788
+ include ActiveModel::MassAssignmentSecurity
1923
1789
  include Callbacks, ActiveModel::Observing, Timestamp
1924
1790
  include Associations, AssociationPreload, NamedScope
1925
1791