forest_admin_datasource_toolkit 1.0.0.pre.beta.21

Sign up to get free protection for your applications and to get access to all the features.
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