diff options
Diffstat (limited to 'lib/declarative_policy.rb')
| -rw-r--r-- | lib/declarative_policy.rb | 40 | 
1 files changed, 35 insertions, 5 deletions
diff --git a/lib/declarative_policy.rb b/lib/declarative_policy.rb index d9959bc1aff..b1eb1a6cef1 100644 --- a/lib/declarative_policy.rb +++ b/lib/declarative_policy.rb @@ -8,7 +8,12 @@ require_dependency 'declarative_policy/step'  require_dependency 'declarative_policy/base' +require 'thread' +  module DeclarativePolicy +  CLASS_CACHE_MUTEX = Mutex.new +  CLASS_CACHE_IVAR = :@__DeclarativePolicy_CLASS_CACHE +    class << self      def policy_for(user, subject, opts = {})        cache = opts[:cache] || {} @@ -23,7 +28,36 @@ module DeclarativePolicy        subject = find_delegate(subject) -      subject.class.ancestors.each do |klass| +      class_for_class(subject.class) +    end + +    private + +    # This method is heavily cached because there are a lot of anonymous +    # modules in play in a typical rails app, and #name performs quite +    # slowly for anonymous classes and modules. +    # +    # See https://bugs.ruby-lang.org/issues/11119 +    # +    # if the above bug is resolved, this caching could likely be removed. +    def class_for_class(subject_class) +      unless subject_class.instance_variable_defined?(CLASS_CACHE_IVAR) +        CLASS_CACHE_MUTEX.synchronize do +          # re-check in case of a race +          break if subject_class.instance_variable_defined?(CLASS_CACHE_IVAR) + +          policy_class = compute_class_for_class(subject_class) +          subject_class.instance_variable_set(CLASS_CACHE_IVAR, policy_class) +        end +      end + +      policy_class = subject_class.instance_variable_get(CLASS_CACHE_IVAR) +      raise "no policy for #{subject.class.name}" if policy_class.nil? +      policy_class +    end + +    def compute_class_for_class(subject_class) +      subject_class.ancestors.each do |klass|          next unless klass.name          begin @@ -37,12 +71,8 @@ module DeclarativePolicy            nil          end        end - -      raise "no policy for #{subject.class.name}"      end -    private -      def find_delegate(subject)        seen = Set.new  | 
