Skip to content

Commit

Permalink
Merge 63fa579 into 86427c9
Browse files Browse the repository at this point in the history
  • Loading branch information
gburgett committed Jun 19, 2020
2 parents 86427c9 + 63fa579 commit f9c58b3
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 42 deletions.
31 changes: 23 additions & 8 deletions wcc-contentful/lib/wcc/contentful/middleware/store.rb
Expand Up @@ -33,12 +33,12 @@ def call(store, *content_delivery_params, **_)

def find(id, **options)
found = store.find(id, **options)
return transform(found) if found && select?(found)
return transform(found) if found && (!has_select? || select?(found))
end

def find_by(options: nil, **args)
result = store.find_by(**args.merge(options: options))
return unless result && select?(result)
return unless result && (!has_select? || select?(result))

result = resolve_includes(result, options[:include]) if options && options[:include]
transform(result)
Expand All @@ -63,7 +63,7 @@ def resolve_includes(entry, depth)
def resolve_link(val)
return val unless resolved_link?(val)

if select?(val)
if !has_select? || select?(val)
transform(val)
else
# Pretend it's an unresolved link -
Expand All @@ -78,8 +78,12 @@ def resolved_link?(value)

# The default version of `#select?` returns true for all entries.
# Override this with your own implementation.
def select?(_entry)
true
# def select?(_entry)
# true
# end

def has_select? # rubocop:disable Naming/PredicateName
respond_to?(:select?)
end

# The default version of `#transform` just returns the entry.
Expand All @@ -95,12 +99,23 @@ class DelegatingQuery
# by default all enumerable methods delegated to the to_enum method
delegate(*(Enumerable.instance_methods - Module.instance_methods), to: :to_enum)

def count
if middleware.has_select?
raise NameError, "Count cannot be determined because the middleware '#{middleware}'" \
" implements the #select? method. Please use '.to_a.count' to count entries that" \
' pass the #select? method.'
end

# The wrapped query may get count from the "Total" field in the response,
# or apply a "COUNT(*)" to the query.
wrapped_query.count
end

attr_reader :wrapped_query, :middleware, :options

def to_enum
result =
wrapped_query.to_enum
.select { |x| middleware.select?(x) }
result = wrapped_query.to_enum
result = result.select { |x| middleware.select?(x) } if middleware.has_select?

if options && options[:include]
result = result.map { |x| middleware.resolve_includes(x, options[:include]) }
Expand Down
26 changes: 25 additions & 1 deletion wcc-contentful/lib/wcc/contentful/model_singleton_methods.rb
Expand Up @@ -41,7 +41,7 @@ def find_all(filter = nil)
store.find_all(content_type: content_type, options: options.except(:preview))
end
query = query.apply(filter) if filter.present?
query.map { |r| new(r, options) }
ModelQuery.new(query, options, self)
end

# Finds the first instance of this content type matching the given query.
Expand Down Expand Up @@ -73,4 +73,28 @@ def inherited(subclass)

WCC::Contentful::Model.register_for_content_type(content_type, klass: subclass)
end

class ModelQuery
include Enumerable

# by default all enumerable methods delegated to the to_enum method
delegate(*(Enumerable.instance_methods - Module.instance_methods), to: :to_enum)
delegate :each, to: :to_enum

# except count - because that needs to pull data off the final query obj
delegate :count, to: :wrapped_query

attr_reader :wrapped_query, :options, :klass

def initialize(wrapped_query, options, klass)
@wrapped_query = wrapped_query
@options = options
@klass = klass
end

def to_enum
wrapped_query.to_enum
.map { |r| klass.new(r, options) }
end
end
end
5 changes: 4 additions & 1 deletion wcc-contentful/lib/wcc/contentful/store/query.rb
Expand Up @@ -14,11 +14,14 @@ class Query
# by default all enumerable methods delegated to the to_enum method
delegate(*(Enumerable.instance_methods - Module.instance_methods), to: :to_enum)

# except count, which should not iterate the lazy enumerator
delegate :count, to: :result_set

