forest_admin_datasource_customizer 1.0.0.pre.beta.21 → 1.0.0.pre.beta.57

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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/forest_admin_datasource_customizer.gemspec +3 -2
  3. data/lib/forest_admin_datasource_customizer/collection_customizer.rb +292 -5
  4. data/lib/forest_admin_datasource_customizer/context/agent_customization_context.rb +18 -0
  5. data/lib/forest_admin_datasource_customizer/context/collection_customization_context.rb +15 -0
  6. data/lib/forest_admin_datasource_customizer/context/relaxed_wrappers/relaxed_collection.rb +50 -0
  7. data/lib/forest_admin_datasource_customizer/context/relaxed_wrappers/relaxed_data_source.rb +18 -0
  8. data/lib/forest_admin_datasource_customizer/datasource_customizer.rb +43 -13
  9. data/lib/forest_admin_datasource_customizer/decorators/action/action_collection_decorator.rb +137 -0
  10. data/lib/forest_admin_datasource_customizer/decorators/action/base_action.rb +67 -0
  11. data/lib/forest_admin_datasource_customizer/decorators/action/context/action_context.rb +56 -0
  12. data/lib/forest_admin_datasource_customizer/decorators/action/context/action_context_single.rb +26 -0
  13. data/lib/forest_admin_datasource_customizer/decorators/action/dynamic_field.rb +50 -0
  14. data/lib/forest_admin_datasource_customizer/decorators/action/result_builder.rb +68 -0
  15. data/lib/forest_admin_datasource_customizer/decorators/action/types/action_scope.rb +15 -0
  16. data/lib/forest_admin_datasource_customizer/decorators/action/types/field_type.rb +35 -0
  17. data/lib/forest_admin_datasource_customizer/decorators/action/widget_field.rb +357 -0
  18. data/lib/forest_admin_datasource_customizer/decorators/binary/binary_collection_decorator.rb +215 -0
  19. data/lib/forest_admin_datasource_customizer/decorators/binary/binary_helper.rb +17 -0
  20. data/lib/forest_admin_datasource_customizer/decorators/chart/chart_collection_decorator.rb +41 -0
  21. data/lib/forest_admin_datasource_customizer/decorators/chart/chart_context.rb +33 -0
  22. data/lib/forest_admin_datasource_customizer/decorators/chart/chart_datasource_decorator.rb +46 -0
  23. data/lib/forest_admin_datasource_customizer/decorators/chart/result_builder.rb +148 -0
  24. data/lib/forest_admin_datasource_customizer/decorators/computed/compute_collection_decorator.rb +115 -0
  25. data/lib/forest_admin_datasource_customizer/decorators/computed/computed_definition.rb +21 -0
  26. data/lib/forest_admin_datasource_customizer/decorators/computed/utils/computed_field.rb +74 -0
  27. data/lib/forest_admin_datasource_customizer/decorators/computed/utils/flattener.rb +49 -0
  28. data/lib/forest_admin_datasource_customizer/decorators/decorators_stack.rb +33 -4
  29. data/lib/forest_admin_datasource_customizer/decorators/hook/context/after/hook_after_aggregate_context.rb +18 -0
  30. data/lib/forest_admin_datasource_customizer/decorators/hook/context/after/hook_after_create_context.rb +18 -0
  31. data/lib/forest_admin_datasource_customizer/decorators/hook/context/after/hook_after_delete_context.rb +12 -0
  32. data/lib/forest_admin_datasource_customizer/decorators/hook/context/after/hook_after_list_context.rb +18 -0
  33. data/lib/forest_admin_datasource_customizer/decorators/hook/context/after/hook_after_update_context.rb +12 -0
  34. data/lib/forest_admin_datasource_customizer/decorators/hook/context/before/hook_before_aggregate_context.rb +20 -0
  35. data/lib/forest_admin_datasource_customizer/decorators/hook/context/before/hook_before_create_context.rb +18 -0
  36. data/lib/forest_admin_datasource_customizer/decorators/hook/context/before/hook_before_delete_context.rb +18 -0
  37. data/lib/forest_admin_datasource_customizer/decorators/hook/context/before/hook_before_list_context.rb +19 -0
  38. data/lib/forest_admin_datasource_customizer/decorators/hook/context/before/hook_before_update_context.rb +19 -0
  39. data/lib/forest_admin_datasource_customizer/decorators/hook/context/hook_context.rb +22 -0
  40. data/lib/forest_admin_datasource_customizer/decorators/hook/hook_collection_decorator.rb +95 -0
  41. data/lib/forest_admin_datasource_customizer/decorators/hook/hooks.rb +26 -0
  42. data/lib/forest_admin_datasource_customizer/decorators/operators_emulate/operators_emulate_collection_decorator.rb +118 -0
  43. data/lib/forest_admin_datasource_customizer/decorators/operators_equivalence/operators_equivalence_collection_decorator.rb +50 -0
  44. data/lib/forest_admin_datasource_customizer/decorators/override/context/create_override_customization_context.rb +16 -0
  45. data/lib/forest_admin_datasource_customizer/decorators/override/context/delete_override_customization_context.rb +16 -0
  46. data/lib/forest_admin_datasource_customizer/decorators/override/context/update_override_customization_context.rb +17 -0
  47. data/lib/forest_admin_datasource_customizer/decorators/override/override_collection_decorator.rb +49 -0
  48. data/lib/forest_admin_datasource_customizer/decorators/publication/publication_collection_decorator.rb +95 -0
  49. data/lib/forest_admin_datasource_customizer/decorators/publication/publication_datasource_decorator.rb +57 -0
  50. data/lib/forest_admin_datasource_customizer/decorators/relation/relation_collection_decorator.rb +268 -0
  51. data/lib/forest_admin_datasource_customizer/decorators/rename_collection/rename_collection_datasource_decorator.rb +70 -0
  52. data/lib/forest_admin_datasource_customizer/decorators/rename_collection/rename_collection_decorator.rb +37 -0
  53. data/lib/forest_admin_datasource_customizer/decorators/rename_field/rename_field_collection_decorator.rb +190 -0
  54. data/lib/forest_admin_datasource_customizer/decorators/schema/schema_collection_decorator.rb +21 -0
  55. data/lib/forest_admin_datasource_customizer/decorators/search/search_collection_decorator.rb +135 -0
  56. data/lib/forest_admin_datasource_customizer/decorators/segment/segment_collection_decorator.rb +60 -0
  57. data/lib/forest_admin_datasource_customizer/decorators/sort/sort_collection_decorator.rb +127 -0
  58. data/lib/forest_admin_datasource_customizer/decorators/validation/validation_collection_decorator.rb +82 -0
  59. data/lib/forest_admin_datasource_customizer/decorators/write/create_relations/create_relations_collection_decorator.rb +75 -0
  60. data/lib/forest_admin_datasource_customizer/decorators/write/update_relations/update_relations_collection_decorator.rb +96 -0
  61. data/lib/forest_admin_datasource_customizer/decorators/write/write_datasource_decorator.rb +14 -0
  62. data/lib/forest_admin_datasource_customizer/decorators/write/write_replace/write_customization_context.rb +18 -0
  63. data/lib/forest_admin_datasource_customizer/decorators/write/write_replace/write_replace_collection_decorator.rb +125 -0
  64. data/lib/forest_admin_datasource_customizer/plugins/add_external_relation.rb +27 -0
  65. data/lib/forest_admin_datasource_customizer/plugins/import_field.rb +74 -0
  66. data/lib/forest_admin_datasource_customizer/version.rb +1 -1
  67. metadata +84 -5
  68. data/README.md +0 -31
