Skip to content

Commit

Permalink
Implement Basic::Scopes::CriteriaScope#match?
Browse files Browse the repository at this point in the history
  • Loading branch information
sleepingkingstudios committed Dec 27, 2023
1 parent 007f6f8 commit e2a72f0
Show file tree
Hide file tree
Showing 4 changed files with 351 additions and 9 deletions.
12 changes: 10 additions & 2 deletions lib/cuprum/collections/basic/scopes/criteria_scope.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,18 @@ class CriteriaScope < Cuprum::Collections::Basic::Scope
def call(data:)
raise ArgumentError, 'data must be an Array' unless data.is_a?(Array)

criteria.reduce(data) do |filtered, (attribute, operator, value)|
filtered.select(&filter_for(attribute, operator, value))
data.select { |item| match?(item: item) }
end

# Returns true if the provided item matches the configured criteria.
def match?(item:)
raise ArgumentError, 'item must be a Hash' unless item.is_a?(Hash)

criteria.all? do |(attribute, operator, value)|
filter_for(attribute, operator, value).call(item)
end
end
alias matches? match?

private

Expand Down
309 changes: 303 additions & 6 deletions lib/cuprum/collections/rspec/contracts/scope_contracts.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
module Cuprum::Collections::RSpec::Contracts
# Contracts for asserting on scope objects.
module ScopeContracts
# Contract validating the behavior of a Container scope.
# Contract validating the behavior of a Container scope implementation.
module ShouldBeAContainerScopeContract
extend RSpec::SleepingKingStudios::Contract

Expand Down Expand Up @@ -469,12 +469,10 @@ module ShouldFilterDataByCriteriaContract
end

describe 'with empty data' do
let(:data) { [] }
let(:data) { [] }
let(:expected) { data }

it 'should raise an exception' do
expect { filtered_data }
.to raise_error error_class, error_message
end
it { expect(filtered_data).to be == expected }
end

wrap_context 'with data' do
Expand All @@ -487,6 +485,305 @@ module ShouldFilterDataByCriteriaContract
end
end

# Contract validating the scope matches items based on the criteria.
module ShouldMatchDataByCriteriaContract
extend RSpec::SleepingKingStudios::Contract

# @!method apply(example_group)
# Adds the contract to the example group.
#
# @param example_group [RSpec::Core::ExampleGroup] the example group to
# which the contract is applied.
contract do
context 'when the scope has no criteria' do
let(:criteria) { [] }

describe 'with an item' do
let(:item) do
Cuprum::Collections::RSpec::Fixtures::BOOKS_FIXTURES
.find { |book| book['title'] == 'The Silmarillion' }
end

it { expect(match_item).to be true }
end
end

context 'when the scope has an equality criterion' do
let(:criteria) do
described_class.parse({ 'title' => 'The Word for World is Forest' })
end

describe 'with a non-matching item' do
let(:item) do
Cuprum::Collections::RSpec::Fixtures::BOOKS_FIXTURES
.find { |book| book['title'] == 'The Silmarillion' }
end

it { expect(match_item).to be false }
end

describe 'with a matching item' do
let(:item) do
Cuprum::Collections::RSpec::Fixtures::BOOKS_FIXTURES
.find do |book|
book['title'] == 'The Word for World is Forest'
end
end

it { expect(match_item).to be true }
end
end

context 'when the scope has a greater than criterion' do
let(:criteria) do
described_class.parse do
{ 'published_at' => greater_than('1972-03-13') }
end
end

describe 'with a non-matching item' do
let(:item) do
Cuprum::Collections::RSpec::Fixtures::BOOKS_FIXTURES
.find do |book|
book['title'] == 'The Word for World is Forest'
end
end

it { expect(match_item).to be false }
end

describe 'with a matching item' do
let(:item) do
Cuprum::Collections::RSpec::Fixtures::BOOKS_FIXTURES
.find do |book|
book['title'] == 'The Ones Who Walk Away From Omelas'
end
end

it { expect(match_item).to be true }
end
end

context 'when the scope has a greater than or equal to criterion' do
let(:criteria) do
described_class.parse do
{ 'published_at' => greater_than_or_equal_to('1972-03-13') }
end
end

describe 'with a non-matching item' do
let(:item) do
Cuprum::Collections::RSpec::Fixtures::BOOKS_FIXTURES
.find do |book|
book['title'] == 'A Wizard of Earthsea'
end
end

it { expect(match_item).to be false }
end

describe 'with a matching item' do
let(:item) do
Cuprum::Collections::RSpec::Fixtures::BOOKS_FIXTURES
.find do |book|
book['title'] == 'The Word for World is Forest'
end
end

it { expect(match_item).to be true }
end
end

context 'when the scope has a less than criterion' do
let(:criteria) do
described_class.parse do
{ 'published_at' => less_than('1972-03-13') }
end
end

describe 'with a non-matching item' do
let(:item) do
Cuprum::Collections::RSpec::Fixtures::BOOKS_FIXTURES
.find do |book|
book['title'] == 'The Word for World is Forest'
end
end

