forest_admin_datasource_toolkit 1.0.0.pre.beta.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/README.md +31 -0
  4. data/Rakefile +12 -0
  5. data/forest_admin_datasource_toolkit.gemspec +37 -0
  6. data/lib/forest_admin_datasource_toolkit/collection.rb +50 -0
  7. data/lib/forest_admin_datasource_toolkit/components/caller.rb +31 -0
  8. data/lib/forest_admin_datasource_toolkit/components/contracts/collection_contract.rb +51 -0
  9. data/lib/forest_admin_datasource_toolkit/components/contracts/datasource_contract.rb +27 -0
  10. data/lib/forest_admin_datasource_toolkit/components/query/aggregation.rb +23 -0
  11. data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_equivalent.rb +66 -0
  12. data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_factory.rb +123 -0
  13. data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree.rb +70 -0
  14. data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_branch.rb +68 -0
  15. data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_leaf.rb +144 -0
  16. data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/operators.rb +100 -0
  17. data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/transforms/comparisons.rb +123 -0
  18. data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/transforms/pattern.rb +48 -0
  19. data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/transforms/times.rb +112 -0
  20. data/lib/forest_admin_datasource_toolkit/components/query/filter.rb +45 -0
  21. data/lib/forest_admin_datasource_toolkit/components/query/filter_factory.rb +158 -0
  22. data/lib/forest_admin_datasource_toolkit/components/query/page.rb +14 -0
  23. data/lib/forest_admin_datasource_toolkit/components/query/projection.rb +42 -0
  24. data/lib/forest_admin_datasource_toolkit/components/query/projection_factory.rb +30 -0
  25. data/lib/forest_admin_datasource_toolkit/datasource.rb +29 -0
  26. data/lib/forest_admin_datasource_toolkit/exceptions/forest_exception.rb +10 -0
  27. data/lib/forest_admin_datasource_toolkit/schema/column_schema.rb +34 -0
  28. data/lib/forest_admin_datasource_toolkit/schema/primitive_type.rb +31 -0
  29. data/lib/forest_admin_datasource_toolkit/schema/relation_schema.rb +13 -0
  30. data/lib/forest_admin_datasource_toolkit/schema/relations/many_to_many_schema.rb +26 -0
  31. data/lib/forest_admin_datasource_toolkit/schema/relations/many_to_one_schema.rb +16 -0
  32. data/lib/forest_admin_datasource_toolkit/schema/relations/one_to_many_schema.rb +16 -0
  33. data/lib/forest_admin_datasource_toolkit/schema/relations/one_to_one_schema.rb +16 -0
  34. data/lib/forest_admin_datasource_toolkit/utils/collection.rb +162 -0
  35. data/lib/forest_admin_datasource_toolkit/utils/record.rb +20 -0
  36. data/lib/forest_admin_datasource_toolkit/utils/schema.rb +42 -0
  37. data/lib/forest_admin_datasource_toolkit/version.rb +3 -0
  38. data/lib/forest_admin_datasource_toolkit.rb +10 -0
  39. data/sig/forest_admin_datasource_toolkit/collection.rbs +26 -0
  40. data/sig/forest_admin_datasource_toolkit/components/contracts/collection_contract.rbs +29 -0
  41. data/sig/forest_admin_datasource_toolkit/components/contracts/datasource_contract.rbs +17 -0
  42. data/sig/forest_admin_datasource_toolkit/datasource.rbs +12 -0
  43. data/sig/forest_admin_datasource_toolkit/schema/column_schema.rbs +15 -0
  44. data/sig/forest_admin_datasource_toolkit/schema/relation_schema.rbs +8 -0
  45. data/sig/forest_admin_datasource_toolkit/schema/relations/many_relation_schema.rbs +10 -0
  46. data/sig/forest_admin_datasource_toolkit/schema/relations/many_to_many_schema.rbs +11 -0
  47. data/sig/forest_admin_datasource_toolkit/schema/relations/single_relation_schema.rbs +10 -0
  48. data/sig/forest_admin_datasource_toolkit.rbs +4 -0
  49. metadata +126 -0