@@ -0,0 +1,74 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Decorators
3
+ module Computed
4
+ module Utils
5
+ class ComputedField
6
+ include ForestAdminDatasourceToolkit::Components::Query
7
+
8
+ def self.compute_field(ctx, computed, computed_dependencies, flatten)
9
+ transform_unique_values(
10
+ Flattener.un_flatten(
11
+ flatten,
12
+ Projection.new(computed_dependencies)
13
+ ),
14
+ ->(unique_partials) { computed.get_values(unique_partials, ctx) }
15
+ )
16
+ end
17
+
18
+ def self.queue_field(ctx, collection, new_path, paths, flatten)
19
+ return if paths.include?(new_path)
20
+
21
+ computed = collection.get_computed(new_path)
22
+ nested_dependencies = Projection.new(computed.dependencies)
23
+ .nest(prefix: new_path.include?(':') ? new_path.split(':')[0] : nil)
24
+
25
+ nested_dependencies.each do |path|
26
+ queue_field(ctx, collection, path, paths, flatten)
27
+ end
28
+
29
+ dependency_values = nested_dependencies.map { |path| flatten[paths.index(path)] }
30
+ paths.push(new_path)
31
+
32
+ flatten << compute_field(ctx, computed, computed.dependencies, dependency_values)
33
+ end
34
+
35
+ def self.compute_from_records(ctx, collection, records_projection, desired_projection, records)
36
+ paths = records_projection.clone
37
+ flatten = Flattener.flatten(records, paths)
38
+
39
+ desired_projection.each do |path|
40
+ queue_field(ctx, collection, path, paths, flatten)
41
+ end
42
+
43
+ Flattener.un_flatten(desired_projection.map { |path| flatten[paths.index(path)] }, desired_projection)
44
+ end
45
+
46
+ def self.transform_unique_values(inputs, callback)
47
+ indexes = {}
48
+ mapping = []
49
+ unique_inputs = []
50
+
51
+ inputs.each do |input|
52
+ if input
53
+ hash = Digest::SHA1.hexdigest(input.to_s)
54
+
55
+ if indexes[hash].nil?
56
+ indexes[hash] = unique_inputs.length
57
+ unique_inputs.push(input)
58
+ end
59
+
60
+ mapping.push(indexes[hash])
61
+ else
62
+ mapping.push(-1)
63
+ end
64
+ end
65
+
66
+ unique_outputs = callback.call(unique_inputs)
67
+
68
+ mapping.map { |index| index == -1 ? nil : unique_outputs[index] }
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,49 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Decorators
3
+ module Computed
4
+ module Utils
5
+ class Flattener
6
+ def self.flatten(records, projection)
7
+ projection.map do |field|
8
+ records.map { |record| ForestAdminDatasourceToolkit::Utils::Record.field_value(record, field) }
9
+ end
10
+ end
11
+
12
+ def self.un_flatten(flatten, projection)
13
+ num_records = flatten[0]&.length || 0
14
+ records = []
15
+
16
+ (0...num_records).each do |record_index|
17
+ records[record_index] = {}
18
+
19
+ projection.each_with_index do |path, path_index|
20
+ parts = path.split(':').reject { |part| part.nil? || part.empty? }
21
+ value = flatten[path_index][record_index]
22
+
23
+ # Ignore undefined values.
24
+ next if value.nil?
25
+
26
+ # Set all others (including null)
27
+ record = records[record_index]
28
+
29
+ (0...parts.length).each do |part_index|
30
+ part = parts[part_index]
31
+
32
+ if part_index == parts.length - 1
33
+ record[part] = value
34
+ elsif record[part].nil?
35
+ record[part] = {}
36
+ end
37
+
38
+ record = record[part]
39
+ end
40
+ end
41
+ end
42
+
43
+ records
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -3,11 +3,40 @@ module ForestAdminDatasourceCustomizer
3
3
  class DecoratorsStack
