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

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