Class: ForestAdminDatasourceMongoid::Utils::Schema::MongoidSchema

Inherits:
Object
  • Object
show all
Includes:
Helpers, ForestAdminDatasourceToolkit::Exceptions
Defined in:
lib/forest_admin_datasource_mongoid/utils/schema/mongoid_schema.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Helpers

#compare_ids, #deep_merge, #escape, #group_ids_by_path, #recursive_delete, #recursive_set, #reformat_patch, #replace_mongo_types, #split_id, #unflatten_record, #unnest

Constructor Details

#initialize(model, fields, is_array, is_leaf) ⇒ MongoidSchema

Returns a new instance of MongoidSchema.



9
10
11
12
13
14
15
16
17
# File 'lib/forest_admin_datasource_mongoid/utils/schema/mongoid_schema.rb', line 9

def initialize(model, fields, is_array, is_leaf)
  @models = ObjectSpace.each_object(Class)
                       .select { |klass| klass < Mongoid::Document && klass.name && !klass.name.start_with?('Mongoid::') }
                       .to_h { |klass| [klass.name, klass] }
  @model = model
  @fields = fields
  @is_array = is_array
  @is_leaf = is_leaf
end

Instance Attribute Details

#fieldsObject (readonly)

Returns the value of attribute fields.



7
8
9
# File 'lib/forest_admin_datasource_mongoid/utils/schema/mongoid_schema.rb', line 7

def fields
  @fields
end

#is_arrayObject (readonly)

Returns the value of attribute is_array.



7
8
9
# File 'lib/forest_admin_datasource_mongoid/utils/schema/mongoid_schema.rb', line 7

def is_array
  @is_array
end

#is_leafObject (readonly)

Returns the value of attribute is_leaf.



7
8
9
# File 'lib/forest_admin_datasource_mongoid/utils/schema/mongoid_schema.rb', line 7

def is_leaf
  @is_leaf
end

#modelsObject (readonly)

Returns the value of attribute models.



7
8
9
# File 'lib/forest_admin_datasource_mongoid/utils/schema/mongoid_schema.rb', line 7

def models
  @models
end

Class Method Details

.build_fields(schema_fields, level = 0) ⇒ Object



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/forest_admin_datasource_mongoid/utils/schema/mongoid_schema.rb', line 42

def self.build_fields(schema_fields, level = 0)
  targets = {}

  schema_fields.each do |name, field|
    next if name.start_with?('$') || name.include?('__') || (name == '_id' && level.positive?)

    if VersionManager.sub_document?(field)
      sub_targets = build_fields(fields_and_embedded_relations(field.klass), level + 1)
      sub_targets.each { |sub_name, sub_field| recursive_set(targets, "#{name}.#{sub_name}", sub_field) }
    elsif VersionManager.sub_document_array?(field)
      sub_targets = build_fields(fields_and_embedded_relations(field.klass), level + 1)
      sub_targets.each { |sub_name, sub_field| recursive_set(targets, "#{name}.[].#{sub_name}", sub_field) }
    else
      recursive_set(targets, name, field)
    end
  end

  targets
end

.fields_and_embedded_relations(model) ⇒ Object



35
36
37
38
39
40
# File 'lib/forest_admin_datasource_mongoid/utils/schema/mongoid_schema.rb', line 35

def self.fields_and_embedded_relations(model)
  embedded_class = [Mongoid::Association::Embedded::EmbedsMany, Mongoid::Association::Embedded::EmbedsOne]
  relations = model.relations.select { |_name, association| embedded_class.include?(association.class) }

  model.fields.merge(relations)
end

.from_model(model) ⇒ Object



29
30
31
32
33
# File 'lib/forest_admin_datasource_mongoid/utils/schema/mongoid_schema.rb', line 29

def self.from_model(model)
  fields = fields_and_embedded_relations(model)

  new(model, build_fields(fields), false, false)
end

.recursive_set(target, path, value) ⇒ Object



62
63
64
65
66
67
68
69
70
71
72
# File 'lib/forest_admin_datasource_mongoid/utils/schema/mongoid_schema.rb', line 62

def self.recursive_set(target, path, value)
  index = path.index('.')
  if index.nil?
    target[path] = value
  else
    prefix = path[0, index]
    suffix = path[index + 1, path.length]
    target[prefix] ||= {}
    recursive_set(target[prefix], suffix, value)
  end
end

Instance Method Details

#apply_stack(stack, skip_as_models: false) ⇒ Object

Raises:

  • (ForestException)


128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/forest_admin_datasource_mongoid/utils/schema/mongoid_schema.rb', line 128

