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,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