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,338 @@
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 collections.
10
+ module CollectionExamples
11
+ include RSpec::SleepingKingStudios::Deferred::Provider
12
+ include Cuprum::Collections::RSpec::Deferred::RelationExamples
13
+
14
+ deferred_examples 'should be a Collection' do |**options|
15
+ include Cuprum::Collections::RSpec::Deferred::RelationExamples
16
+
17
+ deferred_examples 'should define the command' \
18
+ do |command_name, command_class_name = nil|
19
+ next if options[:abstract]
20
+
21
+ tools = SleepingKingStudios::Tools::Toolbelt.instance
22
+ class_name = tools.str.camelize(command_name)
23
+
24
+ describe "##{command_name}" do
25
+ let(:constructor_options) { defined?(super()) ? super() : {} }
26
+ let(:command) { build_command(collection) }
27
+ let(:command_class) do
28
+ (
29
+ command_class_name ||
30
+ "#{options[:commands_namespace]}::#{class_name}"
31
+ )
32
+ .then { |str| Object.const_get(str) }
33
+ end
34
+
35
+ define_method(:build_command) do |collection|
36
+ collection.send(command_name, **constructor_options)
37
+ end
38
+
39
+ it 'should define the command' do
40
+ expect(collection)
41
+ .to respond_to(command_name)
42
+ .with(0).arguments
43
+ .and_any_keywords
44
+ end
45
+
46
+ it { expect(command).to be_a command_class }
47
+
48
+ it { expect(command.collection).to be subject }
49
+ end
50
+ end
51
+
52
+ include_deferred 'should be a Relation',
53
+ constructor: false,
54
+ default_entity_class: options[:default_entity_class]
55
+
56
+ include_deferred 'should define Relation primary key'
57
+
58
+ include_deferred 'should define Relation scope',
59
+ default_scope: options[:default_scope]
60
+
61
+ include_deferred 'should define the command', :assign_one
62
+
63
+ include_deferred 'should define the command', :build_one
64
+
65
+ include_deferred 'should define the command', :destroy_one
66
+
67
+ include_deferred 'should define the command', :find_many
68
+
69
+ include_deferred 'should define the command', :find_matching
70
+
71
+ include_deferred 'should define the command', :find_one
72
+
73
+ include_deferred 'should define the command', :insert_one
74
+
75
+ include_deferred 'should define the command', :update_one
76
+
77
+ include_deferred 'should define the command', :validate_one
78
+
79
+ describe '#==' do
80
+ let(:other_options) { { name: } }
81
+ let(:other_collection) { described_class.new(**other_options) }
82
+
83
+ describe 'with nil' do
84
+ it { expect(collection == nil).to be false } # rubocop:disable Style/NilComparison
85
+ end
86
+
87
+ describe 'with an object' do
88
+ it { expect(collection == Object.new.freeze).to be false }
89
+ end
90
+
91
+ describe 'with a collection with non-matching properties' do
92
+ let(:other_options) { super().merge(custom_option: 'value') }
93
+
94
+ it { expect(collection == other_collection).to be false }
95
+ end
96
+
97
+ describe 'with a collection with matching properties' do
98
+ it { expect(collection == other_collection).to be true }
99
+ end
100
+
101
+ describe 'with another type of collection' do
102
+ let(:other_collection) do
103
+ Spec::OtherCollection.new(**other_options)
104
+ end
105
+
106
+ example_class 'Spec::OtherCollection',
107
+ Cuprum::Collections::Collection
108
+
109
+ it { expect(collection == other_collection).to be false }
110
+ end
111
+
112
+ context 'when initialized with options' do
113
+ let(:constructor_options) do
114
+ super().merge(
115
+ qualified_name: 'spec/scoped_books',
116
+ singular_name: 'grimoire'
117
+ )
118
+ end
119
+
120
+ describe 'with a collection with non-matching properties' do
121
+ it { expect(collection == other_collection).to be false }
122
+ end
123
+
124
+ describe 'with a collection with matching properties' do
125
+ let(:other_options) do
126
+ super().merge(
127
+ qualified_name: 'spec/scoped_books',
128
+ singular_name: 'grimoire'
129
+ )
130
+ end
131
+
132
+ it { expect(collection == other_collection).to be true }
133
+ end
134
+ end
135
+ end
136
+
137
+ describe '#count' do
138
+ it { expect(collection).to respond_to(:count).with(0).arguments }
139
+
140
+ it { expect(collection).to have_aliased_method(:count).as(:size) }
141
+
142
+ next if options[:abstract]
143
+
144
+ it { expect(collection.count).to be 0 }
145
+
146
+ wrap_context 'when the collection has many items' do
147
+ it { expect(collection.count).to be items.count }
148
+ end
149
+ end
150
+
151
+ describe '#matches?' do
152
+ def tools
153
+ SleepingKingStudios::Tools::Toolbelt.instance
154
+ end
155
+
156
+ it 'should define the method' do
157
+ expect(collection)
158
+ .to respond_to(:matches?)
159
+ .with(0).arguments
160
+ .and_any_keywords
161
+ end
162
+
163
+ describe 'with no options' do
164
+ it { expect(collection.matches?).to be true }
165
+ end
166
+
167
+ describe 'with non-matching entity class as a Class' do
168
+ let(:other_options) { { entity_class: Grimoire } }
169
+
170
+ it { expect(collection.matches?(**other_options)).to be false }
171
+ end
172
+
173
+ describe 'with non-matching entity class as a String' do
174
+ let(:other_options) { { entity_class: 'Grimoire' } }
175
+
176
+ it { expect(collection.matches?(**other_options)).to be false }
177
+ end
178
+
179
+ describe 'with non-matching name' do
180
+ it { expect(collection.matches?(name: 'grimoires')).to be false }
181
+ end
182
+
183
+ describe 'with non-matching primary key name' do
184
+ let(:other_options) { { primary_key_name: 'uuid' } }
185
+
186
+ it { expect(collection.matches?(**other_options)).to be false }
187
+ end
188
+
189
+ describe 'with non-matching primary key type' do
190
+ let(:other_options) { { primary_key_type: String } }
191
+
192
+ it { expect(collection.matches?(**other_options)).to be false }
193
+ end
194
+
195
+ describe 'with non-matching qualified name' do
196
+ let(:other_options) { { qualified_name: 'spec/scoped_books' } }
197
+
198
+ it { expect(collection.matches?(**other_options)).to be false }
199
+ end
200
+
201
+ describe 'with non-matching singular name' do
202
+ let(:other_options) { { singular_name: 'grimoire' } }
203
+
204
+ it { expect(collection.matches?(**other_options)).to be false }
205
+ end
206
+
207
+ describe 'with non-matching custom options' do
208
+ let(:other_options) { { custom_option: 'custom value' } }
209
+
210
+ it { expect(collection.matches?(**other_options)).to be false }
211
+ end
212
+
213
+ describe 'with partially-matching options' do
214
+ let(:other_options) do
215
+ {
216
+ name:,
217
+ singular_name: 'grimoire'
218
+ }
219
+ end
220
+
221
+ it { expect(collection.matches?(**other_options)).to be false }
222
+ end
223
+
224
+ describe 'with matching entity class as a Class' do
225
+ let(:configured_entity_class) do
226
+ options.fetch(:default_entity_class, Book)
227
+ end
228
+ let(:other_options) { { entity_class: configured_entity_class } }
229
+
230
+ it { expect(collection.matches?(**other_options)).to be true }
231
+ end
232
+
233
+ describe 'with matching entity class as a String' do
234
+ let(:configured_entity_class) do
235
+ options.fetch(:default_entity_class, Book)
236
+ end
237
+ let(:other_options) do
238
+ { entity_class: configured_entity_class.to_s }
239
+ end
240
+
241
+ it { expect(collection.matches?(**other_options)).to be true }
242
+ end
243
+
244
+ describe 'with matching name' do
245
+ let(:other_options) { { name: } }
246
+
247
+ it { expect(collection.matches?(**other_options)).to be true }
248
+ end
249
+
250
+ describe 'with matching primary key name' do
251
+ let(:other_options) { { primary_key_name: 'id' } }
252
+
253
+ it { expect(collection.matches?(**other_options)).to be true }
254
+ end
255
+
256
+ describe 'with matching primary key type' do
257
+ let(:other_options) { { primary_key_type: Integer } }
258
+
259
+ it { expect(collection.matches?(**other_options)).to be true }
260
+ end
261
+
262
+ describe 'with matching qualified name' do
263
+ let(:other_options) { { qualified_name: name } }
264
+
265
+ it { expect(collection.matches?(**other_options)).to be true }
266
+ end
267
+
268
+ describe 'with matching singular name' do
269
+ let(:other_options) do
270
+ { singular_name: tools.str.singularize(name) }
271
+ end
272
+
273
+ it { expect(collection.matches?(**other_options)).to be true }
274
+ end
275
+
276
+ describe 'with multiple matching options' do
277
+ let(:other_options) do
278
+ {
279
+ name:,
280
+ primary_key_name: 'id',
281
+ qualified_name: name
282
+ }
283
+ end
284
+
285
+ it { expect(collection.matches?(**other_options)).to be true }
286
+ end
287
+ end
288
+
289
+ describe '#query' do
290
+ let(:error_message) do
291
+ "#{described_class.name} is an abstract class. Define a " \
292
+ 'collection subclass and implement the #query method.'
293
+ end
294
+ let(:default_order) { defined?(super()) ? super() : {} }
295
+ let(:query) { collection.query }
296
+
297
+ it { expect(collection).to respond_to(:query).with(0).arguments }
298
+
299
+ if options[:abstract]
300
+ it 'should raise an exception' do
301
+ expect { collection.query }
302
+ .to raise_error(
303
+ described_class::AbstractCollectionError,
304
+ error_message
305
+ )
306
+ end
307
+ else
308
+ it { expect(collection.query).to be_a query_class }
309
+
310
+ it 'should set the query options' do
311
+ query_options.each do |option, value|
312
+ expect(collection.query.send(option)).to be == value
313
+ end
314
+ end
315
+
316
+ it { expect(query.limit).to be nil }
317
+
318
+ it { expect(query.offset).to be nil }
319
+
320
+ it { expect(query.order).to be == default_order }
321
+
322
+ it { expect(query.scope).to be == subject.scope }
323
+
324
+ context 'when initialized with a scope' do
325
+ let(:initial_scope) do
326
+ Cuprum::Collections::Scope.new({ 'ok' => true })
327
+ end
328
+ let(:constructor_options) do
329
+ super().merge(scope: initial_scope)
330
+ end
331
+
332
+ it { expect(query.scope).to be == subject.scope }
333
+ end
334
+ end
335
+ end
336
+ end
337
+ end
338
+ end
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/rspec/deferred/parameter_validation_examples'
4
+ require 'rspec/sleeping_king_studios/deferred'
5
+
6
+ require 'cuprum/collections/rspec/deferred'
7
+
8
+ module Cuprum::Collections::RSpec::Deferred
9
+ # Deferred examples for testing collection commands.
10
+ module CommandExamples
11
+ include RSpec::SleepingKingStudios::Deferred::Provider
12
+ include Cuprum::RSpec::Deferred::ParameterValidationExamples
13
+
14
+ deferred_examples 'should implement the CollectionCommand methods' do
15
+ describe '#collection' do
16
+ include_examples 'should define reader', :collection, -> { collection }
17
+ end
18
+
19
+ describe '#name' do
20
+ include_examples 'should define reader', :name, -> { collection.name }
21
+
22
+ it 'should alias the method' do
23
+ expect(subject).to have_aliased_method(:name).as(:collection_name)
24
+ end
25
+ end
26
+
27
+ describe '#primary_key_name' do
28
+ include_examples 'should define reader',
29
+ :primary_key_name,
30
+ -> { collection.primary_key_name }
31
+ end
32
+
33
+ describe '#primary_key_type' do
34
+ include_examples 'should define reader',
35
+ :primary_key_type,
36
+ -> { collection.primary_key_type }
37
+ end
38
+
39
+ describe '#query' do
40
+ let(:mock_query) { instance_double(Cuprum::Collections::Query) }
41
+
42
+ before(:example) do
43
+ allow(collection).to receive(:query).and_return(mock_query)
44
+ end
45
+
46
+ it { expect(subject).to respond_to(:query).with(0).arguments }
47
+
48
+ it { expect(subject.query).to be mock_query }
49
+ end
50
+
51
+ describe '#singular_name' do
52
+ include_examples 'should define reader',
53
+ :singular_name,
54
+ -> { collection.singular_name }
55
+
56
+ it 'should alias the method' do
57
+ expect(subject)
58
+ .to have_aliased_method(:singular_name)
59
+ .as(:member_name)
60
+ end
61
+ end
62
+ end
63
+
64
+ deferred_examples 'should validate the attributes parameter' do
65
+ describe 'with an invalid attributes value' do
66
+ let(:attributes) { Object.new.freeze }
67
+
68
+ include_deferred 'should validate the parameter',
69
+ :attributes,
70
+ 'sleeping_king_studios.tools.assertions.instance_of',
71
+ expected: Hash
72
+ end
73
+
74
+ describe 'with an attributes value with invalid keys' do
75
+ let(:attributes) do
76
+ {
77
+ nil => 'NilClass',
78
+ 'string' => 'String',
79
+ symbol: 'Symbol'
80
+ }
81
+ end
82
+
83
+ include_deferred 'should validate the parameter',
84
+ 'attributes[nil] key',
85
+ 'sleeping_king_studios.tools.assertions.presence'
86
+ end
87
+ end
88
+
89
+ deferred_examples 'should validate the primary key parameter' do
90
+ describe 'with an invalid primary key value' do
91
+ let(:primary_key) { Object.new.freeze }
92
+ let(:expected_error) do
93
+ Cuprum::Errors::InvalidParameters.new(
94
+ command_class: command.class,
95
+ failures: [
96
+ tools.assertions.error_message_for(
97
+ 'sleeping_king_studios.tools.assertions.instance_of',
98
+ as: 'primary_key',
99
+ expected: collection.primary_key_type
100
+ )
101
+ ]
102
+ )
103
+ end
104
+
105
+ it 'should return a failing result with InvalidParameters error' do
106
+ expect(call_command)
107
+ .to be_a_failing_result
108
+ .with_error(expected_error)
109
+ end
110
+ end
111
+ end
112
+
113
+ deferred_examples 'should validate the primary keys parameter' do
114
+ describe 'with an invalid primary keys value' do
115
+ let(:primary_keys) { Object.new.freeze }
116
+
117
+ include_deferred 'should validate the parameter',
118
+ :primary_keys,
119
+ 'sleeping_king_studios.tools.assertions.instance_of',
120
+ expected: Array
121
+ end
122
+
123
+ describe 'with an attributes value with invalid keys' do
124
+ let(:valid_primary_key_value) do
125
+ defined?(super()) ? super() : 0
126
+ end
127
+ let(:primary_keys) do
128
+ [
129
+ nil,
130
+ Object.new.freeze,
131
+ valid_primary_key_value
132
+ ]
133
+ end
134
+ let(:expected_error) do
135
+ Cuprum::Errors::InvalidParameters.new(
136
+ command_class: command.class,
137
+ failures: [
138
+ tools.assertions.error_message_for(
139
+ 'sleeping_king_studios.tools.assertions.instance_of',
140
+ as: 'primary_keys[0]',
141
+ expected: collection.primary_key_type
142
+ ),
143
+ tools.assertions.error_message_for(
144
+ 'sleeping_king_studios.tools.assertions.instance_of',
145
+ as: 'primary_keys[1]',
146
+ expected: collection.primary_key_type
147
+ )
148
+ ]
149
+ )
150
+ end
151
+
152
+ it 'should return a failing result with InvalidParameters error' do
153
+ expect(call_command)
154
+ .to be_a_failing_result
155
+ .with_error(expected_error)
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/collections/rspec/deferred/command_examples'
4
+ require 'cuprum/collections/rspec/deferred/commands'
5
+
6
+ module Cuprum::Collections::RSpec::Deferred::Commands
7
+ # Namespace for deferred example groups for validating AssignOne commands.
8
+ module AssignOneExamples
9
+ include RSpec::SleepingKingStudios::Deferred::Provider
10
+
11
+ deferred_examples 'should implement the AssignOne command' \
12
+ do |allow_extra_attributes: true|
13
+ describe '#call' do
14
+ include Cuprum::Collections::RSpec::Deferred::CommandExamples
15
+
16
+ shared_examples 'should assign the attributes' do
17
+ it { expect(result).to be_a_passing_result }
18
+
19
+ it { expect(result.value).to be_a entity.class }
20
+
21
+ it { expect(result.value).to be == expected_value }
22
+ end
23
+
24
+ let(:attributes) { {} }
25
+ let(:result) do
26
+ command.call(attributes:, entity:)
27
+ end
28
+ let(:expected_attributes) do
29
+ initial_attributes.merge(attributes)
30
+ end
31
+ let(:expected_value) do
32
+ defined?(super()) ? super() : expected_attributes
33
+ end
34
+
35
+ def call_command
36
+ command.call(attributes:, entity:)
37
+ end
38
+
39
+ include_deferred 'should validate the attributes parameter'
40
+
41
+ if defined_deferred_examples? 'should validate the entity'
42
+ include_deferred 'should validate the entity'
43
+ else
44
+ # :nocov:
45
+ pending \
46
+ 'the command should validate the entity parameter, but entity ' \
47
+ 'validation is not defined - implement a "should validate the ' \
48
+ 'entity" deferred example group to resolve this warning'
49
+ # :nocov:
50
+ end
51
+
52
+ describe 'with an empty attributes hash' do
53
+ let(:attributes) { {} }
54
+
55
+ include_examples 'should assign the attributes'
56
+ end
57
+
58
+ describe 'with an attributes hash with partial attributes' do
59
+ let(:attributes) { { title: 'Gideon the Ninth' } }
60
+
61
+ include_examples 'should assign the attributes'
62
+ end
63
+
64
+ describe 'with an attributes hash with full attributes' do
65
+ let(:attributes) do
66
+ {
67
+ title: 'Gideon the Ninth',
68
+ author: 'Tamsyn Muir',
69
+ series: 'The Locked Tomb',
70
+ category: 'Horror'
71
+ }
72
+ end
73
+
74
+ include_examples 'should assign the attributes'
75
+ end
76
+
77
+ describe 'with an attributes hash with extra attributes' do
78
+ let(:attributes) do
79
+ {
80
+ title: 'The Book of Lost Tales',
81
+ audiobook: true
82
+ }
83
+ end
84
+
85
+ if allow_extra_attributes
86
+ include_examples 'should assign the attributes'
87
+ else
88
+ # :nocov:
89
+ let(:valid_attributes) do
90
+ defined?(super()) ? super() : expected_attributes.keys
91
+ end
92
+ let(:expected_error) do
93
+ Cuprum::Collections::Errors::ExtraAttributes.new(
94
+ entity_class: entity.class,
95
+ extra_attributes: %w[audiobook],
96
+ valid_attributes:
97
+ )
98
+ end
99
+
100
+ it 'should return a failing result' do
101
+ expect(result).to be_a_failing_result.with_error(expected_error)
102
+ end
103
+ # :nocov:
104
+ end
105
+ end
106
+
107
+ context 'when the entity has existing attributes' do
108
+ let(:initial_attributes) do
109
+ # :nocov:
110
+ if defined?(super())
111
+ super().merge(fixtures_data.first)
112
+ else
113
+ fixtures_data.first
114
+ end
115
+ # :nocov:
116
+ end
117
+
118
+ describe 'with an empty attributes hash' do
119
+ let(:attributes) { {} }
120
+
121
+ include_examples 'should assign the attributes'
122
+ end
123
+
124
+ describe 'with an attributes hash with partial attributes' do
125
+ let(:attributes) { { title: 'Gideon the Ninth' } }
126
+
127
+ include_examples 'should assign the attributes'
128
+ end
129
+
130
+ describe 'with an attributes hash with full attributes' do
131
+ let(:attributes) do
132
+ {
133
+ title: 'Gideon the Ninth',
134
+ author: 'Tamsyn Muir',
135
+ series: 'The Locked Tomb',
136
+ category: 'Horror'
137
+ }
138
+ end
139
+
140
+ include_examples 'should assign the attributes'
141
+ end
142
+
143
+ describe 'with an attributes hash with extra attributes' do
144
+ let(:attributes) do
145
+ {
146
+ title: 'The Book of Lost Tales',
147
+ audiobook: true
148
+ }
149
+ end
150
+
151
+ if allow_extra_attributes
152
+ include_examples 'should assign the attributes'
153
+ else
154
+ # :nocov:
155
+ let(:valid_attributes) do
156
+ defined?(super()) ? super() : expected_attributes.keys
157
+ end
158
+ let(:expected_error) do
159
+ Cuprum::Collections::Errors::ExtraAttributes.new(
160
+ entity_class: entity.class,
161
+ extra_attributes: %w[audiobook],
162
+ valid_attributes:
163
+ )
164
+ end
165
+
166
+ it 'should return a failing result' do
167
+ expect(result)
168
+ .to be_a_failing_result
169
+ .with_error(expected_error)
170
+ end
171
+ # :nocov:
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end