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,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections/scopes'
|
4
|
+
|
5
|
+
module Cuprum::Collections::Scopes
|
6
|
+
# Functionality for implementing a scope container.
|
7
|
+
module Container
|
8
|
+
# @param scopes [Array<Scope>] the scopes wrapped by the scope.
|
9
|
+
# @param options [Hash] additional options for the scope.
|
10
|
+
def initialize(scopes:, **options)
|
11
|
+
super(**options)
|
12
|
+
|
13
|
+
@scopes = scopes
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [Array<Scope>] the scopes wrapped by the scope.
|
17
|
+
attr_reader :scopes
|
18
|
+
|
19
|
+
# @param other [Object] the object to compare.
|
20
|
+
#
|
21
|
+
# @return [Boolean] true if the other object is a scope with matching type
|
22
|
+
# and child scopes; otherwise false.
|
23
|
+
def ==(other)
|
24
|
+
return false unless super
|
25
|
+
|
26
|
+
other.scopes == scopes
|
27
|
+
end
|
28
|
+
|
29
|
+
# (see Cuprum::Collections::Scopes::Base#as_json)
|
30
|
+
def as_json
|
31
|
+
super.merge({ 'scopes' => scopes.map(&:as_json) })
|
32
|
+
end
|
33
|
+
|
34
|
+
# @private
|
35
|
+
def debug
|
36
|
+
# :nocov:
|
37
|
+
message = "#{super} (#{scopes.count})"
|
38
|
+
|
39
|
+
return message if empty?
|
40
|
+
|
41
|
+
scopes.reduce("#{message}:") do |str, scope|
|
42
|
+
str + "\n- #{scope.debug.gsub("\n", "\n ")}"
|
43
|
+
end
|
44
|
+
# :nocov:
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [Boolean] true if the scope has no child scopes; otherwise false.
|
48
|
+
def empty?
|
49
|
+
@scopes.empty?
|
50
|
+
end
|
51
|
+
|
52
|
+
# Creates a copy of the scope with the given child scopes.
|
53
|
+
#
|
54
|
+
# @param scopes [Array] the child scopes.
|
55
|
+
#
|
56
|
+
# @return [Scope] the copied scope.
|
57
|
+
def with_scopes(scopes)
|
58
|
+
dup.tap { |copy| copy.scopes = scopes }
|
59
|
+
end
|
60
|
+
|
61
|
+
protected
|
62
|
+
|
63
|
+
attr_writer :scopes
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,241 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
require 'cuprum/collections/scopes/criteria'
|
6
|
+
|
7
|
+
module Cuprum::Collections::Scopes::Criteria
|
8
|
+
# Helper for generating criteria from hash or block inputs.
|
9
|
+
class Parser
|
10
|
+
# Utility class for parsing block operators.
|
11
|
+
class BlockParser
|
12
|
+
# @return [BlockParser] a memoized class instance.
|
13
|
+
def self.instance
|
14
|
+
@instance ||= new
|
15
|
+
end
|
16
|
+
|
17
|
+
def equals(value)
|
18
|
+
OperatorExpression.new(
|
19
|
+
Cuprum::Collections::Queries::Operators::EQUAL,
|
20
|
+
value
|
21
|
+
)
|
22
|
+
end
|
23
|
+
alias equal equals
|
24
|
+
alias eq equals
|
25
|
+
|
26
|
+
def greater_than(value)
|
27
|
+
OperatorExpression.new(
|
28
|
+
Cuprum::Collections::Queries::Operators::GREATER_THAN,
|
29
|
+
value
|
30
|
+
)
|
31
|
+
end
|
32
|
+
alias gt greater_than
|
33
|
+
|
34
|
+
def greater_than_or_equal_to(value)
|
35
|
+
OperatorExpression.new(
|
36
|
+
Cuprum::Collections::Queries::Operators::GREATER_THAN_OR_EQUAL_TO,
|
37
|
+
value
|
38
|
+
)
|
39
|
+
end
|
40
|
+
alias gte greater_than_or_equal_to
|
41
|
+
|
42
|
+
def less_than(value)
|
43
|
+
OperatorExpression.new(
|
44
|
+
Cuprum::Collections::Queries::Operators::LESS_THAN,
|
45
|
+
value
|
46
|
+
)
|
47
|
+
end
|
48
|
+
alias lt less_than
|
49
|
+
|
50
|
+
def less_than_or_equal_to(value)
|
51
|
+
OperatorExpression.new(
|
52
|
+
Cuprum::Collections::Queries::Operators::LESS_THAN_OR_EQUAL_TO,
|
53
|
+
value
|
54
|
+
)
|
55
|
+
end
|
56
|
+
alias lte less_than_or_equal_to
|
57
|
+
|
58
|
+
def not_equal(value)
|
59
|
+
OperatorExpression.new(
|
60
|
+
Cuprum::Collections::Queries::Operators::NOT_EQUAL,
|
61
|
+
value
|
62
|
+
)
|
63
|
+
end
|
64
|
+
alias ne not_equal
|
65
|
+
|
66
|
+
def not_one_of(*values)
|
67
|
+
OperatorExpression.new(
|
68
|
+
Cuprum::Collections::Queries::Operators::NOT_ONE_OF,
|
69
|
+
values.flatten
|
70
|
+
)
|
71
|
+
end
|
72
|
+
|
73
|
+
def one_of(*values)
|
74
|
+
OperatorExpression.new(
|
75
|
+
Cuprum::Collections::Queries::Operators::ONE_OF,
|
76
|
+
values.flatten
|
77
|
+
)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# @deprecated v0.5.0 Implicit receivers are deprecated. Remove this class
|
82
|
+
# when removing the functionality.
|
83
|
+
class ImplicitReceiver
|
84
|
+
ERROR_MESSAGE =
|
85
|
+
'Pass a block with one parameter to #parse: { |scope| { ' \
|
86
|
+
'scope.%s: value } }'
|
87
|
+
private_constant :ERROR_MESSAGE
|
88
|
+
|
89
|
+
OPERATORS = %i[
|
90
|
+
eq
|
91
|
+
equal
|
92
|
+
equals
|
93
|
+
greater_than
|
94
|
+
greater_than_or_equal_to
|
95
|
+
gt
|
96
|
+
gte
|
97
|
+
less_than
|
98
|
+
less_than_or_equal_to
|
99
|
+
lt
|
100
|
+
lte
|
101
|
+
ne
|
102
|
+
not_equal
|
103
|
+
not_one_of
|
104
|
+
one_of
|
105
|
+
].freeze
|
106
|
+
private_constant :OPERATORS
|
107
|
+
|
108
|
+
OPERATORS.each do |operator|
|
109
|
+
define_method(operator) do |*args, **kwargs, &block|
|
110
|
+
tools.core_tools.deprecate(
|
111
|
+
'#parse with implicit receiver',
|
112
|
+
message: format(ERROR_MESSAGE, operator)
|
113
|
+
)
|
114
|
+
|
115
|
+
BlockParser.instance.send(operator, *args, **kwargs, &block)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def tools
|
122
|
+
SleepingKingStudios::Tools::Toolbelt.instance
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
OperatorExpression = Struct.new(:operator, :value)
|
127
|
+
private_constant :OperatorExpression
|
128
|
+
|
129
|
+
UNKNOWN = Object.new.freeze
|
130
|
+
private_constant :UNKNOWN
|
131
|
+
|
132
|
+
class << self
|
133
|
+
# @return [Cuprum::Collections::Scopes::Criteria::Parser] a singleton
|
134
|
+
# instance of the parser class.
|
135
|
+
def instance
|
136
|
+
@instance ||= new
|
137
|
+
end
|
138
|
+
|
139
|
+
# @private
|
140
|
+
def validate_hash(value)
|
141
|
+
return if valid_hash?(value)
|
142
|
+
|
143
|
+
message = 'value must be a Hash with String or Symbol keys'
|
144
|
+
|
145
|
+
raise ArgumentError, message, caller(1..-1)
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
def valid_hash?(value)
|
151
|
+
return false unless value.is_a?(Hash)
|
152
|
+
|
153
|
+
value.each_key.all? do |key|
|
154
|
+
key.is_a?(String) || key.is_a?(Symbol)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# @overload parse(value = nil, &block)
|
160
|
+
# Converts a valid query hash and/or block to criteria.
|
161
|
+
#
|
162
|
+
# The block must return a Hash with String keys. The hash values must
|
163
|
+
# either be literal values (e.g. a String, an Integer, etc) or a call to
|
164
|
+
# an operator function.
|
165
|
+
#
|
166
|
+
# @param value [Hash, nil] the keys and values to parse.
|
167
|
+
#
|
168
|
+
# @return [Array] the generated criteria.
|
169
|
+
#
|
170
|
+
# @yield the query block.
|
171
|
+
#
|
172
|
+
# @yieldreturn [Hash] a Hash with String keys.
|
173
|
+
def parse(value = UNKNOWN, &)
|
174
|
+
if block_given? && value != UNKNOWN
|
175
|
+
parse_hash(value) + parse_block(&)
|
176
|
+
elsif value == UNKNOWN
|
177
|
+
parse_block(&)
|
178
|
+
else
|
179
|
+
parse_hash(value)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Converts a valid query block to criteria.
|
184
|
+
#
|
185
|
+
# The block must return a Hash with String keys. The hash values must
|
186
|
+
# either be literal values (e.g. a String, an Integer, etc) or a call to
|
187
|
+
# an operator function.
|
188
|
+
#
|
189
|
+
# @return [Array] the generated criteria.
|
190
|
+
#
|
191
|
+
# @yield the query block.
|
192
|
+
#
|
193
|
+
# @yieldreturn [Hash] a Hash with String keys.
|
194
|
+
def parse_block(...) # rubocop:disable Metrics/MethodLength
|
195
|
+
raise ArgumentError, 'no block given' unless block_given?
|
196
|
+
|
197
|
+
value = evaluate_block(...)
|
198
|
+
|
199
|
+
Parser.validate_hash(value)
|
200
|
+
|
201
|
+
value.map do |attribute, filter|
|
202
|
+
if filter.is_a?(OperatorExpression)
|
203
|
+
[attribute.to_s, filter.operator, filter.value]
|
204
|
+
else
|
205
|
+
operator = Cuprum::Collections::Queries::Operators::EQUAL
|
206
|
+
|
207
|
+
[attribute.to_s, operator, filter]
|
208
|
+
end
|
209
|
+
end
|
210
|
+
rescue NameError => exception
|
211
|
+
raise Cuprum::Collections::Queries::UnknownOperatorException,
|
212
|
+
%(unknown operator "#{exception.name}")
|
213
|
+
end
|
214
|
+
|
215
|
+
# Converts a hash of expected keys and values to criteria.
|
216
|
+
#
|
217
|
+
# @param value [Hash] the keys and values to parse.
|
218
|
+
#
|
219
|
+
# @return [Array] the generated criteria.
|
220
|
+
def parse_hash(value)
|
221
|
+
Parser.validate_hash(value)
|
222
|
+
|
223
|
+
operator = Cuprum::Collections::Queries::Operators::EQUAL
|
224
|
+
|
225
|
+
value.map do |attribute, filter|
|
226
|
+
[attribute.to_s, operator, filter]
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
private
|
231
|
+
|
232
|
+
# @deprecated v0.5.0 Implicit receivers are deprecated.
|
233
|
+
def evaluate_block(&block)
|
234
|
+
receiver = ImplicitReceiver.new
|
235
|
+
|
236
|
+
return receiver.instance_exec(&block) if block.arity.zero?
|
237
|
+
|
238
|
+
receiver.instance_exec(BlockParser.instance, &block)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections/queries'
|
4
|
+
require 'cuprum/collections/scopes'
|
5
|
+
|
6
|
+
module Cuprum::Collections::Scopes
|
7
|
+
# Functionality for implementing a criteria scope.
|
8
|
+
module Criteria # rubocop:disable Metrics/ModuleLength
|
9
|
+
# Class methods to extend when including the module.
|
10
|
+
module ClassMethods
|
11
|
+
# @overload build(value = nil, &block)
|
12
|
+
# Initializes a new criteria scope with the parsed criteria.
|
13
|
+
#
|
14
|
+
# @param value [Hash, nil] the keys and values to parse.
|
15
|
+
#
|
16
|
+
# @return [Criteria] the scope with the generated criteria.
|
17
|
+
#
|
18
|
+
# @yield the query block.
|
19
|
+
#
|
20
|
+
# @yieldreturn [Hash] a Hash with String keys.
|
21
|
+
def build(...)
|
22
|
+
criteria = parse(...)
|
23
|
+
|
24
|
+
new(criteria:)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @overload parse(value = nil, &block)
|
28
|
+
# (see Cuprum::Collections::Scopes::Criteria::Parser#parse)
|
29
|
+
def parse(*args, &)
|
30
|
+
parser = Cuprum::Collections::Scopes::Criteria::Parser.instance
|
31
|
+
|
32
|
+
args.empty? ? parser.parse(&) : parser.parse(args.first, &)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class << self
|
37
|
+
private
|
38
|
+
|
39
|
+
def included(other)
|
40
|
+
super
|
41
|
+
|
42
|
+
other.extend(ClassMethods)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# @param criteria [Array] the criteria used for filtering query data.
|
47
|
+
# @param inverted [Boolean] if true, the criteria are inverted and should
|
48
|
+
# match on any criterion (per DeMorgan's Laws).
|
49
|
+
# @param options [Hash] additional options for the scope.
|
50
|
+
def initialize(criteria:, inverted: false, **options)
|
51
|
+
super(**options)
|
52
|
+
|
53
|
+
@criteria = criteria
|
54
|
+
@inverted = inverted
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [Array] the criteria used for filtering query data.
|
58
|
+
attr_reader :criteria
|
59
|
+
|
60
|
+
# @param other [Object] the object to compare.
|
61
|
+
#
|
62
|
+
# @return [Boolean] true if the other object is a scope with matching type
|
63
|
+
# and criteria; otherwise false.
|
64
|
+
def ==(other)
|
65
|
+
return false unless super
|
66
|
+
|
67
|
+
other.criteria == criteria && other.inverted? == inverted?
|
68
|
+
end
|
69
|
+
|
70
|
+
# (see Cuprum::Collections::Scopes::Composition#and)
|
71
|
+
def and(*args, &)
|
72
|
+
return super if scope?(args.first)
|
73
|
+
|
74
|
+
return self.class.build(*args, &) if empty?
|
75
|
+
|
76
|
+
return super if inverted?
|
77
|
+
|
78
|
+
with_criteria([*criteria, *self.class.parse(*args, &)])
|
79
|
+
end
|
80
|
+
alias where and
|
81
|
+
|
82
|
+
# (see Cuprum::Collections::Scopes::Base#as_json)
|
83
|
+
def as_json
|
84
|
+
super.merge({ 'criteria' => criteria, 'inverted' => inverted? })
|
85
|
+
end
|
86
|
+
|
87
|
+
# @private
|
88
|
+
def debug
|
89
|
+
# :nocov:
|
90
|
+
message = "#{super} (#{criteria.count})"
|
91
|
+
message += ' (inverted)' if inverted?
|
92
|
+
|
93
|
+
return message if empty?
|
94
|
+
|
95
|
+
criteria.reduce("#{message}:") do |str, (attribute, operator, value)|
|
96
|
+
str + "\n- #{attribute.inspect} #{operator} #{value.inspect}"
|
97
|
+
end
|
98
|
+
# :nocov:
|
99
|
+
end
|
100
|
+
|
101
|
+
# @return [Boolean] true if the scope has no criteria; otherwise false.
|
102
|
+
def empty?
|
103
|
+
@criteria.empty?
|
104
|
+
end
|
105
|
+
|
106
|
+
# @return [Cuprum::Collections::Criteria] a copy of the scope with the
|
107
|
+
# #inverted? predicate flipped and the individual criteria negated.
|
108
|
+
def invert
|
109
|
+
with_criteria(invert_criteria).tap { |copy| copy.inverted = !inverted? }
|
110
|
+
end
|
111
|
+
|
112
|
+
# @return [Boolean] true if the scope is inverted; otherwise false.
|
113
|
+
def inverted?
|
114
|
+
@inverted
|
115
|
+
end
|
116
|
+
|
117
|
+
# (see Cuprum::Collections::Scopes::Composition#or)
|
118
|
+
def or(*args, &)
|
119
|
+
return super if scope?(args.first)
|
120
|
+
|
121
|
+
return self.class.build(*args, &) if empty?
|
122
|
+
|
123
|
+
builder.build_disjunction_scope(
|
124
|
+
safe: false,
|
125
|
+
scopes: [self, self.class.build(*args, &)]
|
126
|
+
)
|
127
|
+
end
|
128
|
+
|
129
|
+
# (see Cuprum::Collections::Scopes::Base#type)
|
130
|
+
def type
|
131
|
+
:criteria
|
132
|
+
end
|
133
|
+
|
134
|
+
# Creates a copy of the scope with the given criteria.
|
135
|
+
#
|
136
|
+
# @param criteria [Array] the criteria used for filtering query data.
|
137
|
+
#
|
138
|
+
# @return [Scope] the copied scope.
|
139
|
+
def with_criteria(criteria)
|
140
|
+
dup.tap { |copy| copy.criteria = criteria }
|
141
|
+
end
|
142
|
+
|
143
|
+
protected
|
144
|
+
|
145
|
+
attr_writer :criteria
|
146
|
+
|
147
|
+
attr_writer :inverted
|
148
|
+
|
149
|
+
private
|
150
|
+
|
151
|
+
def and_all_scope(scope)
|
152
|
+
return builder.transform_scope(scope:) if empty?
|
153
|
+
|
154
|
+
super
|
155
|
+
end
|
156
|
+
|
157
|
+
def and_conjunction_scope(scope)
|
158
|
+
return builder.transform_scope(scope:) if empty?
|
159
|
+
|
160
|
+
super
|
161
|
+
end
|
162
|
+
|
163
|
+
def and_criteria_scope(scope)
|
164
|
+
return builder.transform_scope(scope:) if empty?
|
165
|
+
|
166
|
+
unless inverted? || scope.inverted?
|
167
|
+
return with_criteria([*criteria, *scope.criteria])
|
168
|
+
end
|
169
|
+
|
170
|
+
super
|
171
|
+
end
|
172
|
+
|
173
|
+
def and_generic_scope(scope)
|
174
|
+
return builder.transform_scope(scope:) if empty?
|
175
|
+
|
176
|
+
super
|
177
|
+
end
|
178
|
+
|
179
|
+
def invert_criteria
|
180
|
+
criteria.map do |(attribute, operator, value)|
|
181
|
+
[attribute, invert_operator(operator), value]
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def invert_operator(operator)
|
186
|
+
Cuprum::Collections::Queries::INVERTIBLE_OPERATORS.fetch(operator) do
|
187
|
+
raise Cuprum::Collections::Queries::UninvertibleOperatorException,
|
188
|
+
"uninvertible operator #{operator.inspect}"
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def or_disjunction_scope(scope)
|
193
|
+
return builder.transform_scope(scope:) if empty?
|
194
|
+
|
195
|
+
super
|
196
|
+
end
|
197
|
+
|
198
|
+
def or_generic_scope(scope)
|
199
|
+
return builder.transform_scope(scope:) if empty?
|
200
|
+
|
201
|
+
super
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
require 'cuprum/collections/scopes/criteria/parser'
|
@@ -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/criteria'
|
6
|
+
|
7
|
+
module Cuprum::Collections::Scopes
|
8
|
+
# Generic scope class for defining criteria scopes.
|
9
|
+
class CriteriaScope < Cuprum::Collections::Scopes::Base
|
10
|
+
include Cuprum::Collections::Scopes::Criteria
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,45 @@
|
|
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 OR scope.
|
8
|
+
module Disjunction
|
9
|
+
include Cuprum::Collections::Scopes::Container
|
10
|
+
|
11
|
+
# @return [Cuprum::Collections::Disjunction] a logical AND scope with the
|
12
|
+
# constituent scopes inverted.
|
13
|
+
def invert
|
14
|
+
builder.build_conjunction_scope(scopes: scopes.map(&:invert))
|
15
|
+
end
|
16
|
+
|
17
|
+
# (see Cuprum::Collections::Scopes::Composition#or)
|
18
|
+
def or(*args, &)
|
19
|
+
return super if scope?(args.first)
|
20
|
+
|
21
|
+
scope = builder.build(*args, &)
|
22
|
+
|
23
|
+
with_scopes([*scopes, scope])
|
24
|
+
end
|
25
|
+
|
26
|
+
# (see Cuprum::Collections::Scopes::Base#type)
|
27
|
+
def type
|
28
|
+
:disjunction
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def or_disjunction_scope(scope)
|
34
|
+
scopes = scope.scopes.map do |inner|
|
35
|
+
builder.transform_scope(scope: inner)
|
36
|
+
end
|
37
|
+
|
38
|
+
with_scopes([*self.scopes, *scopes])
|
39
|
+
end
|
40
|
+
|
41
|
+
def or_generic_scope(scope)
|
42
|
+
with_scopes([*scopes, builder.transform_scope(scope:)])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
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/disjunction'
|
6
|
+
|
7
|
+
module Cuprum::Collections::Scopes
|
8
|
+
# Generic scope class for defining collection-independent logical OR scopes.
|
9
|
+
class DisjunctionScope < Cuprum::Collections::Scopes::Base
|
10
|
+
include Cuprum::Collections::Scopes::Disjunction
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections/scopes'
|
4
|
+
|
5
|
+
module Cuprum::Collections::Scopes
|
6
|
+
# Functionality for implementing a none scope, which returns no data.
|
7
|
+
module None
|
8
|
+
# @overload and(hash = nil, &block)
|
9
|
+
# Returns the none scope.
|
10
|
+
#
|
11
|
+
# @overload and(scope)
|
12
|
+
# Returns the none scope.
|
13
|
+
def and(*, &)
|
14
|
+
self
|
15
|
+
end
|
16
|
+
alias where and
|
17
|
+
|
18
|
+
# @return [Boolean] false.
|
19
|
+
def empty?
|
20
|
+
false
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [Cuprum::Collections::Scopes::All] an all scope for the current
|
24
|
+
# collection.
|
25
|
+
def invert
|
26
|
+
builder.build_all_scope
|
27
|
+
end
|
28
|
+
|
29
|
+
# @overload or(hash = nil, &block)
|
30
|
+
# Returns the none scope.
|
31
|
+
#
|
32
|
+
# @overload or(scope)
|
33
|
+
# Returns the none scope.
|
34
|
+
def or(*args, &)
|
35
|
+
return super if scope?(args.first)
|
36
|
+
|
37
|
+
builder.build(*args, &)
|
38
|
+
end
|
39
|
+
|
40
|
+
# @overload not(hash = nil, &block)
|
41
|
+
# Returns the none scope.
|
42
|
+
#
|
43
|
+
# @overload not(scope)
|
44
|
+
# Returns the none scope.
|
45
|
+
def not(*, &)
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
# (see Cuprum::Collections::Scopes::Base#type)
|
50
|
+
def type
|
51
|
+
:none
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def or_scope(scope)
|
57
|
+
return self if scope.empty?
|
58
|
+
|
59
|
+
builder.transform_scope(scope:)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections/scopes'
|
4
|
+
require 'cuprum/collections/scopes/base'
|
5
|
+
require 'cuprum/collections/scopes/none'
|
6
|
+
|
7
|
+
module Cuprum::Collections::Scopes
|
8
|
+
# Generic scope class for defining collection-independent none scopes.
|
9
|
+
class NoneScope < Cuprum::Collections::Scopes::Base
|
10
|
+
include Cuprum::Collections::Scopes::None
|
11
|
+
|
12
|
+
# @return [Cuprum::Collections::Scopes::NoneScope] a cached instance of the
|
13
|
+
# none scope.
|
14
|
+
def self.instance
|
15
|
+
@instance ||= new
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections'
|
4
|
+
|
5
|
+
module Cuprum::Collections
|
6
|
+
# Namespace for scope functionality, which filters query data.
|
7
|
+
module Scopes
|
8
|
+
autoload :All, 'cuprum/collections/scopes/all'
|
9
|
+
autoload :AllScope, 'cuprum/collections/scopes/all_scope'
|
10
|
+
autoload :Base, 'cuprum/collections/scopes/base'
|
11
|
+
autoload :Builder, 'cuprum/collections/scopes/builder'
|
12
|
+
autoload :Collection, 'cuprum/collections/scopes/collection'
|
13
|
+
autoload :Composition, 'cuprum/collections/scopes/composition'
|
14
|
+
autoload :Conjunction, 'cuprum/collections/scopes/conjunction'
|
15
|
+
autoload :ConjunctionScope, 'cuprum/collections/scopes/conjunction_scope'
|
16
|
+
autoload :Criteria, 'cuprum/collections/scopes/criteria'
|
17
|
+
autoload :CriteriaScope, 'cuprum/collections/scopes/criteria_scope'
|
18
|
+
autoload :Disjunction, 'cuprum/collections/scopes/disjunction'
|
19
|
+
autoload :DisjunctionScope, 'cuprum/collections/scopes/disjunction_scope'
|
20
|
+
autoload :None, 'cuprum/collections/scopes/none'
|
21
|
+
autoload :NoneScope, 'cuprum/collections/scopes/none_scope'
|
22
|
+
end
|
23
|
+
end
|
@@ -11,7 +11,7 @@ module Cuprum
|
|
11
11
|
# Major version.
|
12
12
|
MAJOR = 0
|
13
13
|
# Minor version.
|
14
|
-
MINOR =
|
14
|
+
MINOR = 5
|
15
15
|
# Patch version.
|
16
16
|
PATCH = 0
|
17
17
|
# Prerelease version.
|
@@ -28,7 +28,7 @@ module Cuprum
|
|
28
28
|
#
|
29
29
|
# @see SleepingKingStudios::Tools::SemanticVersion#to_gem_version
|
30
30
|
def to_gem_version
|
31
|
-
str =
|
31
|
+
str = "#{MAJOR}.#{MINOR}.#{PATCH}"
|
32
32
|
|
33
33
|
prerelease = value_of(:PRERELEASE)
|
34
34
|
str << ".#{prerelease}" if prerelease
|