4
4
  include ForestAdminDatasourceToolkit::Decorators
5
5
 
6
- attr_reader :datasource, :empty
6
+ attr_reader :datasource, :schema, :search, :early_computed, :late_computed, :action, :relation, :late_op_emulate,
7
+ :early_op_emulate, :validation, :sort, :rename_field, :publication, :write, :chart, :hook, :segment,
8
+ :binary, :override
7
9
 
8
10
  def initialize(datasource)
11
+ @customizations = []
12
+
9
13
  last = datasource
10
- last = @empty = DatasourceDecorator.new(last, Empty::EmptyCollectionDecorator)
14
+ last = @override = DatasourceDecorator.new(last, Override::OverrideCollectionDecorator)
15
+ last = DatasourceDecorator.new(last, Empty::EmptyCollectionDecorator)
16
+ last = DatasourceDecorator.new(last, OperatorsEquivalence::OperatorsEquivalenceCollectionDecorator)
17
+
18
+ last = @early_computed = DatasourceDecorator.new(last, Computed::ComputeCollectionDecorator)
19
+ last = @early_op_emulate = DatasourceDecorator.new(last, OperatorsEmulate::OperatorsEmulateCollectionDecorator)
20
+ last = DatasourceDecorator.new(last, OperatorsEquivalence::OperatorsEquivalenceCollectionDecorator)
21
+ last = @relation = DatasourceDecorator.new(last, Relation::RelationCollectionDecorator)
22
+ last = @late_computed = DatasourceDecorator.new(last, Computed::ComputeCollectionDecorator)
23
+ last = @late_op_emulate = DatasourceDecorator.new(last, OperatorsEmulate::OperatorsEmulateCollectionDecorator)
24
+ last = DatasourceDecorator.new(last, OperatorsEquivalence::OperatorsEquivalenceCollectionDecorator)
25
+
26
+ last = @search = DatasourceDecorator.new(last, Search::SearchCollectionDecorator)
27
+ last = @segment = DatasourceDecorator.new(last, Segment::SegmentCollectionDecorator)
28
+ last = @sort = DatasourceDecorator.new(last, Sort::SortCollectionDecorator)
29
+
30
+ last = @chart = Chart::ChartDatasourceDecorator.new(last)
31
+ last = @action = DatasourceDecorator.new(last, Action::ActionCollectionDecorator)
32
+ last = @schema = DatasourceDecorator.new(last, Schema::SchemaCollectionDecorator)
33
+ last = @write = Write::WriteDatasourceDecorator.new(last)
34
+ last = @hook = DatasourceDecorator.new(last, Hook::HookCollectionDecorator)
35
+ last = @validation = DatasourceDecorator.new(last, Validation::ValidationCollectionDecorator)
36
+ last = @binary = DatasourceDecorator.new(last, Binary::BinaryCollectionDecorator)
37
+
38
+ last = @publication = Publication::PublicationDatasourceDecorator.new(last)
39
+ last = @rename_field = DatasourceDecorator.new(last, RenameField::RenameFieldCollectionDecorator)
11
40
  @datasource = last
