diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index 625a98a8f5..5eb64e75f1 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,7 @@
*SVN*
+* with_scope is protected. #8524 [Josh Peek]
+
* Quickref for association methods. #7723 [marclove, Mindsweeper]
* Calculations: return nil average instead of 0 when there are no rows to average. #8298 [davidw]
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index bda72637c3..4da3f15abe 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -85,14 +85,14 @@ module ActiveRecord
end
def create(attrs = {})
- record = @reflection.klass.with_scope(:create => construct_scope[:create]) { @reflection.klass.create(attrs) }
+ record = @reflection.klass.send(:with_scope, :create => construct_scope[:create]) { @reflection.klass.create(attrs) }
@target ||= [] unless loaded?
@target << record
record
end
def create!(attrs = {})
- record = @reflection.klass.with_scope(:create => construct_scope[:create]) { @reflection.klass.create!(attrs) }
+ record = @reflection.klass.send(:with_scope, :create => construct_scope[:create]) { @reflection.klass.create!(attrs) }
@target ||= [] unless loaded?
@target << record
record
@@ -161,7 +161,7 @@ module ActiveRecord
if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
super
else
- @reflection.klass.with_scope(construct_scope) { @reflection.klass.send(method, *args, &block) }
+ @reflection.klass.send(:with_scope, construct_scope) { @reflection.klass.send(method, *args, &block) }
end
end
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 93f1b2ee6a..7a55334912 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -57,7 +57,7 @@ module ActiveRecord
raise_on_type_mismatch(associate)
raise ActiveRecord::HasManyThroughCantAssociateNewRecords.new(@owner, through) unless associate.respond_to?(:new_record?) && !associate.new_record?
- @owner.send(@reflection.through_reflection.name).proxy_target << klass.with_scope(:create => construct_join_attributes(associate)) { klass.create! }
+ @owner.send(@reflection.through_reflection.name).proxy_target << klass.send(:with_scope, :create => construct_join_attributes(associate)) { klass.create! }
@target << associate if loaded?
end
end
@@ -91,7 +91,7 @@ module ActiveRecord
def create!(attrs = nil)
@reflection.klass.transaction do
- self << @reflection.klass.with_scope(:create => attrs) { @reflection.klass.create! }
+ self << @reflection.klass.send(:with_scope, :create => attrs) { @reflection.klass.create! }
end
end
@@ -105,7 +105,7 @@ module ActiveRecord
if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
super
else
- @reflection.klass.with_scope(construct_scope) { @reflection.klass.send(method, *args, &block) }
+ @reflection.klass.send(:with_scope, construct_scope) { @reflection.klass.send(method, *args, &block) }
end
end
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index b18169ab67..59e5486ad6 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -80,7 +80,7 @@ module ActiveRecord
# instance. Otherwise, if the target has not previously been loaded
# elsewhere, the instance we create will get orphaned.
load_target if replace_existing
- record = @reflection.klass.with_scope(:create => construct_scope[:create]) { yield @reflection.klass }
+ record = @reflection.klass.send(:with_scope, :create => construct_scope[:create]) { yield @reflection.klass }
if replace_existing
replace(record, true)
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 931afa61ba..54f3a8252d 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -947,91 +947,6 @@ module ActiveRecord #:nodoc:
logger.level = old_logger_level if logger
end
- # Scope parameters to method calls within the block. Takes a hash of method_name => parameters hash.
- # method_name may be :find or :create. :find parameters may include the :conditions, :joins,
- # :include, :offset, :limit, and :readonly options. :create parameters are an attributes hash.
- #
- # Article.with_scope(:find => { :conditions => "blog_id = 1" }, :create => { :blog_id => 1 }) do
- # Article.find(1) # => SELECT * from articles WHERE blog_id = 1 AND id = 1
- # a = Article.create(1)
- # a.blog_id # => 1
- # end
- #
- # In nested scopings, all previous parameters are overwritten by inner rule
- # except :conditions in :find, that are merged as hash.
- #
- # Article.with_scope(:find => { :conditions => "blog_id = 1", :limit => 1 }, :create => { :blog_id => 1 }) do
- # Article.with_scope(:find => { :limit => 10})
- # Article.find(:all) # => SELECT * from articles WHERE blog_id = 1 LIMIT 10
- # end
- # Article.with_scope(:find => { :conditions => "author_id = 3" })
- # Article.find(:all) # => SELECT * from articles WHERE blog_id = 1 AND author_id = 3 LIMIT 1
- # end
- # end
- #
- # You can ignore any previous scopings by using with_exclusive_scope method.
- #
- # Article.with_scope(:find => { :conditions => "blog_id = 1", :limit => 1 }) do
- # Article.with_exclusive_scope(:find => { :limit => 10 })
- # Article.find(:all) # => SELECT * from articles LIMIT 10
- # end
- # end
- def with_scope(method_scoping = {}, action = :merge, &block)
- method_scoping = method_scoping.method_scoping if method_scoping.respond_to?(:method_scoping)
-
- # Dup first and second level of hash (method and params).
- method_scoping = method_scoping.inject({}) do |hash, (method, params)|
- hash[method] = (params == true) ? params : params.dup
- hash
- end
-
- method_scoping.assert_valid_keys([ :find, :create ])
-
- if f = method_scoping[:find]
- f.assert_valid_keys([ :conditions, :joins, :select, :include, :from, :offset, :limit, :order, :readonly, :lock ])
- set_readonly_option! f
- end
-
- # Merge scopings
- if action == :merge && current_scoped_methods
- method_scoping = current_scoped_methods.inject(method_scoping) do |hash, (method, params)|
- case hash[method]
- when Hash
- if method == :find
- (hash[method].keys + params.keys).uniq.each do |key|
- merge = hash[method][key] && params[key] # merge if both scopes have the same key
- if key == :conditions && merge
- hash[method][key] = [params[key], hash[method][key]].collect{ |sql| "( %s )" % sanitize_sql(sql) }.join(" AND ")
- elsif key == :include && merge
- hash[method][key] = merge_includes(hash[method][key], params[key]).uniq
- else
- hash[method][key] = hash[method][key] || params[key]
- end
- end
- else
- hash[method] = params.merge(hash[method])
- end
- else
- hash[method] = params
- end
- hash
- end
- end
-
- self.scoped_methods << method_scoping
-
- begin
- yield
- ensure
- self.scoped_methods.pop
- end
- end
-
- # Works like with_scope, but discards any nested properties.
- def with_exclusive_scope(method_scoping = {}, &block)
- with_scope(method_scoping, :overwrite, &block)
- end
-
# Overwrite the default class equality method to provide support for association proxies.
def ===(object)
object.is_a?(self)
@@ -1409,6 +1324,103 @@ module ActiveRecord #:nodoc:
end
protected
+ # Scope parameters to method calls within the block. Takes a hash of method_name => parameters hash.
+ # method_name may be :find or :create. :find parameters may include the :conditions, :joins,
+ # :include, :offset, :limit, and :readonly options. :create parameters are an attributes hash.
+ #
+ # class Article < ActiveRecord::Base
+ # def self.create_with_scope
+ # with_scope(:find => { :conditions => "blog_id = 1" }, :create => { :blog_id => 1 }) do
+ # find(1) # => SELECT * from articles WHERE blog_id = 1 AND id = 1
+ # a = create(1)
+ # a.blog_id # => 1
+ # end
+ # end
+ # end
+ #
+ # In nested scopings, all previous parameters are overwritten by inner rule
+ # except :conditions in :find, that are merged as hash.
+ #
+ # class Article < ActiveRecord::Base
+ # def self.find_with_scope
+ # with_scope(:find => { :conditions => "blog_id = 1", :limit => 1 }, :create => { :blog_id => 1 }) do
+ # with_scope(:find => { :limit => 10})
+ # find(:all) # => SELECT * from articles WHERE blog_id = 1 LIMIT 10
+ # end
+ # with_scope(:find => { :conditions => "author_id = 3" })
+ # find(:all) # => SELECT * from articles WHERE blog_id = 1 AND author_id = 3 LIMIT 1
+ # end
+ # end
+ # end
+ # end
+ #
+ # You can ignore any previous scopings by using with_exclusive_scope method.
+ #
+ # class Article < ActiveRecord::Base
+ # def self.find_with_exclusive_scope
+ # with_scope(:find => { :conditions => "blog_id = 1", :limit => 1 }) do
+ # with_exclusive_scope(:find => { :limit => 10 })
+ # find(:all) # => SELECT * from articles LIMIT 10
+ # end
+ # end
+ # end
+ # end
+ def with_scope(method_scoping = {}, action = :merge, &block)
+ method_scoping = method_scoping.method_scoping if method_scoping.respond_to?(:method_scoping)
+
+ # Dup first and second level of hash (method and params).
+ method_scoping = method_scoping.inject({}) do |hash, (method, params)|
+ hash[method] = (params == true) ? params : params.dup
+ hash
+ end
+
+ method_scoping.assert_valid_keys([ :find, :create ])
+
+ if f = method_scoping[:find]
+ f.assert_valid_keys([ :conditions, :joins, :select, :include, :from, :offset, :limit, :order, :readonly, :lock ])
+ set_readonly_option! f
+ end
+
+ # Merge scopings
+ if action == :merge && current_scoped_methods
+ method_scoping = current_scoped_methods.inject(method_scoping) do |hash, (method, params)|
+ case hash[method]
+ when Hash
+ if method == :find
+ (hash[method].keys + params.keys).uniq.each do |key|
+ merge = hash[method][key] && params[key] # merge if both scopes have the same key
+ if key == :conditions && merge
+ hash[method][key] = [params[key], hash[method][key]].collect{ |sql| "( %s )" % sanitize_sql(sql) }.join(" AND ")
+ elsif key == :include && merge
+ hash[method][key] = merge_includes(hash[method][key], params[key]).uniq
+ else
+ hash[method][key] = hash[method][key] || params[key]
+ end
+ end
+ else
+ hash[method] = params.merge(hash[method])
+ end
+ else
+ hash[method] = params
+ end
+ hash
+ end
+ end
+
+ self.scoped_methods << method_scoping
+
+ begin
+ yield
+ ensure
+ self.scoped_methods.pop
+ end
+ end
+
+ # Works like with_scope, but discards any nested properties.
+ def with_exclusive_scope(method_scoping = {}, &block)
+ with_scope(method_scoping, :overwrite, &block)
+ end
+
def subclasses #:nodoc:
@@subclasses[self] ||= []
@@subclasses[self] + extra = @@subclasses[self].inject([]) {|list, subclass| list + subclass.subclasses }
diff --git a/activerecord/test/abstract_unit.rb b/activerecord/test/abstract_unit.rb
index c3d4dbe591..1317f8dae3 100755
--- a/activerecord/test/abstract_unit.rb
+++ b/activerecord/test/abstract_unit.rb
@@ -75,5 +75,10 @@ ActiveRecord::Base.connection.class.class_eval do
end
end
+# Make with_scope public for tests
+class << ActiveRecord::Base
+ public :with_scope, :with_exclusive_scope
+end
+
#ActiveRecord::Base.logger = Logger.new(STDOUT)
#ActiveRecord::Base.colorize_logging = false