2013-03-08 07:23:32 +08:00
|
|
|
if Rails.version < '3'
|
|
|
|
|
|
|
|
ActiveRecord::Base.class_eval do
|
|
|
|
class << self
|
|
|
|
# taken from fake_arel, and extended further to support combining of :select and :group
|
|
|
|
def with_scope(method_scoping = {}, action = :merge, &block)
|
|
|
|
method_scoping = {:find => method_scoping.proxy_options} if method_scoping.class == ActiveRecord::NamedScope::Scope
|
|
|
|
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(VALID_FIND_OPTIONS)
|
|
|
|
set_readonly_option! f
|
|
|
|
end
|
|
|
|
|
|
|
|
# Merge scopings
|
|
|
|
if [:merge, :reverse_merge].include?(action) && 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
|
|
|
|
if params[key].is_a?(Hash) && hash[method][key].is_a?(Hash)
|
|
|
|
hash[method][key] = merge_conditions(hash[method][key].deep_merge(params[key]))
|
|
|
|
else
|
|
|
|
hash[method][key] = merge_conditions(params[key], hash[method][key])
|
|
|
|
end
|
|
|
|
elsif key == :include && merge
|
|
|
|
hash[method][key] = merge_includes(hash[method][key], params[key]).uniq
|
|
|
|
elsif key == :joins && merge
|
|
|
|
hash[method][key] = merge_joins(params[key], hash[method][key])
|
|
|
|
# see https://rails.lighthouseapp.com/projects/8994/tickets/2810-with_scope-should-accept-and-use-order-option
|
|
|
|
# it works now in reverse order to comply with ActiveRecord 3
|
|
|
|
elsif [:order, :select, :group].include?(key) && merge && !default_scoping.any?{ |s| s[method].keys.include?(key) }
|
|
|
|
hash[method][key] = [hash[method][key], params[key]].select{|o| !o.blank?}.join(', ')
|
|
|
|
else
|
|
|
|
hash[method][key] = hash[method][key] || params[key]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
else
|
|
|
|
if action == :reverse_merge
|
|
|
|
hash[method] = hash[method].merge(params)
|
|
|
|
else
|
|
|
|
hash[method] = params.merge(hash[method])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
else
|
|
|
|
hash[method] = params
|
|
|
|
end
|
|
|
|
hash
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
self.scoped_methods << method_scoping
|
|
|
|
begin
|
|
|
|
yield
|
|
|
|
ensure
|
|
|
|
self.scoped_methods.pop
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# returns a new scope, having removed the options mentioned
|
|
|
|
# does *not* support extended scopes
|
|
|
|
def except(*options)
|
|
|
|
# include is renamed to includes in Rails 3
|
|
|
|
includes = options.delete(:includes)
|
|
|
|
options << :include if includes
|
|
|
|
|
|
|
|
new_options = (scope(:find) || {}).reject { |k, v| options.include?(k) }
|
|
|
|
with_exclusive_scope(:find => new_options) { scoped }
|
|
|
|
end
|
|
|
|
|
|
|
|
# returns a new scope, with just the order replaced
|
|
|
|
# does *not* support extended scopes
|
|
|
|
def reorder(*order)
|
|
|
|
new_options = (scope(:find) || {}).dup
|
|
|
|
new_options[:order] = order.flatten.join(',')
|
|
|
|
with_exclusive_scope(:find =>new_options) { scoped }
|
|
|
|
end
|
|
|
|
|
|
|
|
def uniq(value = true)
|
|
|
|
current_select = scope(:find, :select)
|
|
|
|
if current_select.blank?
|
|
|
|
return scoped unless value
|
|
|
|
return select("DISTINCT #{quoted_table_name}.*")
|
|
|
|
end
|
|
|
|
|
|
|
|
match = current_select =~ /^\s*DISTINCT\s+(.+)/i
|
|
|
|
if match && !value
|
|
|
|
new_options = scope(:find).dup
|
|
|
|
new_options[:select] = $1
|
|
|
|
with_exclusive_scope(:find => new_options) { scoped }
|
|
|
|
elsif !match && value
|
|
|
|
new_options = scope(:find).dup
|
|
|
|
new_options[:select] = "DISTINCT #{current_select}"
|
|
|
|
with_exclusive_scope(:find => new_options) { scoped }
|
|
|
|
else
|
|
|
|
scoped
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def pluck(column)
|
|
|
|
new_options = (scope(:find) || {}).dup
|
|
|
|
new_options[:select] = "#{quoted_table_name}.#{column}"
|
2013-03-19 03:07:47 +08:00
|
|
|
includes = new_options.delete(:include)
|
|
|
|
with_exclusive_scope(:find => new_options) { joins(includes).all.map(&column) }
|
2013-03-08 07:23:32 +08:00
|
|
|
end
|
|
|
|
end
|
2013-03-21 02:28:15 +08:00
|
|
|
|
|
|
|
# support 0 arguments
|
|
|
|
named_scope :lock, lambda { |*lock| lock = [true] if lock.empty?; {:lock => lock.first} }
|
2013-03-08 07:23:32 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
ActiveRecord::NamedScope::ClassMethods.module_eval do
|
|
|
|
# make all arguments optional, like Rails 3
|
|
|
|
def scoped(scope = {}, &block)
|
|
|
|
ActiveRecord::NamedScope::Scope.new(self, scope, &block)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
ActiveRecord::NamedScope::Scope.class_eval do
|
|
|
|
# Scope delegates this to proxy_found because it's an array method; remove
|
|
|
|
# the delegation and let it go through normal method_missing to the model
|
|
|
|
remove_method :uniq
|
|
|
|
|
|
|
|
# fake_arel doesn't quite work right here - somehow it's getting
|
|
|
|
# Kernel#select. so just duplicate the select we want
|
|
|
|
def select(value = Proc.new)
|
|
|
|
if block_given?
|
|
|
|
all.select {|*block_args| value.call(*block_args) }
|
|
|
|
else
|
|
|
|
self.scoped(:select => Array.wrap(value).join(','))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
ActiveRecord::Associations::AssociationCollection.class_eval do
|
|
|
|
# AssociationCollection implements uniq for :uniq option, in its
|
|
|
|
# own special way. re-implement, but as a scope if it's not an
|
|
|
|
# internal use of it
|
|
|
|
def uniq(records = true)
|
|
|
|
if records.is_a?(Array)
|
|
|
|
records.uniq
|
|
|
|
else
|
|
|
|
# do its thing to make a scope, going all the way back to the model
|
|
|
|
scoped.uniq(records)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# fake_arel doesn't quite work right here - somehow it's getting
|
|
|
|
# Kernel#select. so just duplicate the select we want
|
|
|
|
def select(value = Proc.new)
|
|
|
|
if block_given?
|
|
|
|
to_ary.select {|*block_args| value.call(*block_args) }
|
|
|
|
else
|
|
|
|
self.scoped(:select => Array.wrap(value).join(','))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-03-29 05:03:03 +08:00
|
|
|
class Class
|
|
|
|
def self.class_attribute(*attrs)
|
|
|
|
class_inheritable_accessor(*attrs)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|