master
 1module Queryable
 2  extend ActiveSupport::Concern
 3
 4  included { send :include, ClassMethods }
 5
 6  module ClassMethods
 7    def filter_by(filters)
 8      return all if filters.empty?
 9      filters.reduce(self) do |current_scope, filter|
10        filter.call(current_scope)
11      end
12    end
13
14    def query_builder_for(params)
15      Queryable::Builder.new(params).tap do |builder|
16        yield builder if block_given?
17      end
18    end
19  end
20
21  class Builder
22    include Enumerable
23    attr_reader :params, :filters
24    delegate :empty?, to: :filters
25
26    def initialize(params)
27      @params = params
28      @filters = []
29    end
30
31    def always(&filter)
32      add do |relation|
33        filter.call(relation)
34      end
35    end
36
37    def if_present(key, &filter)
38      return if params[key].nil? || params[key] == ''
39      add do |relation|
40        filter.call(relation, normalize(params[key]))
41      end
42    end
43
44    def if_missing(key, &filter)
45      return if params[key].present?
46      add do |relation|
47        filter.call(relation, normalize(params[key]))
48      end
49    end
50
51    def each(&block)
52      @filters.each(&block)
53    end
54
55    private
56
57    def add(&filter)
58      @filters.push(filter)
59    end
60
61    def boolean?(value)
62      true?(value) || false?(value)
63    end
64
65    def true?(value)
66      "true" == value.to_s.downcase
67    end
68
69    def false?(value)
70      "false" == value.to_s.downcase
71    end
72
73    def normalize(value)
74      boolean?(value) ? true?(value) : value
75    end
76  end
77end