Skip to content

Commit

Permalink
Update Scopes::Composition to handle null scopes.
Browse files Browse the repository at this point in the history
  • Loading branch information
sleepingkingstudios committed Jan 19, 2024
1 parent 4e34ffd commit e1cef2d
Show file tree
Hide file tree
Showing 11 changed files with 1,545 additions and 807 deletions.
381 changes: 296 additions & 85 deletions lib/cuprum/collections/rspec/contracts/scope_contracts.rb

Large diffs are not rendered by default.

1,413 changes: 800 additions & 613 deletions lib/cuprum/collections/rspec/contracts/scopes/composition_contracts.rb

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions lib/cuprum/collections/scopes/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ def ==(other)
other.type == type
end

# :nocov:

# @private
#
# Generates a string representation of the scope.
def debug
debug_class_name(self)
end
# :nocov:

# @return [Boolean] false.
def empty?
false
Expand All @@ -35,6 +45,16 @@ def type
def builder
Cuprum::Collections::Scopes::Builder.instance
end

# :nocov:
def debug_class_name(scope)
name = scope.class.name.sub(/\ACuprum::Collections::/, '')
segments =
name.split(/(::)?Scopes(::)?/).reject { |s| s.empty? || s == '::' }

segments.join('::')
end
# :nocov:
end
end

Expand Down
40 changes: 38 additions & 2 deletions lib/cuprum/collections/scopes/composition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ module Composition
#
# @override and(scope)
# Combines with the current scope using a logical AND.
#
# Returns self if the given scope is empty.
def and(*args, &block)
return self if empty_scope?(args.first)

return and_conjunction_scope(args.first) if conjunction_scope?(args.first)

scope = builder.build(*args, &block)
Expand All @@ -30,9 +34,15 @@ def and(*args, &block)
#
# @override not(scope)
# Inverts and combines with the current scope using a logical AND.
#
# Returns self if the given scope is empty.
def not(*args, &block)
return self if empty_scope?(args.first)

return not_conjunction_scope(args.first) if conjunction_scope?(args.first)

return not_negation_scope(args.first) if negation_scope?(args.first)

scope = builder.build(*args, &block)
inverted = builder.build_negation_scope(scopes: [scope], safe: false)

Expand All @@ -48,8 +58,14 @@ def not(*args, &block)
#
# @override and(scope)
# Combines with the current scope using a logical OR.
def or(...)
scope = builder.build(...)
#
# Returns self if the given scope is empty.
def or(*args, &block)
return self if empty_scope?(args.first)

return or_disjunction_scope(args.first) if disjunction_scope?(args.first)

scope = builder.build(*args, &block)

# We control the current and generated scopes, so we can skip validation
# and transformation.
Expand Down Expand Up @@ -78,6 +94,10 @@ def disjunction_scope?(value)
scope?(value) && value.type == :disjunction
end

def empty_scope?(value)
scope?(value) && value.empty?
end

def negation_scope?(value)
scope?(value) && value.type == :negation
end
Expand All @@ -91,6 +111,22 @@ def not_conjunction_scope(scope)
builder.build_conjunction_scope(scopes: [self, inverted], safe: false)
end

def not_negation_scope(scope)
scopes = scope.scopes.map do |inner|
builder.transform_scope(scope: inner)
end

builder.build_conjunction_scope(scopes: [self, *scopes], safe: false)
end

def or_disjunction_scope(scope)
scopes = scope.scopes.map do |inner|
builder.transform_scope(scope: inner)
end

builder.build_disjunction_scope(scopes: [self, *scopes], safe: false)
end

def scope?(value)
value.is_a?(Cuprum::Collections::Scopes::Base)
end
Expand Down
14 changes: 14 additions & 0 deletions lib/cuprum/collections/scopes/conjunction.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ module Conjunction

# (see Cuprum::Collections::Scopes::Composition#and)
def and(*args, &block)
return self if empty_scope?(args.first)

return and_conjunction_scope(args.first) if conjunction_scope?(args.first)

with_scopes([*scopes, builder.build(*args, &block)])
Expand All @@ -18,8 +20,12 @@ def and(*args, &block)

# (see Cuprum::Collections::Scopes::Composition#not)
def not(*args, &block)
return self if empty_scope?(args.first)

return not_conjunction_scope(args.first) if conjunction_scope?(args.first)

return not_negation_scope(args.first) if negation_scope?(args.first)

scope = builder.build(*args, &block)
inverted = builder.build_negation_scope(scopes: [scope], safe: false)

Expand Down Expand Up @@ -49,5 +55,13 @@ def not_conjunction_scope(scope)

