require 'thread' require 'thread_safe' module ActiveRecord module Delegation # :nodoc: extend ActiveSupport::Concern # This module creates compiled delegation methods dynamically at runtime, which makes # subsequent calls to that method faster by avoiding method_missing. The delegations # 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. delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :to => :to_a delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key, :connection, :columns_hash, :auto_explain_threshold_in_seconds, :to => :klass module ClassSpecificRelation extend ActiveSupport::Concern included do @delegation_mutex = Mutex.new end module ClassMethods def name superclass.name end def delegate_to_scoped_klass(method) @delegation_mutex.synchronize do return if method_defined?(method) if method.to_s =~ /\A[a-zA-Z_]\w*[!?]?\z/ module_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{method}(*args, &block) scoping { @klass.#{method}(*args, &block) } end RUBY else module_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{method}(*args, &block) scoping { @klass.send(#{method.inspect}, *args, &block) } end RUBY end end end def delegate(method, opts = {}) @delegation_mutex.synchronize do return if method_defined?(method) super end end end protected def method_missing(method, *args, &block) if @klass.respond_to?(method) self.class.delegate_to_scoped_klass(method) scoping { @klass.send(method, *args, &block) } elsif Array.method_defined?(method) self.class.delegate method, :to => :to_a to_a.send(method, *args, &block) elsif arel.respond_to?(method) self.class.delegate method, :to => :arel arel.send(method, *args, &block) else super end end end module ClassMethods @@subclasses = ThreadSafe::Cache.new(:initial_capacity => 2) def new(klass, *args) relation = relation_class_for(klass).allocate relation.__send__(:initialize, klass, *args) relation end # This doesn't have to be thread-safe. relation_class_for guarantees that this will only be # called exactly once for a given const name. def const_missing(name) const_set(name, Class.new(self) { include ClassSpecificRelation }) end private # Cache the constants in @@subclasses because looking them up via const_get # make instantiation significantly slower. def relation_class_for(klass) if klass && (klass_name = klass.name) my_cache = @@subclasses.compute_if_absent(self) { ThreadSafe::Cache.new } # This hash is keyed by klass.name to avoid memory leaks in development mode my_cache.compute_if_absent(klass_name) do # Cache#compute_if_absent guarantees that the block will only executed once for the given klass_name const_get("#{name.gsub('::', '_')}_#{klass_name.gsub('::', '_')}", false) end else ActiveRecord::Relation end end end def respond_to?(method, include_private = false) super || Array.method_defined?(method) || @klass.respond_to?(method, include_private) || arel.respond_to?(method, include_private) end protected def method_missing(method, *args, &block) if @klass.respond_to?(method) scoping { @klass.send(method, *args, &block) } elsif Array.method_defined?(method) to_a.send(method, *args, &block) elsif arel.respond_to?(method) arel.send(method, *args, &block) else super end end end end