<commit_before>module AutocompleteRails
  module Controller
    def self.included(target)
      target.extend AutocompleteRails::Controller::ClassMethods
    end

    module ClassMethods
      #
      # Generate an autocomplete controller action.
      #
      # The generated method is named  "autocomplete_#{model_class}_#{value_method}", e.g.
      #
      #   class ProductController
      #     autocomplete :user, :email
      #   end
      #
      # generates a method named autocomplete_user_email.
      #
      # Parameters:
      # * model - symbol of the model class to autocomplete.
      # * value_method - symbol of the method on model to autocomplete, supplies the 'value' field in results.
      #                  Also used as the label unless you supply options[:label_method]
      # * options - hash of optional settings.
      #
      # Options:
      # * :label_method - call a separate method for the label, otherwise defaults to value_method. If your label
      #                   method is a method that is *not* a column in your DB, you may need options[:full_model].
      # * :full_model - load full model instead of only selecting the specified values. Default is false.
      # * :limit - default is 10.
      # * :case_sensitive - default is false.
      # * :additional_data - collect additional data. Will be added to select unless full_model is invoked.
      # * :full_search - search the entire value string for the term. Defaults to false, in which case the string must
      #                  start with the term.
      # * :scopes - limit query to these scopes, passed in as an array, e.g. scopes: [:scope1, :scope2]
      #
      # Be sure to add a route to the generated controller method.
      #
      def autocomplete(model, value_method, options = {})
        label_method = options[:label_method] || value_method
        model_constant = model.to_s.camelize.constantize
        autocomplete_method_name = "autocomplete_#{model}_#{value_method}"

        define_method(autocomplete_method_name) do
          results = autocomplete_results(model_constant, value_method, label_method, options)
          render json: autocomplete_build_json(results, value_method, label_method, options), root: false
        end
      end
    end

    protected

    def autocomplete_results(model_class, value_method, label_method = nil, options)
      term = params[:term]
      return {} if term.blank?

      results = model_class.where(nil) # make an empty scope to add select, where, etc, to.
      scopes  = Array.new(options[:scopes])
      scopes.each { |scope| results = results.send(scope) } unless scopes.empty?
      results = results.select(autocomplete_select_clause(model_class, value_method, label_method, options)) unless
        options[:full_model]
      results.
        where(autocomplete_where_clause(term, model_class, value_method, options)).
        limit(autocomplete_limit_clause(options)).
        order(autocomplete_order_clause(model_class, value_method, options))
    end

    def autocomplete_select_clause(model_class, value_method, label_method, options)
      table_name = model_class.table_name
      selects = []
      selects << "#{table_name}.#{model_class.primary_key}"
      selects << "#{table_name}.#{value_method}"
      selects << "#{table_name}.#{label_method}" if label_method
      options[:additional_data].each { |datum| selects << "#{table_name}.#{datum}" } if options[:additional_data]
      selects
    end

    def autocomplete_where_clause(term, model_class, value_method, options)
      term = term.gsub(/[_%]/) { |x| "\\#{x}" } # escape any _'s or %'s in the search term
      term = "#{term}%"
      term = "%#{term}" if options[:full_search]
      table_name = model_class.table_name
      lower = options[:case_sensitive] ? '' : 'LOWER'
      ["#{lower}(#{table_name}.#{value_method}) LIKE #{lower}(?) ESCAPE \"\\\"", term]
    end

    def autocomplete_limit_clause(options)
      options[:limit] ||= 10
    end

    def autocomplete_order_clause(model_class, value_method, options)
      return options[:order] if options[:order]

      # default to ASC order
      table_prefix = "#{model_class.table_name}."
      "LOWER(#{table_prefix}#{value_method}) ASC"
    end

    def autocomplete_build_json(results, value_method, label_method, options)
      results.collect do |result|
        data = HashWithIndifferentAccess.new(id: result.id,
                                             label: result.send(label_method),<commit_msg>distinguishing model_symbol vs model<commit_after>module AutocompleteRails
  module Controller
    def self.included(target)
      target.extend AutocompleteRails::Controller::ClassMethods
    end

    module ClassMethods
      #
      # Generate an autocomplete controller action.
      #
      # The generated method is named  "autocomplete_#{model_class}_#{value_method}", e.g.
      #
      #   class ProductController
      #     autocomplete :user, :email
      #   end
      #
      # generates a method named autocomplete_user_email.
      #
      # Parameters:
      # * model - symbol of the model class to autocomplete.
      # * value_method - symbol of the method on model to autocomplete, supplies the 'value' field in results.
      #                  Also used as the label unless you supply options[:label_method]
      # * options - hash of optional settings.
      #
      # Options:
      # * :label_method - call a separate method for the label, otherwise defaults to value_method. If your label
      #                   method is a method that is *not* a column in your DB, you may need options[:full_model].
      # * :full_model - load full model instead of only selecting the specified values. Default is false.
      # * :limit - default is 10.
      # * :case_sensitive - default is false.
      # * :additional_data - collect additional data. Will be added to select unless full_model is invoked.
      # * :full_search - search the entire value string for the term. Defaults to false, in which case the string must
      #                  start with the term.
      # * :scopes - limit query to these scopes, passed in as an array, e.g. scopes: [:scope1, :scope2]
      #
      # Be sure to add a route to the generated controller method.
      #
      def autocomplete(model_symbol, value_method, options = {})
        label_method = options[:label_method] || value_method
        model = model_symbol.to_s.camelize.constantize
        autocomplete_method_name = "autocomplete_#{model_symbol}_#{value_method}"

        define_method(autocomplete_method_name) do
          results = autocomplete_results(model, value_method, label_method, options)
          render json: autocomplete_build_json(results, value_method, label_method, options), root: false
        end
      end
    end

    protected

    def autocomplete_results(model, value_method, label_method = nil, options)
      term = params[:term]
      return {} if term.blank?

      results = model.where(nil) # make an empty scope to add select, where, etc, to.
      scopes  = Array.new(options[:scopes])
      scopes.each { |scope| results = results.send(scope) } unless scopes.empty?
      results = results.select(autocomplete_select_clause(model, value_method, label_method, options)) unless
        options[:full_model]
      results.
        where(autocomplete_where_clause(term, model, value_method, options)).
        limit(autocomplete_limit_clause(options)).
        order(autocomplete_order_clause(model, value_method, options))
    end

    def autocomplete_select_clause(model, value_method, label_method, options)
      table_name = model.table_name
      selects = []
      selects << "#{table_name}.#{model.primary_key}"
      selects << "#{table_name}.#{value_method}"
      selects << "#{table_name}.#{label_method}" if label_method
      options[:additional_data].each { |datum| selects << "#{table_name}.#{datum}" } if options[:additional_data]
      selects
    end

    def autocomplete_where_clause(term, model, value_method, options)
      term = term.gsub(/[_%]/) { |x| "\\#{x}" } # escape any _'s or %'s in the search term
      term = "#{term}%"
      term = "%#{term}" if options[:full_search]
      table_name = model.table_name
      lower = options[:case_sensitive] ? '' : 'LOWER'
      ["#{lower}(#{table_name}.#{value_method}) LIKE #{lower}(?) ESCAPE \"\\\"", term]
    end

    def autocomplete_limit_clause(options)
      options[:limit] ||= 10
    end

    def autocomplete_order_clause(model, value_method, options)
      return options[:order] if options[:order]

      # default to ASC order
      table_prefix = "#{model.table_name}."
      "LOWER(#{table_prefix}#{value_method}) ASC"
    end

    def autocomplete_build_json(results, value_method, label_method, options)
      results.collect do |result|
        data = HashWithIndifferentAccess.new(id: result.id,
                                             label: result.send(label_method),
{'fraction_non_alphanumeric': 0.09953343701399689, 'fraction_numerical': 0.0013330371028660297, 'mean_word_length': 2.880603448275862, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2870974', 'n_tokens_mistral': 2606, 'n_tokens_neox': 2523, 'n_words': 720}