cuprum-collections 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +73 -0
- data/README.md +5 -5
- data/lib/cuprum/collections/association.rb +9 -28
- data/lib/cuprum/collections/associations/belongs_to.rb +1 -8
- data/lib/cuprum/collections/associations/has_many.rb +1 -10
- data/lib/cuprum/collections/associations/has_one.rb +1 -10
- data/lib/cuprum/collections/basic/collection.rb +56 -49
- data/lib/cuprum/collections/basic/command.rb +22 -88
- data/lib/cuprum/collections/basic/commands/assign_one.rb +2 -6
- data/lib/cuprum/collections/basic/commands/build_one.rb +1 -4
- data/lib/cuprum/collections/basic/commands/destroy_one.rb +4 -8
- data/lib/cuprum/collections/basic/commands/find_many.rb +4 -24
- data/lib/cuprum/collections/basic/commands/find_matching.rb +5 -21
- data/lib/cuprum/collections/basic/commands/find_one.rb +3 -20
- data/lib/cuprum/collections/basic/commands/insert_one.rb +3 -6
- data/lib/cuprum/collections/basic/commands/update_one.rb +3 -6
- data/lib/cuprum/collections/basic/commands/validate_one.rb +13 -18
- data/lib/cuprum/collections/basic/query.rb +26 -40
- data/lib/cuprum/collections/basic/repository.rb +4 -3
- data/lib/cuprum/collections/basic/scopes/all_scope.rb +25 -0
- data/lib/cuprum/collections/basic/scopes/base.rb +32 -0
- data/lib/cuprum/collections/basic/scopes/builder.rb +39 -0
- data/lib/cuprum/collections/basic/scopes/conjunction_scope.rb +20 -0
- data/lib/cuprum/collections/basic/scopes/criteria_scope.rb +62 -0
- data/lib/cuprum/collections/basic/scopes/disjunction_scope.rb +20 -0
- data/lib/cuprum/collections/basic/scopes/none_scope.rb +33 -0
- data/lib/cuprum/collections/basic/scopes.rb +23 -0
- data/lib/cuprum/collections/basic.rb +1 -0
- data/lib/cuprum/collections/collection.rb +24 -82
- data/lib/cuprum/collections/collection_command.rb +116 -0
- data/lib/cuprum/collections/commands/abstract_find_many.rb +11 -21
- data/lib/cuprum/collections/commands/abstract_find_matching.rb +43 -24
- data/lib/cuprum/collections/commands/abstract_find_one.rb +7 -10
- data/lib/cuprum/collections/commands/associations/find_many.rb +3 -8
- data/lib/cuprum/collections/commands/associations/require_many.rb +5 -5
- data/lib/cuprum/collections/commands/create.rb +3 -3
- data/lib/cuprum/collections/commands/find_one_matching.rb +6 -6
- data/lib/cuprum/collections/commands/query_command.rb +19 -0
- data/lib/cuprum/collections/commands/update.rb +3 -3
- data/lib/cuprum/collections/commands/upsert.rb +10 -10
- data/lib/cuprum/collections/commands.rb +1 -0
- data/lib/cuprum/collections/constraints/ordering.rb +2 -2
- data/lib/cuprum/collections/errors/abstract_find_error.rb +25 -42
- data/lib/cuprum/collections/errors/extra_attributes.rb +3 -3
- data/lib/cuprum/collections/errors/failed_validation.rb +2 -2
- data/lib/cuprum/collections/errors/invalid_parameters.rb +2 -2
- data/lib/cuprum/collections/errors/invalid_query.rb +10 -16
- data/lib/cuprum/collections/errors/missing_default_contract.rb +1 -1
- data/lib/cuprum/collections/errors/unknown_operator.rb +1 -1
- data/lib/cuprum/collections/queries.rb +31 -0
- data/lib/cuprum/collections/query.rb +50 -62
- data/lib/cuprum/collections/relation.rb +5 -383
- data/lib/cuprum/collections/relations/cardinality.rb +66 -0
- data/lib/cuprum/collections/relations/options.rb +18 -0
- data/lib/cuprum/collections/relations/parameters.rb +217 -0
- data/lib/cuprum/collections/relations/primary_keys.rb +23 -0
- data/lib/cuprum/collections/relations/scope.rb +65 -0
- data/lib/cuprum/collections/relations.rb +14 -0
- data/lib/cuprum/collections/repository.rb +5 -5
- data/lib/cuprum/collections/resource.rb +10 -41
- data/lib/cuprum/collections/rspec/contracts/association_contracts.rb +80 -90
- data/lib/cuprum/collections/rspec/contracts/collection_contracts.rb +69 -111
- data/lib/cuprum/collections/rspec/contracts/command_contracts.rb +42 -1335
- data/lib/cuprum/collections/rspec/contracts/query_contracts.rb +352 -531
- data/lib/cuprum/collections/rspec/contracts/relation_contracts.rb +74 -191
- data/lib/cuprum/collections/rspec/contracts/repository_contracts.rb +13 -13
- data/lib/cuprum/collections/rspec/contracts/scope_contracts.rb +1029 -0
- data/lib/cuprum/collections/rspec/contracts/scopes/builder_contracts.rb +856 -0
- data/lib/cuprum/collections/rspec/contracts/scopes/composition_contracts.rb +1430 -0
- data/lib/cuprum/collections/rspec/contracts/scopes/criteria_contracts.rb +2217 -0
- data/lib/cuprum/collections/rspec/contracts/scopes/logical_contracts.rb +297 -0
- data/lib/cuprum/collections/rspec/contracts/scopes.rb +13 -0
- data/lib/cuprum/collections/rspec/contracts.rb +2 -0
- data/lib/cuprum/collections/rspec/deferred/association_examples.rb +2098 -0
- data/lib/cuprum/collections/rspec/deferred/collection_examples.rb +338 -0
- data/lib/cuprum/collections/rspec/deferred/command_examples.rb +160 -0
- data/lib/cuprum/collections/rspec/deferred/commands/assign_one_examples.rb +178 -0
- data/lib/cuprum/collections/rspec/deferred/commands/build_one_examples.rb +94 -0
- data/lib/cuprum/collections/rspec/deferred/commands/destroy_one_examples.rb +118 -0
- data/lib/cuprum/collections/rspec/deferred/commands/find_many_examples.rb +307 -0
- data/lib/cuprum/collections/rspec/deferred/commands/find_matching_examples.rb +143 -0
- data/lib/cuprum/collections/rspec/deferred/commands/find_one_examples.rb +116 -0
- data/lib/cuprum/collections/rspec/deferred/commands/insert_one_examples.rb +103 -0
- data/lib/cuprum/collections/rspec/deferred/commands/update_one_examples.rb +99 -0
- data/lib/cuprum/collections/rspec/deferred/commands/validate_one_examples.rb +117 -0
- data/lib/cuprum/collections/rspec/deferred/commands.rb +8 -0
- data/lib/cuprum/collections/rspec/deferred/relation_examples.rb +1437 -0
- data/lib/cuprum/collections/rspec/deferred/resource_examples.rb +26 -0
- data/lib/cuprum/collections/rspec/deferred.rb +8 -0
- data/lib/cuprum/collections/scope.rb +29 -0
- data/lib/cuprum/collections/scopes/all.rb +51 -0
- data/lib/cuprum/collections/scopes/all_scope.rb +18 -0
- data/lib/cuprum/collections/scopes/base.rb +79 -0
- data/lib/cuprum/collections/scopes/builder.rb +39 -0
- data/lib/cuprum/collections/scopes/building.rb +221 -0
- data/lib/cuprum/collections/scopes/composition.rb +162 -0
- data/lib/cuprum/collections/scopes/conjunction.rb +44 -0
- data/lib/cuprum/collections/scopes/conjunction_scope.rb +12 -0
- data/lib/cuprum/collections/scopes/container.rb +65 -0
- data/lib/cuprum/collections/scopes/criteria/parser.rb +241 -0
- data/lib/cuprum/collections/scopes/criteria.rb +206 -0
- data/lib/cuprum/collections/scopes/criteria_scope.rb +12 -0
- data/lib/cuprum/collections/scopes/disjunction.rb +45 -0
- data/lib/cuprum/collections/scopes/disjunction_scope.rb +12 -0
- data/lib/cuprum/collections/scopes/none.rb +62 -0
- data/lib/cuprum/collections/scopes/none_scope.rb +18 -0
- data/lib/cuprum/collections/scopes.rb +23 -0
- data/lib/cuprum/collections/version.rb +2 -2
- data/lib/cuprum/collections.rb +14 -9
- metadata +61 -15
- data/lib/cuprum/collections/basic/query_builder.rb +0 -69
- data/lib/cuprum/collections/command.rb +0 -26
- data/lib/cuprum/collections/queries/parse.rb +0 -22
- data/lib/cuprum/collections/queries/parse_block.rb +0 -206
- data/lib/cuprum/collections/queries/parse_strategy.rb +0 -91
- data/lib/cuprum/collections/query_builder.rb +0 -61
- data/lib/cuprum/collections/rspec/contracts/basic/command_contracts.rb +0 -484
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rspec/sleeping_king_studios/deferred'
|
4
|
+
|
5
|
+
require 'cuprum/collections/rspec/deferred'
|
6
|
+
require 'cuprum/collections/rspec/deferred/relation_examples'
|
7
|
+
|
8
|
+
module Cuprum::Collections::RSpec::Deferred
|
9
|
+
# Deferred examples for testing resources.
|
10
|
+
module ResourceExamples
|
11
|
+
include RSpec::SleepingKingStudios::Deferred::Provider
|
12
|
+
|
13
|
+
deferred_examples 'should be a Resource' do
|
14
|
+
include Cuprum::Collections::RSpec::Deferred::RelationExamples
|
15
|
+
|
16
|
+
include_deferred 'should be a Relation',
|
17
|
+
cardinality: true
|
18
|
+
|
19
|
+
include_deferred 'should define Relation cardinality'
|
20
|
+
|
21
|
+
include_deferred 'should define Relation primary key'
|
22
|
+
|
23
|
+
include_deferred 'should define Relation scope'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections'
|
4
|
+
require 'cuprum/collections/scopes/criteria_scope'
|
5
|
+
|
6
|
+
module Cuprum::Collections
|
7
|
+
# Generic scope class for defining collection-independent criteria scopes.
|
8
|
+
class Scope < Cuprum::Collections::Scopes::CriteriaScope
|
9
|
+
# @overload build(value = nil, &block)
|
10
|
+
# (see Cuprum::Collections::Scopes::Criteria::ClassMethods.build)
|
11
|
+
def self.build(...)
|
12
|
+
new(...)
|
13
|
+
end
|
14
|
+
|
15
|
+
# @overload initialize(value = nil, &block)
|
16
|
+
# @param value [Hash, nil] the keys and values to parse.
|
17
|
+
#
|
18
|
+
# @return [Array] the generated criteria.
|
19
|
+
#
|
20
|
+
# @yield the query block.
|
21
|
+
#
|
22
|
+
# @yieldreturn [Hash] a Hash with String keys.
|
23
|
+
def initialize(*args, inverted: false, &block)
|
24
|
+
criteria = self.class.parse(*args, &block)
|
25
|
+
|
26
|
+
super(criteria:, inverted:)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections/scopes'
|
4
|
+
|
5
|
+
module Cuprum::Collections::Scopes
|
6
|
+
# Functionality for implementing an all scope, which returns all data.
|
7
|
+
module All
|
8
|
+
# @overload and(hash = nil, &block)
|
9
|
+
# Parses the hash or block and returns the parsed scope.
|
10
|
+
#
|
11
|
+
# @see Cuprum::Collections::Scopes::Criteria::Parser#parse.
|
12
|
+
#
|
13
|
+
# @overload and(scope)
|
14
|
+
# Returns the given scope.
|
15
|
+
def and(*args, &)
|
16
|
+
return self if scope?(args.first) && args.first.empty?
|
17
|
+
|
18
|
+
builder.build(*args, &)
|
19
|
+
end
|
20
|
+
alias where and
|
21
|
+
|
22
|
+
# @return [Boolean] false.
|
23
|
+
def empty?
|
24
|
+
false
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [Cuprum::Collections::Scopes::None] a none scope for the current
|
28
|
+
# collection.
|
29
|
+
def invert
|
30
|
+
builder.build_none_scope
|
31
|
+
end
|
32
|
+
|
33
|
+
# @overload or(hash = nil, &block)
|
34
|
+
# Parses the hash or block and returns the parsed scope.
|
35
|
+
#
|
36
|
+
# @see Cuprum::Collections::Scopes::Criteria::Parser#parse.
|
37
|
+
#
|
38
|
+
# @overload or(scope)
|
39
|
+
# Returns the given scope.
|
40
|
+
def or(*args, &)
|
41
|
+
return self if scope?(args.first) && args.first.empty?
|
42
|
+
|
43
|
+
builder.build(*args, &)
|
44
|
+
end
|
45
|
+
|
46
|
+
# (see Cuprum::Collections::Scopes::Base#type)
|
47
|
+
def type
|
48
|
+
:all
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections/scopes'
|
4
|
+
require 'cuprum/collections/scopes/all'
|
5
|
+
require 'cuprum/collections/scopes/base'
|
6
|
+
|
7
|
+
module Cuprum::Collections::Scopes
|
8
|
+
# Generic scope class for defining collection-independent all scopes.
|
9
|
+
class AllScope < Cuprum::Collections::Scopes::Base
|
10
|
+
include Cuprum::Collections::Scopes::All
|
11
|
+
|
12
|
+
# @return [Cuprum::Collections::Scopes::AllScope] a cached instance of the
|
13
|
+
# all scope.
|
14
|
+
def self.instance
|
15
|
+
@instance ||= new
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections/scopes'
|
4
|
+
require 'cuprum/collections/scopes/composition'
|
5
|
+
|
6
|
+
module Cuprum::Collections::Scopes
|
7
|
+
# Abstract class representing a set of filters for a query.
|
8
|
+
class Base
|
9
|
+
include Cuprum::Collections::Scopes::Composition
|
10
|
+
|
11
|
+
# Exception raised when inverting an uninvertible scope.
|
12
|
+
class UninvertibleScopeException < StandardError; end
|
13
|
+
|
14
|
+
def initialize(**); end
|
15
|
+
|
16
|
+
# @param other [Object] the object to compare.
|
17
|
+
#
|
18
|
+
# @return [Boolean] true if the other object is a scope with matching type;
|
19
|
+
# otherwise false.
|
20
|
+
def ==(other)
|
21
|
+
return false unless other.is_a?(Cuprum::Collections::Scopes::Base)
|
22
|
+
|
23
|
+
other.type == type
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [Hash{String=>Object}] a JSON-compatible representation of the
|
27
|
+
# scope.
|
28
|
+
def as_json
|
29
|
+
{ 'type' => type }
|
30
|
+
end
|
31
|
+
|
32
|
+
# :nocov:
|
33
|
+
|
34
|
+
# @private
|
35
|
+
#
|
36
|
+
# Generates a string representation of the scope.
|
37
|
+
def debug
|
38
|
+
debug_class_name(self)
|
39
|
+
end
|
40
|
+
# :nocov:
|
41
|
+
|
42
|
+
# @return [Boolean] false.
|
43
|
+
def empty?
|
44
|
+
false
|
45
|
+
end
|
46
|
+
|
47
|
+
# Generates and returns an inverted copy of the scope.
|
48
|
+
#
|
49
|
+
# @raise [UninvertibleScopeException] if the scope does not implement
|
50
|
+
# #invert.
|
51
|
+
def invert
|
52
|
+
raise UninvertibleScopeException,
|
53
|
+
"Scope class #{self.class.name} does not implement #invert"
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return [Symbol] the scope type.
|
57
|
+
def type
|
58
|
+
:abstract
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def builder
|
64
|
+
Cuprum::Collections::Scopes::Builder.instance
|
65
|
+
end
|
66
|
+
|
67
|
+
# :nocov:
|
68
|
+
def debug_class_name(scope)
|
69
|
+
name = scope.class.name.sub(/\ACuprum::Collections::/, '')
|
70
|
+
segments =
|
71
|
+
name.split(/(::)?Scopes(::)?/).reject { |s| s.empty? || s == '::' }
|
72
|
+
|
73
|
+
segments.join('::')
|
74
|
+
end
|
75
|
+
# :nocov:
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
require 'cuprum/collections/scopes/builder'
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections/scopes'
|
4
|
+
require 'cuprum/collections/scopes/building'
|
5
|
+
|
6
|
+
module Cuprum::Collections::Scopes
|
7
|
+
# Builder for generating generic scopes.
|
8
|
+
class Builder
|
9
|
+
include Cuprum::Collections::Scopes::Building
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def all_scope_class
|
14
|
+
Cuprum::Collections::Scopes::AllScope
|
15
|
+
end
|
16
|
+
|
17
|
+
def conjunction_scope_class
|
18
|
+
Cuprum::Collections::Scopes::ConjunctionScope
|
19
|
+
end
|
20
|
+
|
21
|
+
def criteria_scope_class
|
22
|
+
Cuprum::Collections::Scopes::CriteriaScope
|
23
|
+
end
|
24
|
+
|
25
|
+
def disjunction_scope_class
|
26
|
+
Cuprum::Collections::Scopes::DisjunctionScope
|
27
|
+
end
|
28
|
+
|
29
|
+
def none_scope_class
|
30
|
+
Cuprum::Collections::Scopes::NoneScope
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
require 'cuprum/collections/scopes/all_scope'
|
36
|
+
require 'cuprum/collections/scopes/conjunction_scope'
|
37
|
+
require 'cuprum/collections/scopes/criteria_scope'
|
38
|
+
require 'cuprum/collections/scopes/disjunction_scope'
|
39
|
+
require 'cuprum/collections/scopes/none_scope'
|
@@ -0,0 +1,221 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections/scopes'
|
4
|
+
require 'cuprum/collections/scopes/criteria'
|
5
|
+
|
6
|
+
module Cuprum::Collections::Scopes
|
7
|
+
# Abstraction for generating scopes for a given collection.
|
8
|
+
module Building # rubocop:disable Metrics/ModuleLength
|
9
|
+
# Error raised when trying to call an abstract builder method.
|
10
|
+
class AbstractBuilderError < StandardError; end
|
11
|
+
|
12
|
+
# Error raised when trying to transform an unknown scope type.
|
13
|
+
class UnknownScopeTypeError < StandardError; end
|
14
|
+
|
15
|
+
# Class methods to extend when including the module.
|
16
|
+
module ClassMethods
|
17
|
+
# @return [Cuprum::Collections::Scopes::Builder] a singleton instance of
|
18
|
+
# the builder class.
|
19
|
+
def instance
|
20
|
+
@instance ||= new
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class << self
|
25
|
+
private
|
26
|
+
|
27
|
+
def included(other)
|
28
|
+
super
|
29
|
+
|
30
|
+
other.extend(ClassMethods)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# @overload build(hash = nil, &block)
|
35
|
+
# Parses the hash or block and returns a criteria scope.
|
36
|
+
#
|
37
|
+
# @see Cuprum::Collections::Scopes::Criteria::Parser#parse.
|
38
|
+
#
|
39
|
+
# @overload build(scope)
|
40
|
+
# Returns a new scope with the same scope type and properties.
|
41
|
+
def build(*args, &)
|
42
|
+
if args.first.is_a?(Cuprum::Collections::Scopes::Base)
|
43
|
+
return transform_scope(scope: args.first)
|
44
|
+
end
|
45
|
+
|
46
|
+
criteria_scope_class.build(*args, &)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Creates a new all scope.
|
50
|
+
def build_all_scope
|
51
|
+
all_scope_class.new
|
52
|
+
end
|
53
|
+
|
54
|
+
# Creates a new logical AND scope wrapping the given scopes.
|
55
|
+
#
|
56
|
+
# @param scopes [Array<Cuprum::Collections::Scopes::Base>] the scopes to
|
57
|
+
# wrap in an AND scope.
|
58
|
+
# @param safe [Boolean] if true, validates and converts the scopes to match
|
59
|
+
# the builder's scope classes. Defaults to true.
|
60
|
+
def build_conjunction_scope(scopes:, safe: true)
|
61
|
+
if safe
|
62
|
+
validate_scopes!(scopes)
|
63
|
+
|
64
|
+
scopes = transform_scopes(scopes)
|
65
|
+
end
|
66
|
+
|
67
|
+
conjunction_scope_class.new(scopes:)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Creates a new scope wrapping the given criteria.
|
71
|
+
#
|
72
|
+
# @param criteria [Array] the criteria for the scope.
|
73
|
+
# @param inverted [Boolean] true if the criteria scope is inverted.
|
74
|
+
# @param safe [Boolean] if true, validates the criteria. Defaults to true.
|
75
|
+
def build_criteria_scope(criteria:, inverted: false, safe: true)
|
76
|
+
validate_criteria!(criteria) if safe
|
77
|
+
|
78
|
+
criteria_scope_class.new(criteria:, inverted:)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Creates a new logical OR scope wrapping the given scopes.
|
82
|
+
#
|
83
|
+
# @param scopes [Array<Cuprum::Collections::Scopes::Base>] the scopes to
|
84
|
+
# wrap in an AND scope.
|
85
|
+
# @param safe [Boolean] if true, validates and converts the scopes to match
|
86
|
+
# the builder's scope classes. Defaults to true.
|
87
|
+
def build_disjunction_scope(scopes:, safe: true)
|
88
|
+
if safe
|
89
|
+
validate_scopes!(scopes)
|
90
|
+
|
91
|
+
scopes = transform_scopes(scopes)
|
92
|
+
end
|
93
|
+
|
94
|
+
disjunction_scope_class.new(scopes:)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Creates a new none scope.
|
98
|
+
def build_none_scope
|
99
|
+
none_scope_class.new
|
100
|
+
end
|
101
|
+
|
102
|
+
# Creates a new scope with the same scope type and properties.
|
103
|
+
def transform_scope(scope:)
|
104
|
+
validate_scope!(scope)
|
105
|
+
|
106
|
+
build_transformed_scope(scope)
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def all_scope_class
|
112
|
+
raise AbstractBuilderError,
|
113
|
+
"#{self.class.name} is an abstract class. Define a builder " \
|
114
|
+
'class and implement the #all_scope_class method.',
|
115
|
+
caller(1..-1)
|
116
|
+
end
|
117
|
+
|
118
|
+
def build_transformed_scope(original) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
119
|
+
case original.type
|
120
|
+
when :all
|
121
|
+
return original if original.is_a?(all_scope_class)
|
122
|
+
|
123
|
+
build_all_scope
|
124
|
+
when :conjunction
|
125
|
+
return original if original.is_a?(conjunction_scope_class)
|
126
|
+
|
127
|
+
build_conjunction_scope(
|
128
|
+
safe: false,
|
129
|
+
scopes: transform_scopes(original.scopes)
|
130
|
+
)
|
131
|
+
when :criteria
|
132
|
+
return original if original.is_a?(criteria_scope_class)
|
133
|
+
|
134
|
+
build_criteria_scope(
|
135
|
+
criteria: original.criteria,
|
136
|
+
inverted: original.inverted?,
|
137
|
+
safe: false
|
138
|
+
)
|
139
|
+
when :disjunction
|
140
|
+
return original if original.is_a?(disjunction_scope_class)
|
141
|
+
|
142
|
+
build_disjunction_scope(
|
143
|
+
safe: false,
|
144
|
+
scopes: transform_scopes(original.scopes)
|
145
|
+
)
|
146
|
+
when :none
|
147
|
+
return original if original.is_a?(none_scope_class)
|
148
|
+
|
149
|
+
build_none_scope
|
150
|
+
else
|
151
|
+
error_message =
|
152
|
+
"#{self.class.name} cannot transform scopes of type " \
|
153
|
+
"#{original.type.inspect} (#{original.class.name})"
|
154
|
+
|
155
|
+
raise UnknownScopeTypeError, error_message
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def conjunction_scope_class
|
160
|
+
raise AbstractBuilderError,
|
161
|
+
"#{self.class.name} is an abstract class. Define a builder " \
|
162
|
+
'class and implement the #conjunction_scope_class method.',
|
163
|
+
caller(1..-1)
|
164
|
+
end
|
165
|
+
|
166
|
+
def criteria_scope_class
|
167
|
+
raise AbstractBuilderError,
|
168
|
+
"#{self.class.name} is an abstract class. Define a builder " \
|
169
|
+
'class and implement the #criteria_scope_class method.',
|
170
|
+
caller(1..-1)
|
171
|
+
end
|
172
|
+
|
173
|
+
def disjunction_scope_class
|
174
|
+
raise AbstractBuilderError,
|
175
|
+
"#{self.class.name} is an abstract class. Define a builder " \
|
176
|
+
'class and implement the #disjunction_scope_class method.',
|
177
|
+
caller(1..-1)
|
178
|
+
end
|
179
|
+
|
180
|
+
def none_scope_class
|
181
|
+
raise AbstractBuilderError,
|
182
|
+
"#{self.class.name} is an abstract class. Define a builder " \
|
183
|
+
'class and implement the #none_scope_class method.',
|
184
|
+
caller(1..-1)
|
185
|
+
end
|
186
|
+
|
187
|
+
def transform_scopes(scopes)
|
188
|
+
scopes.map { |scope| build_transformed_scope(scope) }
|
189
|
+
end
|
190
|
+
|
191
|
+
def validate_criteria!(criteria)
|
192
|
+
unless criteria.is_a?(Array)
|
193
|
+
raise ArgumentError, 'criteria must be an Array', caller(1..-1)
|
194
|
+
end
|
195
|
+
|
196
|
+
return if criteria.all? do |criterion|
|
197
|
+
criterion.is_a?(Array) && criterion.size == 3
|
198
|
+
end
|
199
|
+
|
200
|
+
raise ArgumentError, 'criterion must be an Array of size 3', caller(1..-1)
|
201
|
+
end
|
202
|
+
|
203
|
+
def validate_scope!(scope)
|
204
|
+
return if scope.is_a?(Cuprum::Collections::Scopes::Base)
|
205
|
+
|
206
|
+
raise ArgumentError, 'scope must be a Scope instance', caller(1..-1)
|
207
|
+
end
|
208
|
+
|
209
|
+
def validate_scopes!(scopes)
|
210
|
+
unless scopes.is_a?(Array)
|
211
|
+
raise ArgumentError, 'scopes must be an Array', caller(1..-1)
|
212
|
+
end
|
213
|
+
|
214
|
+
return if scopes.all? do |scope|
|
215
|
+
scope.is_a?(Cuprum::Collections::Scopes::Base)
|
216
|
+
end
|
217
|
+
|
218
|
+
raise ArgumentError, 'scope must be a Scope instance', caller(1..-1)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections/scopes'
|
4
|
+
|
5
|
+
module Cuprum::Collections::Scopes
|
6
|
+
# Defines a fluent interface for composing scopes.
|
7
|
+
module Composition
|
8
|
+
# @overload and(hash = nil, &block)
|
9
|
+
# Parses the hash or block and combines using a logical AND.
|
10
|
+
#
|
11
|
+
# @see Cuprum::Collections::Scopes::Criteria::Parser#parse.
|
12
|
+
#
|
13
|
+
# @overload and(scope)
|
14
|
+
# Combines with the current scope using a logical AND.
|
15
|
+
#
|
16
|
+
# Returns self if the given scope is empty.
|
17
|
+
def and(*args, &)
|
18
|
+
if scope?(args.first)
|
19
|
+
return and_scope(args.first) || and_generic_scope(args.first)
|
20
|
+
end
|
21
|
+
|
22
|
+
scope = builder.build(*args, &)
|
23
|
+
|
24
|
+
# We control the current and generated scopes, so we can skip validation
|
25
|
+
# and transformation.
|
26
|
+
builder.build_conjunction_scope(scopes: [self, scope], safe: false)
|
27
|
+
end
|
28
|
+
alias where and
|
29
|
+
|
30
|
+
# @overload not(hash = nil, &block)
|
31
|
+
# Parses and inverts the hash or block and combines using a logical AND.
|
32
|
+
#
|
33
|
+
# @see Cuprum::Collections::Scopes::Criteria::Parser#parse.
|
34
|
+
#
|
35
|
+
# @overload not(scope)
|
36
|
+
# Inverts and combines with the current scope using a logical AND.
|
37
|
+
#
|
38
|
+
# Returns self if the given scope is empty.
|
39
|
+
def not(...)
|
40
|
+
scope = builder.build(...).invert
|
41
|
+
|
42
|
+
self.and(scope)
|
43
|
+
end
|
44
|
+
|
45
|
+
# @overload and(hash = nil, &block)
|
46
|
+
# Parses the hash or block and combines using a logical OR.
|
47
|
+
#
|
48
|
+
# @see Cuprum::Collections::Scopes::Criteria::Parser#parse.
|
49
|
+
#
|
50
|
+
# @overload and(scope)
|
51
|
+
# Combines with the current scope using a logical OR.
|
52
|
+
#
|
53
|
+
# Returns self if the given scope is empty.
|
54
|
+
def or(*args, &)
|
55
|
+
if scope?(args.first)
|
56
|
+
return or_scope(args.first) || or_generic_scope(args.first)
|
57
|
+
end
|
58
|
+
|
59
|
+
scope = builder.build(*args, &)
|
60
|
+
|
61
|
+
# We control the current and generated scopes, so we can skip validation
|
62
|
+
# and transformation.
|
63
|
+
builder.build_disjunction_scope(scopes: [self, scope], safe: false)
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def and_all_scope(_)
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
def and_criteria_scope(scope)
|
73
|
+
and_generic_scope(scope)
|
74
|
+
end
|
75
|
+
|
76
|
+
def and_conjunction_scope(scope)
|
77
|
+
scopes = scope.scopes.map do |inner|
|
78
|
+
builder.transform_scope(scope: inner)
|
79
|
+
end
|
80
|
+
|
81
|
+
builder.build_conjunction_scope(scopes: [self, *scopes], safe: false)
|
82
|
+
end
|
83
|
+
|
84
|
+
def and_disjunction_scope(scope)
|
85
|
+
and_generic_scope(scope)
|
86
|
+
end
|
87
|
+
|
88
|
+
def and_generic_scope(scope)
|
89
|
+
scope = builder.transform_scope(scope:)
|
90
|
+
|
91
|
+
# We control the current and generated scopes, so we can skip validation
|
92
|
+
# and transformation.
|
93
|
+
builder.build_conjunction_scope(scopes: [self, scope], safe: false)
|
94
|
+
end
|
95
|
+
|
96
|
+
def and_scope(scope) # rubocop:disable Metrics/MethodLength
|
97
|
+
return self if scope.empty?
|
98
|
+
|
99
|
+
case scope.type
|
100
|
+
when :all
|
101
|
+
and_all_scope(scope)
|
102
|
+
when :conjunction
|
103
|
+
and_conjunction_scope(scope)
|
104
|
+
when :criteria
|
105
|
+
and_criteria_scope(scope)
|
106
|
+
when :disjunction
|
107
|
+
and_disjunction_scope(scope)
|
108
|
+
when :none
|
109
|
+
scope
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def or_all_scope(scope)
|
114
|
+
builder.transform_scope(scope:)
|
115
|
+
end
|
116
|
+
|
117
|
+
def or_conjunction_scope(scope)
|
118
|
+
or_generic_scope(scope)
|
119
|
+
end
|
120
|
+
|
121
|
+
def or_criteria_scope(scope)
|
122
|
+
or_generic_scope(scope)
|
123
|
+
end
|
124
|
+
|
125
|
+
def or_disjunction_scope(scope)
|
126
|
+
scopes = scope.scopes.map do |inner|
|
127
|
+
builder.transform_scope(scope: inner)
|
128
|
+
end
|
129
|
+
|
130
|
+
builder.build_disjunction_scope(scopes: [self, *scopes], safe: false)
|
131
|
+
end
|
132
|
+
|
133
|
+
def or_generic_scope(scope)
|
134
|
+
scope = builder.transform_scope(scope:)
|
135
|
+
|
136
|
+
# We control the current and generated scopes, so we can skip validation
|
137
|
+
# and transformation.
|
138
|
+
builder.build_disjunction_scope(scopes: [self, scope], safe: false)
|
139
|
+
end
|
140
|
+
|
141
|
+
def or_scope(scope) # rubocop:disable Metrics/MethodLength
|
142
|
+
return self if scope.empty?
|
143
|
+
|
144
|
+
case scope.type
|
145
|
+
when :all
|
146
|
+
or_all_scope(scope)
|
147
|
+
when :conjunction
|
148
|
+
or_conjunction_scope(scope)
|
149
|
+
when :criteria
|
150
|
+
or_criteria_scope(scope)
|
151
|
+
when :disjunction
|
152
|
+
or_disjunction_scope(scope)
|
153
|
+
when :none
|
154
|
+
self
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def scope?(value)
|
159
|
+
value.is_a?(Cuprum::Collections::Scopes::Base)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections/scopes'
|
4
|
+
require 'cuprum/collections/scopes/container'
|
5
|
+
|
6
|
+
module Cuprum::Collections::Scopes
|
7
|
+
# Functionality for implementing a logical AND scope.
|
8
|
+
module Conjunction
|
9
|
+
include Cuprum::Collections::Scopes::Container
|
10
|
+
|
11
|
+
# (see Cuprum::Collections::Scopes::Composition#and)
|
12
|
+
def and(*args, &)
|
13
|
+
return super if scope?(args.first)
|
14
|
+
|
15
|
+
with_scopes([*scopes, builder.build(*args, &)])
|
16
|
+
end
|
17
|
+
alias where and
|
18
|
+
|
19
|
+
# @return [Cuprum::Collections::Disjunction] a logical OR scope with the
|
20
|
+
# constituent scopes inverted.
|
21
|
+
def invert
|
22
|
+
builder.build_disjunction_scope(scopes: scopes.map(&:invert))
|
23
|
+
end
|
24
|
+
|
25
|
+
# (see Cuprum::Collections::Scopes::Base#type)
|
26
|
+
def type
|
27
|
+
:conjunction
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def and_conjunction_scope(scope)
|
33
|
+
scopes = scope.scopes.map do |inner|
|
34
|
+
builder.transform_scope(scope: inner)
|
35
|
+
end
|
36
|
+
|
37
|
+
with_scopes([*self.scopes, *scopes])
|
38
|
+
end
|
39
|
+
|
40
|
+
def and_generic_scope(scope)
|
41
|
+
with_scopes([*scopes, builder.transform_scope(scope:)])
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections/scopes'
|
4
|
+
require 'cuprum/collections/scopes/base'
|
5
|
+
require 'cuprum/collections/scopes/conjunction'
|
6
|
+
|
7
|
+
module Cuprum::Collections::Scopes
|
8
|
+
# Generic scope class for defining collection-independent logical AND scopes.
|
9
|
+
class ConjunctionScope < Cuprum::Collections::Scopes::Base
|
10
|
+
include Cuprum::Collections::Scopes::Conjunction
|
11
|
+
end
|
12
|
+
end
|