with_scope is protected. Closes #8524.

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@6909 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
Jeremy Kemper 2007-05-30 21:40:55 +00:00
parent 962b12fb9f
commit 1edd21bb02
6 changed files with 111 additions and 92 deletions

@ -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]

@ -85,14 +85,14 @@ def destroy_all
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 @@ def method_missing(method, *args, &block)
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

@ -57,7 +57,7 @@ def <<(*records)
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 @@ def build(attrs = nil)
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 @@ def method_missing(method, *args, &block)
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

@ -80,7 +80,7 @@ def new_record(replace_existing)
# 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)

@ -947,91 +947,6 @@ def silence
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 <tt>:conditions</tt>, <tt>:joins</tt>,
# <tt>:include</tt>, <tt>:offset</tt>, <tt>:limit</tt>, and <tt>:readonly</tt> 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 <tt>with_exclusive_scope</tt> 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 @@ def define_attr_method(name, value=nil, &block)
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 <tt>:conditions</tt>, <tt>:joins</tt>,
# <tt>:include</tt>, <tt>:offset</tt>, <tt>:limit</tt>, and <tt>:readonly</tt> 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 <tt>with_exclusive_scope</tt> 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 }

@ -75,5 +75,10 @@ def execute_with_counting(sql, name = nil, &block)
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