Module: Motor::ApiQuery::Filter

Defined in:
lib/motor/api_query/filter.rb

Constant Summary collapse

LIKE_FILTER_VALUE_REGEXP =
/\A%?(.*?)%?\z/.freeze
DISTINCT_RESTRICTED_COLUMN_TYPES =
%i[json point].freeze

Class Method Summary collapse

Class Method Details

.apply_filters(rel, filters) ⇒ Object



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/motor/api_query/filter.rb', line 46

def apply_filters(rel, filters)
  filters = clean_filters(filters)

  rel = apply_predicates(rel, filters)

  alias_tracker =
    if Rails.version.to_f >= 7.2
      rel.alias_tracker
    else
      ActiveRecord::Associations::AliasTracker.create(rel.connection, rel.table.name, [])
    end

  filter_clause_factory = ActiveRecord::Relation::FilterClauseFactory.new(rel.klass, rel.predicate_builder)

  where_clause = filter_clause_factory.build(filters, alias_tracker)

  rel_values = rel.instance_variable_get(:@values)

  if rel_values[:where]
    rel_values[:where] += where_clause
  else
    rel_values[:where] = where_clause
  end

  rel
end

.apply_predicates(rel, filters) ⇒ Object



32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/motor/api_query/filter.rb', line 32

def apply_predicates(rel, filters)
  joins = ActiveRecord::PredicateBuilder.filter_joins(rel.klass, filters)

  joins.flatten.reduce(rel) do |acc, j|
    if j.is_a?(String) || j.is_a?(Arel::Nodes::Join)
      acc.joins(j)
    elsif j.present?
      acc.left_outer_joins(j)
    else
      acc
    end
  end
end

.call(rel, params) ⇒ Object



11
12
13
14
15
16
17
18
19
20
# File 'lib/motor/api_query/filter.rb', line 11

def call(rel, params)
  return rel if params.blank?

  normalized_params = normalize_params(Array.wrap(params))

  rel = apply_filters(rel, normalized_params)
  rel = rel.distinct if can_apply_distinct?(rel)

  rel
end

.can_apply_distinct?(rel) ⇒ Boolean

Returns:

  • (Boolean)


105
106
107
108
109
# File 'lib/motor/api_query/filter.rb', line 105

def can_apply_distinct?(rel)
  rel.columns.none? do |column|
    DISTINCT_RESTRICTED_COLUMN_TYPES.include?(column.type)
  end
end

.clean_filters(value) ⇒ Object



22
23
24
25
26
27
28
29
30
# File 'lib/motor/api_query/filter.rb', line 22

def clean_filters(value)
  if value.class.name == 'ActionController::Parameters'
    value.to_unsafe_h
  elsif value.is_a?(Array)
    value.map { |v| clean_filters(v) }
  else
    value
  end
end

.normalize_action(action, value) ⇒ Object



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/motor/api_query/filter.rb', line 111

def normalize_action(action, value)
  case action
  when 'includes'
    ['contains', value]
  when 'contains'
    ['ilike', value.sub(LIKE_FILTER_VALUE_REGEXP, '%\1%')]
  when 'starts_with'
    ['ilike', value.sub(LIKE_FILTER_VALUE_REGEXP, '\1%')]
  when 'ends_with'
    ['ilike', value.sub(LIKE_FILTER_VALUE_REGEXP, '%\1')]
  when 'eqnull'
    ['eq', nil]
  when 'neqnull'
    ['neq', nil]
  else
    [action, value]
  end
end

.normalize_filter_hash(hash) ⇒ Object



90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/motor/api_query/filter.rb', line 90

def normalize_filter_hash(hash)
  hash.each_with_object({}) do |(action, value), acc|
    new_action, new_value =
      if value.is_a?(Hash)
        [action, normalize_filter_hash(value)]
      else
        normalize_action(action, value)
      end

    acc[new_action] = new_value

    acc
  end
end

.normalize_params(params) ⇒ Object



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/motor/api_query/filter.rb', line 73

def normalize_params(params)
  params.map do |item|
    next item if item.is_a?(String)
    next normalize_params(item) if item.is_a?(Array)

    item = item.to_unsafe_h if item.respond_to?(:to_unsafe_h)

    item.transform_values do |filter|
      if filter.is_a?(Hash)
        normalize_filter_hash(filter)
      else
        filter
      end
    end
  end.split('OR').product(['OR']).flatten(1)[0...-1]
end