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