1module Browseable
 
2  extend ActiveSupport::Concern
 
3
 
4  included do
 
5    class_attribute :facet_definitions, instance_writer: false
 
6    self.facet_definitions = {}
 
7
 
8    class_attribute :filter_definitions, instance_writer: false
 
 9    self.filter_definitions = []
 
10  end
 
 
12  class Facets
 
13    include Enumerable
 
 
15    attr_reader :klass
 
 
17    delegate :facet_definitions, to: :klass
 
18    delegate :key?, :has_key?, :keys, to: :facet_definitions
 
  • Complexity 1 » saikuro
20    def initialize(klass)
 
21      @klass = klass
 
22    end
 
  • Complexity 1 » saikuro
24    def [](key)
 
25      facet_counts[key]
 
26    end
 
  • Complexity 2 » saikuro
28    def each(&block)
 
29      keys.each do |key|
 
30        yield key, self[key]
 
31      end
 
32    end
 
  • Complexity 3 » saikuro
34    def slice(*only_these_keys)
 
35      only_these_keys.each_with_object({}) do |key, hash|
 
36        hash[key] = self[key] if has_key?(key)
 
37      end
 
38    end
 
 
40    private
 
  • Complexity 1 » saikuro
42    def facet_counts
 
43      @facet_counts ||= Hash.new(&facet_count_query)
 
44    end
 
  • Complexity 3 » saikuro
46    def facet_count_query
 
47      lambda do |hash, key|
 
48        unless facet_definitions.key?(key)
 
49          raise ArgumentError, "Unsupported facet: #{key.inspect}"
 
50        end
 
 
52        hash[key] = facet_scope(key).count
 
53      end
 
54    end
 
  • Complexity 1 » saikuro
56    def facet_scope(key)
 
57      klass.instance_exec(&facet_definitions.fetch(key))
 
58    end
 
59  end
 
 
61  class Filters
 
62    attr_reader :klass, :params
 
63    delegate :filter_definitions, to: :klass
 
  • Complexity 1 » saikuro
65    def initialize(klass, params)
 
66      @klass, @params = klass, params
 
67    end
 
  • Complexity 1 » saikuro
69    def to_hash
 
70      params.slice(*filter_definitions)
 
71    end
 
72  end
 
 
74  class Search
 
75    include Enumerable
 
 
77    attr_reader :klass, :params
 
 
79    delegate :offset, :out_of_bounds?, to: :results
 
80    delegate :next_page, :previous_page, to: :results
 
81    delegate :total_entries, :total_pages, to: :results
 
82    delegate :each, :empty?, :map, :to_a, to: :results
 
  • Complexity 1 » saikuro
84    def initialize(klass, params = {})
 
85      @klass, @params = klass, params
 
86    end
 
  • Complexity 1 » saikuro
88    def current_page
 
89      @current_page ||= [params[:page].to_i, 1].max
 
90    end
 
  • Complexity 1 » saikuro
92    def each(&block)
 
93      results.each(&block)
 
94    end
 
  • Complexity 1 » saikuro
96    def find_each(&block)
 
97      execute_search.find_each(&block)
 
98    end
 
 99
  • Complexity 1 » saikuro
100    def facets
 
101      @facets ||= Facets.new(klass)
 
102    end
 
  • Complexity 1 » saikuro
104    def filters
 
105      @filters ||= Filters.new(klass, params)
 
106    end
 
  • Complexity 1 » saikuro
108    def first_page?
 
109      current_page <= 1
 
110    end
 
  • Complexity 1 » saikuro
112    def second_page?
 
113      current_page == 2
 
114    end
 
  • Complexity 1 » saikuro
116    def last_page?
 
117      current_page >= total_pages
 
118    end
 
  • Complexity 1 » saikuro
120    def query
 
121      @query ||= params[:q].to_s
 
122    end
 
  • Complexity 1 » saikuro
124    def page_size
 
125      @page_size ||= [[params.fetch(:count, 50).to_i, 50].min, 1].max
 
126    end
 
  • Complexity 1 » saikuro
128    def previous_params
 
129      new_params(previous_page)
 
130    end
 
  • Complexity 1 » saikuro
132    def next_params
 
133      new_params(next_page)
 
134    end
 
  • Complexity 2 » saikuro
136    def scope
 
137      @scope ||= facets.keys.detect(-> { :all }){ |key| key.to_s == params[:state] }
 
138    end
 
  • Complexity 1 » saikuro
140    def scoped?
 
141      scope != :all
 
142    end
 
  • Complexity 1 » saikuro
144    def search?
 
145      query.present?
 
146    end
 
  • Complexity 1 » saikuro
148    def to_a
 
149      results.to_a
 
150    end
 
  • Complexity 2 » saikuro
152    def in_batches(&block)
 
153      execute_search.find_each do |obj|
 
154        block.call obj
 
155      end
 
156    end
 
  • TooManyStatements - has approx 7 statements » reek
  • Complexity 4 » saikuro
158    def inspect
 
159      [].tap do |parts|
 
160        parts << "#<#{self.class.name}:#{object_id}"
 
161        parts << " class: #{klass.klass.to_s.inspect}"
 
162        parts << " scope: #{scope.to_s.inspect}" if scoped?
 
163        parts << " query: #{query.inspect}" if search?
 
164        parts << " size: #{total_entries}"
 
165        parts << ">"
 
166      end.join
 
167    end
 
  • Complexity 1 » saikuro
169    def model
 
170      klass.klass
 
171    end
 
 
173    private
 
  • Complexity 3 » saikuro
175    def new_params(page)
 
176      {}.tap do |params|
 
177        params[:q] = query if query.present?
 
178        params[:state] = scope
 
179        params[:page] = page
 
180        params.merge!(filters)
 
181      end
 
182    end
 
  • Complexity 1 » saikuro
184    def results
 
185      @results ||= execute_search_with_pagination
 
186    end
 
  • Complexity 1 » saikuro
188    def execute_search_with_pagination
 
189      execute_search.paginate(page: current_page, per_page: page_size)
 
190    end
 
  • Complexity 2 » saikuro
192    def execute_search
 
193      if search?
 
194        relation = klass.basic_search(query)
 
195        relation = relation.except(:select).select(star)
 
196        relation = relation.except(:order)
 
197      else
 
198        relation = klass
 
199      end
 
 
201      relation.instance_exec(&klass.facet_definitions[scope])
 
202    end
 
  • Complexity 1 » saikuro
204    def star
 
205      klass.arel_table[Arel.star]
 
206    end
 
207  end
 
 
209  module ClassMethods
 
210    def facet(key, scope)
 
211      self.facet_definitions[key] = scope
 
212    end
 
 
214    def filter(key)
 
215      self.filter_definitions << key
 
216    end
 
 
218    def search(params)
 
219      Search.new(all, params)
 
220    end
 
221  end
 
222end