Skip to content

Commit

Permalink
Implement Scopes::NullScope.
Browse files Browse the repository at this point in the history
- Implement Scopes::Null.
- Implement Basic::Scopes::NullScope.
  • Loading branch information
sleepingkingstudios committed Jan 18, 2024
1 parent 077e8c8 commit eabd88c
Show file tree
Hide file tree
Showing 9 changed files with 399 additions and 0 deletions.
2 changes: 2 additions & 0 deletions lib/cuprum/collections/basic/scopes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,7 @@ module Scopes
'cuprum/collections/basic/scopes/disjunction_scope'
autoload :NegationScope,
'cuprum/collections/basic/scopes/negation_scope'
autoload :NullScope,
'cuprum/collections/basic/scopes/null_scope'
end
end
19 changes: 19 additions & 0 deletions lib/cuprum/collections/basic/scopes/null_scope.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

require 'cuprum/collections/basic/scopes'
require 'cuprum/collections/basic/scopes/base'
require 'cuprum/collections/scopes/null'

module Cuprum::Collections::Basic::Scopes
# Scope for returning unfiltered data.
class NullScope < Cuprum::Collections::Basic::Scopes::Base
include Cuprum::Collections::Scopes::Null

# Filters the provided data.
def call(data:)
raise ArgumentError, 'data must be an Array' unless data.is_a?(Array)

data
end
end
end
212 changes: 212 additions & 0 deletions lib/cuprum/collections/rspec/contracts/scope_contracts.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

require 'cuprum/collections/rspec/contracts'
require 'cuprum/collections/rspec/fixtures'
require 'cuprum/collections/scope'

module Cuprum::Collections::RSpec::Contracts
# Contracts for asserting on scope objects.
Expand Down Expand Up @@ -82,5 +83,216 @@ module ShouldBeAContainerScopeContract
end
end
end

# Contract validating the behavior of a Null scope implementation.
module ShouldBeANullScopeContract
extend RSpec::SleepingKingStudios::Contract

# @!method apply(example_group, abstract: false)
# Adds the contract to the example group.
#
# @param example_group [RSpec::Core::ExampleGroup] the example group to
# which the contract is applied.
# @param abstract [Boolean] if true, the scope is abstract and does not
# define a #call implementation. Defaults to false.
contract do |abstract: false|
describe '#and' do
shared_examples 'should return the scope' do
it { expect(outer).to be_a Cuprum::Collections::Scopes::Base }

it { expect(outer.type).to be :criteria }

it { expect(outer.criteria).to be == expected }
end

let(:expected) do
operators = Cuprum::Collections::Queries::Operators

[
[
'title',
operators::EQUAL,
'A Wizard of Earthsea'
]
]
end

it 'should define the method' do
expect(subject)
.to respond_to(:and)
.with(0..1).arguments
.and_a_block
end

it { expect(subject).to have_aliased_method(:and).as(:where) }

describe 'with a block' do
let(:block) { -> { { 'title' => 'A Wizard of Earthsea' } } }
let(:outer) { subject.and(&block) }

include_examples 'should return the scope'
end

describe 'with a hash' do
let(:value) { { 'title' => 'A Wizard of Earthsea' } }
let(:outer) { subject.and(value) }

include_examples 'should return the scope'
end

describe 'with a scope' do
let(:value) do
Cuprum::Collections::Scope
.new({ 'title' => 'A Wizard of Earthsea' })
end
let(:outer) { subject.and(value) }

include_examples 'should return the scope'
end
end

describe '#call' do
shared_context 'with data' do
let(:data) do
Cuprum::Collections::RSpec::Fixtures::BOOKS_FIXTURES
end
end

next if abstract

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

it { expect(filtered_data).to be == expected }
end

wrap_context 'with data' do
let(:expected) { data }

it { expect(filtered_data).to be == expected }
end
end

describe '#or' do
shared_examples 'should return the scope' do
it { expect(outer).to be_a Cuprum::Collections::Scopes::Base }

it { expect(outer.type).to be :criteria }

it { expect(outer.criteria).to be == expected }
end

let(:expected) do
operators = Cuprum::Collections::Queries::Operators

[
[
'title',
operators::EQUAL,
'A Wizard of Earthsea'
]
]
end

it 'should define the method' do
expect(subject)
.to respond_to(:or)
.with(0..1).arguments
.and_a_block
end

describe 'with a block' do
let(:block) { -> { { 'title' => 'A Wizard of Earthsea' } } }
let(:outer) { subject.or(&block) }

include_examples 'should return the scope'
end

describe 'with a hash' do
let(:value) { { 'title' => 'A Wizard of Earthsea' } }
let(:outer) { subject.or(value) }

include_examples 'should return the scope'
end

describe 'with a scope' do
let(:value) do
Cuprum::Collections::Scope
.new({ 'title' => 'A Wizard of Earthsea' })
end
let(:outer) { subject.or(value) }

include_examples 'should return the scope'
end
end

describe '#not' do
shared_examples 'should invert and return the scope' do
it { expect(outer).to be_a Cuprum::Collections::Scopes::Base }

