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.



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

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.



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

def fields
  @fields
end

#is_arrayObject (readonly)

Returns the value of attribute is_array.



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

def is_array
  @is_array
end

#is_leafObject (readonly)

Returns the value of attribute is_leaf.



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

def is_leaf
  @is_leaf
end

#modelsObject (readonly)

Returns the value of attribute models.



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

def models
  @models
end

Class Method Details

.build_fields(schema_fields, level = 0) ⇒ Object



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

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



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

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



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

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



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

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)


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
174
# File 'lib/forest_admin_datasource_mongoid/utils/schema/mongoid_schema.rb', line 129

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



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
127
# File 'lib/forest_admin_datasource_mongoid/utils/schema/mongoid_schema.rb', line 89

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)


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

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



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

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



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

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

#schema_typeObject

Raises:

  • (ForestException)


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

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

  @fields[:content]
end