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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/README.md +31 -0
- data/Rakefile +12 -0
- data/forest_admin_datasource_toolkit.gemspec +37 -0
- data/lib/forest_admin_datasource_toolkit/collection.rb +50 -0
- data/lib/forest_admin_datasource_toolkit/components/caller.rb +31 -0
- data/lib/forest_admin_datasource_toolkit/components/contracts/collection_contract.rb +51 -0
- data/lib/forest_admin_datasource_toolkit/components/contracts/datasource_contract.rb +27 -0
- data/lib/forest_admin_datasource_toolkit/components/query/aggregation.rb +23 -0
- data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_equivalent.rb +66 -0
- data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_factory.rb +123 -0
- data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree.rb +70 -0
- data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_branch.rb +68 -0
- data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_leaf.rb +144 -0
- data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/operators.rb +100 -0
- data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/transforms/comparisons.rb +123 -0
- data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/transforms/pattern.rb +48 -0
- data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/transforms/times.rb +112 -0
- data/lib/forest_admin_datasource_toolkit/components/query/filter.rb +45 -0
- data/lib/forest_admin_datasource_toolkit/components/query/filter_factory.rb +158 -0
- data/lib/forest_admin_datasource_toolkit/components/query/page.rb +14 -0
- data/lib/forest_admin_datasource_toolkit/components/query/projection.rb +42 -0
- data/lib/forest_admin_datasource_toolkit/components/query/projection_factory.rb +30 -0
- data/lib/forest_admin_datasource_toolkit/datasource.rb +29 -0
- data/lib/forest_admin_datasource_toolkit/exceptions/forest_exception.rb +10 -0
- data/lib/forest_admin_datasource_toolkit/schema/column_schema.rb +34 -0
- data/lib/forest_admin_datasource_toolkit/schema/primitive_type.rb +31 -0
- data/lib/forest_admin_datasource_toolkit/schema/relation_schema.rb +13 -0
- data/lib/forest_admin_datasource_toolkit/schema/relations/many_to_many_schema.rb +26 -0
- data/lib/forest_admin_datasource_toolkit/schema/relations/many_to_one_schema.rb +16 -0
- data/lib/forest_admin_datasource_toolkit/schema/relations/one_to_many_schema.rb +16 -0
- data/lib/forest_admin_datasource_toolkit/schema/relations/one_to_one_schema.rb +16 -0
- data/lib/forest_admin_datasource_toolkit/utils/collection.rb +162 -0
- data/lib/forest_admin_datasource_toolkit/utils/record.rb +20 -0
- data/lib/forest_admin_datasource_toolkit/utils/schema.rb +42 -0
- data/lib/forest_admin_datasource_toolkit/version.rb +3 -0
- data/lib/forest_admin_datasource_toolkit.rb +10 -0
- data/sig/forest_admin_datasource_toolkit/collection.rbs +26 -0
- data/sig/forest_admin_datasource_toolkit/components/contracts/collection_contract.rbs +29 -0
- data/sig/forest_admin_datasource_toolkit/components/contracts/datasource_contract.rbs +17 -0
- data/sig/forest_admin_datasource_toolkit/datasource.rbs +12 -0
- data/sig/forest_admin_datasource_toolkit/schema/column_schema.rbs +15 -0
- data/sig/forest_admin_datasource_toolkit/schema/relation_schema.rbs +8 -0
- data/sig/forest_admin_datasource_toolkit/schema/relations/many_relation_schema.rbs +10 -0
- data/sig/forest_admin_datasource_toolkit/schema/relations/many_to_many_schema.rbs +11 -0
- data/sig/forest_admin_datasource_toolkit/schema/relations/single_relation_schema.rbs +10 -0
- data/sig/forest_admin_datasource_toolkit.rbs +4 -0
- metadata +126 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7ed1e878cea7868c7a6443190c5dd53b880e154dcc5b4088532a156151464263
|
4
|
+
data.tar.gz: 41f2bf182d1851db0afb7a53a943d023b4698fa33fbfd584bee4ca7e40b702ac
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a8376956ef1d90ae0f54d40aeb5ef81eef793d3aa16e7cd1da293cf927c1155ff7e7f8f848843175f21de6eabce34b11f934335665d29f99003e1d9b002835b6
|
7
|
+
data.tar.gz: 3b89ff9d179863b54a8000b3089acf05dbf6faff27d89fb6692b32107db752ad6ae9778ea34caebcdc133d6cba90e305cb295950666d6fc01f1609c6817658fc
|
data/.rspec
ADDED
data/README.md
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# ForestAdminDatasourceToolkit
|
2
|
+
|
3
|
+
TODO: Delete this and the text below, and describe your gem
|
4
|
+
|
5
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/forest_admin_datasource_toolkit`. To experiment with that code, run `bin/console` for an interactive prompt.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
|
10
|
+
|
11
|
+
Install the gem and add to the application's Gemfile by executing:
|
12
|
+
|
13
|
+
$ bundle add UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
|
14
|
+
|
15
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
16
|
+
|
17
|
+
$ gem install UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Development
|
24
|
+
|
25
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
26
|
+
|
27
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
28
|
+
|
29
|
+
## Contributing
|
30
|
+
|
31
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/forest_admin_datasource_toolkit.
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
2
|
+
$LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib)
|
3
|
+
|
4
|
+
require_relative "lib/forest_admin_datasource_toolkit/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "forest_admin_datasource_toolkit"
|
8
|
+
spec.version = ForestAdminDatasourceToolkit::VERSION
|
9
|
+
spec.authors = ["Matthieu", "Nicolas"]
|
10
|
+
spec.email = ["matthv@gmail.com", "nicolasalexandre9@gmail.com"]
|
11
|
+
spec.homepage = "https://www.forestadmin.com"
|
12
|
+
spec.summary = "Ruby agent for Forest Admin."
|
13
|
+
spec.description = "Forest is a modern admin interface that works on all major web frameworks. This gem makes Forest
|
14
|
+
admin work on any Ruby application."
|
15
|
+
spec.license = "MIT"
|
16
|
+
spec.required_ruby_version = ">= 3.0.0"
|
17
|
+
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
19
|
+
spec.metadata["source_code_uri"] = "https://github.com/ForestAdmin/agent-ruby"
|
20
|
+
spec.metadata["changelog_uri"] = "https://github.com/ForestAdmin/agent-ruby/CHANGELOG.md"
|
21
|
+
spec.metadata["rubygems_mfa_required"] = "false"
|
22
|
+
|
23
|
+
# Specify which files should be added to the gem when it is released.
|
24
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
25
|
+
spec.files = Dir.chdir(__dir__) do
|
26
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
27
|
+
(File.expand_path(f) == __FILE__) ||
|
28
|
+
f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor Gemfile])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
spec.bindir = "exe"
|
32
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
33
|
+
spec.require_paths = ["lib"]
|
34
|
+
|
35
|
+
spec.add_dependency "activesupport", ">= 6.1"
|
36
|
+
spec.add_dependency "zeitwerk", "~> 2.3"
|
37
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module ForestAdminDatasourceToolkit
|
2
|
+
class Collection < Components::Contracts::CollectionContract
|
3
|
+
attr_accessor :fields, :segments
|
4
|
+
|
5
|
+
attr_reader :actions,
|
6
|
+
:charts,
|
7
|
+
:datasource,
|
8
|
+
:name,
|
9
|
+
:schema,
|
10
|
+
:native_driver
|
11
|
+
|
12
|
+
attr_writer :searchable,
|
13
|
+
:countable
|
14
|
+
|
15
|
+
def initialize(datasource, name, native_driver: nil)
|
16
|
+
super()
|
17
|
+
@datasource = datasource
|
18
|
+
@name = name
|
19
|
+
@native_driver = native_driver
|
20
|
+
@fields = {}
|
21
|
+
@schema = {}
|
22
|
+
@fields = {}
|
23
|
+
@actions = {}
|
24
|
+
@segments = {}
|
25
|
+
@charts = {}
|
26
|
+
@searchable = false
|
27
|
+
@countable = true
|
28
|
+
end
|
29
|
+
|
30
|
+
def is_countable?
|
31
|
+
@countable
|
32
|
+
end
|
33
|
+
|
34
|
+
def is_searchable?
|
35
|
+
@searchable
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_field(name, field)
|
39
|
+
raise Exceptions::ForestException, "Field #{name} already defined in collection" if @fields.key?(name)
|
40
|
+
|
41
|
+
@fields[name] = field
|
42
|
+
end
|
43
|
+
|
44
|
+
def add_fields(fields)
|
45
|
+
fields.each do |name, field|
|
46
|
+
add_field(name, field)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module ForestAdminDatasourceToolkit
|
2
|
+
module Components
|
3
|
+
class Caller
|
4
|
+
attr_reader :id, :email, :first_name, :last_name, :tags, :team, :rendering_id, :timezone, :permission_level, :role
|
5
|
+
|
6
|
+
def initialize(
|
7
|
+
id:,
|
8
|
+
email:,
|
9
|
+
first_name:,
|
10
|
+
last_name:,
|
11
|
+
team:,
|
12
|
+
rendering_id:,
|
13
|
+
tags:,
|
14
|
+
timezone:,
|
15
|
+
permission_level:,
|
16
|
+
role: nil
|
17
|
+
)
|
18
|
+
@id = id
|
19
|
+
@email = email
|
20
|
+
@first_name = first_name
|
21
|
+
@last_name = last_name
|
22
|
+
@team = team
|
23
|
+
@rendering_id = rendering_id
|
24
|
+
@tags = tags
|
25
|
+
@timezone = timezone
|
26
|
+
@permission_level = permission_level
|
27
|
+
@role = role
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module ForestAdminDatasourceToolkit
|
2
|
+
module Components
|
3
|
+
module Contracts
|
4
|
+
class CollectionContract
|
5
|
+
def datasource
|
6
|
+
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
|
7
|
+
end
|
8
|
+
|
9
|
+
def schema
|
10
|
+
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
|
11
|
+
end
|
12
|
+
|
13
|
+
def name
|
14
|
+
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
|
15
|
+
end
|
16
|
+
|
17
|
+
def execute
|
18
|
+
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
|
19
|
+
end
|
20
|
+
|
21
|
+
def form
|
22
|
+
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
|
23
|
+
end
|
24
|
+
|
25
|
+
def create(caller, data)
|
26
|
+
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
|
27
|
+
end
|
28
|
+
|
29
|
+
def list(caller, filter, projection)
|
30
|
+
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
|
31
|
+
end
|
32
|
+
|
33
|
+
def update(caller, filter, data)
|
34
|
+
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
|
35
|
+
end
|
36
|
+
|
37
|
+
def delete(caller, filter)
|
38
|
+
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
|
39
|
+
end
|
40
|
+
|
41
|
+
def aggregate(caller, filter, aggregation, limit = nil)
|
42
|
+
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
|
43
|
+
end
|
44
|
+
|
45
|
+
def render_chart
|
46
|
+
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module ForestAdminDatasourceToolkit
|
2
|
+
module Components
|
3
|
+
module Contracts
|
4
|
+
class DatasourceContract
|
5
|
+
def collections
|
6
|
+
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
|
7
|
+
end
|
8
|
+
|
9
|
+
def charts
|
10
|
+
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
|
11
|
+
end
|
12
|
+
|
13
|
+
def collection(name)
|
14
|
+
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_collection(collection)
|
18
|
+
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
|
19
|
+
end
|
20
|
+
|
21
|
+
def render_chart(caller, name)
|
22
|
+
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ForestAdminDatasourceToolkit
|
2
|
+
module Components
|
3
|
+
module Query
|
4
|
+
class Aggregation
|
5
|
+
include ForestAdminDatasourceToolkit::Exceptions
|
6
|
+
attr_reader :operation, :field, :groups
|
7
|
+
|
8
|
+
def initialize(operation:, field: nil, groups: [])
|
9
|
+
validate(operation)
|
10
|
+
@operation = operation
|
11
|
+
@field = field
|
12
|
+
@groups = groups
|
13
|
+
end
|
14
|
+
|
15
|
+
def validate(operation)
|
16
|
+
return if %w[Count Sum Avg Max Min].include? operation
|
17
|
+
|
18
|
+
raise ForestException, "Aggregate operation #{operation} not allowed"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module ForestAdminDatasourceToolkit
|
2
|
+
module Components
|
3
|
+
module Query
|
4
|
+
module ConditionTree
|
5
|
+
class ConditionTreeEquivalent
|
6
|
+
def self.get_equivalent_tree(leaf, operators, column_type, timezone)
|
7
|
+
operator = leaf.operator
|
8
|
+
|
9
|
+
get_replacer(operator, operators, column_type).call(leaf, timezone)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.equivalent_tree?(operator, filter_operators, column_type)
|
13
|
+
return true if filter_operators.include?(operator)
|
14
|
+
|
15
|
+
!get_replacer(operator, filter_operators, column_type).nil?
|
16
|
+
end
|
17
|
+
|
18
|
+
class << self
|
19
|
+
private
|
20
|
+
|
21
|
+
def alternatives
|
22
|
+
@alternatives ||= {}
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_alternatives(operator)
|
26
|
+
@alternatives ||= {}
|
27
|
+
comparisons = Transforms::Comparisons.transforms[operator] || []
|
28
|
+
pattern = Transforms::Pattern.transforms[operator] || []
|
29
|
+
time = Transforms::Times.transforms[operator] || []
|
30
|
+
|
31
|
+
@alternatives[operator] ||= comparisons.concat(pattern).concat(time)
|
32
|
+
|
33
|
+
@alternatives[operator]
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_replacer(operator, filter_operators, column_type, visited = [])
|
37
|
+
return ->(leaf, _timezone) { leaf } if filter_operators.include?(operator)
|
38
|
+
|
39
|
+
get_alternatives(operator)&.each do |alt|
|
40
|
+
replacer = alt[:replacer]
|
41
|
+
depends_on = alt[:depends_on]
|
42
|
+
valid = alt[:for_types].nil? || alt[:for_types].include?(column_type)
|
43
|
+
|
44
|
+
if valid && !visited.include?(alt)
|
45
|
+
depends_replacers = depends_on.map do |replacement|
|
46
|
+
get_replacer(replacement, filter_operators, column_type, visited + [alt])
|
47
|
+
end
|
48
|
+
|
49
|
+
if depends_replacers.all? { |r| !r.nil? }
|
50
|
+
return lambda { |leaf, timezone|
|
51
|
+
replacer.call(leaf).replace_leafs do |sub_leaf|
|
52
|
+
depends_replacers[depends_on.index(sub_leaf.operator)].call(sub_leaf, timezone)
|
53
|
+
end
|
54
|
+
}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_factory.rb
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
module ForestAdminDatasourceToolkit
|
2
|
+
module Components
|
3
|
+
module Query
|
4
|
+
module ConditionTree
|
5
|
+
class ConditionTreeFactory
|
6
|
+
include Nodes
|
7
|
+
include ForestAdminDatasourceToolkit::Exceptions
|
8
|
+
|
9
|
+
def self.match_none
|
10
|
+
ConditionTreeBranch.new('Or', [])
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.match_records(collection, records)
|
14
|
+
ids = records.map { |record| ForestAdminDatasourceToolkit::Utils::Record.primary_keys(collection, record) }
|
15
|
+
|
16
|
+
match_ids(collection, ids)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.match_ids(collection, ids)
|
20
|
+
primary_key_names = ForestAdminDatasourceToolkit::Utils::Schema.primary_keys(collection)
|
21
|
+
|
22
|
+
raise ForestException, 'Collection must have at least one primary key' if primary_key_names.empty?
|
23
|
+
|
24
|
+
primary_key_names.each do |name|
|
25
|
+
operators = collection.fields[name].filter_operators
|
26
|
+
unless operators.include?(Operators::EQUAL) || operators.include?(Operators::IN)
|
27
|
+
raise ForestException, "Field '#{name}' must support operators: ['Equal', 'In']"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
match_fields(primary_key_names, ids)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.intersect(trees = nil)
|
35
|
+
result = group('And', trees)
|
36
|
+
is_empty_and = result.is_a?(ConditionTreeBranch) && result.aggregator == 'And' && result.conditions.empty?
|
37
|
+
|
38
|
+
is_empty_and ? nil : result
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.union(trees)
|
42
|
+
group('Or', trees)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.from_plain_object(json)
|
46
|
+
return nil if json.nil?
|
47
|
+
|
48
|
+
if leaf?(json)
|
49
|
+
ConditionTreeLeaf.new(json[:field], json[:operator], json[:value])
|
50
|
+
elsif branch?(json)
|
51
|
+
branch = ConditionTreeBranch.new(
|
52
|
+
json[:aggregator],
|
53
|
+
json[:conditions].map { |sub_tree| from_plain_object(sub_tree) }
|
54
|
+
)
|
55
|
+
|
56
|
+
branch.conditions.length == 1 ? branch.conditions[0] : branch
|
57
|
+
else
|
58
|
+
raise ForestException, 'Failed to instantiate condition tree from json'
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.group(aggregator, trees = nil)
|
63
|
+
return nil if trees.nil? || trees.empty?
|
64
|
+
|
65
|
+
conditions = trees
|
66
|
+
.filter { |tree| !tree.nil? }
|
67
|
+
.reduce([]) do |current_conditions, tree|
|
68
|
+
if tree.is_a?(ConditionTreeBranch) && tree.aggregator == aggregator
|
69
|
+
current_conditions + tree.conditions
|
70
|
+
else
|
71
|
+
current_conditions + [tree]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
conditions.length == 1 ? conditions[0] : ConditionTreeBranch.new(aggregator, conditions)
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.match_fields(fields, values)
|
79
|
+
return ConditionTreeFactory.match_none if values.empty?
|
80
|
+
|
81
|
+
if fields.length == 1
|
82
|
+
field_values = values.map { |tuple| tuple[0] }
|
83
|
+
|
84
|
+
return field_values.length > 1 ? ConditionTreeLeaf.new(fields[0], 'In', field_values) : ConditionTreeLeaf.new(fields[0], 'Equal', field_values[0])
|
85
|
+
end
|
86
|
+
|
87
|
+
first_field, *other_fields = fields
|
88
|
+
groups = {}
|
89
|
+
|
90
|
+
values.each do |first_value, *other_values|
|
91
|
+
if groups.key?(first_value)
|
92
|
+
groups[first_value].push(other_values)
|
93
|
+
else
|
94
|
+
groups[first_value] = [other_values]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
ConditionTreeFactory.union(
|
99
|
+
groups.map do |first_value, sub_values|
|
100
|
+
ConditionTreeFactory.intersect([
|
101
|
+
ConditionTreeFactory.match_fields([first_field], [[first_value]]),
|
102
|
+
ConditionTreeFactory.match_fields(other_fields, sub_values)
|
103
|
+
])
|
104
|
+
end
|
105
|
+
)
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.leaf?(tree)
|
109
|
+
return false unless tree.is_a?(Hash)
|
110
|
+
|
111
|
+
tree.key?(:field) && tree.key?(:operator) && (tree[:operator] == 'Present' || tree.key?(:value))
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.branch?(tree)
|
115
|
+
return false unless tree.is_a?(Hash)
|
116
|
+
|
117
|
+
tree.key?(:aggregator) && tree.key?(:conditions)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
module ForestAdminDatasourceToolkit
|
2
|
+
module Components
|
3
|
+
module Query
|
4
|
+
module ConditionTree
|
5
|
+
module Nodes
|
6
|
+
class ConditionTree
|
7
|
+
include ForestAdminDatasourceToolkit::Exceptions
|
8
|
+
|
9
|
+
def inverse
|
10
|
+
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
|
11
|
+
end
|
12
|
+
|
13
|
+
def replace_leafs(&handler)
|
14
|
+
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
|
15
|
+
end
|
16
|
+
|
17
|
+
def match(record, collection, timezone)
|
18
|
+
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
|
19
|
+
end
|
20
|
+
|
21
|
+
def for_each_leaf(handler)
|
22
|
+
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
|
23
|
+
end
|
24
|
+
|
25
|
+
def every_leaf(&handler)
|
26
|
+
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
|
27
|
+
end
|
28
|
+
|
29
|
+
def some_leaf(&handler)
|
30
|
+
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
|
31
|
+
end
|
32
|
+
|
33
|
+
def projection
|
34
|
+
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
|
35
|
+
end
|
36
|
+
|
37
|
+
def apply(records, collection, timezone)
|
38
|
+
records.select { |record| match(record, collection, timezone) }
|
39
|
+
end
|
40
|
+
|
41
|
+
def nest(prefix)
|
42
|
+
if prefix.empty?
|
43
|
+
self
|
44
|
+
else
|
45
|
+
replace_leafs { |leaf| leaf.override(field: "#{prefix}:#{leaf.field}") }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def unnest
|
50
|
+
prefix = nil
|
51
|
+
some_leaf do |leaf|
|
52
|
+
prefix = leaf.field.split(':').first
|
53
|
+
false
|
54
|
+
end
|
55
|
+
unless every_leaf { |leaf| leaf.field.start_with?(prefix) }
|
56
|
+
raise ForestException, 'Cannot unnest condition tree.'
|
57
|
+
end
|
58
|
+
|
59
|
+
replace_leafs { |leaf| leaf.override(field: leaf.field[prefix.length + 1..]) }
|
60
|
+
end
|
61
|
+
|
62
|
+
def replace_fields
|
63
|
+
replace_leafs { |leaf| leaf.override(field: yield(leaf.field)) }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module ForestAdminDatasourceToolkit
|
2
|
+
module Components
|
3
|
+
module Query
|
4
|
+
module ConditionTree
|
5
|
+
module Nodes
|
6
|
+
class ConditionTreeBranch < ConditionTree
|
7
|
+
attr_reader :aggregator, :conditions
|
8
|
+
|
9
|
+
def initialize(aggregator, conditions)
|
10
|
+
@aggregator = aggregator
|
11
|
+
@conditions = conditions
|
12
|
+
super()
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_h
|
16
|
+
{
|
17
|
+
aggregator: @aggregator,
|
18
|
+
conditions: @conditions.map(&:to_h)
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def inverse
|
23
|
+
aggregator = @aggregator == 'Or' ? 'And' : 'Or'
|
24
|
+
ConditionTreeBranch.new(
|
25
|
+
aggregator,
|
26
|
+
@conditions.map(&:inverse)
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
def replace_leafs(&handler)
|
31
|
+
ConditionTreeBranch.new(
|
32
|
+
@aggregator,
|
33
|
+
@conditions.map { |condition| condition.replace_leafs(&handler) }
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
def match(record, collection, timezone)
|
38
|
+
if @aggregator == 'And'
|
39
|
+
every_leaf { |condition| condition.match(record, collection, timezone) }
|
40
|
+
else
|
41
|
+
some_leaf { |condition| condition.match(record, collection, timezone) }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def for_each_leaf(&handler)
|
46
|
+
@conditions.each { |condition| condition.for_each_leaf(&handler) }
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def every_leaf(&handler)
|
51
|
+
@conditions.all? { |condition| condition.every_leaf(&handler) }
|
52
|
+
end
|
53
|
+
|
54
|
+
def some_leaf(&handler)
|
55
|
+
@conditions.any? { |condition| condition.some_leaf(&handler) }
|
56
|
+
end
|
57
|
+
|
58
|
+
def projection
|
59
|
+
@conditions.reduce(Projection.new) do |memo, condition|
|
60
|
+
memo.union(condition.projection)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|