<commit_before>module ActiveRecord
  class Base
    def self.acts_as_miq_taggable
      has_many :taggings, :as => :taggable, :dependent => :destroy
      has_many :tags, :through => :taggings
      include ActsAsTaggable
    end
  end
end

module ActsAsTaggable
  extend ActiveSupport::Concern

  # @param tags [String,Array]
  # @param separator [String] Separator used if tags is a string
  # @return array of unique tag names
  def self.split_tag_names(tags, separator)
    case tags
    when Array
      tags.flatten.compact
    when String
      tags.split(separator)
    else
      []
    end.map(&:strip).uniq
  end

  module ClassMethods
    def find_tagged_with(options = {})
      options = {:separator => ' '}.merge(options)
      options[:ns] = Tag.get_namespace(options)

      tag_names = ActsAsTaggable.split_tag_names(options[:any] || options[:all], options[:separator])
      fq_tag_names = tag_names.collect { |tag_name| File.join(options[:ns], tag_name) }
      raise "No tags were passed to :any or :all options" if fq_tag_names.empty?

      tag_ids = Tag.where(:name => fq_tag_names).pluck(:id)

      # Bailout if not enough tags were found
      return none if options[:all] && tag_ids.length != fq_tag_names.length

      taggings = Tagging.arel_table
      self_arel = arel_table
      query = distinct.joins(:taggings).where(taggings[:tag_id].in tag_ids)

      if options[:all]
        grouping_cols = [taggings[:taggable_id]] + column_names.collect { |c| self_arel[c] }
        query = query.group(*grouping_cols).having(taggings[:id].count.gteq tag_names.length)
      end

      query
    end

    def find_tags_by_grouping(list, options = {})
      options[:ns] = Tag.get_namespace(options)

      # If array is flat (contains no inner arrays) we can let find_tagged_with
      # do all the work. This will be much faster.
      # Otherwise we'll need to call find_tagged_with for each inner list, prune the results
      # and honor offset and limit here.
      inner_lists = false
      list.each do |item|
        if item.kind_of?(Array) && item.length > 1
          inner_lists = true
          break
        end
      end
      return find_tagged_with(options.merge(:all => list)) unless inner_lists == true

      offset = options.delete(:offset)
      limit = options.delete(:limit)
      count = options.delete(:count)
      results = nil
      list.each do |inner_list|
        ret = find_tagged_with(options.merge(:any => inner_list))
        if results.nil?
          results = ret
          next
        end

        results = results.select { |obj| ret.include?(obj) }
        break if results.empty?
      end
      if limit
        offset ||= 0
        results = results[offset..offset + limit - 1]
      end
      count ? results.length : results
    end

    def tags(options = {})
      options.merge!(:taggable_type => base_class.name)
      options[:ns] = Tag.get_namespace(options)<commit_msg>ar_taggable - use partition<commit_after>module ActiveRecord
  class Base
    def self.acts_as_miq_taggable
      has_many :taggings, :as => :taggable, :dependent => :destroy
      has_many :tags, :through => :taggings
      include ActsAsTaggable
    end
  end
end

module ActsAsTaggable
  extend ActiveSupport::Concern

  # @param tags [String,Array]
  # @param separator [String] Separator used if tags is a string
  # @return array of unique tag names
  def self.split_tag_names(tags, separator)
    case tags
    when Array
      tags.flatten.compact
    when String
      tags.split(separator)
    else
      []
    end.map(&:strip).uniq
  end

  module ClassMethods
    def find_tagged_with(options = {})
      options = {:separator => ' '}.merge(options)
      options[:ns] = Tag.get_namespace(options)

      tag_names = ActsAsTaggable.split_tag_names(options[:any] || options[:all], options[:separator])
      fq_tag_names = tag_names.collect { |tag_name| File.join(options[:ns], tag_name) }
      raise "No tags were passed to :any or :all options" if fq_tag_names.empty?

      tag_ids = Tag.where(:name => fq_tag_names).pluck(:id)

      # Bailout if not enough tags were found
      return none if options[:all] && tag_ids.length != fq_tag_names.length

      taggings = Tagging.arel_table
      self_arel = arel_table
      query = distinct.joins(:taggings).where(taggings[:tag_id].in tag_ids)

      if options[:all]
        grouping_cols = [taggings[:taggable_id]] + column_names.collect { |c| self_arel[c] }
        query = query.group(*grouping_cols).having(taggings[:id].count.gteq tag_names.length)
      end

      query
    end

    # @param list [Array<Array<String>>] list of tags
    #   the inner list holds a single category grouped together. These are treated as an IN (aka OR) clause
    #   the outer list holds multiple categories. All of these need to match and treaded as an AND clause
    #   so the end result is the AND of a bunch of OR clauses.
    #
    # find_tagged_with(:any) is used for the inner list to handle the IN (aka OR)
    # find_tagged_with(:all) is used for multiple inner lists
    def find_tags_by_grouping(list, options = {})
      options[:ns] = Tag.get_namespace(options)

      # any inner arrays with only 1 element doesn't need an 'IN' clause.
      # These can all be handled together in a single 'AND' query
      #
      # the inner_lists need to be added as separate queries.
      inner_lists, fixed_conditions = list.partition { |item| item.kind_of?(Array) && item.length > 1 }

      return find_tagged_with(options.merge(:all => fixed_conditions)) if inner_lists.empty?

      offset = options.delete(:offset)
      limit = options.delete(:limit)
      count = options.delete(:count)
      results = nil
      list.each do |inner_list|
        ret = find_tagged_with(options.merge(:any => inner_list))
        if results.nil?
          results = ret
          next
        end

        results = results.select { |obj| ret.include?(obj) }
        break if results.empty?
      end
      if limit
        offset ||= 0
        results = results[offset..offset + limit - 1]
      end
      count ? results.length : results
    end

    def tags(options = {})
      options.merge!(:taggable_type => base_class.name)
      options[:ns] = Tag.get_namespace(options)
{'fraction_non_alphanumeric': 0.10006373486297004, 'fraction_numerical': 0.0011153601019757808, 'mean_word_length': 2.788171394085697, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 23, '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': '7633292', 'n_tokens_mistral': 1973, 'n_tokens_neox': 1910, 'n_words': 532}