class WCC::Contentful::Store::Query

The default query object returned by Stores that extend WCC::Contentful::Store::Base. It exposes several chainable query methods to apply query filters. Enumerating the query executes it, caching the result.

Constants

Condition
RESERVED_NAMES

Attributes

conditions[R]
content_type[R]
store[R]

Public Class Methods

new(store, content_type:, conditions: nil, options: nil, **extra) click to toggle source
# File lib/wcc/contentful/store/query.rb, line 29
def initialize(store, content_type:, conditions: nil, options: nil, **extra)
  @store = store
  @content_type = content_type
  @conditions = conditions || []
  @options = options || {}
  @extra = extra
end

Private Class Methods

flatten_filter_hash(hash, path = []) click to toggle source

Turns a hash into a flat array of individual conditions, where each element can be passed as params to apply_operator

# File lib/wcc/contentful/store/query.rb, line 141
def flatten_filter_hash(hash, path = [])
  hash.flat_map do |(k, v)|
    k = k.to_s
    if k.include?('.')
      k, *rest = k.split('.')
      v = { rest.join('.') => v }
    end

    if v.is_a? Hash
      flatten_filter_hash(v, path + [k])
    elsif op?(k)
      { path: path, op: k.to_sym, expected: v }
    else
      { path: path + [k], op: :eq, expected: v }
    end
  end
end
known_locales() click to toggle source
# File lib/wcc/contentful/store/query.rb, line 159
def known_locales
  @known_locales = WCC::Contentful.locales.keys
end
normalize_condition_path(path, context = nil) click to toggle source

Takes a path array in non-normal form and inserts 'sys', 'fields', and the current locale as appropriate to normalize it. rubocop:disable Metrics/BlockNesting

# File lib/wcc/contentful/store/query.rb, line 167
def normalize_condition_path(path, context = nil)
  context_locale = context[:locale] if context.present?
  context_locale ||= 'en-US'

  rev_path = path.reverse
  new_path = []

  current_tuple = []
  current_locale_was_inferred = false
  until rev_path.empty? && current_tuple.empty?
    raise ArgumentError, "Query too complex: #{path.join('.')}" if new_path.length > 7

    case current_tuple.length
    when 0
      # expect a locale
      current_tuple <<
        if known_locales.include?(rev_path[0])
          current_locale_was_inferred = false
          rev_path.shift
        else
          # infer locale
          current_locale_was_inferred = true
          context_locale
        end
    when 1
      # expect a path
      current_tuple << rev_path.shift
    when 2
      # expect 'sys' or 'fields'
      current_tuple <<
        if RESERVED_NAMES.include?(rev_path[0])
          rev_path.shift
        else
          # infer 'sys' or 'fields'
          current_tuple.last == 'id' ? 'sys' : 'fields'
        end

      if current_tuple.last == 'sys' && current_locale_was_inferred
        # remove the inferred current locale
        current_tuple.shift
      end
      new_path << current_tuple
      current_tuple = []
    end
  end

  new_path.flat_map { |x| x }.reverse.freeze
end
op?(key) click to toggle source
# File lib/wcc/contentful/store/query.rb, line 135
def op?(key)
  Interface::OPERATORS.include?(key.to_sym)
end

Public Instance Methods

apply(filter, context = nil) click to toggle source

Called with a filter object by {Base#find_by} in order to apply the filter. The filter in this case is a hash where the keys are paths and the values are expectations. @see apply_operator

# File lib/wcc/contentful/store/query.rb, line 76
def apply(filter, context = nil)
  self.class.flatten_filter_hash(filter).reduce(self) do |query, cond|
    query.apply_operator(cond[:op], cond[:path], cond[:expected], context)
  end
end
apply_operator(operator, field, expected, context = nil) click to toggle source

Returns a new chained Query that has a new condition. The new condition represents the WHERE comparison being applied here. The underlying store implementation translates this condition statement into an appropriate query against the datastore.

@example

query = query.apply_operator(:gt, :timestamp, '2019-01-01', context)
# in a SQL based store, the query now contains a condition like:
#  WHERE table.'timestamp' > '2019-01-01'

@operator one of WCC::Contentful::Store::Query::Interface::OPERATORS @field The path through the fields of the content type that we are querying against.

Can be an array, symbol, or dotted-notation path specification.

@expected The expected value to compare the field's value against. @context A context object optionally containing `context`

# File lib/wcc/contentful/store/query.rb, line 52
def apply_operator(operator, field, expected, context = nil)
  raise ArgumentError, "Operator #{operator} not supported" unless respond_to?(operator)

  field = field.to_s if field.is_a? Symbol
  path = field.is_a?(Array) ? field : field.split('.')

  path = self.class.normalize_condition_path(path, context)

  _append_condition(
    Condition.new(path, operator, expected)
  )
end
result_set() click to toggle source

Override this to provide a result set from the Query object itself rather than from calling execute in the store.

# File lib/wcc/contentful/store/query.rb, line 84
def result_set
  @result_set ||= store.execute(self)
end
to_enum() click to toggle source

Executes the query against the store and memoizes the resulting enumerable.

Subclasses can override this to provide a more efficient implementation.
# File lib/wcc/contentful/store/query.rb, line 22
def to_enum
  @to_enum ||=
    result_set.lazy.map { |row| resolve_includes(row, @options[:include]) }
end

Private Instance Methods

_append_condition(condition) click to toggle source
# File lib/wcc/contentful/store/query.rb, line 90
def _append_condition(condition)
  self.class.new(
    store,
    content_type: content_type,
    conditions: conditions + [condition],
    options: @options,
    **@extra
  )
end
resolve_includes(row, depth) click to toggle source

naive implementation recursively descends the graph to turns links into the actual entry data. If the result set from execute returns a tuple, it tries to pull links from the second column in the tuple. This allows a store implementation to return ex. `SELECT entry, includes FROM…` Otherwise, if the store does not return a tuple or does not have an includes column, it calls {Base#find} for each link and so it is very inefficient.

# File lib/wcc/contentful/store/query.rb, line 106
def resolve_includes(row, depth)
  entry = row.try(:entry) || row.try(:[], 0) || row
  includes = row.try(:includes) || row.try(:[], 1)
  return entry unless entry && depth && depth > 0

  WCC::Contentful::LinkVisitor.new(entry, :Link, :Asset,
    # Walk all the links except for the leaf nodes
    depth: depth - 1).map! do |val|
    resolve_link(val, includes)
  end
end