Noosfero/noosfero

View on GitHub
plugins/solr/lib/acts_as_faceted.rb

Summary

Maintainability
D
2 days
Test Coverage
module ActsAsFaceted
  module ClassMethods
  end

  module ActsMethods
    # Example:
    #
    # acts_as_faceted :fields => {
    #  :f_type => {:label => c_('Type'), :proc => proc{|klass| f_type_proc(klass)}},
    #  :f_published_at => {:type => :date, :label => _('Published date'), :queries => {'[* TO NOW-1YEARS/DAY]' => _("Older than one year"),
    #    '[NOW-1YEARS TO NOW/DAY]' => _("Last year"), '[NOW-1MONTHS TO NOW/DAY]' => _("Last month"), '[NOW-7DAYS TO NOW/DAY]' => _("Last week"), '[NOW-1DAYS TO NOW/DAY]' => _("Last day")}},
    #  :f_profile_type => {:label => c_('Author'), :proc => proc{|klass| f_profile_type_proc(klass)}},
    #  :f_category => {:label => c_('Categories')}},
    #  :order => [:f_type, :f_published_at, :f_profile_type, :f_category]
    #
    # acts_as_searchable :additional_fields => [ {:name => {:type => :string, :as => :name_sort, :boost => 5.0}} ] + facets_fields_for_solr,
    #  :exclude_fields => [:setting],
    #  :include => [:profile],
    #  :facets => facets_option_for_solr,
    #  :if => proc{|a| ! ['RssFeed'].include?(a.class.name)}
    def acts_as_faceted(options)
      extend ClassMethods
      extend ActsAsSolr::CommonMethods

      cattr_accessor :facets
      cattr_accessor :facets_order
      cattr_accessor :to_solr_fields_names
      cattr_accessor :facets_results_containers
      cattr_accessor :solr_fields_names
      cattr_accessor :facets_option_for_solr
      cattr_accessor :facets_fields_for_solr
      cattr_accessor :facet_category_query

      self.facets = options[:fields]
      self.facets_order = options[:order] || self.facets.keys
      self.facets_results_containers = { fields: "facet_fields", queries: "facet_queries", ranges: "facet_ranges" }
      self.facets_option_for_solr = Hash[facets.select { |id, data| !data.has_key?(:queries) }].keys
      self.facets_fields_for_solr = facets.map { |id, data| { id => data[:type] || :facet } }
      self.solr_fields_names = facets.map { |id, data| id.to_s + "_" + get_solr_field_type(data[:type] || :facet) }
      self.facet_category_query = options[:category_query]

      # A hash to retrieve the field key for the solr facet string returned
      # :field_name => "field_name_facet"
      self.to_solr_fields_names = Hash[facets.keys.zip(solr_fields_names)]

      def facet_by_id(id)
        { id: id }.merge(facets[id]) if facets[id]
      end

      def map_facets_for(environment)
        facets_order.map do |id|
          facet = facet_by_id(id)
          next if facet[:type_if] && !facet[:type_if].call(self.new)

          if facet[:multi]
            facet[:label].call(environment).map do |label_id, label|
              facet.merge(id: facet[:id].to_s + "_" + label_id.to_s, solr_field: facet[:id], label_id: label_id, label: label)
            end
          else
            facet.merge(id: facet[:id].to_s, solr_field: facet[:id])
          end
        end.compact.flatten
      end

      def map_facet_results(facet, facet_params, facets_data, unfiltered_facets_data = {}, options = {})
        raise "Use map_facets_for before this method" if facet[:solr_field].nil?

        facets_data = {} if facets_data.blank? # could be empty array
        solr_facet = to_solr_fields_names[facet[:solr_field]]
        unfiltered_facets_data ||= {}

        if facet[:queries]
          container = facets_data[facets_results_containers[:queries]]
          facet_data = (container.nil? || container.empty?) ? [] : container.select { |k, v| k.starts_with? solr_facet }
          container = unfiltered_facets_data[facets_results_containers[:queries]]
          unfiltered_facet_data = (container.nil? || container.empty?) ? [] : container.select { |k, v| k.starts_with? solr_facet }
        else
          container = facets_data[facets_results_containers[:fields]]
          facet_data = (container.nil? || container.empty?) ? [] : container[solr_facet] || []
          container = unfiltered_facets_data[facets_results_containers[:fields]]
          unfiltered_facet_data = (container.nil? || container.empty?) ? [] : container[solr_facet] || []
        end

        if !unfiltered_facets_data.blank? && !facet_params.blank?
          f = Hash[Array(facet_data)]
          zeros = []
          facet_data = unfiltered_facet_data.map do |id, count|
            count = f[id]
            if count.nil?
              zeros.push [id, 0]
              nil
            else
              [id, count]
            end
          end.compact + zeros
        end

        facet_count = facet_data.length

        if facet[:queries]
          result = facet_data.map do |id, count|
            q = id[id.index(":") + 1, id.length]
            label = facet_result_name(facet, q)
            [q, label, count] if count > 0
          end.compact
          result = facet[:queries_order].map { |id| result.detect { |rid, label, count| rid == id } }.compact if facet[:queries_order]
        elsif facet[:proc]
          if facet[:label_id]
            result = facet_data.map do |id, count|
              name = facet_result_name(facet, id)
              [id, name, count] if name
            end.compact
            # FIXME limit is NOT improving performance in this case :(
            facet_count = result.length
            result = result.first(options[:limit]) if options[:limit]
          else
            facet_data = facet_data.first(options[:limit]) if options[:limit]
            result = facet_data.map { |id, count| [id, facet_result_name(facet, id), count] }
          end
        else
          facet_data = facet_data.first(options[:limit]) if options[:limit]
          result = facet_data.map { |id, count| [id, facet_result_name(facet, id), count] }
        end

        sorted = facet_result_sort(facet, result, options[:sort])

        # length can't be used if limit option is given;
        # total_entries comes to help
        sorted.class.send(:define_method, :total_entries, proc { facet_count })

        sorted
      end

      def facet_result_sort(facet, facets_data, sort_by = nil)
        if facet[:queries_order]
          facets_data
        elsif sort_by == :alphabetically
          facets_data.sort { |a, b| Array(a[1])[0] <=> Array(b[1])[0] }
        elsif sort_by == :count
          facets_data.sort { |a, b| -1 * (a[2] <=> b[2]) }
        else
          facets_data
        end
      end

      def facet_result_name(facet, data)
        if facet[:queries]
          gettext(facet[:queries][data])
        elsif facet[:proc]
          if facet[:multi]
            facet[:label_id] ||= 0
            facet[:proc].call(facet, data)
          else
            gettext(facet[:proc].call(data))
          end
        else
          data
        end
      end

      def facet_label(facet)
        return nil unless facet

        _(facet[:label])
      end

      def facets_find_options(facets_selected = {}, options = {})
        browses = []
        facets_selected ||= {}
        facets_selected.map do |id, value|
          next unless facets[id.to_sym]

          if value.kind_of?(Hash)
            value.map do |label_id, value|
              value.to_a.each do |value|
                browses << id.to_s + ":" + (facets[id.to_sym][:queries] ? value : '"' + value.to_s + '"')
              end
            end
          else
            browses << id.to_s + ":" + (facets[id.to_sym][:queries] ? value : '"' + value.to_s + '"')
          end
        end.flatten

        { facets: { zeros: false, sort: :count,
                    fields: facets_option_for_solr,
                    browse: browses,
                    query: facets.map { |f, options| options[:queries].keys.map { |q| f.to_s + ":" + q } if options[:queries] }.compact.flatten, } }
      end
    end
  end
end

ApplicationRecord.extend ActsAsFaceted::ActsMethods