diff options
Diffstat (limited to 'activerecord/lib/active_record/relation/delegation.rb')
-rw-r--r-- | activerecord/lib/active_record/relation/delegation.rb | 126 |
1 files changed, 67 insertions, 59 deletions
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index 86f2c30168..383dc1bf4b 100644 --- a/activerecord/lib/active_record/relation/delegation.rb +++ b/activerecord/lib/active_record/relation/delegation.rb @@ -1,14 +1,13 @@ -require 'set' -require 'active_support/concern' +# frozen_string_literal: true module ActiveRecord module Delegation # :nodoc: - module DelegateCache - def relation_delegate_class(klass) # :nodoc: + module DelegateCache # :nodoc: + def relation_delegate_class(klass) @relation_delegate_cache[klass] end - def initialize_relation_delegate_cache # :nodoc: + def initialize_relation_delegate_cache @relation_delegate_cache = cache = {} [ ActiveRecord::Relation, @@ -18,7 +17,11 @@ module ActiveRecord delegate = Class.new(klass) { include ClassSpecificRelation } - const_set klass.name.gsub('::', '_'), delegate + include_relation_methods(delegate) + mangled_name = klass.name.gsub("::", "_") + const_set mangled_name, delegate + private_constant mangled_name + cache[klass] = delegate end end @@ -27,6 +30,35 @@ module ActiveRecord child_class.initialize_relation_delegate_cache super end + + protected + def include_relation_methods(delegate) + superclass.include_relation_methods(delegate) unless base_class? + delegate.include generated_relation_methods + end + + private + def generated_relation_methods + @generated_relation_methods ||= Module.new.tap do |mod| + mod_name = "GeneratedRelationMethods" + const_set mod_name, mod + private_constant mod_name + end + end + + def generate_relation_method(method) + if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) + generated_relation_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{method}(*args, &block) + scoping { klass.#{method}(*args, &block) } + end + RUBY + else + generated_relation_methods.send(:define_method, method) do |*args, &block| + scoping { klass.public_send(method, *args, &block) } + end + end + end end extend ActiveSupport::Concern @@ -36,16 +68,12 @@ module ActiveRecord # may vary depending on the klass of a relation, so we create a subclass of Relation # for each different klass, and the delegations are compiled into that subclass only. - BLACKLISTED_ARRAY_METHODS = [ - :compact!, :flatten!, :reject!, :reverse!, :rotate!, :map!, - :shuffle!, :slice!, :sort!, :sort_by!, :delete_if, - :keep_if, :pop, :shift, :delete_at, :select! - ].to_set # :nodoc: + delegate :to_xml, :encode_with, :length, :each, :join, + :[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of, + :to_sentence, :to_formatted_s, :as_json, + :shuffle, :split, :slice, :index, :rindex, to: :records - delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join, to: :to_a - - delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key, - :connection, :columns_hash, :to => :klass + delegate :primary_key, :connection, to: :klass module ClassSpecificRelation # :nodoc: extend ActiveSupport::Concern @@ -63,7 +91,7 @@ module ActiveRecord @delegation_mutex.synchronize do return if method_defined?(method) - if method.to_s =~ /\A[a-zA-Z_]\w*[!?]?\z/ + if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) module_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{method}(*args, &block) scoping { @klass.#{method}(*args, &block) } @@ -76,28 +104,27 @@ module ActiveRecord end end end - - def delegate(method, opts = {}) - @delegation_mutex.synchronize do - return if method_defined?(method) - super - end - end end - protected + private - def method_missing(method, *args, &block) - if @klass.respond_to?(method) - self.class.delegate_to_scoped_klass(method) - scoping { @klass.public_send(method, *args, &block) } - elsif arel.respond_to?(method) - self.class.delegate method, :to => :arel - arel.public_send(method, *args, &block) - else - super + def method_missing(method, *args, &block) + if @klass.respond_to?(method) + self.class.delegate_to_scoped_klass(method) + scoping { @klass.public_send(method, *args, &block) } + elsif @delegate_to_klass && @klass.respond_to?(method, true) + ActiveSupport::Deprecation.warn \ + "Delegating missing #{method} method to #{@klass}. " \ + "Accessibility of private/protected class methods in :scope is deprecated and will be removed in Rails 6.0." + @klass.send(method, *args, &block) + elsif arel.respond_to?(method) + ActiveSupport::Deprecation.warn \ + "Delegating #{method} to arel is deprecated and will be removed in Rails 6.0." + arel.public_send(method, *args, &block) + else + super + end end - end end module ClassMethods # :nodoc: @@ -107,33 +134,14 @@ module ActiveRecord private - def relation_class_for(klass) - klass.relation_delegate_class(self) - end - end - - def respond_to?(method, include_private = false) - super || @klass.respond_to?(method, include_private) || - array_delegable?(method) || - arel.respond_to?(method, include_private) - end - - protected - - def array_delegable?(method) - Array.method_defined?(method) && BLACKLISTED_ARRAY_METHODS.exclude?(method) + def relation_class_for(klass) + klass.relation_delegate_class(self) + end end - def method_missing(method, *args, &block) - if @klass.respond_to?(method) - scoping { @klass.public_send(method, *args, &block) } - elsif array_delegable?(method) - to_a.public_send(method, *args, &block) - elsif arel.respond_to?(method) - arel.public_send(method, *args, &block) - else - super + private + def respond_to_missing?(method, _) + super || @klass.respond_to?(method) || arel.respond_to?(method) end - end end end |