forest_admin_datasource_mongoid 1.0.1
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 +7 -0
- data/.rspec +3 -0
- data/LICENSE +674 -0
- data/Rakefile +12 -0
- data/forest_admin_datasource_mongoid.gemspec +38 -0
- data/lib/forest_admin_datasource_mongoid/collection.rb +135 -0
- data/lib/forest_admin_datasource_mongoid/datasource.rb +125 -0
- data/lib/forest_admin_datasource_mongoid/options_parser.rb +79 -0
- data/lib/forest_admin_datasource_mongoid/parser/column.rb +86 -0
- data/lib/forest_admin_datasource_mongoid/parser/relation.rb +18 -0
- data/lib/forest_admin_datasource_mongoid/parser/validation.rb +87 -0
- data/lib/forest_admin_datasource_mongoid/utils/add_null_values.rb +56 -0
- data/lib/forest_admin_datasource_mongoid/utils/helpers.rb +151 -0
- data/lib/forest_admin_datasource_mongoid/utils/mongoid_serializer.rb +38 -0
- data/lib/forest_admin_datasource_mongoid/utils/pipeline/condition_generator.rb +30 -0
- data/lib/forest_admin_datasource_mongoid/utils/pipeline/filter_generator.rb +218 -0
- data/lib/forest_admin_datasource_mongoid/utils/pipeline/group_generator.rb +86 -0
- data/lib/forest_admin_datasource_mongoid/utils/pipeline/lookup_generator.rb +97 -0
- data/lib/forest_admin_datasource_mongoid/utils/pipeline/projection_generator.rb +20 -0
- data/lib/forest_admin_datasource_mongoid/utils/pipeline/reparent_generator.rb +97 -0
- data/lib/forest_admin_datasource_mongoid/utils/pipeline/virtual_field_generator.rb +78 -0
- data/lib/forest_admin_datasource_mongoid/utils/schema/fields_generator.rb +87 -0
- data/lib/forest_admin_datasource_mongoid/utils/schema/mongoid_schema.rb +196 -0
- data/lib/forest_admin_datasource_mongoid/utils/schema/relation_generator.rb +51 -0
- data/lib/forest_admin_datasource_mongoid/utils/version_manager.rb +13 -0
- data/lib/forest_admin_datasource_mongoid/version.rb +3 -0
- data/lib/forest_admin_datasource_mongoid.rb +11 -0
- metadata +119 -0
@@ -0,0 +1,97 @@
|
|
1
|
+
module ForestAdminDatasourceMongoid
|
2
|
+
module Utils
|
3
|
+
module Pipeline
|
4
|
+
# Generate pipeline to query submodels.
|
5
|
+
# The operations make rotations in the documents so that the root is changed to the submodel
|
6
|
+
# without loosing the parent (which may be needed later on).
|
7
|
+
class ReparentGenerator
|
8
|
+
include Utils::Schema
|
9
|
+
|
10
|
+
def self.reparent(model, stack)
|
11
|
+
schema = MongoidSchema.from_model(model)
|
12
|
+
|
13
|
+
stack.flat_map.with_index do |step, index|
|
14
|
+
# If this is the first step in the stack and there are no fields to flatten, return an empty list
|
15
|
+
next [] if index.zero? && step[:as_fields].empty?
|
16
|
+
# If this is the first step in the stack, only flatten the provided fields without reparenting
|
17
|
+
next unflatten(step[:as_fields]) if index.zero?
|
18
|
+
|
19
|
+
local_schema = schema.get_sub_schema(step[:prefix])
|
20
|
+
relative_prefix = if stack[index - 1][:prefix].nil?
|
21
|
+
step[:prefix]
|
22
|
+
else
|
23
|
+
step[:prefix][(stack[index - 1][:prefix].length + 1)..]
|
24
|
+
end
|
25
|
+
|
26
|
+
result = if local_schema.is_array
|
27
|
+
reparent_array(relative_prefix, local_schema.is_leaf)
|
28
|
+
else
|
29
|
+
reparent_object(relative_prefix, local_schema.is_leaf)
|
30
|
+
end
|
31
|
+
|
32
|
+
[*result, *unflatten(step[:as_fields])]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.reparent_array(prefix, in_doc)
|
37
|
+
[
|
38
|
+
{ '$unwind' => { 'path' => "$#{prefix}", 'includeArrayIndex' => 'index' } },
|
39
|
+
{
|
40
|
+
'$replaceRoot' => {
|
41
|
+
'newRoot' => {
|
42
|
+
'$mergeObjects' => [
|
43
|
+
in_doc ? { 'content' => "$#{prefix}" } : "$#{prefix}",
|
44
|
+
ConditionGenerator.tag_record_if_not_exist(
|
45
|
+
prefix,
|
46
|
+
{
|
47
|
+
'_id' => { '$concat' => [{ '$toString' => '$_id' }, ".#{prefix}.",
|
48
|
+
{ '$toString' => '$index' }] },
|
49
|
+
'parent_id' => '$_id',
|
50
|
+
'parent' => '$$ROOT'
|
51
|
+
}
|
52
|
+
)
|
53
|
+
]
|
54
|
+
}
|
55
|
+
}
|
56
|
+
}
|
57
|
+
]
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.reparent_object(prefix, in_doc)
|
61
|
+
[
|
62
|
+
{
|
63
|
+
'$replaceRoot' => {
|
64
|
+
'newRoot' => {
|
65
|
+
'$mergeObjects' => [
|
66
|
+
in_doc ? { 'content' => "$#{prefix}" } : "$#{prefix}",
|
67
|
+
ConditionGenerator.tag_record_if_not_exist(
|
68
|
+
prefix,
|
69
|
+
{
|
70
|
+
'_id' => { '$concat' => [{ '$toString' => '$_id' }, ".#{prefix}"] },
|
71
|
+
'parent_id' => '$_id',
|
72
|
+
'parent' => '$$ROOT'
|
73
|
+
}
|
74
|
+
)
|
75
|
+
]
|
76
|
+
}
|
77
|
+
}
|
78
|
+
}
|
79
|
+
]
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.unflatten(as_fields)
|
83
|
+
return [] if as_fields.empty?
|
84
|
+
|
85
|
+
chunk_size = 30
|
86
|
+
add_fields = as_fields.map { |f| [f.gsub('.', '@@@'), "$#{f}"] }
|
87
|
+
|
88
|
+
# MongoDB (DocumentDB) enforces a limit of 30 fields per $addFields stage.
|
89
|
+
# We split the list into chunks of 30 to prevent errors.
|
90
|
+
unflatten_results = add_fields.each_slice(chunk_size).map { |chunk| { '$addFields' => chunk.to_h } }
|
91
|
+
|
92
|
+
unflatten_results << { '$project' => as_fields.to_h { |f| [f, 0] } }
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module ForestAdminDatasourceMongoid
|
2
|
+
module Utils
|
3
|
+
module Pipeline
|
4
|
+
# When using the `asModel` options, users can request/filter on the virtual _id and parentId fields
|
5
|
+
# of children (using the generated OneToOne relation).
|
6
|
+
#
|
7
|
+
# As those fields are not written to mongo, they are injected here so that they can be used like
|
8
|
+
# any other field.
|
9
|
+
#
|
10
|
+
# This could be also be done by preprocessing the filter, and postprocessing the records, but this
|
11
|
+
# solution seemed simpler, at the cost of additional pipeline stages when making queries.
|
12
|
+
#
|
13
|
+
# Note that a projection is taken as a parameter so that only fields which are actually used are
|
14
|
+
# injected to save resources.
|
15
|
+
class VirtualFieldGenerator
|
16
|
+
def self.add_virtual(_model, stack, projection)
|
17
|
+
set = {}
|
18
|
+
|
19
|
+
projection.each do |colon_field|
|
20
|
+
field = colon_field.tr(':', '.')
|
21
|
+
is_from_one_to_one = stack.last[:as_models].any? { |f| field.start_with?("#{f}.") }
|
22
|
+
|
23
|
+
set[field] = get_path(field) if is_from_one_to_one
|
24
|
+
end
|
25
|
+
|
26
|
+
set.keys.empty? ? [] : [{ '$addFields' => set }]
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.get_path(field)
|
30
|
+
id_identifier = '._id'
|
31
|
+
if field.end_with?(id_identifier)
|
32
|
+
# ... dots to exclude the last character (ex: 'author.' => 'author')
|
33
|
+
suffix = field[0...(field.length - id_identifier.length)]
|
34
|
+
|
35
|
+
return ConditionGenerator.tag_record_if_not_exist_by_value(
|
36
|
+
suffix,
|
37
|
+
{ '$concat' => [{ '$toString' => '$_id' }, (suffix.empty? ? '' : ".#{suffix}")] }
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
parent_id_identifier = '.parent_id'
|
42
|
+
if field.end_with?(parent_id_identifier)
|
43
|
+
|
44
|
+
if field.split('.').length > 2
|
45
|
+
# Implementing this would require us to have knowledge of the value of asModel for
|
46
|
+
# for virtual models under the current one, which the `stack` variable does not have.
|
47
|
+
|
48
|
+
# If the expcetion causes issues we could simply return
|
49
|
+
# `$${field.substring(0, field.length - 9)}._id` but that would not work if the customer
|
50
|
+
# jumped over multiple levels of nesting.
|
51
|
+
|
52
|
+
# As this is a use case that never happens from the UI, and that can be worked around when
|
53
|
+
# using the API, we decided to not implement it.
|
54
|
+
raise ForestAdminDatasourceToolkit::Exceptions::ForestException,
|
55
|
+
'Fetching virtual parent_id deeper than 1 level is not supported.'
|
56
|
+
end
|
57
|
+
suffix = field[0...(field.length - parent_id_identifier.length)]
|
58
|
+
|
59
|
+
return ConditionGenerator.tag_record_if_not_exist_by_value(suffix, '$_id')
|
60
|
+
end
|
61
|
+
|
62
|
+
content_identifier = '.content'
|
63
|
+
if field.end_with?(content_identifier)
|
64
|
+
# FIXME: we should check that this is really a leaf field because "content" can't
|
65
|
+
# really be used as a reserved word
|
66
|
+
|
67
|
+
return "$#{field[0...(field.length - content_identifier.length)]}"
|
68
|
+
end
|
69
|
+
|
70
|
+
parent = field[0..field.rindex('.')]
|
71
|
+
parent = parent.gsub(/\.+$/, '') # Remove trailing dots
|
72
|
+
|
73
|
+
ConditionGenerator.tag_record_if_not_exist_by_value(parent, "$#{field}")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module ForestAdminDatasourceMongoid
|
2
|
+
module Utils
|
3
|
+
module Schema
|
4
|
+
class FieldsGenerator
|
5
|
+
include ForestAdminDatasourceToolkit::Components::Query::ConditionTree
|
6
|
+
extend Utils::Helpers
|
7
|
+
extend Parser::Column
|
8
|
+
extend Parser::Validation
|
9
|
+
|
10
|
+
def self.build_fields_schema(model, stack)
|
11
|
+
our_schema = {}
|
12
|
+
child_schema = MongoidSchema.from_model(model).apply_stack(stack)
|
13
|
+
|
14
|
+
child_schema.fields.each do |name, field|
|
15
|
+
next unless name != 'parent'
|
16
|
+
|
17
|
+
default_value = if field.respond_to?(:object_id_field?) && field.object_id_field?
|
18
|
+
nil
|
19
|
+
else
|
20
|
+
get_default_value(field)
|
21
|
+
end
|
22
|
+
|
23
|
+
our_schema[name] = ForestAdminDatasourceToolkit::Schema::ColumnSchema.new(
|
24
|
+
column_type: get_column_type(field),
|
25
|
+
filter_operators: operators_for_column_type(get_column_type(field)),
|
26
|
+
is_primary_key: name == '_id',
|
27
|
+
is_read_only: false,
|
28
|
+
is_sortable: get_column_type(field) != 'Json',
|
29
|
+
default_value: default_value,
|
30
|
+
enum_values: [],
|
31
|
+
validations: get_validations(model, field)
|
32
|
+
)
|
33
|
+
|
34
|
+
if !field.is_a?(Hash) && field.foreign_key? && field.type != Array && !field.association.polymorphic?
|
35
|
+
our_schema["#{name}__many_to_one"] = build_many_to_one(field)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
return our_schema unless stack.length > 1
|
40
|
+
|
41
|
+
parent_prefix = stack[stack.length - 2][:prefix]
|
42
|
+
|
43
|
+
our_schema['_id'] = build_virtual_primary_key
|
44
|
+
parent_id = child_schema.fields['parent']['_id']
|
45
|
+
our_schema['parent_id'] = ForestAdminDatasourceToolkit::Schema::ColumnSchema.new(
|
46
|
+
column_type: get_column_type(parent_id),
|
47
|
+
filter_operators: operators_for_column_type(get_column_type(parent_id)),
|
48
|
+
is_primary_key: false,
|
49
|
+
is_read_only: false,
|
50
|
+
is_sortable: get_column_type(parent_id) != 'Json',
|
51
|
+
default_value: parent_id.object_id_field? ? nil : get_default_value(parent_id),
|
52
|
+
enum_values: [],
|
53
|
+
validations: [{ operator: 'Present' }]
|
54
|
+
)
|
55
|
+
|
56
|
+
model_name = model.name.gsub('::', '__')
|
57
|
+
our_schema['parent'] = ForestAdminDatasourceToolkit::Schema::Relations::ManyToOneSchema.new(
|
58
|
+
foreign_collection: escape(parent_prefix.nil? ? model_name : "#{model_name}.#{parent_prefix}"),
|
59
|
+
foreign_key: 'parent_id',
|
60
|
+
foreign_key_target: '_id'
|
61
|
+
)
|
62
|
+
|
63
|
+
our_schema
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.build_virtual_primary_key
|
67
|
+
ForestAdminDatasourceToolkit::Schema::ColumnSchema.new(
|
68
|
+
column_type: 'String',
|
69
|
+
filter_operators: operators_for_column_type('String'),
|
70
|
+
is_primary_key: true,
|
71
|
+
is_read_only: true,
|
72
|
+
is_sortable: true
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.build_many_to_one(field)
|
77
|
+
association = field.options[:association]
|
78
|
+
ForestAdminDatasourceToolkit::Schema::Relations::ManyToOneSchema.new(
|
79
|
+
foreign_collection: association.klass.name.gsub('::', '__'),
|
80
|
+
foreign_key: association.foreign_key,
|
81
|
+
foreign_key_target: '_id'
|
82
|
+
)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,196 @@
|
|
1
|
+
module ForestAdminDatasourceMongoid
|
2
|
+
module Utils
|
3
|
+
module Schema
|
4
|
+
class MongoidSchema
|
5
|
+
include ForestAdminDatasourceToolkit::Exceptions
|
6
|
+
include Utils::Helpers
|
7
|
+
attr_reader :is_array, :is_leaf, :fields, :models
|
8
|
+
|
9
|
+
def initialize(model, fields, is_array, is_leaf)
|
10
|
+
@models = ObjectSpace.each_object(Class)
|
11
|
+
.select { |klass| klass < Mongoid::Document && klass.name && !klass.name.start_with?('Mongoid::') }
|
12
|
+
.to_h { |klass| [klass.name, klass] }
|
13
|
+
@model = model
|
14
|
+
@fields = fields
|
15
|
+
@is_array = is_array
|
16
|
+
@is_leaf = is_leaf
|
17
|
+
end
|
18
|
+
|
19
|
+
def schema_node
|
20
|
+
@is_leaf ? @fields[:content] : @fields
|
21
|
+
end
|
22
|
+
|
23
|
+
def schema_type
|
24
|
+
raise ForestException, 'Schema is not a leaf.' unless @is_leaf
|
25
|
+
|
26
|
+
@fields[:content]
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.from_model(model)
|
30
|
+
fields = fields_and_embedded_relations(model)
|
31
|
+
|
32
|
+
new(model, build_fields(fields), false, false)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.fields_and_embedded_relations(model)
|
36
|
+
embedded_class = [Mongoid::Association::Embedded::EmbedsMany, Mongoid::Association::Embedded::EmbedsOne]
|
37
|
+
relations = model.relations.select { |_name, association| embedded_class.include?(association.class) }
|
38
|
+
|
39
|
+
model.fields.merge(relations)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.build_fields(schema_fields, level = 0)
|
43
|
+
targets = {}
|
44
|
+
|
45
|
+
schema_fields.each do |name, field|
|
46
|
+
next if name.start_with?('$') || name.include?('__') || (name == '_id' && level.positive?)
|
47
|
+
|
48
|
+
if VersionManager.sub_document?(field)
|
49
|
+
sub_targets = build_fields(fields_and_embedded_relations(field.klass), level + 1)
|
50
|
+
sub_targets.each { |sub_name, sub_field| recursive_set(targets, "#{name}.#{sub_name}", sub_field) }
|
51
|
+
elsif VersionManager.sub_document_array?(field)
|
52
|
+
sub_targets = build_fields(fields_and_embedded_relations(field.klass), level + 1)
|
53
|
+
sub_targets.each { |sub_name, sub_field| recursive_set(targets, "#{name}.[].#{sub_name}", sub_field) }
|
54
|
+
else
|
55
|
+
recursive_set(targets, name, field)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
targets
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.recursive_set(target, path, value)
|
63
|
+
index = path.index('.')
|
64
|
+
if index.nil?
|
65
|
+
target[path] = value
|
66
|
+
else
|
67
|
+
prefix = path[0, index]
|
68
|
+
suffix = path[index + 1, path.length]
|
69
|
+
target[prefix] ||= {}
|
70
|
+
recursive_set(target[prefix], suffix, value)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def list_paths_matching(handle, prefix = nil)
|
75
|
+
return [] if @is_leaf
|
76
|
+
|
77
|
+
@fields.keys
|
78
|
+
.filter(&:present?)
|
79
|
+
.flat_map do |field|
|
80
|
+
schema = get_sub_schema(field)
|
81
|
+
sub_prefix = prefix ? "#{prefix}.#{field}" : field
|
82
|
+
sub_fields = schema.list_paths_matching(handle, sub_prefix)
|
83
|
+
sub_fields.map { |sub_field| "#{field}.#{sub_field}" }
|
84
|
+
# debugger
|
85
|
+
handle.call(sub_prefix, schema) ? [sub_prefix, *sub_fields] : sub_fields
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def get_sub_schema(path)
|
90
|
+
# Terminating condition
|
91
|
+
return self if path.blank?
|
92
|
+
|
93
|
+
# General case: go down the tree
|
94
|
+
prefix, suffix = path.split(/\.(.*)/)
|
95
|
+
is_leaf = false
|
96
|
+
child = @fields[prefix]
|
97
|
+
is_array = child.is_a?(Mongoid::Fields::Standard) && child.options[:type] == Array
|
98
|
+
|
99
|
+
# Traverse relations
|
100
|
+
if child.is_a?(Hash)
|
101
|
+
relation_name = @model.relations[prefix].class_name
|
102
|
+
|
103
|
+
raise ForestException, "Collection '#{relation_name}' not found." unless @models.key?(relation_name)
|
104
|
+
|
105
|
+
# Traverse arrays
|
106
|
+
if child.is_a?(Hash) && child['[]']
|
107
|
+
# (has_many embed)
|
108
|
+
child = child['[]']
|
109
|
+
is_array = true
|
110
|
+
else
|
111
|
+
# (has_one embed)
|
112
|
+
child = MongoidSchema.from_model(@models[relation_name]).fields
|
113
|
+
end
|
114
|
+
|
115
|
+
return MongoidSchema.new(@models[relation_name], child, is_array, is_leaf).get_sub_schema(suffix)
|
116
|
+
elsif child.nil?
|
117
|
+
raise ForestException, "Field '#{prefix}' not found. Available fields are: #{list_fields}"
|
118
|
+
end
|
119
|
+
|
120
|
+
# We ended up on a field => box it.
|
121
|
+
if child.is_a? Mongoid::Fields::Standard
|
122
|
+
child = { content: child }
|
123
|
+
is_leaf = true
|
124
|
+
end
|
125
|
+
|
126
|
+
MongoidSchema.new(@model, child, is_array, is_leaf).get_sub_schema(suffix)
|
127
|
+
end
|
128
|
+
|
129
|
+
def apply_stack(stack, skip_as_models: false)
|
130
|
+
raise ForestException, 'Stack can never be empty.' if stack.empty?
|
131
|
+
|
132
|
+
step = stack.pop
|
133
|
+
sub_schema = get_sub_schema(step[:prefix])
|
134
|
+
|
135
|
+
step[:as_fields].each do |field|
|
136
|
+
field_schema = sub_schema.get_sub_schema(field)
|
137
|
+
recursive_delete(sub_schema.fields, field)
|
138
|
+
|
139
|
+
sub_schema.fields[field.gsub('.', '@@@')] = if field_schema.is_array
|
140
|
+
{ '[]' => field_schema.schema_node }
|
141
|
+
else
|
142
|
+
field_schema.schema_node
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
unless stack.empty?
|
147
|
+
sub_schema.fields['_id'] = Mongoid::Fields::Standard.new('__placeholder__', { type: String })
|
148
|
+
sub_schema.fields['parent'] = apply_stack(stack).fields
|
149
|
+
sub_schema.fields['parent_id'] = sub_schema.fields['parent']['_id']
|
150
|
+
end
|
151
|
+
|
152
|
+
if skip_as_models
|
153
|
+
# Here we actually should recurse into the subSchema and add the _id and parentId fields
|
154
|
+
# to the virtual one-to-one relations.
|
155
|
+
#
|
156
|
+
# The issue is that we can't do that because we don't know where the relations are after
|
157
|
+
# the first level of nesting (we would need to have the complete asModel / asFields like in
|
158
|
+
# the datasource.ts file).
|
159
|
+
#
|
160
|
+
# Because of that, we need to work around the missing fields in:
|
161
|
+
# - pipeline/virtual-fields.ts file: we're throwing an error when we can't guess the value
|
162
|
+
# of a given _id / parentId field.
|
163
|
+
# - pipeline/filter.ts: we're using an educated guess for the types of the _id / parentId
|
164
|
+
# fields (String or ObjectId)
|
165
|
+
else
|
166
|
+
step[:as_models].each do |field|
|
167
|
+
recursive_delete(@fields, field)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
stack << step
|
172
|
+
|
173
|
+
sub_schema
|
174
|
+
end
|
175
|
+
|
176
|
+
# List leafs and arrays up to a certain level
|
177
|
+
# Arrays are never traversed
|
178
|
+
def list_fields(level = Float::INFINITY)
|
179
|
+
raise ForestException, 'Cannot list fields on a leaf schema.' if @is_leaf
|
180
|
+
raise ForestException, 'Level must be greater than 0.' if level.zero?
|
181
|
+
|
182
|
+
return @fields.keys if level == 1
|
183
|
+
|
184
|
+
@fields.keys.flat_map do |field|
|
185
|
+
schema = get_sub_schema(field)
|
186
|
+
if schema.is_leaf || schema.is_array
|
187
|
+
[field]
|
188
|
+
else
|
189
|
+
schema.list_fields(level - 1).map { |sub_field| "#{field}.#{sub_field}" }
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module ForestAdminDatasourceMongoid
|
2
|
+
module Utils
|
3
|
+
module Schema
|
4
|
+
class RelationGenerator
|
5
|
+
include ForestAdminDatasourceToolkit::Schema::Relations
|
6
|
+
extend ForestAdminDatasourceMongoid::Utils::Helpers
|
7
|
+
|
8
|
+
def self.add_implicit_relations(collections)
|
9
|
+
collections.each_value do |collection|
|
10
|
+
many_to_ones = collection.schema[:fields].select { |_, f| f.type == 'ManyToOne' }
|
11
|
+
|
12
|
+
many_to_ones.each do |(name, field)|
|
13
|
+
add_many_to_one_inverse(collection, name, field)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Given any many to one relation, generated while parsing mongoose schema, generate the
|
19
|
+
# inverse relationship on the foreignCollection.
|
20
|
+
# /!\ The inverse can be a OneToOne, or a ManyToOne
|
21
|
+
def self.add_many_to_one_inverse(collection, name, schema)
|
22
|
+
if name == 'parent'
|
23
|
+
# Create inverse of 'parent' relationship so that the relation name matches the actual name
|
24
|
+
# of the data which is stored in the database.
|
25
|
+
stack = collection.stack
|
26
|
+
prefix = stack[stack.length - 1][:prefix]
|
27
|
+
is_array = MongoidSchema.from_model(collection.model).apply_stack(stack).is_array
|
28
|
+
|
29
|
+
type = is_array ? OneToManySchema : OneToOneSchema
|
30
|
+
inverse_name = escape(prefix)
|
31
|
+
|
32
|
+
if stack.length > 2
|
33
|
+
previous_length = stack[stack.length - 2][:prefix].length + 1
|
34
|
+
inverse_name = prefix[previous_length..]
|
35
|
+
end
|
36
|
+
else
|
37
|
+
inverse_name = escape("#{collection.name}_#{name}__inverse")
|
38
|
+
type = OneToManySchema
|
39
|
+
end
|
40
|
+
|
41
|
+
other_collection = collection.datasource.get_collection(schema.foreign_collection)
|
42
|
+
other_collection.schema[:fields][inverse_name] = type.new(
|
43
|
+
foreign_collection: collection.name,
|
44
|
+
origin_key: schema.foreign_key,
|
45
|
+
origin_key_target: schema.foreign_key_target
|
46
|
+
)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module ForestAdminDatasourceMongoid
|
2
|
+
module Utils
|
3
|
+
class VersionManager
|
4
|
+
def self.sub_document?(field)
|
5
|
+
field.is_a?(Mongoid::Association::Embedded::EmbedsOne)
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.sub_document_array?(field)
|
9
|
+
field.is_a?(Mongoid::Association::Embedded::EmbedsMany)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require_relative 'forest_admin_datasource_mongoid/version'
|
2
|
+
require 'zeitwerk'
|
3
|
+
|
4
|
+
loader = Zeitwerk::Loader.for_gem
|
5
|
+
loader.ignore("#{__dir__}/models")
|
6
|
+
loader.setup
|
7
|
+
|
8
|
+
module ForestAdminDatasourceMongoid
|
9
|
+
class Error < StandardError; end
|
10
|
+
# Your code goes here...
|
11
|
+
end
|
metadata
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: forest_admin_datasource_mongoid
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matthieu
|
8
|
+
- Nicolas
|
9
|
+
autorequire:
|
10
|
+
bindir: exe
|
11
|
+
cert_chain: []
|
12
|
+
date: 2025-09-23 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: mongoid
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '9.0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '9.0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: activesupport
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '6.1'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '6.1'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: zeitwerk
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '2.3'
|
49
|
+
type: :runtime
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '2.3'
|
56
|
+
description: |-
|
57
|
+
Forest is a modern admin interface that works on all major web frameworks. This gem makes Forest
|
58
|
+
admin work on any Ruby application.
|
59
|
+
email:
|
60
|
+
- matthv@gmail.com
|
61
|
+
- nicolasalexandre9@gmail.com
|
62
|
+
executables: []
|
63
|
+
extensions: []
|
64
|
+
extra_rdoc_files: []
|
65
|
+
files:
|
66
|
+
- ".rspec"
|
67
|
+
- LICENSE
|
68
|
+
- Rakefile
|
69
|
+
- forest_admin_datasource_mongoid.gemspec
|
70
|
+
- lib/forest_admin_datasource_mongoid.rb
|
71
|
+
- lib/forest_admin_datasource_mongoid/collection.rb
|
72
|
+
- lib/forest_admin_datasource_mongoid/datasource.rb
|
73
|
+
- lib/forest_admin_datasource_mongoid/options_parser.rb
|
74
|
+
- lib/forest_admin_datasource_mongoid/parser/column.rb
|
75
|
+
- lib/forest_admin_datasource_mongoid/parser/relation.rb
|
76
|
+
- lib/forest_admin_datasource_mongoid/parser/validation.rb
|
77
|
+
- lib/forest_admin_datasource_mongoid/utils/add_null_values.rb
|
78
|
+
- lib/forest_admin_datasource_mongoid/utils/helpers.rb
|
79
|
+
- lib/forest_admin_datasource_mongoid/utils/mongoid_serializer.rb
|
80
|
+
- lib/forest_admin_datasource_mongoid/utils/pipeline/condition_generator.rb
|
81
|
+
- lib/forest_admin_datasource_mongoid/utils/pipeline/filter_generator.rb
|
82
|
+
- lib/forest_admin_datasource_mongoid/utils/pipeline/group_generator.rb
|
83
|
+
- lib/forest_admin_datasource_mongoid/utils/pipeline/lookup_generator.rb
|
84
|
+
- lib/forest_admin_datasource_mongoid/utils/pipeline/projection_generator.rb
|
85
|
+
- lib/forest_admin_datasource_mongoid/utils/pipeline/reparent_generator.rb
|
86
|
+
- lib/forest_admin_datasource_mongoid/utils/pipeline/virtual_field_generator.rb
|
87
|
+
- lib/forest_admin_datasource_mongoid/utils/schema/fields_generator.rb
|
88
|
+
- lib/forest_admin_datasource_mongoid/utils/schema/mongoid_schema.rb
|
89
|
+
- lib/forest_admin_datasource_mongoid/utils/schema/relation_generator.rb
|
90
|
+
- lib/forest_admin_datasource_mongoid/utils/version_manager.rb
|
91
|
+
- lib/forest_admin_datasource_mongoid/version.rb
|
92
|
+
homepage: https://www.forestadmin.com
|
93
|
+
licenses:
|
94
|
+
- GPL-3.0
|
95
|
+
metadata:
|
96
|
+
homepage_uri: https://www.forestadmin.com
|
97
|
+
source_code_uri: https://github.com/ForestAdmin/agent-ruby
|
98
|
+
changelog_uri: https://github.com/ForestAdmin/agent-ruby/blob/main/CHANGELOG.md
|
99
|
+
rubygems_mfa_required: 'false'
|
100
|
+
post_install_message:
|
101
|
+
rdoc_options: []
|
102
|
+
require_paths:
|
103
|
+
- lib
|
104
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: 3.0.0
|
109
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
requirements: []
|
115
|
+
rubygems_version: 3.4.20
|
116
|
+
signing_key:
|
117
|
+
specification_version: 4
|
118
|
+
summary: Ruby agent for Forest Admin.
|
119
|
+
test_files: []
|