<commit_before>module Permissable
  
  module Member
    
    def self.included(base)      
      base.send :include, InstanceMethods
      base.send :attr_protected, :member_identifier
      base.send :attr_protected, :permissions_cache
      base.send :attr_protected, :association_scopes
    end
     
    # This module includes methods that should exist on ALL members.
    module InstanceMethods
      
      # The can? method returns a boolen value specifying whether or not this member can perform the specific method on resource
      def can?(methods, resource, chain = true)
        unless allow_permission_with_method.nil?
          if self.respond_to? "#{allow_permission_with_method}"
            return true if (send "#{allow_permission_with_method}")
          end
        end
        
        if chain
          methods = [methods].flatten.collect{ |m| m.to_sym }
          methods = find_methods_from_chain(methods)
        end
        
        permissions_for?(resource, methods)
        
      end
      
      # Alias to can? to get the inverse.
      def cannot?(method, resource); !can?(method, resource); end
      
      # Assign permissions to a member if they don't already exist.
      # TODO: There has to be a friendlier way to mass assign permissions to roles rather than
      # looping each one for existence and then saving separately.
      def can!(methods, resources)
        
        # This method should return all of the new permissions that were created, so we build a 
        # response array to return
        result_response = []
        
        # Load all permissions fresh so we can kill dupes.
        saved_permissions = Permission.all
        # Store new permissions in an array so we can squeeze into one transaction.
        permissions_to_add    = []
        permissions_to_update = []
        
        [resources].flatten.each do |resource|
          
          # Kind of unecessary but since some methods allow you to specify a Classname directly, this just
          # safegaurds against trying to do the same here.
          next if resource.is_a?(Class)
          
          # Get the member identifier for this resource
          identifier = member_identifier(resource)
          
          [methods].flatten.each do |method|
            
            resource_type = (resource.respond_to?(:base_class) ? resource.base_class.to_s : resource.class.base_class.to_s)
            
            # Create a new permission for each member (once if its self, multiple times if its associated)
            [identifier[:member_id]].flatten.each do |member_id|              
              perm = saved_permissions.detect{ |p| p.member_id == member_id && p.member_type == identifier[:member_type] && p.resource_id == resource.id && p.resource_type == resource_type }  || Permission.new(:member_id => member_id, :member_type => identifier[:member_type])
              perm.permission_type = method.to_s.downcase
              perm.resource        = resource if perm.new_record?
              (perm.new_record?) ? (permissions_to_add << perm) : (permissions_to_update << perm)
            end
          end
        end
        
        
        unless permissions_to_add.empty?
          attrs = permissions_to_add.first.attributes.keys.collect{ |k| k.to_s }
          sql_insert = "INSERT INTO `permissions` (`#{attrs.join("`,`")}`) VALUES"
          sql_values = []
          permissions_to_add.each do |perm|
            vals = []
            attrs.each{ |a| vals << ((a == "created_at" || a == "updated_at") ? DateTime.now.utc.strftime("%Y-%m-%d %H:%M:%S") : ((a == "id") ? "NULL" : perm.send(a.to_sym))) }
            sql_values << "('#{vals.join("','")}')"
          end        
          Permission.connection.execute("#{sql_insert} #{sql_values.join(',')}")
        end
        
        unless permissions_to_update.empty?
          permissions_to_update.each do |perm|
            Permission.update_all(['permission_type = ?', perm.permission_type ], ['id = ?', perm.id])
          end
        end
        
      end
      
      # This sets the member information for our permission lookup based on the current resource scope.
      # These attributes correspond to the correct member_id and member_type in our permissions table.
      def member_identifier(resource)
        
        @member_identifier ||= {}
        scope = fetch_scope(resource)
        
        return @member_identifier[scope] unless @member_identifier[scope].nil?
        return { :member_id => self.id, :member_type => self.class.to_s } unless permissable_associations.has_key?(scope)
        
        @association_scopes ||= {}
        assoc_key = permissable_associations[scope]
        @association_scopes[assoc_key] ||= send("#{assoc_key}".to_sym)
        assoc = @association_scopes[assoc_key]
        
        @member_identifier[scope] = { :member_id => (assoc.is_a?(Array) ? assoc.collect{ |a| a.id } : assoc.id ), :member_type => assoc_key.to_s.classify }
        
      end
          
      # Provide an instance method to our associations
      def permissable_associations; self.class.permissable_associations || {}; end
      # Find our permission override if available
      def allow_permission_with_method; self.class.permissable_options[:allow_permission_with_method]; end
      # See if there is a permissions chain
      def permission_chain; self.class.permissable_options[:permission_chain] || {}; end
      
      def permissions_for(resource)
        fetch_permissions_for(resource).all.collect{ |perm| perm.permission_type.to_sym }
      end
      
      def lookup_permissions!
        @permissions_cache ||= PermissionsCache.new(Permission.where(:resource_type => self.class.permissable_resources.collect{ |r| r.to_s.classify }).all)
      end
      
      private 
      
      def find_methods_from_chain(methods)<commit_msg>Made eager loading be more specific to handle large datasets<commit_after>module Permissable
  
  module Member
    
    def self.included(base)      
      base.send :include, InstanceMethods
      base.send :attr_protected, :member_identifier
      base.send :attr_protected, :permissions_cache
      base.send :attr_protected, :association_scopes
    end
     
    # This module includes methods that should exist on ALL members.
    module InstanceMethods
      
      # The can? method returns a boolen value specifying whether or not this member can perform the specific method on resource
      def can?(methods, resource, chain = true)
        unless allow_permission_with_method.nil?
          if self.respond_to? "#{allow_permission_with_method}"
            return true if (send "#{allow_permission_with_method}")
          end
        end
        
        if chain
          methods = [methods].flatten.collect{ |m| m.to_sym }
          methods = find_methods_from_chain(methods)
        end
        
        permissions_for?(resource, methods)
        
      end
      
      # Alias to can? to get the inverse.
      def cannot?(method, resource); !can?(method, resource); end
      
      # Assign permissions to a member if they don't already exist.
      # TODO: There has to be a friendlier way to mass assign permissions to roles rather than
      # looping each one for existence and then saving separately.
      def can!(methods, resources)
        
        # This method should return all of the new permissions that were created, so we build a 
        # response array to return
        result_response = []
        
        # Load all permissions fresh so we can kill dupes.
        saved_permissions = Permission.all
        # Store new permissions in an array so we can squeeze into one transaction.
        permissions_to_add    = []
        permissions_to_update = []
        
        [resources].flatten.each do |resource|
          
          # Kind of unecessary but since some methods allow you to specify a Classname directly, this just
          # safegaurds against trying to do the same here.
          next if resource.is_a?(Class)
          
          # Get the member identifier for this resource
          identifier = member_identifier(resource)
          
          [methods].flatten.each do |method|
            
            resource_type = (resource.respond_to?(:base_class) ? resource.base_class.to_s : resource.class.base_class.to_s)
            
            # Create a new permission for each member (once if its self, multiple times if its associated)
            [identifier[:member_id]].flatten.each do |member_id|              
              perm = saved_permissions.detect{ |p| p.member_id == member_id && p.member_type == identifier[:member_type] && p.resource_id == resource.id && p.resource_type == resource_type }  || Permission.new(:member_id => member_id, :member_type => identifier[:member_type])
              perm.permission_type = method.to_s.downcase
              perm.resource        = resource if perm.new_record?
              (perm.new_record?) ? (permissions_to_add << perm) : (permissions_to_update << perm)
            end
          end
        end
        
        
        unless permissions_to_add.empty?
          attrs = permissions_to_add.first.attributes.keys.collect{ |k| k.to_s }
          sql_insert = "INSERT INTO `permissions` (`#{attrs.join("`,`")}`) VALUES"
          sql_values = []
          permissions_to_add.each do |perm|
            vals = []
            attrs.each{ |a| vals << ((a == "created_at" || a == "updated_at") ? DateTime.now.utc.strftime("%Y-%m-%d %H:%M:%S") : ((a == "id") ? "NULL" : perm.send(a.to_sym))) }
            sql_values << "('#{vals.join("','")}')"
          end        
          Permission.connection.execute("#{sql_insert} #{sql_values.join(',')}")
        end
        
        unless permissions_to_update.empty?
          permissions_to_update.each do |perm|
            Permission.update_all(['permission_type = ?', perm.permission_type ], ['id = ?', perm.id])
          end
        end
        
      end
      
      # This sets the member information for our permission lookup based on the current resource scope.
      # These attributes correspond to the correct member_id and member_type in our permissions table.
      def member_identifier(resource)
        
        @member_identifier ||= {}
        scope = fetch_scope(resource)
        
        return @member_identifier[scope] unless @member_identifier[scope].nil?
        return { :member_id => self.id, :member_type => self.class.to_s } unless permissable_associations.has_key?(scope)
        
        @association_scopes ||= {}
        assoc_key = permissable_associations[scope]
        @association_scopes[assoc_key] ||= send("#{assoc_key}".to_sym)
        assoc = @association_scopes[assoc_key]
        
        @member_identifier[scope] = { :member_id => (assoc.is_a?(Array) ? assoc.collect{ |a| a.id } : assoc.id ), :member_type => assoc_key.to_s.classify }
        
      end
          
      # Provide an instance method to our associations
      def permissable_associations; self.class.permissable_associations || {}; end
      # Find our permission override if available
      def allow_permission_with_method; self.class.permissable_options[:allow_permission_with_method]; end
      # See if there is a permissions chain
      def permission_chain; self.class.permissable_options[:permission_chain] || {}; end
      
      def permissions_for(resource)
        fetch_permissions_for(resource).all.collect{ |perm| perm.permission_type.to_sym }
      end
      
      def lookup_permissions!

        resource_types = self.class.permissable_resources.collect{ |r| r.to_s.classify }
        member_ids   = []
        member_types = []
        
        resource_types.each do |type|
          identifier = member_identifier(type)
          member_ids   << identifier[:member_id]
          member_types << identifier[:member_type]
        end
        
        member_ids   = member_ids.uniq; member_types = member_types.uniq;
        member_ids   = member_ids.first unless member_ids.size > 1
        member_types = member_types.first unless member_types.size > 1
        
        @permissions_cache ||= PermissionsCache.new(Permission.where(:member_id => member_ids, :member_type => member_types, :resource_type => resource_types).all)
        
      end
      
      private 
      
      def find_methods_from_chain(methods)
{'fraction_non_alphanumeric': 0.09168615434239175, 'fraction_numerical': 0.00016127731634545602, 'mean_word_length': 2.3627982646420826, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '631012', 'n_tokens_mistral': 3398, 'n_tokens_neox': 3390, 'n_words': 901}