module WCC::Contentful::ModelMethods
This module is included by all {WCC::Contentful::Model models} and defines instance methods that are not dynamically generated.
@api Model
Constants
Public Instance Methods
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.
@param [Fixnum] depth how far to recursively resolve. Must be >= 1 @param [Array<String, Symbol>] fields (optional) A subset of fields whose
links should be resolved. Defaults to all fields.
@param [Hash] context passed to the resolved model's `new` function to provide
contextual information ex. current locale. See {WCC::Contentful::ModelSingletonMethods#find Model#find}, {WCC::Contentful::Sys#context}
@param [Hash] options The remaining optional parameters, defined below @option options [Symbol] circular_reference Determines how circular references are
handled. `:raise` causes a {WCC::Contentful::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.
# File lib/wcc/contentful/model_methods.rb, line 34 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) } raw_link_ids = links.map { |field_name| raw.dig('fields', field_name, sys.locale) } .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 self.class.store(context[:preview]) .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 unless raw raise WCC::Contentful::ResolveError, "Cannot find #{self.class.content_type} with ID #{id}" end @raw = raw.freeze links.each { |f| instance_variable_set('@' + f, raw.dig('fields', f, sys.locale)) } end links.each { |f| _resolve_field(f, depth, context, options) } self end
Determines whether the object has been resolved up to the prescribed depth.
# File lib/wcc/contentful/model_methods.rb, line 77 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
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. It also sets the fields to the value for the entry's `#sys.locale`, as though the entry had been retrieved from the API with `locale={#sys.locale}` rather than `locale=*`.
# File lib/wcc/contentful/model_methods.rb, line 95 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' => { 'locale' => @sys.locale }.merge!(@raw['sys']), 'fields' => fields } end
Protected Instance Methods
# File lib/wcc/contentful/model_methods.rb, line 138 def _instrumentation_event_prefix '.model.contentful.wcc' end
Private Instance Methods
# File lib/wcc/contentful/model_methods.rb, line 144 def _resolve_field(field_name, depth = 1, context = {}, options = {}) return if depth <= 0 var_name = '@' + field_name return unless val = instance_variable_get(var_name) context = sys.context.to_h.merge(context) # load a single link from a raw link or entry, by either finding it via the API # or instantiating it directly from a raw entry load = ->(raw) { id = raw.dig('sys', 'id') already_resolved = context[:backlinks]&.find { |m| m.id == id } new_context = context.merge({ backlinks: [self, *context[:backlinks]].freeze }) if already_resolved && %i[ignore raise].include?(options[:circular_reference]) raise WCC::Contentful::CircularReferenceError.new( new_context[:backlinks].map(&:id).reverse, id ) end # Use the already resolved circular reference, or resolve a link, or # instantiate from already resolved raw entry data. m = already_resolved || if raw.dig('sys', 'type') == 'Link' _instrument 'resolve', id: self.id, depth: depth, backlinks: context[:backlinks]&.map(&:id) do WCC::Contentful::Model.find(id, options: new_context) end else WCC::Contentful::Model.new_from_raw(raw, new_context) end m.resolve(depth: depth - 1, context: new_context, **options) if m && depth > 1 m } begin val = _try_map(val) { |v| load.call(v) } val = val.compact if val.is_a? Array instance_variable_set(var_name + '_resolved', val) rescue WCC::Contentful::CircularReferenceError raise unless options[:circular_reference] == :ignore end end
# File lib/wcc/contentful/model_methods.rb, line 193 def _resolved_field?(field_name, depth = 1) var_name = '@' + field_name raw = instance_variable_get(var_name) return true if raw.nil? || (raw.is_a?(Array) && raw.all?(&:nil?)) return false unless val = instance_variable_get(var_name + '_resolved') return true if depth <= 1 return val.resolved?(depth: depth - 1) unless val.is_a? Array val.all? { |i| i.nil? || i.resolved?(depth: depth - 1) } end
# File lib/wcc/contentful/model_methods.rb, line 205 def _try_map(val) if val.is_a? Array return val&.map do |item| yield item unless item.nil? end end yield val unless val.nil? end