@@ -0,0 +1,144 @@
1
+ module ForestAdminDatasourceToolkit
2
+ module Components
3
+ module Query
4
+ module ConditionTree
5
+ module Nodes
6
+ class ConditionTreeLeaf < ConditionTree
7
+ include ForestAdminDatasourceToolkit::Utils
8
+ include ForestAdminDatasourceToolkit::Exceptions
9
+
10
+ attr_reader :field, :operator, :value
11
+
12
+ def initialize(field, operator, value = nil)
13
+ @field = field
14
+ @operator = operator
15
+ @value = value
16
+ valid_operator(@operator) if @operator
17
+ super()
18
+ end
19
+
20
+ def to_h
21
+ {
22
+ field: @field,
23
+ operator: @operator,
24
+ value: @value
25
+ }
26
+ end
27
+
28
+ def valid_operator(value)
29
+ return if Operators.exist?(value)
30
+
31
+ raise ForestException, "Invalid operators, the #{value} operator does not exist."
32
+ end
33
+
34
+ def inverse
35
+ return override(operator: "Not_#{@operator}") if Operators.exist?("Not_#{@operator}")
36
+ return override(operator: @operator[4..]) if @operator.start_with?('Not')
37
+
38
+ case @operator
39
+ when 'Blank'
40
+ override(operator: 'Present')
41
+ when 'Present'
42
+ override(operator: 'Blank')
43
+ else
44
+ raise ForestException, "Operator: #{@operator} cannot be inverted."
45
+ end
46
+ end
47
+
48
+ def replace_leafs
49
+ result = yield(self)
50
+ if result.nil?
51
+ nil
52
+ elsif result.is_a?(ConditionTree)
53
+ result
54
+ else
55
+ ConditionTreeFactory.from_array(result)
56
+ end
57
+ end
58
+
59
+ def match(record, collection, timezone)
60
+ field_value = Record.field_value(record, @field)
61
+ column_type = Utils::Collection.get_field_schema(collection, @field).column_type
62
+
63
+ supported = [
64
+ Operators::IN,
65
+ Operators::EQUAL,
66
+ Operators::LESS_THAN,
67
+ Operators::GREATER_THAN,
68
+ Operators::MATCH,
69
+ Operators::STARTS_WITH,
70
+ Operators::ENDS_WITH,
71
+ Operators::LONGER_THAN,
72
+ Operators::SHORTER_THAN,
73
+ Operators::INCLUDES_ALL,
74
+ Operators::NOT_IN,
75
+ Operators::NOT_EQUAL,
76
+ Operators::NOT_CONTAINS
77
+ ]
78
+
79
+ case @operator
80
+ when Operators::IN
81
+ Array(@value).include?(field_value)
82
+ when Operators::EQUAL
83
+ field_value == @value
84
+ when Operators::LESS_THAN
85
+ field_value < @value
86
+ when Operators::GREATER_THAN
87
+ field_value > @value
88
+ when Operators::MATCH
89
+ field_value.is_a?(String) && @value.match(field_value)
90
+ when Operators::STARTS_WITH
91
+ field_value.is_a?(String) && field_value.start_with?(@value)
92
+ when Operators::ENDS_WITH
93
+ field_value.is_a?(String) && field_value.end_with?(@value)
94
+ when Operators::LONGER_THAN
95
+ field_value.is_a?(String) && field_value.length > @value
96
+ when Operators::SHORTER_THAN
97
+ field_value.is_a?(String) && field_value.length < @value
98
+ when Operators::INCLUDES_ALL
99
+ Array(@value).all? { |v| field_value.include?(v) }
100
+ when Operators::NOT_IN, Operators::NOT_EQUAL, Operators::NOT_CONTAINS
101
+ !inverse.match(record, collection, timezone)
102
+ else
103
+ ConditionTreeEquivalent.get_equivalent_tree(
104
+ self,
105
+ supported,
106
+ column_type,
107
+ timezone
108
+ )&.match(record, collection, timezone)
109
+ end
110
+ end
111
+
112
+ def for_each_leaf
113
+ yield(self)
114
+ end
115
+
116
+ def every_leaf
117
+ yield(self)
118
+ end
119
+
120
+ def some_leaf
121
+ yield(self)
122
+ end
123
+
124
+ def projection
125
+ Projection.new([@field])
126
+ end
127
+
128
+ def override(args)
129
+ ConditionTreeLeaf.new(
130
+ args[:field] || @field,
131
+ args[:operator] || @operator,
132
+ args[:value] || @value
133
+ )
134
+ end
135
+
136
+ def use_interval_operator
137
+ Operators.interval_operators.include?(@operator)
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,100 @@
1
+ module ForestAdminDatasourceToolkit
2
+ module Components
3
+ module Query
4
+ module ConditionTree
5
+ class Operators
6
+ EQUAL = 'Equal'.freeze
7
+ NOT_EQUAL = 'Not_Equal'.freeze
8
+ LESS_THAN = 'Less_Than'.freeze
9
+ GREATER_THAN = 'Greater_Than'.freeze
10
+ MATCH = 'Match'.freeze
11
+ LIKE = 'Like'.freeze
12
+ I_LIKE = 'ILike'.freeze
13
+ NOT_CONTAINS = 'Not_Contains'.freeze
14
+ CONTAINS = 'Contains'.freeze
15
+ I_CONTAINS = 'IContains'.freeze
16
+ LONGER_THAN = 'Longer_Than'.freeze
17
+ SHORTER_THAN = 'Shorter_Than'.freeze
18
+ INCLUDES_ALL = 'Includes_All'.freeze
19
+ PRESENT = 'Present'.freeze
20
+ BLANK = 'Blank'.freeze
21
+ IN = 'In'.freeze
22
+ NOT_IN = 'Not_In'.freeze
23
+ STARTS_WITH = 'Starts_With'.freeze
24
+ I_STARTS_WITH = 'IStarts_With'.freeze
25
+ ENDS_WITH = 'Ends_With'.freeze
26
+ I_ENDS_WITH = 'IEnds_With'.freeze
27
+ MISSING = 'Missing'.freeze
28
+ BEFORE = 'Before'.freeze
29
+ AFTER = 'After'.freeze
30
+ AFTER_X_HOURS_AGO = 'After_X_Hours_Ago'.freeze
31
+ BEFORE_X_HOURS_AGO = 'Before_X_Hours_Ago'.freeze
32
+ FUTURE = 'Future'.freeze
33
+ PAST = 'Past'.freeze
34
+ TODAY = 'Today'.freeze
35
+ YESTERDAY = 'Yesterday'.freeze
36
+ PREVIOUS_WEEK = 'Previous_Week'.freeze
37
+ PREVIOUS_MONTH = 'Previous_Month'.freeze
38
+ PREVIOUS_QUARTER = 'Previous_Quarter'.freeze
39
+ PREVIOUS_YEAR = 'Previous_Year'.freeze
40
+ PREVIOUS_WEEK_TO_DATE = 'Previous_Week_To_Date'.freeze
41
+ PREVIOUS_MONTH_TO_DATE = 'Previous_Month_To_Date'.freeze
42
+ PREVIOUS_QUARTER_TO_DATE = 'Previous_Quarter_To_Date'.freeze
43
+ PREVIOUS_YEAR_TO_DATE = 'Previous_Year_To_Date'.freeze
44
+ PREVIOUS_X_DAYS = 'Previous_X_Days'.freeze
45
+ PREVIOUS_X_DAYS_TO_DATE = 'Previous_X_Days_To_Date'.freeze
46
+
47
+ def self.all
48
+ constants
49
+ end
50
+
51
+ def self.exist?(operator_value)
52
+ case operator_value
53
+ when 'ILike'
54
+ operator_value = 'I_LIKE'
55
+ when 'IContains'
56
+ operator_value = 'I_CONTAINS'
57
+ when 'IStarts_With'
58
+ operator_value = 'I_STARTS_WITH'
59
+ when 'IEnds_With'
60
+ operator_value = 'I_ENDS_WITH'
61
+ end
62
+
63
+ all.include?(operator_value.upcase.to_sym)
64
+ end
65
+
66
+ def self.interval_operators
67
+ [
68
+ self::TODAY,
69
+ self::YESTERDAY,
70
+ self::PREVIOUS_MONTH,
71
+ self::PREVIOUS_QUARTER,
72
+ self::PREVIOUS_WEEK,
73
+ self::PREVIOUS_YEAR,
74
+ self::PREVIOUS_MONTH_TO_DATE,
75
+ self::PREVIOUS_QUARTER_TO_DATE,
76
+ self::PREVIOUS_WEEK_TO_DATE,
77
+ self::PREVIOUS_X_DAYS_TO_DATE,
78
+ self::PREVIOUS_X_DAYS,
79
+ self::PREVIOUS_YEAR_TO_DATE
80
+ ]
81
+ end
82
+
83
+ def self.unique_operators
84
+ [
85
+ self::EQUAL,
86
+ self::NOT_EQUAL,
87
+ self::LESS_THAN,
88
+ self::GREATER_THAN,
89
+ self::MATCH,
90
+ self::NOT_CONTAINS,
91
+ self::LONGER_THAN,
92
+ self::SHORTER_THAN,
93
+ self::INCLUDES_ALL
94
+ ]
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,123 @@
1
+ module ForestAdminDatasourceToolkit
2
+ module Components
3
+ module Query
4
+ module ConditionTree
5
+ module Transforms
6
+ class Comparisons
7
+ include ForestAdminDatasourceToolkit::Components::Query::ConditionTree::Nodes
8
+
9
+ def self.transforms
10
+ {
11
+ Operators::BLANK => [
12
+ {
13
+ depends_on: [Operators::IN],
14
+ for_types: ['String'],
15
+ replacer: ->(leaf) { leaf.override({ operator: Operators::IN, value: [nil, ''] }) }
16
+ },
17
+ {
18
+ depends_on: [Operators::MISSING],
19
+ replacer: ->(leaf) { leaf.override({ operator: Operators::MISSING }) }
20
+ }
21
+ ],
22
+ Operators::MISSING => [
23
+ {
24
+ depends_on: [Operators::EQUAL],
25
+ replacer: ->(leaf) { leaf.override({ operator: Operators::EQUAL, value: nil }) }
26
+ }
27
+ ],
28
+ Operators::PRESENT => [
29
+ {
30
+ depends_on: [Operators::NOT_IN],
31
+ for_types: ['String'],
32
+ replacer: ->(leaf) { leaf.override({ operator: Operators::NOT_IN, value: [nil, ''] }) }
33
+ },
34
+ {
35
+ depends_on: [Operators::NOT_EQUAL],
36
+ replacer: ->(leaf) { leaf.override({ operator: Operators::NOT_EQUAL, value: nil }) }
37
+ }
38
+ ],
39
+ Operators::EQUAL => [
40
+ {
41
+ depends_on: [Operators::IN],
42
+ replacer: ->(leaf) { leaf.override({ operator: Operators::IN, value: [leaf.value] }) }
43
+ }
44
+ ],
45
+ Operators::IN => [
46
+ {
47
+ depends_on: [Operators::EQUAL, Operators::MATCH],
48
+ for_types: ['String'],
49
+ replacer: lambda { |leaf|
50
+ values = leaf.value
51
+ conditions = []
52
+
53
+ [nil, ''].each do |value|
54
+ if values.include?(value)
55
+ conditions.push(ConditionTreeLeaf.new(leaf.field, Operators::EQUAL, value))
56
+ end
57
+ end
58
+
59
+ if values.any? { |value| !value.nil? && value != '' }
60
+ escaped = values.filter { |value| !value.nil? && value != '' }
61
+
62
+ conditions.push(ConditionTreeLeaf.new(leaf.field, Operators::MATCH,
63
+ "/(#{escaped.join("|")})/g"))
64
+ end
65
+
66
+ ConditionTreeFactory.union(conditions)
67
+ }
68
+ },
69
+ {
70
+ depends_on: [Operators::EQUAL],
71
+ replacer: lambda { |leaf|
72
+ ConditionTreeFactory.union(
73
+ leaf.value.map { |item| leaf.override({ operator: Operators::EQUAL, value: item }) }
74
+ )
75
+ }
76
+ }
77
+ ],
78
+ Operators::NOT_EQUAL => [
79
+ {
80
+ depends_on: [Operators::NOT_IN],
81
+ replacer: ->(leaf) { leaf.override({ operator: Operators::NOT_IN, value: [leaf.value] }) }
82
+ }
83
+ ],
84
+ Operators::NOT_IN => [
85
+ {
86
+ depends_on: [Operators::NOT_EQUAL, Operators::MATCH],
87
+ for_types: ['String'],
88
+ replacer: lambda { |leaf|
89
+ values = leaf.value
90
+ conditions = []
91
+
92
+ [nil, ''].each do |value|
93
+ if values.include?(value)
94
+ conditions.push(ConditionTreeLeaf.new(leaf.field, Operators::NOT_EQUAL, value))
95
+ end
96
+ end
97
+
98
+ if values.any? { |value| !value.nil? && value != '' }
99
+ escaped = values.filter { |value| !value.nil? && value != '' }
100
+ conditions.push(ConditionTreeLeaf.new(leaf.field, Operators::MATCH,
101
+ "/(?!(#{escaped.join("|")}))/g"))
102
+ end
103
+
104
+ ConditionTreeFactory.intersect(conditions)
105
+ }
106
+ },
107
+ {
108
+ depends_on: [Operators::NOT_EQUAL],
109
+ replacer: lambda { |leaf|
110
+ ConditionTreeFactory.intersect(
111
+ leaf.value.map { |item| leaf.override({ operator: Operators::NOT_EQUAL, value: item }) }
112
+ )
113
+ }
114
+ }
115
+ ]
116
+ }
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,48 @@
1
+ module ForestAdminDatasourceToolkit
2
+ module Components
3
+ module Query
4
+ module ConditionTree
5
+ module Transforms
6
+ class Pattern
7
+ def self.likes(get_pattern, case_sensitive)
8
+ operator = case_sensitive ? Operators::LIKE : Operators::I_LIKE
9
+
10
+ {
11
+ dependsOn: [operator],
12
+ forTypes: ['String'],
13
+ replacer: ->(leaf) { leaf.override(operator: operator, value: get_pattern.call(leaf.value)) }
14
+ }
15
+ end
16
+
17
+ def self.match(case_sensitive)
18
+ {
19
+ dependsOn: [Operators::MATCH],
20
+ forTypes: ['String'],
21
+ replacer: lambda { |leaf|
22
+ regex = leaf.value.gsub(/([\.\\\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:\-])/, '\\\\\1')
23
+ regex.gsub!('%', '.*')
24
+ regex.tr!('_', '.')
25
+
26
+ leaf.override(operator: Operators::MATCH, value: "/^#{regex}$/#{case_sensitive ? "" : "i"}")
27
+ }
28
+ }
29
+ end
30
+
31
+ def self.transforms
32
+ {
33
+ Operators::CONTAINS => [likes(->(value) { "%#{value}%" }, true)],
34
+ Operators::STARTS_WITH => [likes(->(value) { "#{value}%" }, true)],
35
+ Operators::ENDS_WITH => [likes(->(value) { "%#{value}" }, true)],
36
+ Operators::I_CONTAINS => [likes(->(value) { "%#{value}%" }, false)],
37
+ Operators::I_STARTS_WITH => [likes(->(value) { "#{value}%" }, false)],
38
+ Operators::I_ENDS_WITH => [likes(->(value) { "%#{value}" }, false)],
39
+ Operators::I_LIKE => [match(false)],
40
+ Operators::LIKE => [match(true)]
41
+ }
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,112 @@
1
+ require 'active_support/all'
2
+ require 'active_support/core_ext/numeric/time'
3
+
4
+ module ForestAdminDatasourceToolkit
5
+ module Components
6
+ module Query
7
+ module ConditionTree
8
+ module Transforms
9
+ class Times
10
+ def self.format(value)
11
+ value.in_time_zone('UTC').iso8601
12
+ end
13
+
14
+ def self.compare(operator)
15
+ {
16
+ dependsOn: [operator],
17
+ forTypes: ['Date', 'Dateonly'],
18
+ replacer: lambda { |leaf, tz|
19
+ leaf.override(operator: operator, value: format(yield(Time.now.in_time_zone(tz), leaf.value)))
20
+ }
21
+ }
22
+ end
23
+
24
+ def self.interval(start_fn, end_fn)
25
+ {
26
+ dependsOn: [Operators::LESS_THAN, Operators::GREATER_THAN],
27
+ forTypes: ['Date', 'Dateonly'],
28
+ replacer: lambda do |leaf, tz|
29
+ value_greater_than = if leaf.value.nil?
30
+ format(start_fn.call(Time.now.in_time_zone(tz)))
31
+ else
32
+ format(start_fn.call(
33
+ Time.now.in_time_zone(tz), leaf.value
34
+ ))
35
+ end
36
+ value_less_than = if leaf.value.nil?
37
+ format(end_fn.call(Time.now.in_time_zone(tz)))
38
+ else
39
+ format(end_fn.call(
40
+ Time.now.in_time_zone(tz), leaf.value
41
+ ))
42
+ end
43
+
44
+ ConditionTreeFactory.intersect(
45
+ [
46
+ leaf.override(operator: Operators::GREATER_THAN, value: value_greater_than),
47
+ leaf.override(operator: Operators::LESS_THAN, value: value_less_than)
48
+ ]
49
+ )
50
+ end
51
+ }
52
+ end
53
+
54
+ def self.previous_interval(duration)
55
+ interval(
56
+ lambda { |now|
57
+ duration == 'quarter' ? now.prev_quarter : (now - 1.send(duration)).send("beginning_of_#{duration}")
58
+ },
59
+ ->(now) { now.send("beginning_of_#{duration}") }
60
+ )
61
+ end
62
+
63
+ def self.previous_interval_to_date(duration)
64
+ interval(
65
+ ->(now) { now.send("beginning_of_#{duration}") },
66
+ ->(now) { now }
67
+ )
68
+ end
69
+
70
+ def self.transforms
71
+ {
72
+ Operators::BEFORE => [compare(Operators::LESS_THAN) { |_now, value| Time.parse(value.to_s) }],
73
+ Operators::AFTER => [compare(Operators::GREATER_THAN) { |_now, value| Time.parse(value.to_s) }],
74
+ Operators::PAST => [compare(Operators::LESS_THAN) { |now| now }],
75
+ Operators::FUTURE => [compare(Operators::GREATER_THAN) { |now| now }],
76
+ Operators::BEFORE_X_HOURS_AGO => [compare(Operators::LESS_THAN) { |now, value| now - value.hours }],
77
+ Operators::AFTER_X_HOURS_AGO => [compare(Operators::GREATER_THAN) { |now, value| now - value.hours }],
78
+ Operators::PREVIOUS_WEEK_TO_DATE => [previous_interval_to_date('week')],
79
+ Operators::PREVIOUS_MONTH_TO_DATE => [previous_interval_to_date('month')],
80
+ Operators::PREVIOUS_QUARTER_TO_DATE => [previous_interval_to_date('quarter')],
81
+ Operators::PREVIOUS_YEAR_TO_DATE => [previous_interval_to_date('year')],
82
+ Operators::YESTERDAY => [previous_interval('day')],
83
+ Operators::PREVIOUS_WEEK => [previous_interval('week')],
84
+ Operators::PREVIOUS_MONTH => [previous_interval('month')],
85
+ Operators::PREVIOUS_QUARTER => [previous_interval('quarter')],
86
+ Operators::PREVIOUS_YEAR => [previous_interval('year')],
87
+ Operators::PREVIOUS_X_DAYS_TO_DATE => [
88
+ interval(
89
+ ->(now, value) { (now - value.days).beginning_of_day },
90
+ ->(now, _value) { now }
91
+ )
92
+ ],
93
+ Operators::PREVIOUS_X_DAYS => [
94
+ interval(
95
+ ->(now, value) { (now - value.days).beginning_of_day },
96
+ ->(now, _value) { now.beginning_of_day }
97
+ )
98
+ ],
99
+ Operators::TODAY => [
100
+ interval(
101
+ ->(now) { now.beginning_of_day },
102
+ ->(now) { (now + 1.day).beginning_of_day }
103
+ )
104
+ ]
105
+ }
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,45 @@
1
+ module ForestAdminDatasourceToolkit
2
+ module Components
3
+ module Query
4
+ class Filter
5
+ attr_reader :condition_tree, :segment, :sort, :search, :search_extended, :page
6
+
7
+ def initialize(condition_tree: nil, search: nil, search_extended: nil, segment: nil, sort: nil, page: nil)
8
+ @condition_tree = condition_tree
9
+ @search = search
10
+ @search_extended = search_extended
11
+ @segment = segment
12
+ @sort = sort
13
+ @page = page
14
+ end
15
+
16
+ def to_h
17
+ {
18
+ condition_tree: @condition_tree,
19
+ search: @search,
20
+ search_extended: @search_extended,
21
+ segment: @segment,
22
+ sort: @sort,
23
+ page: @page
24
+ }
25
+ end
26
+
27
+ def nestable?
28
+ !@search && !@segment
29
+ end
30
+
31
+ def override(args)
32
+ args = to_h.merge(args)
33
+
34
+ Filter.new(**args)
35
+ end
36
+
37
+ def nest(prefix)
38
+ raise ForestException, "Filter can't be nested" unless nestable?
39
+
40
+ override(condition_tree: @condition_tree&.nest(prefix))
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end