Class: ForestAdminDatasourceMongoid::Utils::Pipeline::VirtualFieldGenerator

Inherits:
Object
  • Object
show all
Defined in:
lib/forest_admin_datasource_mongoid/utils/pipeline/virtual_field_generator.rb

Overview

When using the ‘asModel` options, users can request/filter on the virtual _id and parentId fields of children (using the generated OneToOne relation).

As those fields are not written to mongo, they are injected here so that they can be used like any other field.

This could be also be done by preprocessing the filter, and postprocessing the records, but this solution seemed simpler, at the cost of additional pipeline stages when making queries.

Note that a projection is taken as a parameter so that only fields which are actually used are injected to save resources.

Class Method Summary collapse

Class Method Details

.add_virtual(_model, stack, projection) ⇒ Object



16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/forest_admin_datasource_mongoid/utils/pipeline/virtual_field_generator.rb', line 16

def self.add_virtual(_model, stack, projection)
  set = {}

  projection.each do |colon_field|
    field = colon_field.tr(':', '.')
    is_from_one_to_one = stack.last[:as_models].any? { |f| field.start_with?("#{f}.") }

    set[field] = get_path(field) if is_from_one_to_one
  end

  set.keys.empty? ? [] : [{ '$addFields' => set }]
end

.get_path(field) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/forest_admin_datasource_mongoid/utils/pipeline/virtual_field_generator.rb', line 29

def self.get_path(field)
  id_identifier = '._id'
  if field.end_with?(id_identifier)
    # ... dots to exclude the last character (ex: 'author.' => 'author')
    suffix = field[0...(field.length - id_identifier.length)]

    return ConditionGenerator.tag_record_if_not_exist_by_value(
      suffix,
      { '$concat' => [{ '$toString' => '$_id' }, (suffix.empty? ? '' : ".#{suffix}")] }
    )
  end

  parent_id_identifier = '.parent_id'
  if field.end_with?(parent_id_identifier)

    if field.split('.').length > 2
      # Implementing this would require us to have knowledge of the value of asModel for
      # for virtual models under the current one, which the `stack` variable does not have.

      # If the expcetion causes issues we could simply return
      # `$${field.substring(0, field.length - 9)}._id` but that would not work if the customer
      # jumped over multiple levels of nesting.

      # As this is a use case that never happens from the UI, and that can be worked around when
      # using the API, we decided to not implement it.
      raise ForestAdminDatasourceToolkit::Exceptions::ForestException,
            'Fetching virtual parent_id deeper than 1 level is not supported.'
    end
    suffix = field[0...(field.length - parent_id_identifier.length)]

    return ConditionGenerator.tag_record_if_not_exist_by_value(suffix, '$_id')
  end

  content_identifier = '.content'
  if field.end_with?(content_identifier)
    # FIXME: we should check that this is really a leaf field because "content" can't
    # really be used as a reserved word

    return "$#{field[0...(field.length - content_identifier.length)]}"
  end

  parent = field[0..field.rindex('.')]
  parent = parent.gsub(/\.+$/, '') # Remove trailing dots

  ConditionGenerator.tag_record_if_not_exist_by_value(parent, "$#{field}")
end