Class: ForestLiana::FiltersParser

Inherits:
Object
  • Object
show all
Defined in:
app/services/forest_liana/filters_parser.rb

Constant Summary collapse

AGGREGATOR_OPERATOR =
%w(and or)

Instance Method Summary collapse

Constructor Details

#initialize(filters, resource, timezone, params = nil) ⇒ FiltersParser

Returns a new instance of FiltersParser.



5
6
7
8
9
10
11
# File 'app/services/forest_liana/filters_parser.rb', line 5

def initialize(filters, resource, timezone, params = nil)
  @filters = filters
  @params = params
  @resource = resource
  @operator_date_parser = OperatorDateIntervalParser.new(timezone)
  @joins = []
end

Instance Method Details

#apply_filtersObject



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'app/services/forest_liana/filters_parser.rb', line 13

def apply_filters
  return @resource unless @filters

  where = parse_aggregation(@filters)
  return @resource unless where

  @joins.each do |join|
    current_resource = @resource.reflect_on_association(join.name).klass
    current_resource.include(ArelHelpers::Aliases)
    current_resource.aliased_as(join.name) do |aliased_resource|
      @resource = @resource.joins(ArelHelpers.join_association(@resource, join.name, Arel::Nodes::OuterJoin, aliases: [aliased_resource]))
    end
  end

  @resource.where(where)
end

#apply_filters_on_previous_interval(previous_condition) ⇒ Object



235
236
237
238
239
240
# File 'app/services/forest_liana/filters_parser.rb', line 235

def apply_filters_on_previous_interval(previous_condition)
  # Ressource should have already been joined
  where = parse_aggregation_on_previous_interval(@filters, previous_condition)

  @resource.where(where)
end

#ensure_valid_aggregation(node) ⇒ Object

Raises:

  • (ForestLiana::Errors::HTTP422Error)


280
281
282
283
# File 'app/services/forest_liana/filters_parser.rb', line 280

def ensure_valid_aggregation(node)
  raise ForestLiana::Errors::HTTP422Error.new('Filters cannot be a raw value') unless node.is_a?(Hash)
  raise_empty_condition_in_filter_error if node.empty?
end

#ensure_valid_condition(condition) ⇒ Object

Raises:

  • (ForestLiana::Errors::HTTP422Error)


285
286
287
288
289
290
291
# File 'app/services/forest_liana/filters_parser.rb', line 285

def ensure_valid_condition(condition)
  raise_empty_condition_in_filter_error if condition.empty?
  raise ForestLiana::Errors::HTTP422Error.new('Condition cannot be a raw value') unless condition.is_a?(Hash)
  unless condition['field'].is_a?(String) and condition['operator'].is_a?(String)
    raise ForestLiana::Errors::HTTP422Error.new('Invalid condition format')
  end
end

#get_association_field_and_resource(field_name) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'app/services/forest_liana/filters_parser.rb', line 66

def get_association_field_and_resource(field_name)
  if is_belongs_to(field_name)
    association = field_name.partition(':').first.to_sym
    association_field = field_name.partition(':').last

    unless @resource.reflect_on_association(association)
      raise ForestLiana::Errors::HTTP422Error.new("Association '#{association}' not found")
    end

    current_resource = @resource.reflect_on_association(association).klass

    return association_field, current_resource
  else
    return field_name, @resource
  end
end

#get_association_name_for_condition(field) ⇒ Object



195
196
197
198
199
200
201
202
203
204
# File 'app/services/forest_liana/filters_parser.rb', line 195

def get_association_name_for_condition(field)
  field, subfield = field.split(':')

  association = @resource.reflect_on_association(field.to_sym)
  return nil if association.blank?

  @joins << association unless @joins.include? association

  association.name
end

#get_previous_interval_conditionObject

NOTICE: Look for a previous interval condition matching the following:

- If the filter is a simple condition at the root the check is done right away.
- There can't be a previous interval condition if the aggregator is 'or' (no meaning).
- The condition's operator has to be elligible for a previous interval.
- There can't be two previous interval condition.