# Executes the query against the store and memoizes the resulting enumerable.
# Subclasses can override this to provide a more efficient implementation.
def to_enum
@to_enum ||=
result_set.map { |row| resolve_includes(row, @options[:include]) }.lazy
result_set.lazy.map { |row| resolve_includes(row, @options[:include]) }
end

attr_reader :store, :content_type, :conditions
Expand Down
98 changes: 69 additions & 29 deletions wcc-contentful/spec/wcc/contentful/middleware/store_spec.rb
Expand Up @@ -36,6 +36,61 @@
end
end

it 'delegates Enumerable methods to lazy enum' do
sys_arr = [sys, sys, sys]
entries = [
{
'sys' => sys_arr[0],
'fields' => {
'a' => 1
}
}, {
'sys' => sys_arr[1],
'fields' => {
'a' => 2
}
}, {
'sys' => sys_arr[2],
'fields' => {
'a' => 3
}
}
]
enum =
Enumerator.new do |y|
y << entries.shift until entries.empty?
end
enum = enum.lazy

allow(next_store).to receive(:find_all)
.and_return(double('lazy query', to_enum: enum))

result = instance.find_all

result = result.take(2).to_a
# critical - did it not iterate the third time?
expect(entries).to eq([{
'sys' => sys_arr[2],
'fields' => {
'a' => 3
}
}])
expect(result.length).to eq(2)
end

it 'delegates Count to query not to enum' do
query = double('lazy query')
expect(query).to receive(:count)
.and_return(512)

allow(next_store).to receive(:find_all)
.and_return(query)

result = instance.find_all

expect(result.count).to eq(512)
end

context 'has select?' do
let(:implementation) {
Class.new do
Expand Down Expand Up @@ -209,35 +264,6 @@ def select?(entry)
])
end

it 'counts only entries that matches select?' do
entries = [
{
'sys' => sys,
'fields' => {
'exclude' => nil
}
}, {
'sys' => sys,
'fields' => {
'exclude' => { 'en-US' => true }
}
}, {
'sys' => sys,
'fields' => {
'exclude' => { 'en-US' => false }
}
}
]
expect(next_store).to receive(:find_all)
.with(content_type: 'test', filter: { 'test' => 'ok' }, options: nil)
.and_return(entries)

# act
found = instance.find_all(content_type: 'test', filter: { 'test' => 'ok' })

expect(found.count).to eq(2)
end

it 'resolves as broken link for linked entry that doesnt match select?' do
entries = [{
'sys' => sys,
Expand Down Expand Up @@ -322,6 +348,20 @@ def select?(entry)
])
expect(found.count).to eq(2)
end

it 'Does not delegate count to the query b/c it has to go through #select' do
query = double('lazy query')
expect(query).to_not receive(:count)

allow(next_store).to receive(:find_all)
.and_return(query)

result = instance.find_all

expect {
result.count
}.to raise_error(NameError)
end
end
end

Expand Down
19 changes: 16 additions & 3 deletions wcc-contentful/spec/wcc/contentful/model_builder_spec.rb
Expand Up @@ -172,15 +172,28 @@
expect(store).to receive(:find_all).and_return(store_resp)
expect(store_resp).to receive(:apply).and_return(store_resp)

expect(store_resp).to_not be_nil
expect(store_resp).to receive(:to_enum).and_return([])

expect(store_resp).to receive(:map).and_return([])
# act
menu_items = WCC::Contentful::Model::MenuButton.find_all(button_style: 'asdf')

# assert
expect(menu_items.to_a).to eq([])
end

it 'delegates #count to the underlying store w/o iterating the enumerable' do
@schema = subject.build_models
store_resp = double(count: 1234)
expect(store).to receive(:find_all).and_return(store_resp)
allow(store_resp).to receive(:apply).and_return(store_resp)

expect(store_resp).to_not receive(:to_enum)

# act
menu_items = WCC::Contentful::Model::MenuButton.find_all(button_style: 'asdf')

# assert
expect(menu_items).to eq([])
expect(menu_items.count).to eq(1234)
end

it 'finds single item with filter' do
Expand Down

0 comments on commit f9c58b3

Please sign in to comment.