def apply_stack(stack, skip_as_models: false)
  raise ForestException, 'Stack can never be empty.' if stack.empty?

  step = stack.pop
  sub_schema = get_sub_schema(step[:prefix])

  step[:as_fields].each do |field|
    field_schema = sub_schema.get_sub_schema(field)
    recursive_delete(sub_schema.fields, field)

    sub_schema.fields[field.gsub('.', '@@@')] = if field_schema.is_array
                                                  { '[]' => field_schema.schema_node }
                                                else
                                                  field_schema.schema_node
                                                end
  end

  unless stack.empty?
    sub_schema.fields['_id'] = Mongoid::Fields::Standard.new('__placeholder__', { type: String })
    sub_schema.fields['parent'] = apply_stack(stack).fields
    sub_schema.fields['parent_id'] = sub_schema.fields['parent']['_id']
  end

  if skip_as_models
    # Here we actually should recurse into the subSchema and add the _id and parentId fields
    # to the virtual one-to-one relations.
    #
    # The issue is that we can't do that because we don't know where the relations are after
    # the first level of nesting (we would need to have the complete asModel / asFields like in
    # the datasource.ts file).
    #
    # Because of that, we need to work around the missing fields in:
    # - pipeline/virtual-fields.ts file: we're throwing an error when we can't guess the value
    #   of a given _id / parentId field.
    # - pipeline/filter.ts: we're using an educated guess for the types of the _id / parentId
    #   fields (String or ObjectId)
  else
    step[:as_models].each do |field|
      recursive_delete(@fields, field)
    end
  end

  stack << step

  sub_schema
end

#get_sub_schema(path) ⇒ Object



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/forest_admin_datasource_mongoid/utils/schema/mongoid_schema.rb', line 88

def get_sub_schema(path)
  # Terminating condition
  return self if path.blank?

  # General case: go down the tree
  prefix, suffix = path.split(/\.(.*)/)
  is_leaf = false
  child = @fields[prefix]
  is_array = child.is_a?(Mongoid::Fields::Standard) && child.options[:type] == Array

  # Traverse relations
  if child.is_a?(Hash)
    relation_name = @model.relations[prefix].class_name

    raise ForestException, "Collection '#{relation_name}' not found." unless @models.key?(relation_name)

    # Traverse arrays
    if child.is_a?(Hash) && child['[]']
      # (has_many embed)
      child = child['[]']
      is_array = true
    else
      # (has_one embed)
      child = MongoidSchema.from_model(@models[relation_name]).fields
    end

    return MongoidSchema.new(@models[relation_name], child, is_array, is_leaf).get_sub_schema(suffix)
  elsif child.nil?
    raise ForestException, "Field '#{prefix}' not found. Available fields are: #{list_fields}"
  end

  # We ended up on a field => box it.
  if child.is_a? Mongoid::Fields::Standard
    child = { content: child }
    is_leaf = true
  end

  MongoidSchema.new(@model, child, is_array, is_leaf).get_sub_schema(suffix)
end

#list_fields(level = Float::INFINITY) ⇒ Object

List leafs and arrays up to a certain level Arrays are never traversed

Raises:

  • (ForestException)


177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/forest_admin_datasource_mongoid/utils/schema/mongoid_schema.rb', line 177

def list_fields(level = Float::INFINITY)
  raise ForestException, 'Cannot list fields on a leaf schema.' if @is_leaf
  raise ForestException, 'Level must be greater than 0.' if level.zero?

  return @fields.keys if level == 1

  @fields.keys.flat_map do |field|
    schema = get_sub_schema(field)
    if schema.is_leaf || schema.is_array
      [field]
    else
      schema.list_fields(level - 1).map { |sub_field| "#{field}.#{sub_field}" }
    end
  end
end

#list_paths_matching(handle, prefix = nil) ⇒ Object



74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/forest_admin_datasource_mongoid/utils/schema/mongoid_schema.rb', line 74

def list_paths_matching(handle, prefix = nil)
  return [] if @is_leaf

  @fields.keys
         .filter(&:present?)
         .flat_map do |field|
    schema = get_sub_schema(field)
    sub_prefix = prefix ? "#{prefix}.#{field}" : field
    sub_fields = schema.list_paths_matching(handle, sub_prefix)
    sub_fields.map { |sub_field| "#{field}.#{sub_field}" }
    handle.call(sub_prefix, schema) ? [sub_prefix, *sub_fields] : sub_fields
  end
end

#schema_nodeObject



19
20
21
# File 'lib/forest_admin_datasource_mongoid/utils/schema/mongoid_schema.rb', line 19

def schema_node
  @is_leaf ? @fields[:content] : @fields
end

#schema_typeObject

Raises:

  • (ForestException)


23
24
25
26
27
# File 'lib/forest_admin_datasource_mongoid/utils/schema/mongoid_schema.rb', line 23

def schema_type
  raise ForestException, 'Schema is not a leaf.' unless @is_leaf

  @fields[:content]
end