aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG10
-rwxr-xr-xactiverecord/lib/active_record/associations.rb34
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb2
-rw-r--r--activerecord/lib/active_record/autosave_association.rb10
-rwxr-xr-xactiverecord/lib/active_record/base.rb10
-rw-r--r--activerecord/lib/active_record/calculations.rb59
-rw-r--r--activerecord/lib/active_record/callbacks.rb2
-rw-r--r--activerecord/lib/active_record/named_scope.rb153
-rw-r--r--activerecord/lib/active_record/railties/databases.rake2
-rw-r--r--activerecord/lib/active_record/railties/subscriber.rb2
-rw-r--r--activerecord/lib/active_record/relation.rb44
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb36
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb3
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb20
-rw-r--r--activerecord/lib/active_record/transactions.rb4
-rw-r--r--activerecord/lib/active_record/validations.rb12
-rw-r--r--activerecord/lib/generators/active_record.rb31
-rw-r--r--activerecord/lib/generators/active_record/migration/migration_generator.rb25
-rw-r--r--activerecord/lib/generators/active_record/migration/templates/migration.rb11
-rw-r--r--activerecord/lib/generators/active_record/model/model_generator.rb33
-rw-r--r--activerecord/lib/generators/active_record/model/templates/migration.rb16
-rw-r--r--activerecord/lib/generators/active_record/model/templates/model.rb5
-rw-r--r--activerecord/lib/generators/active_record/observer/observer_generator.rb15
-rw-r--r--activerecord/lib/generators/active_record/observer/templates/observer.rb2
-rw-r--r--activerecord/lib/generators/active_record/session_migration/session_migration_generator.rb24
-rw-r--r--activerecord/lib/generators/active_record/session_migration/templates/migration.rb16
-rw-r--r--activerecord/test/cases/autosave_association_test.rb12
-rwxr-xr-xactiverecord/test/cases/base_test.rb4
-rw-r--r--activerecord/test/cases/method_scoping_test.rb14
-rw-r--r--activerecord/test/cases/named_scope_test.rb43
-rw-r--r--activerecord/test/cases/relations_test.rb17
-rw-r--r--activerecord/test/cases/validations_test.rb29
-rw-r--r--activerecord/test/models/comment.rb6
-rw-r--r--activerecord/test/models/company.rb8
-rw-r--r--activerecord/test/models/developer.rb4
-rw-r--r--activerecord/test/models/organization.rb2
-rw-r--r--activerecord/test/models/person.rb4
-rw-r--r--activerecord/test/models/post.rb18
-rw-r--r--activerecord/test/models/reply.rb2
-rw-r--r--activerecord/test/models/topic.rb22
43 files changed, 472 insertions, 300 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index 38bcf0c787..ffff0b7e09 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,15 @@
*Edge*
+* Allow relations to be used as scope.
+
+ class Item
+ scope :red, where(:colour => 'red')
+ end
+
+ Item.red.limit(10) # Ten red items
+
+* Rename named_scope to scope. [Pratik Naik]
+
* Changed ActiveRecord::Base.store_full_sti_class to be true by default reflecting the previously announced Rails 3 default [DHH]
* Add Relation#except. [Pratik Naik]
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 468a6cd9f8..ebf1a41e85 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1701,30 +1701,19 @@ module ActiveRecord
end
def construct_finder_arel_with_included_associations(options, join_dependency)
- relation = unscoped
+ relation = scoped
for association in join_dependency.join_associations
relation = association.join_relation(relation)
end
- relation = relation.joins(options[:joins]).
- select(column_aliases(join_dependency)).
- group(options[:group]).
- having(options[:having]).
- order(options[:order]).
- where(options[:conditions]).
- from(options[:from])
+ relation = relation.apply_finder_options(options).select(column_aliases(join_dependency))
- scoped_relation = current_scoped_methods
- scoped_relation_limit = scoped_relation.taken if scoped_relation
-
- relation = current_scoped_methods.except(:limit).merge(relation) if current_scoped_methods
-
- if !using_limitable_reflections?(join_dependency.reflections) && ((scoped_relation && scoped_relation.taken) || options[:limit])
+ if !using_limitable_reflections?(join_dependency.reflections) && relation.limit_value
relation = relation.where(construct_arel_limited_ids_condition(options, join_dependency))
end
- relation = relation.limit(options[:limit] || scoped_relation_limit) if using_limitable_reflections?(join_dependency.reflections)
+ relation = relation.except(:limit, :offset) unless using_limitable_reflections?(join_dependency.reflections)
relation
end
@@ -1752,23 +1741,14 @@ module ActiveRecord
end
def construct_finder_sql_for_association_limiting(options, join_dependency)
- relation = unscoped
+ relation = scoped
for association in join_dependency.join_associations
relation = association.join_relation(relation)
end
- relation = relation.joins(options[:joins]).
- where(options[:conditions]).
- group(options[:group]).
- having(options[:having]).
- order(options[:order]).
- limit(options[:limit]).
- offset(options[:offset]).
- from(options[:from])
-
- relation = current_scoped_methods.except(:select, :includes, :eager_load).merge(relation) if current_scoped_methods
- relation = relation.select(connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", options[:order]))
+ relation = relation.apply_finder_options(options).except(:select)
+ relation = relation.select(connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", relation.order_values.join(", ")))
relation.to_sql
end
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index 64dd5cf629..e9402d3547 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -403,8 +403,6 @@ module ActiveRecord
else
super
end
- elsif @reflection.klass.scopes.include?(method)
- @reflection.klass.scopes[method].call(self, *args)
else
with_scope(construct_scope) do
if block_given?
diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
index bd05d1014c..7f39a189e4 100644
--- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
@@ -37,7 +37,7 @@ module ActiveRecord
if force
record.save!
else
- return false unless record.save(validate)
+ return false unless record.save(:validate => validate)
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index d3336cf2d2..146a6ca55f 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -58,7 +58,7 @@ module ActiveRecord
def insert_record(record, force = false, validate = true)
set_belongs_to_association_for(record)
- force ? record.save! : record.save(validate)
+ force ? record.save! : record.save(:validate => validate)
end
# Deletes the records according to the <tt>:dependent</tt> option.
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index 387b85aacd..bd2acd4340 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -60,7 +60,7 @@ module ActiveRecord
if force
record.save!
else
- return false unless record.save(validate)
+ return false unless record.save(:validate => validate)
end
end
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index e178cb4ef2..325a8aa7ec 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -116,14 +116,14 @@ module ActiveRecord
# post = Post.find(1)
# post.author.name = ''
# post.save # => false
- # post.errors # => #<ActiveRecord::Errors:0x174498c @errors={"author_name"=>["can't be blank"]}, @base=#<Post ...>>
+ # post.errors # => #<ActiveRecord::Errors:0x174498c @errors={"author.name"=>["can't be blank"]}, @base=#<Post ...>>
#
# No validations will be performed on the associated models when validations
# are skipped for the parent:
#
# post = Post.find(1)
# post.author.name = ''
- # post.save(false) # => true
+ # post.save(:validate => false) # => true
module AutosaveAssociation
extend ActiveSupport::Concern
@@ -302,7 +302,7 @@ module ActiveRecord
association.send(:insert_record, record)
end
elsif autosave
- saved = record.save(false)
+ saved = record.save(:validate => false)
end
raise ActiveRecord::Rollback if saved == false
@@ -332,7 +332,7 @@ module ActiveRecord
key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
if autosave != false && (new_record? || association.new_record? || association[reflection.primary_key_name] != key || autosave)
association[reflection.primary_key_name] = key
- saved = association.save(!autosave)
+ saved = association.save(:validate => !autosave)
raise ActiveRecord::Rollback if !saved && autosave
saved
end
@@ -355,7 +355,7 @@ module ActiveRecord
if autosave && association.marked_for_destruction?
association.destroy
elsif autosave != false
- saved = association.save(!autosave) if association.new_record? || autosave
+ saved = association.save(:validate => !autosave) if association.new_record? || autosave
if association.updated?
association_id = association.send(reflection.options[:primary_key] || :id)
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 4ee9887186..06244d1132 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -2136,16 +2136,16 @@ module ActiveRecord #:nodoc:
end
# :call-seq:
- # save(perform_validation = true)
+ # save(options)
#
# Saves the model.
#
# If the model is new a record gets created in the database, otherwise
# the existing record gets updated.
#
- # If +perform_validation+ is true validations run. If any of them fail
- # the action is cancelled and +save+ returns +false+. If the flag is
- # false validations are bypassed altogether. See
+ # By default, save always run validations. If any of them fail the action
+ # is cancelled and +save+ returns +false+. However, if you supply
+ # :validate => false, validations are bypassed altogether. See
# ActiveRecord::Validations for more information.
#
# There's a series of callbacks associated with +save+. If any of the
@@ -2220,7 +2220,7 @@ module ActiveRecord #:nodoc:
# in Base is replaced with this when the validations module is mixed in, which it is by default.
def update_attribute(name, value)
send(name.to_s + '=', value)
- save(false)
+ save(:validate => false)
end
# Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will
diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb
index e4b3caab4e..8a44dc7df1 100644
--- a/activerecord/lib/active_record/calculations.rb
+++ b/activerecord/lib/active_record/calculations.rb
@@ -46,19 +46,19 @@ module ActiveRecord
def count(*args)
case args.size
when 0
- construct_calculation_arel({}, current_scoped_methods).count
+ construct_calculation_arel.count
when 1
if args[0].is_a?(Hash)
options = args[0]
distinct = options.has_key?(:distinct) ? options.delete(:distinct) : false
- construct_calculation_arel(options, current_scoped_methods).count(options[:select], :distinct => distinct)
+ construct_calculation_arel(options).count(options[:select], :distinct => distinct)
else
- construct_calculation_arel({}, current_scoped_methods).count(args[0])
+ construct_calculation_arel.count(args[0])
end
when 2
column_name, options = args
distinct = options.has_key?(:distinct) ? options.delete(:distinct) : false
- construct_calculation_arel(options, current_scoped_methods).count(column_name, :distinct => distinct)
+ construct_calculation_arel(options).count(column_name, :distinct => distinct)
else
raise ArgumentError, "Unexpected parameters passed to count(): #{args.inspect}"
end
@@ -141,7 +141,7 @@ module ActiveRecord
# Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name) # Selects the minimum age for any family without any minors
# Person.sum("2 * age")
def calculate(operation, column_name, options = {})
- construct_calculation_arel(options, current_scoped_methods).calculate(operation, column_name, options.slice(:distinct))
+ construct_calculation_arel(options).calculate(operation, column_name, options.slice(:distinct))
rescue ThrowResult
0
end
@@ -151,60 +151,21 @@ module ActiveRecord
options.assert_valid_keys(CALCULATIONS_OPTIONS)
end
- def construct_calculation_arel(options = {}, merge_with_relation = nil)
+ def construct_calculation_arel(options = {})
validate_calculation_options(options)
options = options.except(:distinct)
- merge_with_includes = merge_with_relation ? merge_with_relation.includes_values : []
+ merge_with_includes = current_scoped_methods ? current_scoped_methods.includes_values : []
includes = (merge_with_includes + Array.wrap(options[:include])).uniq
if includes.any?
- merge_with_joins = merge_with_relation ? merge_with_relation.joins_values : []
+ merge_with_joins = current_scoped_methods ? current_scoped_methods.joins_values : []
joins = (merge_with_joins + Array.wrap(options[:joins])).uniq
join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, includes, construct_join(joins))
- construct_calculation_arel_with_included_associations(options, join_dependency, merge_with_relation)
+ construct_finder_arel_with_included_associations(options, join_dependency)
else
- relation = unscoped.apply_finder_options(options.slice(:joins, :conditions, :order, :limit, :offset, :group, :having))
-
- if merge_with_relation
- relation = merge_with_relation.except(:select, :order, :limit, :offset, :group, :from).merge(relation)
- end
-
- from = merge_with_relation.from_value if merge_with_relation && merge_with_relation.from_value.present?
- from = options[:from] if from.blank? && options[:from].present?
- relation = relation.from(from)
-
- select = options[:select].presence || (merge_with_relation ? merge_with_relation.select_values.join(", ") : nil)
- relation = relation.select(select)
-
- relation
- end
- end
-
- def construct_calculation_arel_with_included_associations(options, join_dependency, merge_with_relation = nil)
- relation = unscoped
-
- for association in join_dependency.join_associations
- relation = association.join_relation(relation)
+ scoped.apply_finder_options(options)
end
-
- if merge_with_relation
- relation.joins_values = (merge_with_relation.joins_values + relation.joins_values).uniq
- relation.where_values = merge_with_relation.where_values
-
- merge_limit = merge_with_relation.taken
- end
-
- relation = relation.apply_finder_options(options.slice(:joins, :group, :having, :order, :conditions, :from)).
- select(column_aliases(join_dependency))
-
- if !using_limitable_reflections?(join_dependency.reflections) && (merge_limit || options[:limit])
- relation = relation.where(construct_arel_limited_ids_condition(options, join_dependency))
- end
-
- relation = relation.limit(options[:limit] || merge_limit) if using_limitable_reflections?(join_dependency.reflections)
-
- relation
end
end
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index aecde5848c..1128286f2b 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -291,7 +291,7 @@ module ActiveRecord
end
def deprecated_callback_method(symbol) #:nodoc:
- if respond_to?(symbol)
+ if respond_to?(symbol, true)
ActiveSupport::Deprecation.warn("Overwriting #{symbol} in your models has been deprecated, please use Base##{symbol} :method_name instead")
send(symbol)
end
diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb
index 90fd700437..92030e5bfd 100644
--- a/activerecord/lib/active_record/named_scope.rb
+++ b/activerecord/lib/active_record/named_scope.rb
@@ -9,7 +9,7 @@ module ActiveRecord
module ClassMethods
# Returns a relation if invoked without any arguments.
#
- # posts = Post.scoped
+ # posts = Post.scoped
# posts.size # Fires "select count(*) from posts" and returns the count
# posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects
#
@@ -24,15 +24,9 @@ module ActiveRecord
# You can define a scope that applies to all finders using ActiveRecord::Base.default_scope.
def scoped(options = {}, &block)
if options.present?
- Scope.new(self, options, &block)
+ Scope.init(self, options, &block)
else
- current_scope = current_scoped_methods
-
- unless current_scope
- unscoped.spawn
- else
- construct_finder_arel({}, current_scoped_methods)
- end
+ current_scoped_methods ? unscoped.merge(current_scoped_methods) : unscoped.spawn
end
end
@@ -44,11 +38,11 @@ module ActiveRecord
# such as <tt>:conditions => {:color => :red}, :select => 'shirts.*', :include => :washing_instructions</tt>.
#
# class Shirt < ActiveRecord::Base
- # named_scope :red, :conditions => {:color => 'red'}
- # named_scope :dry_clean_only, :joins => :washing_instructions, :conditions => ['washing_instructions.dry_clean_only = ?', true]
+ # scope :red, :conditions => {:color => 'red'}
+ # scope :dry_clean_only, :joins => :washing_instructions, :conditions => ['washing_instructions.dry_clean_only = ?', true]
# end
- #
- # The above calls to <tt>named_scope</tt> define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red,
+ #
+ # The above calls to <tt>scope</tt> define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red,
# in effect, represents the query <tt>Shirt.find(:all, :conditions => {:color => 'red'})</tt>.
#
# Unlike <tt>Shirt.find(...)</tt>, however, the object returned by Shirt.red is not an Array; it resembles the association object
@@ -74,7 +68,7 @@ module ActiveRecord
# Named \scopes can also be procedural:
#
# class Shirt < ActiveRecord::Base
- # named_scope :colored, lambda { |color|
+ # scope :colored, lambda { |color|
# { :conditions => { :color => color } }
# }
# end
@@ -84,7 +78,7 @@ module ActiveRecord
# Named \scopes can also have extensions, just as with <tt>has_many</tt> declarations:
#
# class Shirt < ActiveRecord::Base
- # named_scope :red, :conditions => {:color => 'red'} do
+ # scope :red, :conditions => {:color => 'red'} do
# def dom_id
# 'red_shirts'
# end
@@ -96,18 +90,23 @@ module ActiveRecord
# <tt>proxy_options</tt> method on the proxy itself.
#
# class Shirt < ActiveRecord::Base
- # named_scope :colored, lambda { |color|
+ # scope :colored, lambda { |color|
# { :conditions => { :color => color } }
# }
# end
#
# expected_options = { :conditions => { :colored => 'red' } }
# assert_equal expected_options, Shirt.colored('red').proxy_options
- def named_scope(name, options = {}, &block)
+ def scope(name, options = {}, &block)
name = name.to_sym
+
+ if !scopes[name] && respond_to?(name, true)
+ raise ArgumentError, "Cannot define scope :#{name} because #{self.name}.#{name} method already exists."
+ end
+
scopes[name] = lambda do |parent_scope, *args|
- Scope.new(parent_scope, case options
- when Hash
+ Scope.init(parent_scope, case options
+ when Hash, Relation
options
when Proc
options.call(*args)
@@ -119,104 +118,90 @@ module ActiveRecord
end
end
end
- end
- class Scope
- attr_reader :proxy_scope, :proxy_options, :current_scoped_methods_when_defined
- NON_DELEGATE_METHODS = %w(nil? send object_id class extend find size count sum average maximum minimum paginate first last empty? any? many? respond_to?).to_set
- [].methods.each do |m|
- unless m =~ /^__/ || NON_DELEGATE_METHODS.include?(m.to_s)
- delegate m, :to => :proxy_found
- end
+ def named_scope(*args, &block)
+ ActiveSupport::Deprecation.warn("Base.named_scope has been deprecated, please use Base.scope instead", caller)
+ scope(*args, &block)
end
+ end
- delegate :scopes, :with_scope, :scoped_methods, :to => :proxy_scope
+ class Scope < Relation
+ attr_accessor :current_scoped_methods_when_defined
- def initialize(proxy_scope, options, &block)
- options ||= {}
- [options[:extend]].flatten.each { |extension| extend extension } if options[:extend]
- extend Module.new(&block) if block_given?
- unless Scope === proxy_scope
- @current_scoped_methods_when_defined = proxy_scope.send(:current_scoped_methods)
- end
- @proxy_scope, @proxy_options = proxy_scope, options.except(:extend)
- end
+ delegate :scopes, :with_scope, :with_exclusive_scope, :scoped_methods, :scoped, :to => :klass
- def reload
- load_found; self
- end
+ def self.init(klass, options, &block)
+ relation = new(klass, klass.arel_table)
- def first(*args)
- if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash))
- proxy_found.first(*args)
+ scope = if options.is_a?(Hash)
+ klass.scoped.apply_finder_options(options.except(:extend))
else
- find(:first, *args)
+ options ? klass.scoped.merge(options) : klass.scoped
end
- end
- def last(*args)
- if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash))
- proxy_found.last(*args)
- else
- find(:last, *args)
- end
- end
+ relation = relation.merge(scope)
- def size
- @found ? @found.length : count
- end
+ Array.wrap(options[:extend]).each {|extension| relation.send(:extend, extension) } if options.is_a?(Hash)
+ relation.send(:extend, Module.new(&block)) if block_given?
- def empty?
- @found ? @found.empty? : count.zero?
+ relation.current_scoped_methods_when_defined = klass.send(:current_scoped_methods)
+ relation
end
- def respond_to?(method, include_private = false)
- super || @proxy_scope.respond_to?(method, include_private)
+ def find(*args)
+ options = args.extract_options!
+ relation = options.present? ? apply_finder_options(options) : self
+
+ case args.first
+ when :first, :last, :all
+ relation.send(args.first)
+ else
+ options.present? ? relation.find(*args) : super
+ end
end
- def any?
- if block_given?
- proxy_found.any? { |*block_args| yield(*block_args) }
+ def first(*args)
+ if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash))
+ to_a.first(*args)
else
- !empty?
+ args.first.present? ? apply_finder_options(args.first).first : super
end
end
- # Returns true if the named scope has more than 1 matching record.
- def many?
- if block_given?
- proxy_found.many? { |*block_args| yield(*block_args) }
+ def last(*args)
+ if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash))
+ to_a.last(*args)
else
- size > 1
+ args.first.present? ? apply_finder_options(args.first).last : super
end
end
- protected
- def proxy_found
- @found || load_found
+ def count(*args)
+ options = args.extract_options!
+ options.present? ? apply_finder_options(options).count(*args) : super
+ end
+
+ def ==(other)
+ to_a == other.to_a
end
private
+
def method_missing(method, *args, &block)
- if scopes.include?(method)
- scopes[method].call(self, *args)
- else
- with_scope({:find => proxy_options, :create => proxy_options[:conditions].is_a?(Hash) ? proxy_options[:conditions] : {}}, :reverse_merge) do
- method = :new if method == :build
- if current_scoped_methods_when_defined && !scoped_methods.include?(current_scoped_methods_when_defined)
- with_scope current_scoped_methods_when_defined do
- proxy_scope.send(method, *args, &block)
- end
+ if klass.respond_to?(method)
+ with_scope(self) do
+ if current_scoped_methods_when_defined && !scoped_methods.include?(current_scoped_methods_when_defined) && !scopes.include?(method)
+ with_scope(current_scoped_methods_when_defined) { klass.send(method, *args, &block) }
else
- proxy_scope.send(method, *args, &block)
+ klass.send(method, *args, &block)
end
end
+ else
+ super
end
end
- def load_found
- @found = find(:all)
- end
end
+
end
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index b39e064e45..88974dd786 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -428,7 +428,7 @@ namespace :db do
task :create => :environment do
raise "Task unavailable to this database (no migration support)" unless ActiveRecord::Base.connection.supports_migrations?
require 'rails/generators'
- require 'rails/generators/rails/session_migration/session_migration_generator'
+ require 'generators/rails/session_migration/session_migration_generator'
Rails::Generators::SessionMigrationGenerator.start [ ENV["MIGRATION"] || "add_sessions_table" ]
end
diff --git a/activerecord/lib/active_record/railties/subscriber.rb b/activerecord/lib/active_record/railties/subscriber.rb
index 7c2a10cf0f..fd873dbff8 100644
--- a/activerecord/lib/active_record/railties/subscriber.rb
+++ b/activerecord/lib/active_record/railties/subscriber.rb
@@ -12,7 +12,7 @@ module ActiveRecord
name = color(name, :magenta, true)
end
- debug "#{name} #{sql}"
+ debug " #{name} #{sql}"
end
def odd?
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 85bf878416..e37e692a97 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -7,7 +7,7 @@ module ActiveRecord
include FinderMethods, CalculationMethods, SpawnMethods, QueryMethods
- delegate :length, :collect, :map, :each, :all?, :to => :to_a
+ delegate :length, :collect, :map, :each, :all?, :include?, :to => :to_a
attr_reader :table, :klass
@@ -20,6 +20,8 @@ module ActiveRecord
with_create_scope { @klass.new(*args, &block) }
end
+ alias build new
+
def create(*args, &block)
with_create_scope { @klass.create(*args, &block) }
end
@@ -43,33 +45,12 @@ module ActiveRecord
def to_a
return @records if loaded?
- find_with_associations = @eager_load_values.any? || (@includes_values.any? && references_eager_loaded_tables?)
-
- @records = if find_with_associations
- begin
- options = {
- :select => @select_values.any? ? @select_values.join(", ") : nil,
- :joins => arel.joins(arel),
- :group => @group_values.any? ? @group_values.join(", ") : nil,
- :order => order_clause,
- :conditions => where_clause,
- :limit => arel.taken,
- :offset => arel.skipped,
- :from => (arel.send(:from_clauses) if arel.send(:sources).present?)
- }
-
- including = (@eager_load_values + @includes_values).uniq
- join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, including, nil)
- @klass.send(:find_with_associations, options, join_dependency)
- rescue ThrowResult
- []
- end
- else
- @klass.find_by_sql(arel.to_sql)
- end
+ eager_loading = @eager_load_values.any? || (@includes_values.any? && references_eager_loaded_tables?)
+
+ @records = eager_loading ? find_with_associations : @klass.find_by_sql(arel.to_sql)
preload = @preload_values
- preload += @includes_values unless find_with_associations
+ preload += @includes_values unless eager_loading
preload.each {|associations| @klass.send(:preload_associations, @records, associations) }
# @readonly_value is true only if set explicity. @implicit_readonly is true if there are JOINS and no explicit SELECT.
@@ -124,12 +105,13 @@ module ActiveRecord
end
def reload
- @loaded = false
reset
+ to_a # force reload
+ self
end
def reset
- @first = @last = @to_sql = @order_clause = @scope_for_create = @arel = nil
+ @first = @last = @to_sql = @order_clause = @scope_for_create = @arel = @loaded = nil
@records = []
self
end
@@ -172,6 +154,8 @@ module ActiveRecord
end
end
+ private
+
def with_create_scope
@klass.send(:with_scope, :create => scope_for_create, :find => {}) { yield }
end
@@ -180,10 +164,6 @@ module ActiveRecord
arel.send(:where_clauses).join(join_string)
end
- def order_clause
- @order_clause ||= arel.send(:order_clauses).join(', ')
- end
-
def references_eager_loaded_tables?
joined_tables = (tables_in_string(arel.joins(arel)) + [table.name, table.table_alias]).compact.uniq
(tables_in_string(to_sql) - joined_tables).any?
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 3668b0997f..980c5796f3 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -44,6 +44,42 @@ module ActiveRecord
protected
+ def find_with_associations
+ including = (@eager_load_values + @includes_values).uniq
+ join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, including, nil)
+ rows = construct_relation_for_association_find(join_dependency).to_a
+ join_dependency.instantiate(rows)
+ rescue ThrowResult
+ []
+ end
+
+ def construct_relation_for_association_find(join_dependency)
+ relation = except(:includes, :eager_load, :preload, :select).select(@klass.send(:column_aliases, join_dependency))
+
+ for association in join_dependency.join_associations
+ relation = association.join_relation(relation)
+ end
+
+ limitable_reflections = @klass.send(:using_limitable_reflections?, join_dependency.reflections)
+
+ if !limitable_reflections && relation.limit_value
+ limited_id_condition = construct_limited_ids_condition(relation.except(:select))
+ relation = relation.where(limited_id_condition)
+ end
+
+ relation = relation.except(:limit, :offset) unless limitable_reflections
+
+ relation
+ end
+
+ def construct_limited_ids_condition(relation)
+ orders = relation.order_values.join(", ")
+ values = @klass.connection.distinct("#{@klass.connection.quote_table_name @klass.table_name}.#{@klass.primary_key}", orders)
+
+ ids_array = relation.select(values).collect {|row| row[@klass.primary_key]}
+ ids_array.empty? ? raise(ThrowResult) : primary_key.in(ids_array)
+ end
+
def find_by_attributes(match, attributes, *args)
conditions = attributes.inject({}) {|h, a| h[a] = args[attributes.index(a)]; h}
result = where(conditions).send(match.finder)
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 6b7d941350..9e855209f9 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -24,7 +24,8 @@ module ActiveRecord
case value
when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope
- attribute.in(value)
+ values = value.to_a
+ values.any? ? attribute.in(values) : attribute.eq(nil)
when Range
# TODO : Arel should handle ranges with excluded end.
if value.exclude_end?
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index f4abaae43e..d5b13c6100 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -1,7 +1,7 @@
module ActiveRecord
module SpawnMethods
def spawn(arel_table = self.table)
- relation = Relation.new(@klass, arel_table)
+ relation = self.class.new(@klass, arel_table)
(Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).each do |query_method|
relation.send(:"#{query_method}_values=", send(:"#{query_method}_values"))
@@ -15,11 +15,10 @@ module ActiveRecord
end
def merge(r)
- if r.klass != @klass
- raise ArgumentError, "Cannot merge a #{r.klass.name}(##{r.klass.object_id}) relation with #{@klass.name}(##{@klass.object_id}) relation"
- end
+ merged_relation = spawn
+ return merged_relation unless r
- merged_relation = spawn.eager_load(r.eager_load_values).preload(r.preload_values).includes(r.includes_values)
+ merged_relation = merged_relation.eager_load(r.eager_load_values).preload(r.preload_values).includes(r.includes_values)
merged_relation.readonly_value = r.readonly_value unless r.readonly_value.nil?
merged_relation.limit_value = r.limit_value if r.limit_value.present?
@@ -33,7 +32,7 @@ module ActiveRecord
from(r.from_value).
having(r.having_values)
- merged_relation.order_values = Array.wrap(order_values) + Array.wrap(r.order_values)
+ merged_relation.order_values = r.order_values if r.order_values.present?
merged_relation.create_with_value = @create_with_value
@@ -61,7 +60,7 @@ module ActiveRecord
alias :& :merge
def except(*skips)
- result = Relation.new(@klass, table)
+ result = self.class.new(@klass, table)
(Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).each do |method|
result.send(:"#{method}_values=", send(:"#{method}_values")) unless skips.include?(method)
@@ -75,7 +74,7 @@ module ActiveRecord
end
def only(*onlies)
- result = Relation.new(@klass, table)
+ result = self.class.new(@klass, table)
onlies.each do |only|
if (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).include?(only)
@@ -94,9 +93,10 @@ module ActiveRecord
:order, :select, :readonly, :group, :having, :from, :lock ]
def apply_finder_options(options)
- options.assert_valid_keys(VALID_FIND_OPTIONS)
-
relation = spawn
+ return relation unless options
+
+ options.assert_valid_keys(VALID_FIND_OPTIONS)
relation = relation.joins(options[:joins]).
where(options[:conditions]).
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 4f8ccdd40e..cf0fe8934d 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -192,8 +192,8 @@ module ActiveRecord
with_transaction_returning_status(:destroy_without_transactions)
end
- def save_with_transactions(perform_validation = true) #:nodoc:
- rollback_active_record_state! { with_transaction_returning_status(:save_without_transactions, perform_validation) }
+ def save_with_transactions(*args) #:nodoc:
+ rollback_active_record_state! { with_transaction_returning_status(:save_without_transactions, *args) }
end
def save_with_transactions! #:nodoc:
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index d5adcba3ba..a9743aa1ea 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -42,7 +42,17 @@ module ActiveRecord
module InstanceMethods
# The validation process on save can be skipped by passing false. The regular Base#save method is
# replaced with this when the validations module is mixed in, which it is by default.
- def save_with_validation(perform_validation = true)
+ def save_with_validation(options=nil)
+ perform_validation = case options
+ when NilClass
+ true
+ when Hash
+ options[:validate] != false
+ else
+ ActiveSupport::Deprecation.warn "save(#{options}) is deprecated, please give save(:validate => #{options}) instead", caller
+ options
+ end
+
if perform_validation && valid? || !perform_validation
save_without_validation
else
diff --git a/activerecord/lib/generators/active_record.rb b/activerecord/lib/generators/active_record.rb
new file mode 100644
index 0000000000..25b982f296
--- /dev/null
+++ b/activerecord/lib/generators/active_record.rb
@@ -0,0 +1,31 @@
+require 'rails/generators/named_base'
+require 'rails/generators/migration'
+require 'rails/generators/active_model'
+require 'active_record'
+
+module ActiveRecord
+ module Generators
+ class Base < Rails::Generators::NamedBase #:nodoc:
+ include Rails::Generators::Migration
+
+ def self.source_root
+ @_ar_source_root ||= begin
+ if base_name && generator_name
+ File.expand_path(File.join(base_name, generator_name, 'templates'), File.dirname(__FILE__))
+ end
+ end
+ end
+
+ protected
+ # Implement the required interface for Rails::Generators::Migration.
+ #
+ def next_migration_number(dirname) #:nodoc:
+ if ActiveRecord::Base.timestamped_migrations
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
+ else
+ "%.3d" % (current_migration_number(dirname) + 1)
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/generators/active_record/migration/migration_generator.rb b/activerecord/lib/generators/active_record/migration/migration_generator.rb
new file mode 100644
index 0000000000..7939977f72
--- /dev/null
+++ b/activerecord/lib/generators/active_record/migration/migration_generator.rb
@@ -0,0 +1,25 @@
+require 'generators/active_record'
+
+module ActiveRecord
+ module Generators
+ class MigrationGenerator < Base
+ argument :attributes, :type => :array, :default => [], :banner => "field:type field:type"
+
+ def create_migration_file
+ set_local_assigns!
+ migration_template "migration.rb", "db/migrate/#{file_name}.rb"
+ end
+
+ protected
+ attr_reader :migration_action
+
+ def set_local_assigns!
+ if file_name =~ /^(add|remove)_.*_(?:to|from)_(.*)/
+ @migration_action = $1
+ @table_name = $2.pluralize
+ end
+ end
+
+ end
+ end
+end
diff --git a/activerecord/lib/generators/active_record/migration/templates/migration.rb b/activerecord/lib/generators/active_record/migration/templates/migration.rb
new file mode 100644
index 0000000000..bbb7c53d86
--- /dev/null
+++ b/activerecord/lib/generators/active_record/migration/templates/migration.rb
@@ -0,0 +1,11 @@
+class <%= migration_class_name %> < ActiveRecord::Migration
+ def self.up<% attributes.each do |attribute| %>
+ <%= migration_action %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'add' %>, :<%= attribute.type %><% end -%>
+ <%- end %>
+ end
+
+ def self.down<% attributes.reverse.each do |attribute| %>
+ <%= migration_action == 'add' ? 'remove' : 'add' %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'remove' %>, :<%= attribute.type %><% end -%>
+ <%- end %>
+ end
+end
diff --git a/activerecord/lib/generators/active_record/model/model_generator.rb b/activerecord/lib/generators/active_record/model/model_generator.rb
new file mode 100644
index 0000000000..2641083e0d
--- /dev/null
+++ b/activerecord/lib/generators/active_record/model/model_generator.rb
@@ -0,0 +1,33 @@
+require 'generators/active_record'
+
+module ActiveRecord
+ module Generators
+ class ModelGenerator < Base
+ argument :attributes, :type => :array, :default => [], :banner => "field:type field:type"
+
+ check_class_collision
+
+ class_option :migration, :type => :boolean
+ class_option :timestamps, :type => :boolean
+ class_option :parent, :type => :string, :desc => "The parent class for the generated model"
+
+ def create_migration_file
+ return unless options[:migration] && options[:parent].nil?
+ migration_template "migration.rb", "db/migrate/create_#{table_name}.rb"
+ end
+
+ def create_model_file
+ template 'model.rb', File.join('app/models', class_path, "#{file_name}.rb")
+ end
+
+ hook_for :test_framework
+
+ protected
+
+ def parent_class_name
+ options[:parent] || "ActiveRecord::Base"
+ end
+
+ end
+ end
+end
diff --git a/activerecord/lib/generators/active_record/model/templates/migration.rb b/activerecord/lib/generators/active_record/model/templates/migration.rb
new file mode 100644
index 0000000000..1f68487304
--- /dev/null
+++ b/activerecord/lib/generators/active_record/model/templates/migration.rb
@@ -0,0 +1,16 @@
+class <%= migration_class_name %> < ActiveRecord::Migration
+ def self.up
+ create_table :<%= table_name %> do |t|
+<% for attribute in attributes -%>
+ t.<%= attribute.type %> :<%= attribute.name %>
+<% end -%>
+<% if options[:timestamps] %>
+ t.timestamps
+<% end -%>
+ end
+ end
+
+ def self.down
+ drop_table :<%= table_name %>
+ end
+end
diff --git a/activerecord/lib/generators/active_record/model/templates/model.rb b/activerecord/lib/generators/active_record/model/templates/model.rb
new file mode 100644
index 0000000000..21ae29e9f2
--- /dev/null
+++ b/activerecord/lib/generators/active_record/model/templates/model.rb
@@ -0,0 +1,5 @@
+class <%= class_name %> < <%= parent_class_name.classify %>
+<% attributes.select {|attr| attr.reference? }.each do |attribute| -%>
+ belongs_to :<%= attribute.name %>
+<% end -%>
+end
diff --git a/activerecord/lib/generators/active_record/observer/observer_generator.rb b/activerecord/lib/generators/active_record/observer/observer_generator.rb
new file mode 100644
index 0000000000..a6b57423b8
--- /dev/null
+++ b/activerecord/lib/generators/active_record/observer/observer_generator.rb
@@ -0,0 +1,15 @@
+require 'generators/active_record'
+
+module ActiveRecord
+ module Generators
+ class ObserverGenerator < Base
+ check_class_collision :suffix => "Observer"
+
+ def create_observer_file
+ template 'observer.rb', File.join('app/models', class_path, "#{file_name}_observer.rb")
+ end
+
+ hook_for :test_framework
+ end
+ end
+end
diff --git a/activerecord/lib/generators/active_record/observer/templates/observer.rb b/activerecord/lib/generators/active_record/observer/templates/observer.rb
new file mode 100644
index 0000000000..b9a3004161
--- /dev/null
+++ b/activerecord/lib/generators/active_record/observer/templates/observer.rb
@@ -0,0 +1,2 @@
+class <%= class_name %>Observer < ActiveRecord::Observer
+end
diff --git a/activerecord/lib/generators/active_record/session_migration/session_migration_generator.rb b/activerecord/lib/generators/active_record/session_migration/session_migration_generator.rb
new file mode 100644
index 0000000000..59c4792066
--- /dev/null
+++ b/activerecord/lib/generators/active_record/session_migration/session_migration_generator.rb
@@ -0,0 +1,24 @@
+require 'generators/active_record'
+
+module ActiveRecord
+ module Generators
+ class SessionMigrationGenerator < Base
+ argument :name, :type => :string, :default => "add_sessions_table"
+
+ def create_migration_file
+ migration_template "migration.rb", "db/migrate/#{file_name}.rb"
+ end
+
+ protected
+
+ def session_table_name
+ current_table_name = ActiveRecord::SessionStore::Session.table_name
+ if ["sessions", "session"].include?(current_table_name)
+ current_table_name = (ActiveRecord::Base.pluralize_table_names ? 'session'.pluralize : 'session')
+ end
+ current_table_name
+ end
+
+ end
+ end
+end
diff --git a/activerecord/lib/generators/active_record/session_migration/templates/migration.rb b/activerecord/lib/generators/active_record/session_migration/templates/migration.rb
new file mode 100644
index 0000000000..919822af7b
--- /dev/null
+++ b/activerecord/lib/generators/active_record/session_migration/templates/migration.rb
@@ -0,0 +1,16 @@
+class <%= migration_class_name %> < ActiveRecord::Migration
+ def self.up
+ create_table :<%= session_table_name %> do |t|
+ t.string :session_id, :null => false
+ t.text :data
+ t.timestamps
+ end
+
+ add_index :<%= session_table_name %>, :session_id
+ add_index :<%= session_table_name %>, :updated_at
+ end
+
+ def self.down
+ drop_table :<%= session_table_name %>
+ end
+end
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index cc36a6dc5b..cc5460ddb7 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -805,7 +805,7 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
def test_should_still_allow_to_bypass_validations_on_the_associated_model
@pirate.catchphrase = ''
@pirate.ship.name = ''
- @pirate.save(false)
+ @pirate.save(:validate => false)
# Oracle saves empty string as NULL
if current_adapter?(:OracleAdapter)
assert_equal [nil, nil], [@pirate.reload.catchphrase, @pirate.ship.name]
@@ -820,7 +820,7 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
@pirate.catchphrase = ''
@pirate.ship.name = ''
@pirate.ship.parts.each { |part| part.name = '' }
- @pirate.save(false)
+ @pirate.save(:validate => false)
values = [@pirate.reload.catchphrase, @pirate.ship.name, *@pirate.ship.parts.map(&:name)]
# Oracle saves empty string as NULL
@@ -917,7 +917,7 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
def test_should_still_allow_to_bypass_validations_on_the_associated_model
@ship.pirate.catchphrase = ''
@ship.name = ''
- @ship.save(false)
+ @ship.save(:validate => false)
# Oracle saves empty string as NULL
if current_adapter?(:OracleAdapter)
assert_equal [nil, nil], [@ship.reload.name, @ship.pirate.catchphrase]
@@ -1029,7 +1029,7 @@ module AutosaveAssociationOnACollectionAssociationTests
@pirate.catchphrase = ''
@pirate.send(@association_name).each { |child| child.name = '' }
- assert @pirate.save(false)
+ assert @pirate.save(:validate => false)
# Oracle saves empty string as NULL
if current_adapter?(:OracleAdapter)
assert_equal [nil, nil, nil], [
@@ -1049,14 +1049,14 @@ module AutosaveAssociationOnACollectionAssociationTests
def test_should_validation_the_associated_models_on_create
assert_no_difference("#{ @association_name == :birds ? 'Bird' : 'Parrot' }.count") do
2.times { @pirate.send(@association_name).build }
- @pirate.save(true)
+ @pirate.save
end
end
def test_should_allow_to_bypass_validations_on_the_associated_models_on_create
assert_difference("#{ @association_name == :birds ? 'Bird' : 'Parrot' }.count", +2) do
2.times { @pirate.send(@association_name).build }
- @pirate.save(false)
+ @pirate.save(:validate => false)
end
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index aea6aed8d9..1441b4278d 100755
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -1865,7 +1865,9 @@ class BasicsTest < ActiveRecord::TestCase
end
assert scoped_developers.include?(developers(:poor_jamis))
assert scoped_developers.include?(developers(:david))
- assert scoped_developers.include?(developers(:dev_10))
+ assert ! scoped_developers.include?(developers(:jamis))
+ assert_equal 3, scoped_developers.size
+
# Test without scoped find conditions to ensure we get the right thing
developers = Developer.find(:all, :order => 'id', :limit => 1)
assert scoped_developers.include?(developers(:david))
diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb
index 26aa3ed8d5..7ca5b5a988 100644
--- a/activerecord/test/cases/method_scoping_test.rb
+++ b/activerecord/test/cases/method_scoping_test.rb
@@ -608,7 +608,7 @@ class DefaultScopingTest < ActiveRecord::TestCase
def test_default_scoping_with_threads
2.times do
- Thread.new { assert_equal 'salary DESC', DeveloperOrderedBySalary.scoped.send(:order_clause) }.join
+ Thread.new { assert_equal ['salary DESC'], DeveloperOrderedBySalary.scoped.order_values }.join
end
end
@@ -618,28 +618,28 @@ class DefaultScopingTest < ActiveRecord::TestCase
klass.send :default_scope, {}
# Scopes added on children should append to parent scope
- assert klass.scoped.send(:order_clause).blank?
+ assert klass.scoped.order_values.blank?
# Parent should still have the original scope
- assert_equal 'salary DESC', DeveloperOrderedBySalary.scoped.send(:order_clause)
+ assert_equal ['salary DESC'], DeveloperOrderedBySalary.scoped.order_values
end
def test_method_scope
- expected = Developer.find(:all, :order => 'name DESC, salary DESC').collect { |dev| dev.salary }
+ expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary }
received = DeveloperOrderedBySalary.all_ordered_by_name.collect { |dev| dev.salary }
assert_equal expected, received
end
def test_nested_scope
- expected = Developer.find(:all, :order => 'name DESC, salary DESC').collect { |dev| dev.salary }
+ expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary }
received = DeveloperOrderedBySalary.send(:with_scope, :find => { :order => 'name DESC'}) do
DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
end
assert_equal expected, received
end
- def test_named_scope_order_appended_to_default_scope_order
- expected = Developer.find(:all, :order => 'name DESC, salary DESC').collect { |dev| dev.name }
+ def test_named_scope_overwrites_default
+ expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.name }
received = DeveloperOrderedBySalary.by_name.find(:all).collect { |dev| dev.name }
assert_equal expected, received
end
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index 5d9232bc52..3e2bd58f9a 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -31,7 +31,7 @@ class NamedScopeTest < ActiveRecord::TestCase
def test_reload_expires_cache_of_found_items
all_posts = Topic.base
- all_posts.inspect
+ all_posts.all
new_post = Topic.create!
assert !all_posts.include?(new_post)
@@ -48,14 +48,14 @@ class NamedScopeTest < ActiveRecord::TestCase
end
def test_scope_should_respond_to_own_methods_and_methods_of_the_proxy
- assert Topic.approved.respond_to?(:proxy_found)
+ assert Topic.approved.respond_to?(:limit)
assert Topic.approved.respond_to?(:count)
assert Topic.approved.respond_to?(:length)
end
def test_respond_to_respects_include_private_parameter
- assert !Topic.approved.respond_to?(:load_found)
- assert Topic.approved.respond_to?(:load_found, true)
+ assert !Topic.approved.respond_to?(:with_create_scope)
+ assert Topic.approved.respond_to?(:with_create_scope, true)
end
def test_subclasses_inherit_scopes
@@ -150,13 +150,13 @@ class NamedScopeTest < ActiveRecord::TestCase
end
def test_named_scopes_honor_current_scopes_from_when_defined
- assert !Post.ranked_by_comments.limit(5).empty?
- assert !authors(:david).posts.ranked_by_comments.limit(5).empty?
- assert_not_equal Post.ranked_by_comments.limit(5), authors(:david).posts.ranked_by_comments.limit(5)
+ assert !Post.ranked_by_comments.limit_by(5).empty?
+ assert !authors(:david).posts.ranked_by_comments.limit_by(5).empty?
+ assert_not_equal Post.ranked_by_comments.limit_by(5), authors(:david).posts.ranked_by_comments.limit_by(5)
assert_not_equal Post.top(5), authors(:david).posts.top(5)
# Oracle sometimes sorts differently if WHERE condition is changed
- assert_equal authors(:david).posts.ranked_by_comments.limit(5).sort_by(&:id), authors(:david).posts.top(5).sort_by(&:id)
- assert_equal Post.ranked_by_comments.limit(5), Post.top(5)
+ assert_equal authors(:david).posts.ranked_by_comments.limit_by(5).to_a.sort_by(&:id), authors(:david).posts.top(5).to_a.sort_by(&:id)
+ assert_equal Post.ranked_by_comments.limit_by(5), Post.top(5)
end
def test_active_records_have_scope_named__all__
@@ -171,11 +171,6 @@ class NamedScopeTest < ActiveRecord::TestCase
assert_equal Topic.find(:all, scope), Topic.scoped(scope)
end
- def test_proxy_options
- expected_proxy_options = { :conditions => { :approved => true } }
- assert_equal expected_proxy_options, Topic.approved.proxy_options
- end
-
def test_first_and_last_should_support_find_options
assert_equal Topic.base.first(:order => 'title'), Topic.base.find(:first, :order => 'title')
assert_equal Topic.base.last(:order => 'title'), Topic.base.find(:last, :order => 'title')
@@ -297,7 +292,7 @@ class NamedScopeTest < ActiveRecord::TestCase
end
def test_find_all_should_behave_like_select
- assert_equal Topic.base.select(&:approved), Topic.base.find_all(&:approved)
+ assert_equal Topic.base.to_a.select(&:approved), Topic.base.to_a.find_all(&:approved)
end
def test_rand_should_select_a_random_object_from_proxy
@@ -345,14 +340,14 @@ class NamedScopeTest < ActiveRecord::TestCase
def test_chaining_should_use_latest_conditions_when_searching
# Normal hash conditions
- assert_equal Topic.where(:approved => true).to_a, Topic.rejected.approved.all.to_a
- assert_equal Topic.where(:approved => false).to_a, Topic.approved.rejected.all.to_a
+ assert_equal Topic.where(:approved => true).to_a, Topic.rejected.approved.all
+ assert_equal Topic.where(:approved => false).to_a, Topic.approved.rejected.all
# Nested hash conditions with same keys
- assert_equal [posts(:sti_comments)], Post.with_special_comments.with_very_special_comments.all.to_a
+ assert_equal [posts(:sti_comments)], Post.with_special_comments.with_very_special_comments.all
# Nested hash conditions with different keys
- assert_equal [posts(:sti_comments)], Post.with_special_comments.with_post(4).all.to_a.uniq
+ assert_equal [posts(:sti_comments)], Post.with_special_comments.with_post(4).all.uniq
end
def test_named_scopes_batch_finders
@@ -374,6 +369,16 @@ class NamedScopeTest < ActiveRecord::TestCase
Comment.for_first_post.for_first_author.all
end
end
+
+ def test_named_scopes_with_reserved_names
+ [:where, :with_scope].each do |protected_method|
+ assert_raises(ArgumentError) { Topic.scope protected_method }
+ end
+ end
+
+ def test_deprecated_named_scope_method
+ assert_deprecated('named_scope has been deprecated') { Topic.named_scope :deprecated_named_scope }
+ end
end
class DynamicScopeMatchTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 195889f1df..d34c9b4895 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -68,10 +68,12 @@ class RelationTest < ActiveRecord::TestCase
assert topics.loaded?
- topics.reload
- assert ! topics.loaded?
+ original_size = topics.to_a.size
+ Topic.create! :title => 'fake'
- assert_queries(1) { topics.to_a }
+ assert_queries(1) { topics.reload }
+ assert_equal original_size + 1, topics.size
+ assert topics.loaded?
end
def test_finding_with_conditions
@@ -337,6 +339,11 @@ class RelationTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::RecordNotFound) { authors.find(['42', 43]) }
end
+ def test_find_in_empty_array
+ authors = Author.scoped.where(:id => [])
+ assert authors.all.blank?
+ end
+
def test_exists
davids = Author.where(:name => 'David')
assert davids.exists?
@@ -418,10 +425,6 @@ class RelationTest < ActiveRecord::TestCase
end
end
- def test_invalid_merge
- assert_raises(ArgumentError) { Post.scoped & Developer.scoped }
- end
-
def test_count
posts = Post.scoped
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index 3a1d5ae212..5aac0229cd 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -19,7 +19,7 @@ end
class DeprecatedPerson < ActiveRecord::Base
set_table_name 'people'
- protected
+ private
def validate
errors[:name] << "always invalid"
@@ -124,7 +124,15 @@ class ValidationsTest < ActiveRecord::TestCase
def test_create_without_validation
reply = WrongReply.new
assert !reply.save
- assert reply.save(false)
+ assert reply.save(:validate => false)
+ end
+
+ def test_deprecated_create_without_validation
+ reply = WrongReply.new
+ assert !reply.save
+ assert_deprecated do
+ assert reply.save(false)
+ end
end
def test_create_without_validation_bang
@@ -153,4 +161,21 @@ class ValidationsTest < ActiveRecord::TestCase
topic = Topic.create("author_name" => "Dan Brown")
assert_equal "Dan Brown", topic["author_name"]
end
+
+ def test_validate_is_deprecated_on_create
+ p = DeprecatedPerson.new
+ assert_deprecated do
+ assert !p.valid?
+ end
+ assert_equal ["always invalid", "invalid on create"], p.errors[:name]
+ end
+
+ def test_validate_is_deprecated_on_update
+ p = DeprecatedPerson.new(:first_name => "David")
+ assert p.save(:validate => false)
+ assert_deprecated do
+ assert !p.valid?
+ end
+ assert_equal ["always invalid", "invalid on update"], p.errors[:name]
+ end
end
diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb
index 399dea9f12..a8a99f6dce 100644
--- a/activerecord/test/models/comment.rb
+++ b/activerecord/test/models/comment.rb
@@ -1,7 +1,7 @@
class Comment < ActiveRecord::Base
- named_scope :containing_the_letter_e, :conditions => "comments.body LIKE '%e%'"
- named_scope :for_first_post, :conditions => { :post_id => 1 }
- named_scope :for_first_author,
+ scope :containing_the_letter_e, :conditions => "comments.body LIKE '%e%'"
+ scope :for_first_post, :conditions => { :post_id => 1 }
+ scope :for_first_author,
:joins => :post,
:conditions => { "posts.author_id" => 1 }
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index 7e93fda1eb..df5fd10b6b 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -45,7 +45,7 @@ class Firm < Company
has_many :unvalidated_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :validate => false
has_many :dependent_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :destroy
has_many :exclusively_dependent_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :delete_all
- has_many :limited_clients, :class_name => "Client", :order => "id", :limit => 1
+ has_many :limited_clients, :class_name => "Client", :limit => 1
has_many :clients_like_ms, :conditions => "name = 'Microsoft'", :class_name => "Client", :order => "id"
has_many :clients_with_interpolated_conditions, :class_name => "Client", :conditions => 'rating > #{rating}'
has_many :clients_like_ms_with_hash_conditions, :conditions => { :name => 'Microsoft' }, :class_name => "Client", :order => "id"
@@ -91,10 +91,8 @@ class Firm < Company
end
class DependentFirm < Company
- # added order by id as in fixtures there are two accounts for Rails Core
- # Oracle tests were failing because of that as the second fixture was selected
- has_one :account, :foreign_key => "firm_id", :dependent => :nullify, :order => "id"
- has_many :companies, :foreign_key => 'client_of', :order => "id", :dependent => :nullify
+ has_one :account, :foreign_key => "firm_id", :dependent => :nullify
+ has_many :companies, :foreign_key => 'client_of', :dependent => :nullify
end
class Client < Company
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index 058970336b..e7a1e110d7 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -43,7 +43,7 @@ class Developer < ActiveRecord::Base
has_many :audit_logs
- named_scope :jamises, :conditions => {:name => 'Jamis'}
+ scope :jamises, :conditions => {:name => 'Jamis'}
validates_inclusion_of :salary, :in => 50000..200000
validates_length_of :name, :within => 3..20
@@ -81,7 +81,7 @@ end
class DeveloperOrderedBySalary < ActiveRecord::Base
self.table_name = 'developers'
default_scope :order => 'salary DESC'
- named_scope :by_name, :order => 'name DESC'
+ scope :by_name, :order => 'name DESC'
def self.all_ordered_by_name
with_scope(:find => { :order => 'name DESC' }) do
diff --git a/activerecord/test/models/organization.rb b/activerecord/test/models/organization.rb
index c85726169e..1da342a0bd 100644
--- a/activerecord/test/models/organization.rb
+++ b/activerecord/test/models/organization.rb
@@ -2,5 +2,5 @@ class Organization < ActiveRecord::Base
has_many :member_details
has_many :members, :through => :member_details
- named_scope :clubs, { :from => 'clubs' }
+ scope :clubs, { :from => 'clubs' }
end \ No newline at end of file
diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb
index 57fa6418f1..2a73b1ee01 100644
--- a/activerecord/test/models/person.rb
+++ b/activerecord/test/models/person.rb
@@ -12,6 +12,6 @@ class Person < ActiveRecord::Base
has_many :agents, :class_name => 'Person', :foreign_key => 'primary_contact_id'
belongs_to :number1_fan, :class_name => 'Person'
- named_scope :males, :conditions => { :gender => 'M' }
- named_scope :females, :conditions => { :gender => 'F' }
+ scope :males, :conditions => { :gender => 'M' }
+ scope :females, :conditions => { :gender => 'F' }
end
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 7392515ec7..f48b35486c 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -1,8 +1,8 @@
class Post < ActiveRecord::Base
- named_scope :containing_the_letter_a, :conditions => "body LIKE '%a%'"
- named_scope :ranked_by_comments, :order => "comments_count DESC"
- named_scope :limit, lambda {|limit| {:limit => limit} }
- named_scope :with_authors_at_address, lambda { |address| {
+ scope :containing_the_letter_a, where("body LIKE '%a%'")
+ scope :ranked_by_comments, order("comments_count DESC")
+ scope :limit_by, lambda {|l| limit(l) }
+ scope :with_authors_at_address, lambda { |address| {
:conditions => [ 'authors.author_address_id = ?', address.id ],
:joins => 'JOIN authors ON authors.id = posts.author_id'
}
@@ -19,13 +19,13 @@ class Post < ActiveRecord::Base
has_one :last_comment, :class_name => 'Comment', :order => 'id desc'
- named_scope :with_special_comments, :joins => :comments, :conditions => {:comments => {:type => 'SpecialComment'} }
- named_scope :with_very_special_comments, :joins => :comments, :conditions => {:comments => {:type => 'VerySpecialComment'} }
- named_scope :with_post, lambda {|post_id|
+ scope :with_special_comments, :joins => :comments, :conditions => {:comments => {:type => 'SpecialComment'} }
+ scope :with_very_special_comments, joins(:comments).where(:comments => {:type => 'VerySpecialComment'})
+ scope :with_post, lambda {|post_id|
{ :joins => :comments, :conditions => {:comments => {:post_id => post_id} } }
}
- has_many :comments, :order => "body" do
+ has_many :comments do
def find_most_recent
find(:first, :order => "id DESC")
end
@@ -73,7 +73,7 @@ class Post < ActiveRecord::Base
has_many :impatient_people, :through => :skimmers, :source => :person
def self.top(limit)
- ranked_by_comments.limit(limit)
+ ranked_by_comments.limit_by(limit)
end
def self.reset_log
diff --git a/activerecord/test/models/reply.rb b/activerecord/test/models/reply.rb
index f1ba45b528..264a49b465 100644
--- a/activerecord/test/models/reply.rb
+++ b/activerecord/test/models/reply.rb
@@ -1,7 +1,7 @@
require 'models/topic'
class Reply < Topic
- named_scope :base
+ scope :base
belongs_to :topic, :foreign_key => "parent_id", :counter_cache => true
belongs_to :topic_with_primary_key, :class_name => "Topic", :primary_key => "title", :foreign_key => "parent_title", :counter_cache => "replies_count"
diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb
index baca4972cb..91fc7c9416 100644
--- a/activerecord/test/models/topic.rb
+++ b/activerecord/test/models/topic.rb
@@ -1,19 +1,19 @@
class Topic < ActiveRecord::Base
- named_scope :base
- named_scope :written_before, lambda { |time|
+ scope :base
+ scope :written_before, lambda { |time|
if time
{ :conditions => ['written_on < ?', time] }
end
}
- named_scope :approved, :conditions => {:approved => true}
- named_scope :rejected, :conditions => {:approved => false}
+ scope :approved, :conditions => {:approved => true}
+ scope :rejected, :conditions => {:approved => false}
- named_scope :by_lifo, :conditions => {:author_name => 'lifo'}
+ scope :by_lifo, :conditions => {:author_name => 'lifo'}
- named_scope :approved_as_hash_condition, :conditions => {:topics => {:approved => true}}
- named_scope 'approved_as_string', :conditions => {:approved => true}
- named_scope :replied, :conditions => ['replies_count > 0']
- named_scope :anonymous_extension do
+ scope :approved_as_hash_condition, :conditions => {:topics => {:approved => true}}
+ scope 'approved_as_string', :conditions => {:approved => true}
+ scope :replied, :conditions => ['replies_count > 0']
+ scope :anonymous_extension do
def one
1
end
@@ -33,8 +33,8 @@ class Topic < ActiveRecord::Base
2
end
end
- named_scope :named_extension, :extend => NamedExtension
- named_scope :multiple_extensions, :extend => [MultipleExtensionTwo, MultipleExtensionOne]
+ scope :named_extension, :extend => NamedExtension
+ scope :multiple_extensions, :extend => [MultipleExtensionTwo, MultipleExtensionOne]
has_many :replies, :dependent => :destroy, :foreign_key => "parent_id"
has_many :replies_with_primary_key, :class_name => "Reply", :dependent => :destroy, :primary_key => "title", :foreign_key => "parent_title"