Skip to content
Vadim edited this page Apr 22, 2017 · 3 revisions

Steps for adding filters:

  • Redefine views/rademade_admin/_blocks/_list.html.erb to add:

      <div>
        <% if @search_fields %>
          <div><%= render 'rademade_admin/_blocks/filters' %></div>
          <div class="clear"></div>
        <% end %>
      </div>
  • Create views/rademade_admin/_blocks/_filters.html.erb:

  <div class="content-box custom-filter">
  <div class="clear"></div>
  <div class="custom-form">
    <form action="<%= admin_list_uri(@model_info.model) %>" method="get" data-turboform="true"><%
      %><label class="form-box">
      <span class="form-label">По названию</span>
      <span class="input-holder"><%=
        text_field_tag :q, params[:q], class: "form-input", id: "item-name-filter", placeholder: "Поиск по названию"
      %></span></label><%
        %><%
      (@search_fields[:text] || []).each do |field|
          %><label class="form-box"><span class="form-label"><%= field[:label] %></span>
          <span class="input-holder"><%=
            text_field_tag field[:name], params[field[:name]], class: 'form-input', id: "item-name-filter", placeholder: field[:label]
          %></span></label><%
      end
      %><%

      (@search_fields[:multiple] || []).each do |field|
        %><%=
          render layout: '/rademade_admin/_blocks/select_input', locals: { name: field[:name], is_multiple: true, label_title: field[:label] } do
            options_from_collection_for_select(
              field[:model].all, "id", "name", @search_params[field[:name]].try(:map, &:to_i)
            )
          end
        %><%
      end
      (@search_fields[:singular] || []).each do |field|
      %><div class="clear"></div>
        <label class="form-box select optional data_status form-group">
          <span class="select optional form-label"><%= field[:label] %></span>
          <div class="input-holder"><%
            options = case field[:model]
              when Hash
                options_for_select(field[:model].stringify_keys, @search_params[field[:name]])
              when Array
                options_for_select(field[:model], @search_params[field[:name]])
              else
                options_from_collection_for_select(field[:model].all, "id", "name", @search_params[field[:name]])
              end
            %><%=
            select_tag field[:name], options, include_blank: 'Выберите для поиска', class: "select optional form-input"
          %></div>
        </label><%
      end
      %><div class="btn-list">
          <div class="btn-box align-left">
            <button class="btn is-blue" id='items-search-submit' type="submit">Search</button><%=
            link_to 'Reset', admin_list_uri(@model_info.model), class: "btn is-red"
          %></div>
        </div>

    </form>
  </div>
  • Add this to abstract admin controller:

    include Health24Admin::Search::Searchable

  • And define that module and modify the code is needed(f.e. if you dont need translation filters - remove them)

  # frozen_string_literal: true
  module Health24Admin
    class SearchParams

      attr_reader :params, :order_conditions, :available_filters

      def initialize(params, filters)
        @filters_singular = init_available_filters(filters, :singular)
        @filters_multiple = init_available_filters(filters, :multiple)
        @filters_text = init_available_filters(filters, :text)
        @available_filters = (@filters_singular + @filters_multiple + @filters_text).push :q
        @order_conditions = params.permit(:sort, :direction)
        @params = sanitize(params.symbolize_keys)
      end

      def singular?(param_name)
        @filters_singular.include? param_name
      end

      def multiple?(param_name)
        @filters_multiple.include? param_name
      end

      def text?(param_name)
        @filters_text.include? param_name
      end

      def ready_for_search?
        @params.count.positive? || @order_conditions[:sort].present?
      end

      private

      def init_available_filters(filters, type)
        return [] unless filters[type]
        filters[type].map { |filter| filter[:name] }.flatten
      end

      def sanitize(init_params)
        available_param_list = init_params.keys & available_filters
        @sanitized_param_list = available_param_list.select do |param|
          init_params[param] && !init_params[param].empty? && !empty_array?(init_params[param])
        end
        init_params.select { |key| @sanitized_param_list.include?(key) }
      end

      def empty_array?(array)
        return false if array.is_a? String
        array.all?(&:empty?)
      end

    end
  end

  # frozen_string_literal: true
  class Health24Admin::ItemSearch

    def initialize(model, params_source)
      @model = model
      @params_source = params_source
    end

    def filter
      items = query_filter if @params_source.params.key?(:q)
      return items if items && !items.exists?
      collection = filter_items(items || @model)
      if @params_source.order_conditions[:sort]
        order(collection)
      else
        collection
      end
    end

    private

    def filter_items(collection)
      collect_filters.each do |filter|
        raise(Exception::NoFilterError.new(filter[:scope], @model.to_s)) unless @model.respond_to?(filter[:scope])
        collection = collection.public_send(filter[:scope].to_sym, filter[:value])
      end
      collection
    end

    def order(collection)
      @params_source.order_conditions[:direction] ||= :asc

      # check if model is globalized
      translated_attributes = collection.try(:translated_attribute_names)
      # if globalized and current attribute is globalized we search by it
      if translated_attributes&.include?(@params_source.order_conditions[:sort].to_sym)
        where_string = <<-SQLL
          "#{collection.translation_class.table_name}"."#{@params_source.order_conditions[:sort]}" IS NOT NULL AND
          TRIM(LOWER("#{collection.translation_class.table_name}"."#{@params_source.order_conditions[:sort]}")) != ""
        SQLL
        order_string = <<-SQLL
          TRIM(LOWER("#{collection.translation_class.table_name}"."#{@params_source.order_conditions[:sort]}")) #{@params_source.order_conditions[:direction]}
        SQLL
        collection.joins(:translations).where(where_string.gsub(/\s+/, ' ')).reorder(order_string.gsub(/\s+/, ' '))
      else
        collection.reorder(@params_source.order_conditions[:sort] => @params_source.order_conditions[:direction])
      end
    end

    def collect_filters
      @params_source.params.map do |key, value|
        { scope: filter_name(key), value: filter_value(value) }
      end
    end

    def filter_name(param_name)
      return param_name.to_s.singularize.foreign_key if @params_source.multiple?(param_name)
      param_name.to_s.singularize if @params_source.singular?(param_name) || @params_source.text?(param_name)
    end

    def filter_value(value)
      return value if value.is_a?(Array) || value.to_i.to_s == value
      try_to_bool(value)
    end

    def try_to_bool(value)
      return true if value == 'true'
      return false if value == 'false'
      value
    end

    def query_filter
      Health24Admin::Search::SearchWithTranslation.new(
        @model,
        @params_source.params.delete(:q),
        paginate: nil
      ).search_in(translated_locales)
    end

    def translated_locales
      @model.try(:translated_locales)
    end

  end

  module Health24Admin::Search::Searchable

    DEFAULT_PAGE = 1
    DEFAULT_PAGE_ITEMS = 20

    def self.included(base)
      base.before_action(:init_search_params, only: :index)
      base.before_action(:set_search_fields, only: %i(autocomplete index))
    end

    def autocomplete_items
      return super unless @search_fields && @search_fields[:q]
      model.public_send(@search_fields[:q], params[:q])
    end

    def index_items(_ = true)
      init_search_fields

      result = found_items || if @translation_field
                                super.includes(:translations)
                              else
                                super
                              end

      clear_empty_params

      paginate_items(result)
    end

    private

    # name is predefined as translated field
    # redefine in controller to get search fields in admin
    # example:
    # def set_search_fields
    #   @search_fields = {
    #     singular: [
    #       { name: :characteristic_type, label: 'Фильтр по типу характеристики', model: CharacteristicType::MANIFESTS.invert },
    #       { name: :is_common, label: 'Фильтр по общая для человека', model: Type::BOOLEAN }
    #     ],
    #     multiple: [
    #       { name: :organs, label: 'Фильтр по органу', model: ::OrganInfo::Organ },
    #       { name: :body_parts, label: 'Фильтр по частям тела', model: ::BodyInfo::BodyPart },
    #       { name: :organ_systems, label: 'Фильтр по системам органов', model: ::OrganInfo::OrganSystem }
    #     ],
    #     text: [
    #       { name: :by_code, label: 'Фильтр по коду' }
    #     ]
    #   }
    # end
    # name = existing model scope name
    # label = search input label
    # model = model to apply scope to
    def set_search_fields
      @search_fields = nil
    end

    def init_search_params
      @search_params ||= {}
    end

    WITHOUT_TRANSLATION_FIELD = {
      name: :without_translations,
      label: 'Без перевода на языке',
      model: Type::LOCALES
    }.freeze

    def init_search_fields
      return unless model_info.model.try(:translated_locales)

      @search_fields ||= {}
      @search_fields[:singular] ||= []
      @search_fields[:singular] << WITHOUT_TRANSLATION_FIELD
    end

    def clear_empty_params
      params.reject! { |_, v| v.blank? }
    end

    def search(params_source)
      @search_params = params_source.params
      Health24Admin::ItemSearch.new(model_info.model, params_source).filter
    end

    def paginate_items(items)
      items.page(params[:page] || DEFAULT_PAGE).per(params[:paginate] || DEFAULT_PAGE_ITEMS)
    end

    def found_items
      return unless @search_fields

      search_params = Health24Admin::SearchParams.new(params, @search_fields)

      return unless search_params.ready_for_search?
      search(search_params)
    end

  end
  • Define your own config method in controller on which route you want to perform filtering, example:
   def set_search_fields
     @search_fields = {
       singular: [
         { name: :characteristic_type, label: 'Фильтр по типу характеристики', model: CharacteristicType::MANIFESTS.invert },
         { name: :is_common, label: 'Фильтр по общая для человека', model: Type::BOOLEAN }
       ],
       multiple: [
         { name: :organs, label: 'Фильтр по органу', model: ::OrganInfo::Organ },
         { name: :body_parts, label: 'Фильтр по частям тела', model: ::BodyInfo::BodyPart },
         { name: :organ_systems, label: 'Фильтр по системам органов', model: ::OrganInfo::OrganSystem }
       ],
       text: [
       	{ name: :by_code, label: 'Фильтр по коду' }
       ],
       q: :by_username
     }
   end
Clone this wiki locally