it { expect(match_item).to be false }
end

describe 'with a matching item' do
let(:item) do
Cuprum::Collections::RSpec::Fixtures::BOOKS_FIXTURES
.find do |book|
book['title'] == 'A Wizard of Earthsea'
end
end

it { expect(match_item).to be true }
end
end

context 'when the scope has a less than or equal to criterion' do
let(:criteria) do
described_class.parse do
{ 'published_at' => less_than_or_equal_to('1972-03-13') }
end
end

describe 'with a non-matching item' do
let(:item) do
Cuprum::Collections::RSpec::Fixtures::BOOKS_FIXTURES
.find do |book|
book['title'] == 'The Ones Who Walk Away From Omelas'
end
end

it { expect(match_item).to be false }
end

describe 'with a matching item' do
let(:item) do
Cuprum::Collections::RSpec::Fixtures::BOOKS_FIXTURES
.find do |book|
book['title'] == 'The Word for World is Forest'
end
end

it { expect(match_item).to be true }
end
end

context 'when the scope has a not equal criterion' do
let(:criteria) do
described_class.parse do
{ 'author' => not_equal('J.R.R. Tolkien') }
end
end

describe 'with a non-matching item' do
let(:item) do
Cuprum::Collections::RSpec::Fixtures::BOOKS_FIXTURES
.find { |book| book['title'] == 'The Silmarillion' }
end

it { expect(match_item).to be false }
end

describe 'with a matching item' do
let(:item) do
Cuprum::Collections::RSpec::Fixtures::BOOKS_FIXTURES
.find { |book| book['title'] == 'A Wizard of Earthsea' }
end

it { expect(match_item).to be true }
end
end

context 'when the scope has a not one of criterion' do
let(:criteria) do
described_class.parse do
titles = ['The Fellowship Of The Ring', 'The Two Towers']

{ 'title' => not_one_of(titles) }
end
end

describe 'with a non-matching item' do
let(:item) do
Cuprum::Collections::RSpec::Fixtures::BOOKS_FIXTURES
.find { |book| book['title'] == 'The Two Towers' }
end

it { expect(match_item).to be false }
end

describe 'with a matching item' do
let(:item) do
Cuprum::Collections::RSpec::Fixtures::BOOKS_FIXTURES
.find { |book| book['title'] == 'The Return of the King' }
end

it { expect(match_item).to be true }
end
end

context 'when the scope has a one of criterion' do
let(:criteria) do
described_class.parse do
titles = ['The Fellowship Of The Ring', 'The Two Towers']

{ 'title' => one_of(titles) }
end
end

describe 'with a non-matching item' do
let(:item) do
Cuprum::Collections::RSpec::Fixtures::BOOKS_FIXTURES
.find { |book| book['title'] == 'The Return of the King' }
end

it { expect(match_item).to be false }
end

describe 'with a matching item' do
let(:item) do
Cuprum::Collections::RSpec::Fixtures::BOOKS_FIXTURES
.find { |book| book['title'] == 'The Two Towers' }
end

it { expect(match_item).to be true }
end
end

context 'when the scope has multiple criteria' do
let(:criteria) do
described_class.parse do
titles = ['The Fellowship Of The Ring', 'The Two Towers']

{ 'author' => 'J.R.R. Tolkien', 'title' => not_one_of(titles) }
end
end

describe 'with a non-matching item' do
let(:item) do
Cuprum::Collections::RSpec::Fixtures::BOOKS_FIXTURES
.find { |book| book['title'] == 'The Two Towers' }
end

it { expect(match_item).to be false }
end

describe 'with a matching item' do
let(:item) do
Cuprum::Collections::RSpec::Fixtures::BOOKS_FIXTURES
.find { |book| book['title'] == 'The Return of the King' }
end

it { expect(match_item).to be true }
end
end

context 'when the scope has invalid criteria' do
let(:criteria) { [['title', :random, nil]] }
let(:item) do
Cuprum::Collections::RSpec::Fixtures::BOOKS_FIXTURES
.find { |book| book['title'] == 'The Two Towers' }
end
let(:error_class) do
Cuprum::Collections::Scopes::Criteria::UnknownOperatorException
end
let(:error_message) do
'unknown operator "random"'
end

it 'should raise an exception' do
expect { match_item }.to raise_error error_class, error_message
end
end
end
end

# Contract validating the parsing of criteria.
module ShouldParseCriteriaContract
extend RSpec::SleepingKingStudios::Contract
Expand Down
3 changes: 2 additions & 1 deletion lib/cuprum/collections/scopes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
module Cuprum::Collections
# Namespace for scope functionality, which filters query data.
module Scopes
autoload :Criteria, 'cuprum/collections/scopes/criteria'
autoload :Collection, 'cuprum/collections/scopes/collection'
autoload :Criteria, 'cuprum/collections/scopes/criteria'
end
end
Loading

0 comments on commit e2a72f0

Please sign in to comment.