8parth/make_model_searchable

View on GitHub
lib/make_model_searchable.rb

Summary

Maintainability
B
4 hrs
Test Coverage
require "make_model_searchable/version"
require 'active_record'
require 'models/searchable_field'

module MakeModelSearchable

  def searchable_attributes(*options)
    # self.connection
    joined_options = options.select{ |option| option.is_a? (Hash) }
    options = options.select{ |option| !option.is_a?(Hash) }
    setup_fields(options)
    if joined_options.present?
      setup_joined_fields(joined_options)
    end
    self.extend(ClassMethods)
  end

  def setup_joined_fields(options_array)
    joined_fields = []
    association_names = self.reflect_on_all_associations.map(&:name)
    valid_opts = options_array.first.select {|k,v| association_names.include?(k)  }
    searchable_fields = []

    valid_opts.each do |key, val|
      assoc =  self.reflect_on_association(key)
      joined_fields << get_valid_joined_fields(key, val)
      joined_fields.flatten.each do |f|
        searchable_fields << SearchableField.new(f.name, f.type, assoc.name,  assoc.klass)
      end
    end
    self.instance_variable_set(:@joined_fields, searchable_fields.flatten) if searchable_fields.present?
  end

  def setup_fields(options)
    unless options.present?
      options = self.column_names
    end
    valid_fields = get_valid_fields(options)
    self.instance_variable_set(:@selected_fields, valid_fields)
  end

  def get_valid_fields(options)
    column_names = self.columns.map(&:name)
    column_types = self.columns.map(&:type)
    searchable_columns = []
    if column_names.length == column_types.length
      column_names.each_with_index do |column_name, index|
        searchable_columns << column_name if column_types[index].to_sym == :string 
      end
    end
    if searchable_columns.present?
      valid_fields = options.select{ |option| searchable_columns.include?(option.to_s) }
    else
      valid_fields = []
    end
    valid_fields
  end

  def get_valid_joined_fields(key, columns_array)
    associated_model = self.reflect_on_association(key).klass
    associated_model.connection
    if columns_array.empty?
      valid_joied_fields = associated_model.columns.select{ |col| col.type == :string or col.type == :text }
    else
      valid_joied_fields = associated_model.columns.select{ |col| (col.type == :string or col.type == :text) and columns_array.include?(col.name.to_sym) }
    end
    valid_joied_fields
  end

  module ClassMethods 
    def search(search_term)
      valid_fields = self.instance_variable_get(:@selected_fields)
      joined_fields = self.instance_variable_get(:@joined_fields)
      
      if valid_fields.present?
        if search_term
          search_term = "%#{search_term.downcase}%"
          own_table = self.arel_table
          arel_node = Arel::Nodes::Node.new
          valid_fields.each_with_index do |val, index|
            if index == 0
              arel_node = own_table[val].lower.matches(search_term)
            else
              arel_node = arel_node.or(own_table[val].lower.matches(search_term))
            end
          end
          join_table_names = []
          
          if joined_fields.present?
            joined_fields.each do |field|
              associated_relation = field.associated_klass.arel_table
              join_table_names << field.association_name
              if arel_node.present?
                arel_node = arel_node.or(associated_relation[field.name].lower.matches(search_term))
              else
                arel_node = associated_relation[field.name].lower.matches(search_term)
              end
            end
          end

          joins(join_table_names).where(arel_node)
        else
          all
        end
      else
        raise Exception,  "Please pass valid attributes for class: #{self.name}"
      end
    end
  end
  ActiveRecord::Base.extend MakeModelSearchable
end