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.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +73 -0
  3. data/README.md +5 -5
  4. data/lib/cuprum/collections/association.rb +9 -28
  5. data/lib/cuprum/collections/associations/belongs_to.rb +1 -8
  6. data/lib/cuprum/collections/associations/has_many.rb +1 -10
  7. data/lib/cuprum/collections/associations/has_one.rb +1 -10
  8. data/lib/cuprum/collections/basic/collection.rb +56 -49
  9. data/lib/cuprum/collections/basic/command.rb +22 -88
  10. data/lib/cuprum/collections/basic/commands/assign_one.rb +2 -6
  11. data/lib/cuprum/collections/basic/commands/build_one.rb +1 -4
  12. data/lib/cuprum/collections/basic/commands/destroy_one.rb +4 -8
  13. data/lib/cuprum/collections/basic/commands/find_many.rb +4 -24
  14. data/lib/cuprum/collections/basic/commands/find_matching.rb +5 -21
  15. data/lib/cuprum/collections/basic/commands/find_one.rb +3 -20
  16. data/lib/cuprum/collections/basic/commands/insert_one.rb +3 -6
  17. data/lib/cuprum/collections/basic/commands/update_one.rb +3 -6
  18. data/lib/cuprum/collections/basic/commands/validate_one.rb +13 -18
  19. data/lib/cuprum/collections/basic/query.rb +26 -40
  20. data/lib/cuprum/collections/basic/repository.rb +4 -3
  21. data/lib/cuprum/collections/basic/scopes/all_scope.rb +25 -0
  22. data/lib/cuprum/collections/basic/scopes/base.rb +32 -0
  23. data/lib/cuprum/collections/basic/scopes/builder.rb +39 -0
  24. data/lib/cuprum/collections/basic/scopes/conjunction_scope.rb +20 -0
  25. data/lib/cuprum/collections/basic/scopes/criteria_scope.rb +62 -0
  26. data/lib/cuprum/collections/basic/scopes/disjunction_scope.rb +20 -0
  27. data/lib/cuprum/collections/basic/scopes/none_scope.rb +33 -0
  28. data/lib/cuprum/collections/basic/scopes.rb +23 -0
  29. data/lib/cuprum/collections/basic.rb +1 -0
  30. data/lib/cuprum/collections/collection.rb +24 -82
  31. data/lib/cuprum/collections/collection_command.rb +116 -0
  32. data/lib/cuprum/collections/commands/abstract_find_many.rb +11 -21
  33. data/lib/cuprum/collections/commands/abstract_find_matching.rb +43 -24
  34. data/lib/cuprum/collections/commands/abstract_find_one.rb +7 -10
  35. data/lib/cuprum/collections/commands/associations/find_many.rb +3 -8
  36. data/lib/cuprum/collections/commands/associations/require_many.rb +5 -5
  37. data/lib/cuprum/collections/commands/create.rb +3 -3
  38. data/lib/cuprum/collections/commands/find_one_matching.rb +6 -6
  39. data/lib/cuprum/collections/commands/query_command.rb +19 -0
  40. data/lib/cuprum/collections/commands/update.rb +3 -3
  41. data/lib/cuprum/collections/commands/upsert.rb +10 -10
  42. data/lib/cuprum/collections/commands.rb +1 -0
  43. data/lib/cuprum/collections/constraints/ordering.rb +2 -2
  44. data/lib/cuprum/collections/errors/abstract_find_error.rb +25 -42
  45. data/lib/cuprum/collections/errors/extra_attributes.rb +3 -3
  46. data/lib/cuprum/collections/errors/failed_validation.rb +2 -2
  47. data/lib/cuprum/collections/errors/invalid_parameters.rb +2 -2
  48. data/lib/cuprum/collections/errors/invalid_query.rb +10 -16
  49. data/lib/cuprum/collections/errors/missing_default_contract.rb +1 -1
  50. data/lib/cuprum/collections/errors/unknown_operator.rb +1 -1
  51. data/lib/cuprum/collections/queries.rb +31 -0
  52. data/lib/cuprum/collections/query.rb +50 -62
  53. data/lib/cuprum/collections/relation.rb +5 -383
  54. data/lib/cuprum/collections/relations/cardinality.rb +66 -0
  55. data/lib/cuprum/collections/relations/options.rb +18 -0
  56. data/lib/cuprum/collections/relations/parameters.rb +217 -0
  57. data/lib/cuprum/collections/relations/primary_keys.rb +23 -0
  58. data/lib/cuprum/collections/relations/scope.rb +65 -0
  59. data/lib/cuprum/collections/relations.rb +14 -0
  60. data/lib/cuprum/collections/repository.rb +5 -5
  61. data/lib/cuprum/collections/resource.rb +10 -41
  62. data/lib/cuprum/collections/rspec/contracts/association_contracts.rb +80 -90
  63. data/lib/cuprum/collections/rspec/contracts/collection_contracts.rb +69 -111
  64. data/lib/cuprum/collections/rspec/contracts/command_contracts.rb +42 -1335
  65. data/lib/cuprum/collections/rspec/contracts/query_contracts.rb +352 -531
  66. data/lib/cuprum/collections/rspec/contracts/relation_contracts.rb +74 -191
  67. data/lib/cuprum/collections/rspec/contracts/repository_contracts.rb +13 -13
  68. data/lib/cuprum/collections/rspec/contracts/scope_contracts.rb +1029 -0
  69. data/lib/cuprum/collections/rspec/contracts/scopes/builder_contracts.rb +856 -0
  70. data/lib/cuprum/collections/rspec/contracts/scopes/composition_contracts.rb +1430 -0
  71. data/lib/cuprum/collections/rspec/contracts/scopes/criteria_contracts.rb +2217 -0
  72. data/lib/cuprum/collections/rspec/contracts/scopes/logical_contracts.rb +297 -0
  73. data/lib/cuprum/collections/rspec/contracts/scopes.rb +13 -0
  74. data/lib/cuprum/collections/rspec/contracts.rb +2 -0
  75. data/lib/cuprum/collections/rspec/deferred/association_examples.rb +2098 -0
  76. data/lib/cuprum/collections/rspec/deferred/collection_examples.rb +338 -0
  77. data/lib/cuprum/collections/rspec/deferred/command_examples.rb +160 -0
  78. data/lib/cuprum/collections/rspec/deferred/commands/assign_one_examples.rb +178 -0
  79. data/lib/cuprum/collections/rspec/deferred/commands/build_one_examples.rb +94 -0
  80. data/lib/cuprum/collections/rspec/deferred/commands/destroy_one_examples.rb +118 -0
  81. data/lib/cuprum/collections/rspec/deferred/commands/find_many_examples.rb +307 -0
  82. data/lib/cuprum/collections/rspec/deferred/commands/find_matching_examples.rb +143 -0
  83. data/lib/cuprum/collections/rspec/deferred/commands/find_one_examples.rb +116 -0
  84. data/lib/cuprum/collections/rspec/deferred/commands/insert_one_examples.rb +103 -0
  85. data/lib/cuprum/collections/rspec/deferred/commands/update_one_examples.rb +99 -0
  86. data/lib/cuprum/collections/rspec/deferred/commands/validate_one_examples.rb +117 -0
  87. data/lib/cuprum/collections/rspec/deferred/commands.rb +8 -0
  88. data/lib/cuprum/collections/rspec/deferred/relation_examples.rb +1437 -0
  89. data/lib/cuprum/collections/rspec/deferred/resource_examples.rb +26 -0
  90. data/lib/cuprum/collections/rspec/deferred.rb +8 -0
  91. data/lib/cuprum/collections/scope.rb +29 -0
  92. data/lib/cuprum/collections/scopes/all.rb +51 -0
  93. data/lib/cuprum/collections/scopes/all_scope.rb +18 -0
  94. data/lib/cuprum/collections/scopes/base.rb +79 -0
  95. data/lib/cuprum/collections/scopes/builder.rb +39 -0
  96. data/lib/cuprum/collections/scopes/building.rb +221 -0
  97. data/lib/cuprum/collections/scopes/composition.rb +162 -0
  98. data/lib/cuprum/collections/scopes/conjunction.rb +44 -0
  99. data/lib/cuprum/collections/scopes/conjunction_scope.rb +12 -0
  100. data/lib/cuprum/collections/scopes/container.rb +65 -0
  101. data/lib/cuprum/collections/scopes/criteria/parser.rb +241 -0
  102. data/lib/cuprum/collections/scopes/criteria.rb +206 -0
  103. data/lib/cuprum/collections/scopes/criteria_scope.rb +12 -0
  104. data/lib/cuprum/collections/scopes/disjunction.rb +45 -0
  105. data/lib/cuprum/collections/scopes/disjunction_scope.rb +12 -0
  106. data/lib/cuprum/collections/scopes/none.rb +62 -0
  107. data/lib/cuprum/collections/scopes/none_scope.rb +18 -0
  108. data/lib/cuprum/collections/scopes.rb +23 -0
  109. data/lib/cuprum/collections/version.rb +2 -2
  110. data/lib/cuprum/collections.rb +14 -9
  111. metadata +61 -15
  112. data/lib/cuprum/collections/basic/query_builder.rb +0 -69
  113. data/lib/cuprum/collections/command.rb +0 -26
  114. data/lib/cuprum/collections/queries/parse.rb +0 -22
  115. data/lib/cuprum/collections/queries/parse_block.rb +0 -206
  116. data/lib/cuprum/collections/queries/parse_strategy.rb +0 -91
  117. data/lib/cuprum/collections/query_builder.rb +0 -61
  118. 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 = 4
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 = +"#{MAJOR}.#{MINOR}.#{PATCH}"
31
+ str = "#{MAJOR}.#{MINOR}.#{PATCH}"
32
32
 
33
33
  prerelease = value_of(:PRERELEASE)
34
34
  str << ".#{prerelease}" if prerelease