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,16 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Decorators
3
+ module Override
4
+ module Context
5
+ class CreateOverrideCustomizationContext < ForestAdminDatasourceCustomizer::Context::CollectionCustomizationContext
6
+ attr_reader :data
7
+
8
+ def initialize(collection, caller, data)
9
+ super(collection, caller)
10
+ @data = data
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Decorators
3
+ module Override
4
+ module Context
5
+ class DeleteOverrideCustomizationContext < ForestAdminDatasourceCustomizer::Context::CollectionCustomizationContext
6
+ attr_reader :filter
7
+
8
+ def initialize(collection, caller, filter)
9
+ super(collection, caller)
10
+ @filter = filter
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Decorators
3
+ module Override
4
+ module Context
5
+ class UpdateOverrideCustomizationContext < ForestAdminDatasourceCustomizer::Context::CollectionCustomizationContext
6
+ attr_reader :filter, :patch
7
+
8
+ def initialize(collection, caller, filter, patch)
9
+ super(collection, caller)
10
+ @filter = filter
11
+ @patch = patch
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,49 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Decorators
3
+ module Override
4
+ class OverrideCollectionDecorator < ForestAdminDatasourceToolkit::Decorators::CollectionDecorator
5
+ include Context
6
+ attr_reader :create_handler, :update_handler, :delete_handler
7
+
8
+ def create(caller, data)
9
+ if @create_handler
10
+ context = CreateOverrideCustomizationContext.new(@child_collection, caller, data)
11
+ return @create_handler.call(context)
12
+ end
13
+
14
+ super
15
+ end
16
+
17
+ def add_create_handler(handler)
18
+ @create_handler = handler
19
+ end
20
+
21
+ def update(caller, filter, patch)
22
+ if @update_handler
23
+ context = UpdateOverrideCustomizationContext.new(@child_collection, caller, filter, patch)
24
+ return @update_handler.call(context)
25
+ end
26
+
27
+ super
28
+ end
29
+
30
+ def add_update_handler(handler)
31
+ @update_handler = handler
32
+ end
33
+
34
+ def delete(caller, filter)
35
+ if @delete_handler
36
+ context = DeleteOverrideCustomizationContext.new(@child_collection, caller, filter)
37
+ return @delete_handler.call(context)
38
+ end
39
+
40
+ super
41
+ end
42
+
43
+ def add_delete_handler(handler)
44
+ @delete_handler = handler
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,95 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Decorators
3
+ module Publication
4
+ class PublicationCollectionDecorator < ForestAdminDatasourceToolkit::Decorators::CollectionDecorator
5
+ include ForestAdminDatasourceToolkit::Exceptions
6
+ attr_reader :blacklist
7
+
8
+ def initialize(child_collection, datasource)
9
+ super
10
+ @blacklist = []
11
+ end
12
+
13
+ def change_field_visibility(name, visible)
14
+ field = child_collection.schema[:fields][name]
15
+ raise ForestException, "No such field '#{name}'" unless field
16
+ raise ForestException, 'Cannot hide primary key' if ForestAdminDatasourceToolkit::Utils::Schema.primary_key?(
17
+ child_collection, name
18
+ )
19
+
20
+ if visible
21
+ @blacklist.delete(name)
22
+ else
23
+ @blacklist << name
24
+ end
25
+
26
+ mark_schema_as_dirty
27
+ end
28
+
29
+ def create(caller, data)
30
+ record = {}
31
+ child_collection.create(caller, data).each do |key, value|
32
+ record[key] = value unless @blacklist.include?(key)
33
+ end
34
+
35
+ record
36
+ end
37
+
38
+ def refine_schema(child_schema)
39
+ fields = {}
40
+
41
+ child_schema[:fields].each do |name, field|
42
+ fields[name] = field if published?(name)
43
+ end
44
+
45
+ child_schema[:fields] = fields
46
+
47
+ child_schema
48
+ end
49
+
50
+ def published?(name)
51
+ # Explicitly hidden
52
+ return false if @blacklist.include?(name)
53
+
54
+ # Implicitly hidden
55
+ field = child_collection.schema[:fields][name]
56
+
57
+ if field.type == 'ManyToOne'
58
+ return (
59
+ datasource.published?(field.foreign_collection) &&
60
+ published?(field.foreign_key) &&
61
+ datasource.get_collection(field.foreign_collection).published?(field.foreign_key_target)
62
+ )
63
+ end
64
+
65
+ if field.type == 'OneToOne' || field.type == 'OneToMany'
66
+ return (
67
+ datasource.published?(field.foreign_collection) &&
68
+ datasource.get_collection(field.foreign_collection).published?(field.origin_key) &&
69
+ published?(field.origin_key_target)
70
+ )
71
+ end
72
+
73
+ if field.type == 'ManyToMany'
74
+ return (
75
+ datasource.published?(field.through_collection) &&
76
+ datasource.published?(field.foreign_collection) &&
77
+ datasource.get_collection(field.through_collection).published?(field.foreign_key) &&
78
+ datasource.get_collection(field.through_collection).published?(field.origin_key) &&
79
+ published?(field.origin_key_target) &&
80
+ datasource.get_collection(field.foreign_collection).published?(field.foreign_key_target)
81
+ )
82
+ end
83
+
84
+ true
85
+ end
86
+
87
+ # rubocop:disable Lint/UselessMethodDefinition
88
+ def mark_schema_as_dirty
89
+ super
90
+ end
91
+ # rubocop:enable Lint/UselessMethodDefinition
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,57 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Decorators
3
+ module Publication
4
+ class PublicationDatasourceDecorator < ForestAdminDatasourceToolkit::Decorators::DatasourceDecorator
5
+ include ForestAdminDatasourceToolkit::Exceptions
6
+ attr_reader :blacklist
7
+
8
+ def initialize(child_datasource)
9
+ super(child_datasource, PublicationCollectionDecorator)
10
+ @blacklist = []
11
+ end
12
+
13
+ def collections
14
+ @child_datasource.collections.except(*@blacklist).to_h do |name, _collection|
15
+ [name, get_collection(name)]
16
+ end
17
+ end
18
+
19
+ def get_collection(name)
20
+ raise ForestException, "Collection '#{name}' was removed." if @blacklist.include?(name)
21
+
22
+ super
23
+ end
24
+
25
+ def keep_collections_matching(include = [], exclude = [])
26
+ validate_collection_names(include.to_a + exclude.to_a)
27
+
28
+ # List collection we're keeping from the white/black list.
29
+ @child_datasource.collections.each_key do |collection|
30
+ remove_collection(collection) if (include && !include.include?(collection)) || exclude&.include?(collection)
31
+ end
32
+ end
33
+
34
+ def remove_collection(collection_name)
35
+ validate_collection_names([collection_name])
36
+
37
+ # Delete the collection
38
+ @blacklist << collection_name
39
+
40
+ # Tell all collections that their schema is dirty: if we removed a collection, all
41
+ # relations to this collection are now invalid and should be unpublished.
42
+ collections.each_value(&:mark_schema_as_dirty)
43
+ end
44
+
45
+ def published?(collection_name)
46
+ !@blacklist.include?(collection_name)
47
+ end
48
+
49
+ private
50
+
51
+ def validate_collection_names(names)
52
+ names.each { |name| get_collection(name) }
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,268 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Decorators
3
+ module Relation
4
+ class RelationCollectionDecorator < ForestAdminDatasourceToolkit::Decorators::CollectionDecorator
5
+ include ForestAdminDatasourceToolkit::Exceptions
6
+ include ForestAdminDatasourceToolkit::Utils
7
+ include ForestAdminDatasourceToolkit::Components::Query
8
+ include ForestAdminDatasourceToolkit::Components::Query::ConditionTree
9
+ include ForestAdminDatasourceToolkit::Components::Query::ConditionTree::Nodes
10
+ include ForestAdminDatasourceToolkit::Schema
11
+
12
+ attr_reader :relations
13
+
14
+ def initialize(child_collection, datasource)
15
+ super
16
+ @relations = {}
17
+ end
18
+
19
+ def add_relation(name, partial_joint)
20
+ relation = relation_with_optional_fields(partial_joint)
21
+ check_foreign_keys(relation)
22
+ check_origin_keys(relation)
23
+
24
+ @relations[name] = relation
25
+ mark_schema_as_dirty
26
+ end
27
+
28
+ def list(caller, filter, projection)
29
+ new_filter = refine_filter(caller, filter)
30
+ new_projection = projection.replace { |field| rewrite_field(field) }
31
+ records = child_collection.list(caller, new_filter, new_projection)
32
+ return records if new_projection.equals(projection)
33
+
34
+ records = re_project_in_place(caller, records, projection)
35
+
36
+ projection.apply(records)
37
+ end
38
+
39
+ def aggregate(caller, filter, aggregation, limit = nil)
40
+ new_filter = refine_filter(caller, filter)
41
+
42
+ # No emulated relations are used in the aggregation
43
+ if aggregation.projection.relations.keys.all? { |prefix| !@relations.key?(prefix) }
44
+ return child_collection.aggregate(caller, new_filter, aggregation, limit)
45
+ end
46
+
47
+ # Fallback to full emulation.
48
+ aggregation.apply(list(caller, filter, aggregation.projection), caller.timezone, limit)
49
+ end
50
+
51
+ protected
52
+
53
+ def refine_schema(child_schema)
54
+ @relations.each do |name, relation|
55
+ child_schema[:fields][name] = relation
56
+ end
57
+
58
+ child_schema
59
+ end
60
+
61
+ def refine_filter(caller, filter)
62
+ filter.override({
63
+ condition_tree: filter.condition_tree&.replace_leafs do |leaf|
64
+ rewrite_leaf(caller, leaf)
65
+ end,
66
+ sort: filter.sort&.replace_clauses do |clause|
67
+ rewrite_field(clause[:field]).map do |field|
68
+ clause.merge(field: field)
69
+ end
70
+ end
71
+ })
72
+ end
73
+
74
+ def relation_with_optional_fields(partial_joint)
75
+ relation = partial_joint.dup
76
+ target = datasource.get_collection(partial_joint[:foreign_collection])
77
+
78
+ case relation[:type]
79
+ when 'ManyToOne'
80
+ relation = Relations::ManyToOneSchema.new(
81
+ foreign_key: relation[:foreign_key],
82
+ foreign_key_target: if relation[:foreign_key_target].nil?
83
+ ForestAdminDatasourceToolkit::Utils::Schema.primary_keys(target).first
84
+ else
85
+ relation[:foreign_key_target]
86
+ end,
87
+ foreign_collection: relation[:foreign_collection]
88
+ )
89
+ when 'OneToOne'
90
+ relation = Relations::OneToOneSchema.new(
91
+ origin_key: relation[:origin_key],
92
+ origin_key_target: if relation[:origin_key_target].nil?
93
+ ForestAdminDatasourceToolkit::Utils::Schema.primary_keys(self).first
94
+ else
95
+ relation[:origin_key_target]
96
+ end,
97
+ foreign_collection: relation[:foreign_collection]
98
+ )
99
+ when 'OneToMany'
100
+ relation = Relations::OneToManySchema.new(
101
+ origin_key: relation[:origin_key],
102
+ origin_key_target: if relation[:origin_key_target].nil?
103
+ ForestAdminDatasourceToolkit::Utils::Schema.primary_keys(self).first
104
+ else
105
+ relation[:origin_key_target]
106
+ end,
107
+ foreign_collection: relation[:foreign_collection]
108
+ )
109
+ when 'ManyToMany'
110
+ relation = Relations::ManyToManySchema.new(
111
+ origin_key: relation[:origin_key],
112
+ origin_key_target: if relation[:origin_key_target].nil?
113
+ ForestAdminDatasourceToolkit::Utils::Schema.primary_keys(self).first
114
+ else
115
+ relation[:origin_key_target]
116
+ end,
117
+ foreign_key: relation[:foreign_key],
118
+ foreign_key_target: if relation[:foreign_key_target].nil?
119
+ ForestAdminDatasourceToolkit::Utils::Schema.primary_keys(target).first
120
+ else
121
+ relation[:foreign_key_target]
122
+ end,
123
+ foreign_collection: relation[:foreign_collection],
124
+ through_collection: relation[:through_collection]
125
+ )
126
+ end
127
+
128
+ relation
129
+ end
130
+
131
+ def check_foreign_keys(relation)
132
+ return unless relation.type == 'ManyToOne' || relation.type == 'ManyToMany'
133
+
134
+ check_keys(
135
+ relation.type == 'ManyToMany' ? datasource.get_collection(relation.through_collection) : self,
136
+ datasource.get_collection(relation.foreign_collection),
137
+ relation.foreign_key,
138
+ relation.foreign_key_target
139
+ )
140
+ end
141
+
142
+ def check_origin_keys(relation)
143
+ return unless relation.type == 'OneToMany' || relation.type == 'OneToOne' || relation.type == 'ManyToMany'
144
+
145
+ check_keys(
146
+ relation.type == 'ManyToMany' ? datasource.get_collection(relation.through_collection) : datasource.get_collection(relation.foreign_collection),
147
+ self,
148
+ relation.origin_key,
149
+ relation.origin_key_target
150
+ )
151
+ end
152
+
153
+ def check_keys(owner, target_owner, key_name, target_name)
154
+ check_column(owner, key_name)
155
+ check_column(target_owner, target_name)
156
+
157
+ key = owner.schema[:fields][key_name]
158
+ target = target_owner.schema[:fields][target_name]
159
+
160
+ return unless key.column_type != target.column_type
161
+
162
+ raise ForestException,
163
+ "Types from '#{owner.name}.#{key_name}' and '#{target_owner.name}.#{target_name}' do not match."
164
+ end
165
+
166
+ def check_column(owner, name)
167
+ column = owner.schema[:fields][name]
168
+
169
+ raise ForestException, "Column not found: '#{owner.name}.#{name}'" if !column || column.type != 'Column'
170
+
171
+ return if column.filter_operators.include?(Operators::IN)
172
+
173
+ raise ForestException, "Column does not support the In operator: '#{owner.name}.#{name}'"
174
+ end
175
+
176
+ def rewrite_field(field)
177
+ prefix = field.split(':').first
178
+ field_schema = schema[:fields][prefix]
179
+
180
+ return [field] if field_schema.type == 'Column'
181
+
182
+ relation = datasource.get_collection(field_schema.foreign_collection)
183
+ result = []
184
+
185
+ if !@relations.key?(prefix)
186
+ result = relation.rewrite_field(field[prefix.length + 1..]).map { |sub_field| "#{prefix}:#{sub_field}" }
187
+ elsif field_schema.is_a? Relations::ManyToOneSchema
188
+ result = [field_schema.foreign_key]
189
+ elsif field_schema.is_a?(Relations::OneToOneSchema) ||
190
+ field_schema.is_a?(Relations::OneToManySchema) ||
191
+ field_schema.is_a?(Relations::ManyToManySchema)
192
+ result = [field_schema.origin_key_target]
193
+ end
194
+
195
+ result
196
+ end
197
+
198
+ def rewrite_leaf(caller, leaf)
199
+ prefix = leaf.field.split(':').first
200
+ field_schema = schema[:fields][prefix]
201
+ return leaf if field_schema.type == 'Column'
202
+
203
+ relation = datasource.get_collection(field_schema.foreign_collection)
204
+ result = leaf
205
+
206
+ if !@relations.key?(prefix)
207
+ result = relation.rewrite_leaf(caller, leaf.unnest).nest(prefix)
208
+ elsif field_schema.type == 'ManyToOne'
209
+ records = relation.list(
210
+ caller,
211
+ Filter.new(condition_tree: leaf.unnest),
212
+ Projection.new([field_schema.foreign_key_target])
213
+ )
214
+
215
+ result = ConditionTreeLeaf.new(field_schema.foreign_key, 'In', records.map do |record|
216
+ record[field_schema.foreign_key_target]
217
+ end.uniq)
218
+ elsif field_schema.type == 'OneToOne'
219
+ records = relation.list(
220
+ caller,
221
+ Filter.new(condition_tree: leaf.unnest),
222
+ Projection.new([field_schema.origin_key])
223
+ )
224
+
225
+ result = ConditionTreeLeaf.new(field_schema.origin_key_target, 'In', records.map do |record|
226
+ record[field_schema.origin_key]
227
+ end.uniq)
228
+ end
229
+
230
+ result
231
+ end
232
+
233
+ def re_project_in_place(caller, records, projection)
234
+ projection.relations.each do |prefix, sub_projection|
235
+ re_project_relation_in_place(caller, records, prefix, sub_projection)
236
+ end
237
+
238
+ records
239
+ end
240
+
241
+ def re_project_relation_in_place(caller, records, name, projection)
242
+ field_schema = schema[:fields][name]
243
+ association = datasource.get_collection(field_schema.foreign_collection)
244
+
245
+ if !@relations[name]
246
+ association.re_project_in_place(caller, records.map { |r| r[name] }.filter { |fk| !fk.nil? }, projection)
247
+ elsif field_schema.type == 'ManyToOne'
248
+ ids = records.map { |record| record[field_schema.foreign_key] }.filter { |fk| !fk.nil? }.uniq
249
+ sub_filter = Filter.new(condition_tree: ConditionTreeLeaf.new(field_schema.foreign_key_target, 'In', ids))
250
+ sub_records = association.list(caller, sub_filter, projection.union([field_schema.foreign_key_target]))
251
+
252
+ records.each do |record|
253
+ record[name] = sub_records.find { |sr| sr[field_schema.foreign_key_target] == record[field_schema.foreign_key] }
254
+ end
255
+ elsif field_schema.type == 'OneToOne' || field_schema.type == 'OneToMany'
256
+ ids = records.map { |record| record[field_schema.origin_key_target] }.filter { |okt| !okt.nil? }.uniq
257
+ sub_filter = Filter.new(condition_tree: ConditionTreeLeaf.new(field_schema.origin_key, 'In', ids))
258
+ sub_records = association.list(caller, sub_filter, projection.union([field_schema.origin_key]))
259
+
260
+ records.each do |record|
261
+ record[name] = sub_records.find { |sr| sr[field_schema.origin_key] == record[field_schema.origin_key_target] }
262
+ end
263
+ end
264
+ end
265
+ end
266
+ end
267
+ end
268
+ end
@@ -0,0 +1,70 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Decorators
3
+ module RenameCollection
4
+ class RenameCollectionDatasourceDecorator < ForestAdminDatasourceToolkit::Decorators::DatasourceDecorator
5
+ include ForestAdminDatasourceToolkit
6
+ include ForestAdminDatasourceToolkit::Decorators
7
+ include ForestAdminDatasourceToolkit::Components::Query::ConditionTree
8
+
9
+ def initialize(child_datasource)
10
+ @from_child_name = {}
11
+ @to_child_name = {}
12
+ super(child_datasource, RenameCollectionDecorator)
13
+ end
14
+
15
+ def collections
16
+ @child_datasource.collections.to_h do |name, _collection|
17
+ [name, method(:get_collection).super_method.call(name)]
18
+ end
19
+ end
20
+
21
+ def get_collection(name)
22
+ # Collection has been renamed, user is using the new name
23
+ return super(@to_child_name[name]) if @to_child_name.key?(name)
24
+
25
+ # Collection has been renamed, user is using the old name
26
+ if @from_child_name.key?(name)
27
+ raise Exceptions::ForestException, "Collection '#{name}' has been renamed to '#{@from_child_name[name]}'"
28
+ end
29
+
30
+ # Collection has not been renamed
31
+ super
32
+ end
33
+
34
+ def rename_collections(renames = [])
35
+ renames.each do |current_name, new_name|
36
+ rename_collection(current_name, new_name)
37
+ end
38
+ end
39
+
40
+ def rename_collection(current_name, new_name)
41
+ # Check collection exists
42
+ get_collection(current_name)
43
+
44
+ return unless current_name != new_name
45
+
46
+ # Check new name is not already used
47
+ if collections.any? { |name, _collection| name == new_name }
48
+ raise Exceptions::ForestException,
49
+ "The given new collection name '#{new_name}' is already defined"
50
+ end
51
+
52
+ # Check we don't rename a collection twice
53
+ if @to_child_name[current_name]
54
+ raise Exceptions::ForestException,
55
+ "Cannot rename a collection twice: #{@to_child_name[current_name]}->#{current_name}->#{new_name}"
56
+ end
57
+
58
+ @from_child_name[current_name] = new_name
59
+ @to_child_name[new_name] = current_name
60
+
61
+ collections.each_value(&:mark_schema_as_dirty)
62
+ end
63
+
64
+ def get_collection_name(child_name)
65
+ @from_child_name[child_name] || child_name
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,37 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Decorators
3
+ module RenameCollection
4
+ class RenameCollectionDecorator < ForestAdminDatasourceToolkit::Decorators::CollectionDecorator
5
+ include ForestAdminDatasourceToolkit::Decorators
6
+ include ForestAdminDatasourceToolkit::Components::Query::ConditionTree
7
+
8
+ def name
9
+ datasource.get_collection_name(super)
10
+ end
11
+
12
+ def refine_schema(sub_schema)
13
+ fields = {}
14
+
15
+ sub_schema[:fields].each do |name, old_schema|
16
+ if old_schema.type != 'Column'
17
+ old_schema.foreign_collection = datasource.get_collection_name(old_schema.foreign_collection)
18
+ if old_schema.type == 'ManyToMany'
19
+ old_schema.through_collection = datasource.get_collection_name(old_schema.through_collection)
20
+ end
21
+ end
22
+
23
+ fields[name] = old_schema
24
+ end
25
+
26
+ sub_schema
27
+ end
28
+
29
+ # rubocop:disable Lint/UselessMethodDefinition
30
+ def mark_schema_as_dirty
31
+ super
32
+ end
33
+ # rubocop:enable Lint/UselessMethodDefinition
34
+ end
35
+ end
36
+ end
37
+ end