211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'app/services/forest_liana/filters_parser.rb', line 211

def get_previous_interval_condition
  current_previous_interval = nil
  # NOTICE: Leaf condition at root
  unless @filters['aggregator']
    return @filters if @operator_date_parser.has_previous_interval?(@filters['operator'])
  end

  if @filters['aggregator'] === 'and'
    @filters['conditions'].each do |condition|
      # NOTICE: Nested conditions
      return nil if condition['aggregator']

      if @operator_date_parser.has_previous_interval?(condition['operator'])
        # NOTICE: There can't be two previous_interval.
        return nil if current_previous_interval

        current_previous_interval = condition
      end
    end
  end

  current_previous_interval
end

#is_belongs_to(field) ⇒ Object



191
192
193
# File 'app/services/forest_liana/filters_parser.rb', line 191

def is_belongs_to(field)
  field.include?(':')
end

#parse_aggregation(node) ⇒ Object



30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'app/services/forest_liana/filters_parser.rb', line 30

def parse_aggregation(node)
  ensure_valid_aggregation(node)

  return parse_condition(node) unless node['aggregator']

  conditions = []
  node['conditions'].each do |condition|
    conditions.push(parse_aggregation(condition))
  end

  operator = parse_aggregation_operator(node['aggregator'])

  conditions.empty? ? nil : "(#{conditions.join(" #{operator} ")})"
end

#parse_aggregation_on_previous_interval(node, previous_condition) ⇒ Object



242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'app/services/forest_liana/filters_parser.rb', line 242

def parse_aggregation_on_previous_interval(node, previous_condition)
  raise_empty_condition_in_filter_error unless node

  return parse_previous_interval_condition(node) unless node['aggregator']

  conditions = []
  node['conditions'].each do |condition|
    if condition == previous_condition
      conditions.push(parse_previous_interval_condition(condition))
    else
      conditions.push(parse_aggregation(condition))
    end
  end

  operator = parse_aggregation_operator(node['aggregator'])

  conditions.empty? ? nil : "(#{conditions.join(" #{operator} ")})"
end

#parse_aggregation_operator(aggregator_operator) ⇒ Object



111
112
113
114
115
116
117
# File 'app/services/forest_liana/filters_parser.rb', line 111

def parse_aggregation_operator(aggregator_operator)
  unless AGGREGATOR_OPERATOR.include?(aggregator_operator)
    raise_unknown_operator_error(aggregator_operator)
  end

  aggregator_operator.upcase
end

#parse_condition(condition) ⇒ Object



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'app/services/forest_liana/filters_parser.rb', line 45

def parse_condition(condition)
  where = parse_condition_without_smart_field(condition)

  field_name = condition['field']

  if ForestLiana::SchemaHelper.is_smart_field?(@resource, field_name)
    schema = ForestLiana.schema_for_resource(@resource)
    field_schema = schema.fields.find do |field|
      field[:field].to_s == field_name
    end

    unless field_schema.try(:[], :filter)
      raise ForestLiana::Errors::NotImplementedMethodError.new("method filter on smart field '#{field_name}' not found")
    end

    return field_schema[:filter].call(condition, where)
  end

  where
end

#parse_condition_without_smart_field(condition) ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'app/services/forest_liana/filters_parser.rb', line 83

def parse_condition_without_smart_field(condition)
  ensure_valid_condition(condition)

  operator = condition['operator']
  value = condition['value']
  field_name = condition['field']

  if @operator_date_parser.is_date_operator?(operator)
    condition = @operator_date_parser.get_date_filter(operator, value)
    return "#{parse_field_name(field_name)} #{condition}"
  end

  association_field, current_resource = get_association_field_and_resource(field_name)

  # NOTICE: Set the integer value instead of a string if "enum" type
  # NOTICE: Rails 3 do not have a defined_enums method
  if current_resource.respond_to?(:defined_enums) && current_resource.defined_enums.has_key?(association_field)
    value = current_resource.defined_enums[association_field][value]
  end

  parsed_field = parse_field_name(field_name)
  parsed_operator = parse_operator(operator)
  parsed_value = parse_value(operator, value)
  field_and_operator = "#{parsed_field} #{parsed_operator}"

  sanitize_condition(field_and_operator, operator, parsed_value)
