Class: WCC::Contentful::Store::Query

Inherits:
Object
  • Object
show all
Includes:
Enumerable, Interface
Defined in:
lib/wcc/contentful/store/query.rb,
lib/wcc/contentful/store/query/condition.rb,
lib/wcc/contentful/store/query/interface.rb

Overview

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.

Direct Known Subclasses

PostgresStore::Query

Defined Under Namespace

Modules: Interface Classes: Condition

Constant Summary collapse

FALSE_VALUES =
[
  false, 0,
  '0', :'0',
  'f', :f,
  'F', :F,
  'false', :false, # rubocop:disable Lint/BooleanSymbol
  'FALSE', :FALSE,
  'off', :off,
  'OFF', :OFF
].to_set.freeze
RESERVED_NAMES =
%w[fields sys].freeze

Constants included from Interface

Interface::OPERATORS

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Interface

#eq

Constructor Details

#initialize(store, content_type:, conditions: nil, options: nil, configuration: nil, **extra) ⇒ Query

rubocop:disable Metrics/ParameterLists



30
31
32
33
34
35
36
37
# File 'lib/wcc/contentful/store/query.rb', line 30

def initialize(store, content_type:, conditions: nil, options: nil, configuration: nil, **extra) # rubocop:disable Metrics/ParameterLists
  @store = store
  @content_type = content_type
  @conditions = conditions || []
  @options = options || {}
  @configuration = configuration || WCC::Contentful.configuration
  @extra = extra
end

Instance Attribute Details

#conditionsObject (readonly)



28
29
30
# File 'lib/wcc/contentful/store/query.rb', line 28

def conditions
  @conditions
end

#content_typeObject (readonly)



28
29
30
# File 'lib/wcc/contentful/store/query.rb', line 28

def content_type
  @content_type
end

#storeObject (readonly)



28
29
30
# File 'lib/wcc/contentful/store/query.rb', line 28

def store
  @store
end

Class Method Details

.flatten_filter_hash(hash, path = []) ⇒ Object

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



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/wcc/contentful/store/query.rb', line 163

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: nil, expected: v }
    end
  end
end

.known_localesObject



181
182
183
# File 'lib/wcc/contentful/store/query.rb', line 181

def known_locales
  @known_locales ||= WCC::Contentful.locales&.keys || ['en-US']
end

.normalize_condition_path(path, options = nil) ⇒ Object

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



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/wcc/contentful/store/query.rb', line 189

def normalize_condition_path(path, options = nil)
  context_locale = options[:locale]&.to_s if options.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) ⇒ Boolean

Returns:

  • (Boolean)


157
158
159
# File 'lib/wcc/contentful/store/query.rb', line 157

def op?(key)
  Interface::OPERATORS.include?(key.to_sym)
end

Instance Method Details

#apply(filter, context = nil) ⇒ Object

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 Also:



98
99
100
101
102
# File 'lib/wcc/contentful/store/query.rb', line 98

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) ⇒ Object

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.

Examples:

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'

Raises:

  • (ArgumentError)


65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/wcc/contentful/store/query.rb', line 65

def apply_operator(operator, field, expected, _context = nil)
  operator ||= expected.is_a?(Array) ? :in : :eq
  raise ArgumentError, "Operator #{operator} not supported" unless respond_to?(operator)
  raise ArgumentError, 'value cannot be nil (try using exists: false)' if expected.nil?

  case operator
  when :in, :nin, :all
    expected = Array(expected)
  when :exists
    expected = !FALSE_VALUES.include?(expected)
  end

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

  path = self.class.normalize_condition_path(path, @options)

  _append_condition(
    Condition.new(path, operator, expected, @configuration&.locale_fallbacks || {})
  )
end

#result_setObject

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



106
107
108
# File 'lib/wcc/contentful/store/query.rb', line 106

def result_set
  @result_set ||= store.execute(self)
end

#to_enumObject

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

Subclasses can override this to provide a more efficient implementation.


23
24
25
26
# File 'lib/wcc/contentful/store/query.rb', line 23

def to_enum
  @to_enum ||=
    result_set.lazy.map { |row| resolve_includes(row, @options[:include]) }
end