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.
- checksums.yaml +4 -4
- data/forest_admin_datasource_customizer.gemspec +3 -2
- data/lib/forest_admin_datasource_customizer/collection_customizer.rb +292 -5
- data/lib/forest_admin_datasource_customizer/context/agent_customization_context.rb +18 -0
- data/lib/forest_admin_datasource_customizer/context/collection_customization_context.rb +15 -0
- data/lib/forest_admin_datasource_customizer/context/relaxed_wrappers/relaxed_collection.rb +50 -0
- data/lib/forest_admin_datasource_customizer/context/relaxed_wrappers/relaxed_data_source.rb +18 -0
- data/lib/forest_admin_datasource_customizer/datasource_customizer.rb +43 -13
- data/lib/forest_admin_datasource_customizer/decorators/action/action_collection_decorator.rb +137 -0
- data/lib/forest_admin_datasource_customizer/decorators/action/base_action.rb +67 -0
- data/lib/forest_admin_datasource_customizer/decorators/action/context/action_context.rb +56 -0
- data/lib/forest_admin_datasource_customizer/decorators/action/context/action_context_single.rb +26 -0
- data/lib/forest_admin_datasource_customizer/decorators/action/dynamic_field.rb +50 -0
- data/lib/forest_admin_datasource_customizer/decorators/action/result_builder.rb +68 -0
- data/lib/forest_admin_datasource_customizer/decorators/action/types/action_scope.rb +15 -0
- data/lib/forest_admin_datasource_customizer/decorators/action/types/field_type.rb +35 -0
- data/lib/forest_admin_datasource_customizer/decorators/action/widget_field.rb +357 -0
- data/lib/forest_admin_datasource_customizer/decorators/binary/binary_collection_decorator.rb +215 -0
- data/lib/forest_admin_datasource_customizer/decorators/binary/binary_helper.rb +17 -0
- data/lib/forest_admin_datasource_customizer/decorators/chart/chart_collection_decorator.rb +41 -0
- data/lib/forest_admin_datasource_customizer/decorators/chart/chart_context.rb +33 -0
- data/lib/forest_admin_datasource_customizer/decorators/chart/chart_datasource_decorator.rb +46 -0
- data/lib/forest_admin_datasource_customizer/decorators/chart/result_builder.rb +148 -0
- data/lib/forest_admin_datasource_customizer/decorators/computed/compute_collection_decorator.rb +115 -0
- data/lib/forest_admin_datasource_customizer/decorators/computed/computed_definition.rb +21 -0
- data/lib/forest_admin_datasource_customizer/decorators/computed/utils/computed_field.rb +74 -0
- data/lib/forest_admin_datasource_customizer/decorators/computed/utils/flattener.rb +49 -0
- data/lib/forest_admin_datasource_customizer/decorators/decorators_stack.rb +33 -4
- data/lib/forest_admin_datasource_customizer/decorators/hook/context/after/hook_after_aggregate_context.rb +18 -0
- data/lib/forest_admin_datasource_customizer/decorators/hook/context/after/hook_after_create_context.rb +18 -0
- data/lib/forest_admin_datasource_customizer/decorators/hook/context/after/hook_after_delete_context.rb +12 -0
- data/lib/forest_admin_datasource_customizer/decorators/hook/context/after/hook_after_list_context.rb +18 -0
- data/lib/forest_admin_datasource_customizer/decorators/hook/context/after/hook_after_update_context.rb +12 -0
- data/lib/forest_admin_datasource_customizer/decorators/hook/context/before/hook_before_aggregate_context.rb +20 -0
- data/lib/forest_admin_datasource_customizer/decorators/hook/context/before/hook_before_create_context.rb +18 -0
- data/lib/forest_admin_datasource_customizer/decorators/hook/context/before/hook_before_delete_context.rb +18 -0
- data/lib/forest_admin_datasource_customizer/decorators/hook/context/before/hook_before_list_context.rb +19 -0
- data/lib/forest_admin_datasource_customizer/decorators/hook/context/before/hook_before_update_context.rb +19 -0
- data/lib/forest_admin_datasource_customizer/decorators/hook/context/hook_context.rb +22 -0
- data/lib/forest_admin_datasource_customizer/decorators/hook/hook_collection_decorator.rb +95 -0
- data/lib/forest_admin_datasource_customizer/decorators/hook/hooks.rb +26 -0
- data/lib/forest_admin_datasource_customizer/decorators/operators_emulate/operators_emulate_collection_decorator.rb +118 -0
- data/lib/forest_admin_datasource_customizer/decorators/operators_equivalence/operators_equivalence_collection_decorator.rb +50 -0
- data/lib/forest_admin_datasource_customizer/decorators/override/context/create_override_customization_context.rb +16 -0
- data/lib/forest_admin_datasource_customizer/decorators/override/context/delete_override_customization_context.rb +16 -0
- data/lib/forest_admin_datasource_customizer/decorators/override/context/update_override_customization_context.rb +17 -0
- data/lib/forest_admin_datasource_customizer/decorators/override/override_collection_decorator.rb +49 -0
- data/lib/forest_admin_datasource_customizer/decorators/publication/publication_collection_decorator.rb +95 -0
- data/lib/forest_admin_datasource_customizer/decorators/publication/publication_datasource_decorator.rb +57 -0
- data/lib/forest_admin_datasource_customizer/decorators/relation/relation_collection_decorator.rb +268 -0
- data/lib/forest_admin_datasource_customizer/decorators/rename_collection/rename_collection_datasource_decorator.rb +70 -0
- data/lib/forest_admin_datasource_customizer/decorators/rename_collection/rename_collection_decorator.rb +37 -0
- data/lib/forest_admin_datasource_customizer/decorators/rename_field/rename_field_collection_decorator.rb +190 -0
- data/lib/forest_admin_datasource_customizer/decorators/schema/schema_collection_decorator.rb +21 -0
- data/lib/forest_admin_datasource_customizer/decorators/search/search_collection_decorator.rb +135 -0
- data/lib/forest_admin_datasource_customizer/decorators/segment/segment_collection_decorator.rb +60 -0
- data/lib/forest_admin_datasource_customizer/decorators/sort/sort_collection_decorator.rb +127 -0
- data/lib/forest_admin_datasource_customizer/decorators/validation/validation_collection_decorator.rb +82 -0
- data/lib/forest_admin_datasource_customizer/decorators/write/create_relations/create_relations_collection_decorator.rb +75 -0
- data/lib/forest_admin_datasource_customizer/decorators/write/update_relations/update_relations_collection_decorator.rb +96 -0
- data/lib/forest_admin_datasource_customizer/decorators/write/write_datasource_decorator.rb +14 -0
- data/lib/forest_admin_datasource_customizer/decorators/write/write_replace/write_customization_context.rb +18 -0
- data/lib/forest_admin_datasource_customizer/decorators/write/write_replace/write_replace_collection_decorator.rb +125 -0
- data/lib/forest_admin_datasource_customizer/plugins/add_external_relation.rb +27 -0
- data/lib/forest_admin_datasource_customizer/plugins/import_field.rb +74 -0
- data/lib/forest_admin_datasource_customizer/version.rb +1 -1
- metadata +84 -5
- 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
|
data/lib/forest_admin_datasource_customizer/decorators/override/override_collection_decorator.rb
ADDED
|
@@ -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
|
data/lib/forest_admin_datasource_customizer/decorators/relation/relation_collection_decorator.rb
ADDED
|
@@ -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
|