end

#parse_field_name(field) ⇒ Object



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'app/services/forest_liana/filters_parser.rb', line 168

def parse_field_name(field)
  if is_belongs_to(field)
    current_resource = @resource.reflect_on_association(field.split(':').first.to_sym)&.klass
    raise ForestLiana::Errors::HTTP422Error.new("Field '#{field}' not found") unless current_resource

    association = get_association_name_for_condition(field)
    quoted_table_name = ActiveRecord::Base.connection.quote_column_name(association)
    field_name = field.split(':')[1]
  else
    quoted_table_name = @resource.quoted_table_name
    current_resource = @resource
    field_name = field
  end
  quoted_field_name = ActiveRecord::Base.connection.quote_column_name(field_name)

  column_found = current_resource.columns.find { |column| column.name == field.split(':').last }
  if column_found.nil? && !ForestLiana::SchemaHelper.is_smart_field?(current_resource, field_name)
    raise ForestLiana::Errors::HTTP422Error.new("Field '#{field}' not found")
  end

  "#{quoted_table_name}.#{quoted_field_name}"
end

#parse_operator(operator) ⇒ Object



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'app/services/forest_liana/filters_parser.rb', line 119

def parse_operator(operator)
  case operator
  when 'not'
    'NOT'
  when 'greater_than', 'after'
    '>'
  when 'less_than', 'before'
    '<'
  when 'contains', 'starts_with', 'ends_with'
    'LIKE'
  when 'not_contains'
    'NOT LIKE'
  when 'not_equal'
    '!='
  when 'equal'
    '='
  when 'blank'
    'IS'
  when 'present'
    'IS NOT'
  when 'in'
    'IN'
  else
    raise_unknown_operator_error(operator)
  end
end

#parse_previous_interval_condition(condition) ⇒ Object



261
262
263
264
265
266
267
268
269
270
# File 'app/services/forest_liana/filters_parser.rb', line 261

def parse_previous_interval_condition(condition)
  raise_empty_condition_in_filter_error unless condition

  parsed_condition = @operator_date_parser.get_date_filter_for_previous_interval(
    condition['operator'],
    condition['value']
  )

  "#{parse_field_name(condition['field'])} #{parsed_condition}"
end

#parse_value(operator, value) ⇒ Object



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'app/services/forest_liana/filters_parser.rb', line 146

def parse_value(operator, value)
  case operator
  when 'not', 'greater_than', 'less_than', 'not_equal', 'equal', 'before', 'after'
    value
  when 'contains', 'not_contains'
    "%#{value}%"
  when 'starts_with'
    "#{value}%"
  when 'ends_with'
    "%#{value}"
  when 'in'
    if value.kind_of?(String)
      value.split(',').map { |val| val.strip() }
    else
      value
    end
  when 'present', 'blank'
  else
    raise_unknown_operator_error(operator)
  end
end

#raise_empty_condition_in_filter_errorObject

Raises:

  • (ForestLiana::Errors::HTTP422Error)


276
277
278
# File 'app/services/forest_liana/filters_parser.rb', line 276

def raise_empty_condition_in_filter_error
  raise ForestLiana::Errors::HTTP422Error.new('Empty condition in filter')
end

#raise_unknown_operator_error(operator) ⇒ Object

Raises:

  • (ForestLiana::Errors::HTTP422Error)


272
273
274
# File 'app/services/forest_liana/filters_parser.rb', line 272

def raise_unknown_operator_error(operator)
  raise ForestLiana::Errors::HTTP422Error.new("Unknown provided operator '#{operator}'")
end