12
41
  end
13
42
 
@@ -21,11 +50,11 @@ module ForestAdminDatasourceCustomizer
21
50
  # This method will be called recursively and clears the queue at each recursion to ensure
22
51
  # that all customizations are applied in the right order.
23
52
  def apply_queued_customizations(logger)
24
- queued_customizations = @customizations.pop
53
+ queued_customizations = @customizations.clone
25
54
  @customizations = []
26
55
 
27
56
  while queued_customizations.length.positive?
28
- queued_customizations.shift.call(logger)
57
+ queued_customizations.shift.call
29
58
  apply_queued_customizations(logger)
30
59
  end
31
60
  end
@@ -0,0 +1,18 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Decorators
3
+ module Hook
4
+ module Context
5
+ module After
6
+ class HookAfterAggregateContext < Hook::Context::Before::HookBeforeAggregateContext
7
+ attr_reader :aggregate_result
8
+
9
+ def initialize(collection, caller, filter, aggregation, aggregate_result, limit = nil)
10
+ super(collection, caller, filter, aggregation, limit)
11
+ @aggregate_result = aggregate_result
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Decorators
3
+ module Hook
4
+ module Context
5
+ module After
6
+ class HookAfterCreateContext < Hook::Context::Before::HookBeforeCreateContext
7
+ attr_reader :record
8
+
9
+ def initialize(collection, caller, data, record)
10
+ super(collection, caller, data)
11
+ @record = record
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,12 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Decorators
3
+ module Hook
4
+ module Context
5
+ module After
6
+ class HookAfterDeleteContext < Hook::Context::Before::HookBeforeDeleteContext
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,18 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Decorators
3
+ module Hook
4
+ module Context
5
+ module After
6
+ class HookAfterListContext < Hook::Context::Before::HookBeforeListContext
7
+ attr_reader :records
8
+
9
+ def initialize(collection, caller, filter, projection, records)
10
+ super(collection, caller, filter, projection)
11
+ @records = records
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,12 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Decorators
3
+ module Hook
4
+ module Context
5
+ module After
6
+ class HookAfterUpdateContext < Hook::Context::Before::HookBeforeUpdateContext
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,20 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Decorators
3
+ module Hook
4
+ module Context
5
+ module Before
6
+ class HookBeforeAggregateContext < Hook::Context::HookContext
7
+ attr_reader :filter, :aggregation, :limit
8
+
9
+ def initialize(collection, caller, filter, aggregation, limit = nil)
10
+ super(collection, caller)
11
+ @filter = filter
12
+ @aggregation = aggregation
13
+ @limit = limit
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Decorators
3
+ module Hook
4
+ module Context
5
+ module Before
6
+ class HookBeforeCreateContext < Hook::Context::HookContext
7
+ attr_reader :data
8
+
9
+ def initialize(collection, caller, data)
10
+ super(collection, caller)
11
+ @data = data
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Decorators
3
+ module Hook
4
+ module Context
5
+ module Before
6
+ class HookBeforeDeleteContext < Hook::Context::HookContext
7
+ attr_reader :filter
8
+
9
+ def initialize(collection, caller, filter)
10
+ super(collection, caller)
11
+ @filter = filter
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Decorators
3
+ module Hook
4
+ module Context
5
+ module Before
6
+ class HookBeforeListContext < Hook::Context::HookContext
7
+ attr_reader :filter, :projection
8
+
9
+ def initialize(collection, caller, filter, projection)
10
+ super(collection, caller)
11
+ @filter = filter
12
+ @projection = projection
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Decorators
3
+ module Hook
4
+ module Context
5
+ module Before
6
+ class HookBeforeUpdateContext < Hook::Context::HookContext
7
+ attr_reader :filter, :patch
8
+
9
+ def initialize(collection, caller, filter, patch)
10
+ super(collection, caller)
11
+ @filter = filter
12
+ @patch = patch
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Decorators
3
+ module Hook
4
+ module Context
5
+ class HookContext < ForestAdminDatasourceCustomizer::Context::CollectionCustomizationContext
6
+ include ForestAdminAgent::Http::Exceptions
7
+ def raise_validation_error(message)
8
+ raise ValidationError, message
9
+ end
10
+
11
+ def raise_forbidden_error(message)
12
+ raise ForbiddenError, message
13
+ end
14
+
15
+ def raise_error(message)
16
+ raise UnprocessableError, message
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,95 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Decorators
3
+ module Hook
4
+ class HookCollectionDecorator < ForestAdminDatasourceToolkit::Decorators::CollectionDecorator
5
+ include ForestAdminDatasourceToolkit::Components
6
+ include Context
7
+
8
+ attr_reader :hooks
9
+
10
+ def initialize(child_collection, datasource)
11
+ super
12
+ @hooks = {
13
+ 'List' => Hooks.new,
14
+ 'Create' => Hooks.new,
15
+ 'Update' => Hooks.new,
16
+ 'Delete' => Hooks.new,
17
+ 'Aggregate' => Hooks.new
18
+ }
19
+ end
20
+
21
+ def add_hook(position, type, hook)
22
+ @hooks[type].add_handler(position, hook)
23
+ end
24
+
25
+ def create(caller, data)
26
+ before_context = Before::HookBeforeCreateContext.new(@child_collection, caller, data)
27
+ @hooks['Create'].execute_before(before_context)
28
+
29
+ record = @child_collection.create(caller, before_context.data)
30
+
31
+ after_context = After::HookAfterCreateContext.new(@child_collection, caller, data, record)
32
+ @hooks['Create'].execute_after(after_context)
33
+
34
+ record
35
+ end
36
+
37
+ def list(caller, filter, projection)
38
+ before_context = Before::HookBeforeListContext.new(@child_collection, caller, filter, projection)
39
+ @hooks['List'].execute_before(before_context)
40
+
41
+ records = @child_collection.list(caller, before_context.filter, before_context.projection)
42
+
43
+ after_context = After::HookAfterListContext.new(@child_collection, caller, filter, projection, records)
44
+ @hooks['List'].execute_after(after_context)
45
+
46
+ records
47
+ end
48
+
49
+ def update(caller, filter, patch)
50
+ before_context = Before::HookBeforeUpdateContext.new(@child_collection, caller, filter, patch)
51
+ @hooks['Update'].execute_before(before_context)
52
+
53
+ @child_collection.update(caller, before_context.filter, before_context.patch)
54
+
55
+ after_context = After::HookAfterUpdateContext.new(@child_collection, caller, filter, patch)
56
+ @hooks['Update'].execute_after(after_context)
57
+ end
58
+
59
+ def delete(caller, filter)
60
+ before_context = Before::HookBeforeDeleteContext.new(@child_collection, caller, filter)
61
+ @hooks['Delete'].execute_before(before_context)
62
+
63
+ @child_collection.delete(caller, before_context.filter)
64
+
65
+ after_context = After::HookAfterDeleteContext.new(@child_collection, caller, filter)
66
+ @hooks['Delete'].execute_after(after_context)
67
+ end
68
+
69
+ def aggregate(caller, filter, aggregation, limit = nil)
70
+ before_context = Before::HookBeforeAggregateContext.new(@child_collection, caller, filter, aggregation, limit)
71
+ @hooks['Aggregate'].execute_before(before_context)
72
+
73
+ results = @child_collection.aggregate(
74
+ caller,
75
+ before_context.filter,
76
+ before_context.aggregation,
77
+ before_context.limit
78
+ )
79
+
80
+ after_context = After::HookAfterAggregateContext.new(
81
+ @child_collection,
82
+ caller,
83
+ filter,
84
+ aggregation,
85
+ results,
86
+ limit
87
+ )
88
+ @hooks['Aggregate'].execute_after(after_context)
89
+
90
+ results
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,26 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Decorators
3
+ module Hook
4
+ class Hooks
5
+ attr_reader :before, :after
6
+
7
+ def initialize
8
+ @before = []
9
+ @after = []
10
+ end
11
+
12
+ def execute_before(context)
13
+ @before.each { |hook| hook.call(context) }
14
+ end
15
+
16
+ def execute_after(context)
17
+ @after.each { |hook| hook.call(context) }
18
+ end
19
+
20
+ def add_handler(position, hook)
21
+ position == 'After' ? @after << hook : @before << hook
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,118 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Decorators
3
+ module OperatorsEmulate
4
+ class OperatorsEmulateCollectionDecorator < ForestAdminDatasourceToolkit::Decorators::CollectionDecorator
5
+ include ForestAdminDatasourceToolkit
6
+ include ForestAdminDatasourceToolkit::Decorators
7
+ include ForestAdminDatasourceToolkit::Components::Query
8
+ include ForestAdminDatasourceToolkit::Components::Query::ConditionTree
9
+
10
+ attr_accessor :fields
11
+
12
+ def initialize(child_collection, datasource)
13
+ super
14
+ @fields = {}
15
+ end
16
+
17
+ def emulate_field_operator(name, operator)
18
+ replace_field_operator(name, operator)
19
+ end
20
+
21
+ def replace_field_operator(name, operator, &replace_by)
22
+ # Check that the collection can actually support our rewriting
23
+ pks = Utils::Schema.primary_keys(child_collection)
24
+ pks.each do |pk|
25
+ schema = child_collection.schema[:fields][pk]
26
+ operators = schema.filter_operators
27
+
28
+ if !operators.include?(Operators::EQUAL) || !operators.include?(Operators::IN)
29
+ raise Exceptions::ForestException, "Cannot override operators on collection #{self.name}: " \
30
+ "the primary key columns must support 'Equal' and 'In' operators."
31
+ end
32
+ end
33
+
34
+ # Check that targeted field is valid
35
+ field = child_collection.schema[:fields][name]
36
+ Validations::FieldValidator.validate(self, name)
37
+ unless field.is_a?(ForestAdminDatasourceToolkit::Schema::ColumnSchema)
38
+ raise Exceptions::ForestException, 'Cannot replace operator for relation'
39
+ end
40
+
41
+ # Mark the field operator as replaced.
42
+ fields[name] = {} unless fields.key?(name)
43
+ fields[name][operator] = replace_by
44
+ mark_schema_as_dirty
45
+ end
46
+
47
+ protected
48
+
49
+ def refine_schema(sub_schema)
50
+ sub_schema[:fields].map do |name, schema|
51
+ schema.filter_operators = schema.filter_operators.union(fields[name].keys) if fields.key?(name)
52
+
53
+ schema
54
+ end
55
+
56
+ sub_schema
57
+ end
58
+
59
+ def refine_filter(caller, filter = nil)
60
+ filter&.override(
61
+ condition_tree: filter.condition_tree&.replace_leafs do |leaf|
62
+ replace_leaf(caller, leaf, [])
63
+ end
64
+ )
65
+ end
66
+
67
+ def replace_leaf(caller, leaf, replacements)
68
+ # ConditionTree is targeting a field on another collection => recurse.
69
+ if leaf.field.include?(':')
70
+ prefix = leaf.field.split(':').first
71
+ relation_schema = schema[:fields][prefix]
72
+ association = datasource.get_collection(relation_schema.foreign_collection)
73
+ association_leaf = leaf.unnest.replace_leafs do |sub_leaf|
74
+ association.replace_leaf(caller, sub_leaf, replacements)
75
+ end
76
+
77
+ return association_leaf.nest(prefix)
78
+ end
79
+
80
+ fields[leaf.field]&.key?(leaf.operator) ? compute_equivalent(caller, leaf, replacements) : leaf
81
+ end
82
+
83
+ def compute_equivalent(caller, leaf, replacements)
84
+ handler = fields.dig(leaf.field, leaf.operator)
85
+ if handler
86
+ replacement_id = "#{name}.#{leaf.field}[#{leaf.operator}]"
87
+ sub_replacements = replacements.union([replacement_id])
88
+ if replacements.include?(replacement_id)
89
+ raise Exceptions::ForestException, "Operator replacement cycle: #{sub_replacements.join(" -> ")}"
90
+ end
91
+
92
+ result = handler.call(leaf.value, Context::CollectionCustomizationContext.new(self, caller))
93
+
94
+ if result
95
+ equivalent = result.class < Nodes::ConditionTree ? result : ConditionTreeFactory.from_plain_object(result)
96
+ equivalent.replace_leafs do |sub_leaf|
97
+ replace_leaf(caller, sub_leaf, sub_replacements)
98
+ end
99
+
100
+ Validations::ConditionTreeValidator.validate(equivalent, self)
101
+
102
+ return equivalent
103
+ end
104
+ end
105
+
106
+ ConditionTreeFactory.match_records(
107
+ self,
108
+ leaf.apply(
109
+ list(caller, Filter.new, leaf.projection.with_pks(self)),
110
+ self,
111
+ caller.timezone
112
+ )
113
+ )
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,50 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Decorators
3
+ module OperatorsEquivalence
4
+ class OperatorsEquivalenceCollectionDecorator < ForestAdminDatasourceToolkit::Decorators::CollectionDecorator
5
+ include ForestAdminDatasourceToolkit::Decorators
6
+ include ForestAdminDatasourceToolkit::Components::Query::ConditionTree
7
+
8
+ protected
9
+
10
+ def refine_schema(sub_schema)
11
+ schema = sub_schema.dup
12
+ schema[:fields] = sub_schema[:fields].dup
13
+
14
+ schema[:fields].map do |_name, field_schema|
15
+ if field_schema.type == 'Column'
16
+ new_operators = Operators.all.select do |operator|
17
+ ConditionTreeEquivalent.equivalent_tree?(operator, field_schema.filter_operators,
18
+ field_schema.column_type)
19
+ end
20
+
21
+ field_schema.filter_operators = new_operators
22
+ else
23
+ field_schema
24
+ end
25
+ end
26
+
27
+ schema
28
+ end
29
+
30
+ def refine_filter(caller, filter = nil)
31
+ filter&.override(
32
+ condition_tree: filter.condition_tree&.replace_leafs do |leaf|
33
+ schema = ForestAdminDatasourceToolkit::Utils::Collection.get_field_schema(
34
+ @child_collection,
35
+ leaf.field
36
+ )
37
+
38
+ ConditionTreeEquivalent.get_equivalent_tree(
39
+ leaf,
40
+ schema.filter_operators,
41
+ schema.column_type,
42
+ caller.timezone
43
+ )
44
+ end
45
+ )
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end