it { expect(outer.type).to be :negation }

it { expect(outer.scopes.size).to be 1 }

it { expect(inner).to be_a Cuprum::Collections::Scopes::Base }

it { expect(inner.type).to be :criteria }

it { expect(inner.criteria).to be == expected }
end

let(:expected) do
operators = Cuprum::Collections::Queries::Operators

[
[
'title',
operators::EQUAL,
'A Wizard of Earthsea'
]
]
end

it 'should define the method' do
expect(subject)
.to respond_to(:not)
.with(0..1).arguments
.and_a_block
end

describe 'with a block' do
let(:block) { -> { { 'title' => 'A Wizard of Earthsea' } } }
let(:outer) { subject.not(&block) }
let(:inner) { outer.scopes.first }

include_examples 'should invert and return the scope'
end

describe 'with a hash' do
let(:value) { { 'title' => 'A Wizard of Earthsea' } }
let(:outer) { subject.not(value) }
let(:inner) { outer.scopes.first }

include_examples 'should invert and return the scope'
end

describe 'with a scope' do
let(:value) do
Cuprum::Collections::Scope
.new({ 'title' => 'A Wizard of Earthsea' })
end
let(:outer) { subject.not(value) }
let(:inner) { outer.scopes.first }

include_examples 'should invert and return the scope'
end
end

describe '#type' do
include_examples 'should define reader', :type, :null
end
end
end
end
end
2 changes: 2 additions & 0 deletions lib/cuprum/collections/scopes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,7 @@ module Scopes
autoload :DisjunctionScope, 'cuprum/collections/scopes/disjunction_scope'
autoload :Negation, 'cuprum/collections/scopes/negation'
autoload :NegationScope, 'cuprum/collections/scopes/negation_scope'
autoload :Null, 'cuprum/collections/scopes/null'
autoload :NullScope, 'cuprum/collections/scopes/null_scope'
end
end
49 changes: 49 additions & 0 deletions lib/cuprum/collections/scopes/null.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# frozen_string_literal: true

require 'cuprum/collections/scopes'

module Cuprum::Collections::Scopes
# Functionality for implementing a null scope.
module Null
# @override and(hash = nil, &block)
# Parses the hash or block and returns the parsed scope.
#
# @see Cuprum::Collections::Scopes::Criteria::Parser#parse.
#
# @override and(scope)
# Returns the given scope.
def and(...)
builder.build(...)
end
alias where and

# @override or(hash = nil, &block)
# Parses the hash or block and returns the parsed scope.
#
# @see Cuprum::Collections::Scopes::Criteria::Parser#parse.
#
# @override or(scope)
# Returns the given scope.
def or(...)
builder.build(...)
end

# @override not(hash = nil, &block)
# Parses and inverts the hash or block and returns the inverted scope.
#
# @see Cuprum::Collections::Scopes::Criteria::Parser#parse.
#
# @override not(scope)
# Inverts and returns the given scope.
def not(...)
scope = builder.build(...)

builder.build_negation_scope(scopes: [scope], safe: false)
end

# (see Cuprum::Collections::Scopes::Base#type)
def type
:null
end
end
end
12 changes: 12 additions & 0 deletions lib/cuprum/collections/scopes/null_scope.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# frozen_string_literal: true

require 'cuprum/collections/scopes'
require 'cuprum/collections/scopes/base'
require 'cuprum/collections/scopes/null'

module Cuprum::Collections::Scopes
# Generic scope class for defining collection-independent null scopes.
class NullScope < Cuprum::Collections::Scopes::Base
include Cuprum::Collections::Scopes::Null
end
end
66 changes: 66 additions & 0 deletions spec/cuprum/collections/basic/scopes/null_scope_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# frozen_string_literal: true

require 'cuprum/collections/rspec/contracts/scope_contracts'
require 'cuprum/collections/basic/scopes/null_scope'

RSpec.describe Cuprum::Collections::Basic::Scopes::NullScope do
include Cuprum::Collections::RSpec::Contracts::ScopeContracts

subject(:scope) { described_class.new }

let(:data) { [] }

def filtered_data
scope.call(data: data)
end

describe '.new' do
it 'should define the constructor' do
expect(described_class)
.to be_constructible
.with(0).arguments
.and_any_keywords
end
end

include_contract 'should be a null scope'

describe '#match' do
let(:item) { {} }

it 'should define the method' do
expect(scope).to respond_to(:match?).with(0).arguments.and_keywords(:item)
end

it 'should alias the method' do
expect(scope).to have_aliased_method(:match?).as(:matches?)
end

describe 'with nil' do
let(:error_message) { 'item must be a Hash' }

it 'should raise an exception' do
expect { scope.match?(item: nil) }
.to raise_error ArgumentError, error_message
end
end

describe 'with an Object' do
let(:error_message) { 'item must be a Hash' }

it 'should raise an exception' do
expect { scope.match?(item: Object.new.freeze) }
.to raise_error ArgumentError, error_message
end
end

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

it { expect(scope.match?(item: item)).to be true }
end
end
end
Loading

0 comments on commit eabd88c

Please sign in to comment.