with_scopes([*self.scopes, inverted])
end

def not_negation_scope(scope)
scopes = scope.scopes.map do |inner|
builder.transform_scope(scope: inner)
end

with_scopes([*self.scopes, *scopes])
end
end
end
11 changes: 11 additions & 0 deletions lib/cuprum/collections/scopes/container.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@ def ==(other)
other.scopes == scopes
end

# @private
def debug
message = "#{super} (#{scopes.count})"

return message if empty?

scopes.reduce("#{message}:") do |str, scope|
str + "\n- #{scope.debug.gsub("\n", "\n ")}"
end
end

# @return [Boolean] true if the scope has no child scopes; otherwise false.
def empty?
@scopes.empty?
Expand Down
13 changes: 13 additions & 0 deletions lib/cuprum/collections/scopes/criteria.rb
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,8 @@ def ==(other)

# (see Cuprum::Collections::Scopes::Composition#and)
def and(*args, &block)
return self if empty_scope?(args.first)

return and_criteria_scope(args.first) if criteria_scope?(args.first)

return super if scope?(args.first)
Expand All @@ -261,6 +263,17 @@ def and(*args, &block)
end
alias where and

# @private
def debug
message = "#{super} (#{criteria.count})"

return message if empty?

criteria.reduce("#{message}:") do |str, (attribute, operator, value)|
str + "\n- #{attribute.inspect} #{operator} #{value.inspect}"
end
end

# @return [Boolean] true if the scope has no criteria; otherwise false.
def empty?
@criteria.empty?
Expand Down
6 changes: 4 additions & 2 deletions lib/cuprum/collections/scopes/disjunction.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ module Disjunction

# (see Cuprum::Collections::Scopes::Composition#or)
def or(*args, &block)
return or_disjuncton_scope(args.first) if disjunction_scope?(args.first)
return self if empty_scope?(args.first)

return or_disjunction_scope(args.first) if disjunction_scope?(args.first)

with_scopes([*scopes, builder.build(*args, &block)])
end
Expand All @@ -22,7 +24,7 @@ def type

private

def or_disjuncton_scope(scope)
def or_disjunction_scope(scope)
scopes = scope.scopes.map do |inner|
builder.transform_scope(scope: inner)
end
Expand Down
4 changes: 4 additions & 0 deletions lib/cuprum/collections/scopes/negation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ module Negation

# (see Cuprum::Collections::Scopes::Composition#and)
def and(*args, &block)
return self if empty_scope?(args.first)

return super unless negation_scope?(args.first)

scopes = args.first.scopes.map do |scope|
Expand All @@ -22,6 +24,8 @@ def and(*args, &block)

# (see Cuprum::Collections::Scopes::Composition#not)
def not(*args, &block)
return self if empty_scope?(args.first)

return super unless negation_scope?(args.first)

scopes = args.first.scopes.map do |scope|
Expand Down
42 changes: 36 additions & 6 deletions lib/cuprum/collections/scopes/null.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ module Null
#
# @override and(scope)
# Returns the given scope.
def and(...)
builder.build(...)
def and(*args, &block)
return self if empty_scope?(args.first)

builder.build(*args, &block)
end
alias where and

Expand All @@ -29,8 +31,10 @@ def empty?
#
# @override or(scope)
# Returns the given scope.
def or(...)
builder.build(...)
def or(*args, &block)
return self if empty_scope?(args.first)

builder.build(*args, &block)
end

# @override not(hash = nil, &block)
Expand All @@ -40,8 +44,14 @@ def or(...)
#
# @override not(scope)
# Inverts and returns the given scope.
def not(...)
scope = builder.build(...)
def not(*args, &block)
return self if empty_scope?(args.first)

return not_conjunction_scope(args.first) if conjunction_scope?(args.first)

return not_negation_scope(args.first) if negation_scope?(args.first)

scope = builder.build(*args, &block)

builder.build_negation_scope(scopes: [scope], safe: false)
end
Expand All @@ -50,5 +60,25 @@ def not(...)
def type
:null
end

private

def not_conjunction_scope(scope)
scopes = scope.scopes.map do |inner|
builder.transform_scope(scope: inner)
end

builder.build_negation_scope(scopes: scopes, safe: false)
end

def not_negation_scope(scope)
scopes = scope.scopes.map do |inner|
builder.transform_scope(scope: inner)
end

return scopes.first if scopes.size == 1

builder.build_conjunction_scope(scopes: scopes, safe: false)
end
end
end
Loading

0 comments on commit e1cef2d

Please sign in to comment.