Module: WCC::Contentful::ModelMethods

Defined in:
lib/wcc/contentful/model_methods.rb

Overview

This module is included by all models and defines instance methods that are not dynamically generated.

Constant Summary collapse

MODEL_LAYER_CONTEXT_KEYS =

The set of options keys that are specific to the Model layer and shouldn’t be passed down to the Store layer.

%i[
  preview
  backlinks
].freeze

Instance Method Summary collapse

Instance Method Details

#resolve(depth: 1, fields: nil, context: sys.context.to_h, **options) ⇒ Object

Resolves all links in an entry to the specified depth.

Each link in the entry is recursively retrieved from the store until the given depth is satisfied. Depth resolution is unlimited, circular references will be resolved to the same object.

Parameters:

  • depth (Fixnum) (defaults to: 1)

    how far to recursively resolve. Must be >= 1

  • fields (Array<String, Symbol>) (defaults to: nil)

    (optional) A subset of fields whose links should be resolved. Defaults to all fields.

  • context (Hash) (defaults to: sys.context.to_h)

    passed to the resolved model’s ‘new` function to provide contextual information ex. current locale. See Model#find, Sys#context

  • options (Hash)

    The remaining optional parameters, defined below

Options Hash (**options):

  • circular_reference (Symbol)

    Determines how circular references are handled. ‘:raise` causes a CircularReferenceError to be raised, `:ignore` will cause the field to remain unresolved, and any other value (or nil) will cause the field to point to the previously resolved ruby object for that ID.

Raises:

  • (ArgumentError)


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
# File 'lib/wcc/contentful/model_methods.rb', line 32

def resolve(depth: 1, fields: nil, context: sys.context.to_h, **options)
  raise ArgumentError, "Depth must be > 0 (was #{depth})" unless depth && depth > 0
  return self if resolved?(depth: depth, fields: fields)

  fields = fields.map { |f| f.to_s.camelize(:lower) } if fields.present?
  fields ||= self.class::FIELDS

  typedef = self.class.content_type_definition
  links = fields.select { |f| %i[Asset Link].include?(typedef.fields[f].type) }
  store = context[:preview] ? self.class.services.preview_store : self.class.services.store

  raw_link_ids =
    links.map { |field_name| raw.dig('fields', field_name) }
      .flat_map do |raw_value|
        _try_map(raw_value) { |v| v.dig('sys', 'id') if v.dig('sys', 'type') == 'Link' }
      end
  raw_link_ids = raw_link_ids.compact
  backlinked_ids = (context[:backlinks]&.map { |m| m.id } || [])

  has_unresolved_raw_links = (raw_link_ids - backlinked_ids).any?
  if has_unresolved_raw_links
    raw =
      _instrument 'resolve', id: id, depth: depth, backlinks: backlinked_ids do
        # use include param to do resolution
        store.find_by(content_type: self.class.content_type,
          filter: { 'sys.id' => id },
          options: context.except(*MODEL_LAYER_CONTEXT_KEYS).merge!({
            include: [depth, 10].min
          }))
      end
    raise WCC::Contentful::ResolveError, "Cannot find #{self.class.content_type} with ID #{id}" unless raw

    @raw = raw.freeze
    links.each { |f| instance_variable_set("@#{f}", raw.dig('fields', f)) }
  end

  links.each { |f| _resolve_field(f, depth, context, options) }
  self
end

#resolved?(depth: 1, fields: nil) ⇒ Boolean

Determines whether the object has been resolved up to the prescribed depth.

Returns:

  • (Boolean)

Raises:

  • (ArgumentError)


73
74
75
76
77
78
79
80
81
82
# File 'lib/wcc/contentful/model_methods.rb', line 73

def resolved?(depth: 1, fields: nil)
  raise ArgumentError, "Depth must be > 0 (was #{depth})" unless depth && depth > 0

  fields = fields.map { |f| f.to_s.camelize(:lower) } if fields.present?
  fields ||= self.class::FIELDS

  typedef = self.class.content_type_definition
  links = fields.select { |f| %i[Asset Link].include?(typedef.fields[f].type) }
  links.all? { |f| _resolved_field?(f, depth) }
end

#to_h(stack = nil) ⇒ Object

Turns the current model into a hash representation as though it had been retrieved from the Contentful API.

This differs from ‘#raw` in that it recursively includes the `#to_h` of resolved links.



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/wcc/contentful/model_methods.rb', line 89

def to_h(stack = nil)
  raise WCC::Contentful::CircularReferenceError.new(stack, id) if stack&.include?(id)

  stack = [*stack, id]
  typedef = self.class.content_type_definition
  fields =
    typedef.fields.each_with_object({}) do |(name, field_def), h|
      if field_def.type == :Link || field_def.type == :Asset
        if _resolved_field?(name, 0)
          val = public_send(name)
          val =
            _try_map(val) { |v| v.to_h(stack) }
        else
          ids = field_def.array ? public_send("#{name}_ids") : public_send("#{name}_id")
          val =
            _try_map(ids) do |id|
              {
                'sys' => {
                  'type' => 'Link',
                  'linkType' => field_def.type == :Asset ? 'Asset' : 'Entry',
                  'id' => id
                }
              }
            end
        end
      else
        val = public_send(name)
        val = _try_map(val) { |v| v.respond_to?(:to_h) ? v.to_h.stringify_keys! : v }
      end

      h[name] = val
    end

  {
    'sys' => @raw['sys'].dup,
    'fields' => fields
  }
end