cuprum-collections 0.3.0 → 0.4.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 +48 -0
- data/DEVELOPMENT.md +2 -2
- data/README.md +13 -11
- data/lib/cuprum/collections/association.rb +256 -0
- data/lib/cuprum/collections/associations/belongs_to.rb +32 -0
- data/lib/cuprum/collections/associations/has_many.rb +23 -0
- data/lib/cuprum/collections/associations/has_one.rb +23 -0
- data/lib/cuprum/collections/associations.rb +10 -0
- data/lib/cuprum/collections/basic/collection.rb +39 -74
- data/lib/cuprum/collections/basic/commands/find_many.rb +1 -1
- data/lib/cuprum/collections/basic/commands/find_matching.rb +1 -1
- data/lib/cuprum/collections/basic/repository.rb +9 -33
- data/lib/cuprum/collections/basic.rb +1 -0
- data/lib/cuprum/collections/collection.rb +154 -0
- data/lib/cuprum/collections/commands/associations/find_many.rb +161 -0
- data/lib/cuprum/collections/commands/associations/require_many.rb +48 -0
- data/lib/cuprum/collections/commands/associations.rb +13 -0
- data/lib/cuprum/collections/commands/find_one_matching.rb +1 -1
- data/lib/cuprum/collections/commands.rb +1 -0
- data/lib/cuprum/collections/errors/abstract_find_error.rb +1 -1
- data/lib/cuprum/collections/relation.rb +401 -0
- data/lib/cuprum/collections/repository.rb +71 -4
- data/lib/cuprum/collections/resource.rb +65 -0
- data/lib/cuprum/collections/rspec/contracts/association_contracts.rb +2137 -0
- data/lib/cuprum/collections/rspec/contracts/basic/command_contracts.rb +484 -0
- data/lib/cuprum/collections/rspec/contracts/basic.rb +11 -0
- data/lib/cuprum/collections/rspec/contracts/collection_contracts.rb +429 -0
- data/lib/cuprum/collections/rspec/contracts/command_contracts.rb +1462 -0
- data/lib/cuprum/collections/rspec/contracts/query_contracts.rb +1093 -0
- data/lib/cuprum/collections/rspec/contracts/relation_contracts.rb +1381 -0
- data/lib/cuprum/collections/rspec/contracts/repository_contracts.rb +605 -0
- data/lib/cuprum/collections/rspec/contracts.rb +23 -0
- data/lib/cuprum/collections/rspec/fixtures.rb +85 -82
- data/lib/cuprum/collections/rspec.rb +4 -1
- data/lib/cuprum/collections/version.rb +1 -1
- data/lib/cuprum/collections.rb +9 -4
- metadata +23 -19
- data/lib/cuprum/collections/base.rb +0 -11
- data/lib/cuprum/collections/basic/rspec/command_contract.rb +0 -392
- data/lib/cuprum/collections/rspec/assign_one_command_contract.rb +0 -168
- data/lib/cuprum/collections/rspec/build_one_command_contract.rb +0 -93
- data/lib/cuprum/collections/rspec/collection_contract.rb +0 -190
- data/lib/cuprum/collections/rspec/destroy_one_command_contract.rb +0 -108
- data/lib/cuprum/collections/rspec/find_many_command_contract.rb +0 -407
- data/lib/cuprum/collections/rspec/find_matching_command_contract.rb +0 -194
- data/lib/cuprum/collections/rspec/find_one_command_contract.rb +0 -157
- data/lib/cuprum/collections/rspec/insert_one_command_contract.rb +0 -84
- data/lib/cuprum/collections/rspec/query_builder_contract.rb +0 -92
- data/lib/cuprum/collections/rspec/query_contract.rb +0 -650
- data/lib/cuprum/collections/rspec/querying_contract.rb +0 -298
- data/lib/cuprum/collections/rspec/repository_contract.rb +0 -235
- data/lib/cuprum/collections/rspec/update_one_command_contract.rb +0 -80
- data/lib/cuprum/collections/rspec/validate_one_command_contract.rb +0 -96
@@ -0,0 +1,401 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
require 'cuprum/collections'
|
6
|
+
|
7
|
+
module Cuprum::Collections
|
8
|
+
# Abstract class representing a group or view of entities.
|
9
|
+
class Relation
|
10
|
+
# Methods for resolving a singular or plural relation.
|
11
|
+
module Cardinality
|
12
|
+
# @return [Boolean] true if the relation is plural; otherwise false.
|
13
|
+
def plural?
|
14
|
+
@plural
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [Boolean] true if the relation is singular; otherwise false.
|
18
|
+
def singular?
|
19
|
+
!@plural
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def resolve_plurality(**params) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
25
|
+
if params.key?(:plural) && !params[:plural].nil?
|
26
|
+
if params.key?(:singular) && !params[:singular].nil?
|
27
|
+
message =
|
28
|
+
'ambiguous cardinality: initialized with parameters ' \
|
29
|
+
"plural: #{params[:plural].inspect} and singular: " \
|
30
|
+
"#{params[:singular].inspect}"
|
31
|
+
|
32
|
+
raise ArgumentError, message
|
33
|
+
end
|
34
|
+
|
35
|
+
validate_cardinality(params[:plural], as: 'plural')
|
36
|
+
|
37
|
+
return params[:plural]
|
38
|
+
end
|
39
|
+
|
40
|
+
if params.key?(:singular) && !params[:singular].nil?
|
41
|
+
validate_cardinality(params[:singular], as: 'singular')
|
42
|
+
|
43
|
+
return !params[:singular]
|
44
|
+
end
|
45
|
+
|
46
|
+
true
|
47
|
+
end
|
48
|
+
|
49
|
+
def validate_cardinality(value, as:)
|
50
|
+
return if value == true || value == false # rubocop:disable Style/MultipleComparison
|
51
|
+
|
52
|
+
raise ArgumentError, "#{as} must be true or false"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Methods for disambiguating parameters with multiple keywords.
|
57
|
+
module Disambiguation
|
58
|
+
class << self
|
59
|
+
# Helper method for resolving an ambiguous keyword.
|
60
|
+
#
|
61
|
+
# @param params [Hash] the original method keywords.
|
62
|
+
# @param key [Symbol] the original key to resolve.
|
63
|
+
# @param alternatives [Symbol, Array<Symbol>] the additional keywords.
|
64
|
+
#
|
65
|
+
# @return [Hash] the disambiguated keywords.
|
66
|
+
def disambiguate_keyword(params, key, *alternatives) # rubocop:disable Metrics/MethodLength
|
67
|
+
params = params.dup
|
68
|
+
values = keyword_values(params, key, *alternatives)
|
69
|
+
|
70
|
+
return params if values.empty?
|
71
|
+
|
72
|
+
if values.size == 1
|
73
|
+
match, value = values.first
|
74
|
+
|
75
|
+
unless match == key
|
76
|
+
tools.core_tools.deprecate(
|
77
|
+
"#{match.inspect} keyword",
|
78
|
+
message: "Use #{key.inspect} instead"
|
79
|
+
)
|
80
|
+
end
|
81
|
+
|
82
|
+
return params.merge(key => value)
|
83
|
+
end
|
84
|
+
|
85
|
+
raise ArgumentError, ambiguous_keywords_error(values)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Helper method for resolving a Relation's required parameters.
|
89
|
+
#
|
90
|
+
# The returned Hash will define the :entity_class, :singular_name,
|
91
|
+
# :name, and :qualified_name keys.
|
92
|
+
#
|
93
|
+
# @param params [Hash] the parameters to resolve.
|
94
|
+
# @param ambiguous [Hash{Symbol => Symbol, Array<Symbol>}] ambiguous
|
95
|
+
# keywords to resolve. Each key-value pair is passed to
|
96
|
+
# .disambiguate_keyword before the parameters are resolved.
|
97
|
+
#
|
98
|
+
# @return [Hash] the resolved parameters.
|
99
|
+
#
|
100
|
+
# @see .disambiguate_keyword
|
101
|
+
# @see Cuprum::Collections::Relation::Parameters.resolve_parameters
|
102
|
+
def resolve_parameters(params, **ambiguous)
|
103
|
+
params = ambiguous.reduce(params) do |hsh, (key, alternatives)|
|
104
|
+
disambiguate_keyword(hsh, key, *alternatives)
|
105
|
+
end
|
106
|
+
|
107
|
+
Cuprum::Collections::Relation::Parameters.resolve_parameters(params)
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def ambiguous_keywords_error(values)
|
113
|
+
expected, _ = values.first
|
114
|
+
formatted =
|
115
|
+
values
|
116
|
+
.map { |key, value| "#{key}: #{value.inspect}" }
|
117
|
+
.join(', ')
|
118
|
+
|
119
|
+
"ambiguous parameter #{expected}: initialized with parameters " \
|
120
|
+
"#{formatted}"
|
121
|
+
end
|
122
|
+
|
123
|
+
def keyword_values(keywords, *keys)
|
124
|
+
keys
|
125
|
+
.map { |key| [key, keywords.delete(key)] }
|
126
|
+
.reject { |_, value| value.nil? } # rubocop:disable Style/CollectionCompact
|
127
|
+
end
|
128
|
+
|
129
|
+
def tools
|
130
|
+
SleepingKingStudios::Tools::Toolbelt.instance
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# (see Cuprum::Collections::Relation::Disambiguation.disambiguate_keyword)
|
135
|
+
def disambiguate_keyword(params, key, *alternatives)
|
136
|
+
Disambiguation.disambiguate_keyword(params, key, *alternatives)
|
137
|
+
end
|
138
|
+
|
139
|
+
# (see Cuprum::Collections::Relation::Disambiguation.resolve_parameters)
|
140
|
+
def resolve_parameters(params, **ambiguous)
|
141
|
+
Disambiguation.resolve_parameters(params, **ambiguous)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Methods for resolving a relations's naming and entity class from options.
|
146
|
+
module Parameters # rubocop:disable Metrics/ModuleLength
|
147
|
+
PARAMETER_KEYS = %i[entity_class name qualified_name].freeze
|
148
|
+
private_constant :PARAMETER_KEYS
|
149
|
+
|
150
|
+
class << self
|
151
|
+
# @overload resolve_parameters(entity_class: nil, singular_name: nil, name: nil, qualified_name: nil)
|
152
|
+
# Helper method for resolving a Relation's required parameters.
|
153
|
+
#
|
154
|
+
# The returned Hash will define the :entity_class, :singular_name,
|
155
|
+
# :name, and :qualified_name keys.
|
156
|
+
#
|
157
|
+
# @param entity_class [Class, String] the class of entity represented
|
158
|
+
# by the relation.
|
159
|
+
# @param singular_name [String] the name of an entity in the relation.
|
160
|
+
# @param name [String] the name of the relation.
|
161
|
+
# @param qualified_name [String] a scoped name for the relation.
|
162
|
+
#
|
163
|
+
# @return [Hash] the resolved parameters.
|
164
|
+
def resolve_parameters(params) # rubocop:disable Metrics/MethodLength
|
165
|
+
validate_parameters(**params)
|
166
|
+
|
167
|
+
entity_class = entity_class_from(**params)
|
168
|
+
class_name = entity_class_name(entity_class)
|
169
|
+
name = relation_name_from(**params, class_name: class_name)
|
170
|
+
plural_name = plural_name_from(**params, name: name)
|
171
|
+
qualified_name = qualified_name_from(**params, class_name: class_name)
|
172
|
+
singular_name = singular_name_from(**params, name: name)
|
173
|
+
|
174
|
+
{
|
175
|
+
entity_class: entity_class,
|
176
|
+
name: name,
|
177
|
+
plural_name: plural_name,
|
178
|
+
qualified_name: qualified_name,
|
179
|
+
singular_name: singular_name
|
180
|
+
}
|
181
|
+
end
|
182
|
+
|
183
|
+
private
|
184
|
+
|
185
|
+
def classify(raw)
|
186
|
+
raw
|
187
|
+
.then { |str| tools.string_tools.singularize(str).to_s }
|
188
|
+
.split('/')
|
189
|
+
.map { |str| tools.string_tools.camelize(str) }
|
190
|
+
.join('::')
|
191
|
+
end
|
192
|
+
|
193
|
+
def entity_class_from(**params)
|
194
|
+
if has_key?(params, :entity_class)
|
195
|
+
entity_class = params[:entity_class]
|
196
|
+
|
197
|
+
return entity_class.is_a?(Class) ? entity_class : entity_class.to_s
|
198
|
+
end
|
199
|
+
|
200
|
+
if has_key?(params, :qualified_name)
|
201
|
+
return classify(params[:qualified_name])
|
202
|
+
end
|
203
|
+
|
204
|
+
classify(params[:name])
|
205
|
+
end
|
206
|
+
|
207
|
+
def entity_class_name(entity_class, scoped: true)
|
208
|
+
(entity_class.is_a?(Class) ? entity_class.name : entity_class)
|
209
|
+
.split('::')
|
210
|
+
.map { |str| tools.string_tools.underscore(str) }
|
211
|
+
.then { |ary| scoped ? ary.join('/') : ary.last }
|
212
|
+
end
|
213
|
+
|
214
|
+
def has_key?(params, key) # rubocop:disable Naming/PredicateName
|
215
|
+
return false unless params.key?(key)
|
216
|
+
|
217
|
+
!params[key].nil?
|
218
|
+
end
|
219
|
+
|
220
|
+
def plural_name_from(name:, **parameters)
|
221
|
+
if parameters.key?(:plural_name) && !parameters[:plural_name].nil?
|
222
|
+
return validate_parameter(
|
223
|
+
parameters[:plural_name],
|
224
|
+
as: 'plural name'
|
225
|
+
)
|
226
|
+
end
|
227
|
+
|
228
|
+
tools.string_tools.pluralize(name)
|
229
|
+
end
|
230
|
+
|
231
|
+
def qualified_name_from(class_name:, **params)
|
232
|
+
if has_key?(params, :qualified_name)
|
233
|
+
return params[:qualified_name].to_s
|
234
|
+
end
|
235
|
+
|
236
|
+
tools.string_tools.pluralize(class_name)
|
237
|
+
end
|
238
|
+
|
239
|
+
def relation_name_from(class_name:, **params)
|
240
|
+
return params[:name].to_s if has_key?(params, :name)
|
241
|
+
|
242
|
+
tools.string_tools.pluralize(class_name.split('/').last)
|
243
|
+
end
|
244
|
+
|
245
|
+
def singular_name_from(name:, **parameters)
|
246
|
+
if parameters.key?(:singular_name) && !parameters[:singular_name].nil?
|
247
|
+
return validate_parameter(
|
248
|
+
parameters[:singular_name],
|
249
|
+
as: 'singular name'
|
250
|
+
)
|
251
|
+
end
|
252
|
+
|
253
|
+
tools.string_tools.singularize(name)
|
254
|
+
end
|
255
|
+
|
256
|
+
def tools
|
257
|
+
SleepingKingStudios::Tools::Toolbelt.instance
|
258
|
+
end
|
259
|
+
|
260
|
+
def validate_entity_class(value)
|
261
|
+
return if value.is_a?(Class)
|
262
|
+
|
263
|
+
if value.nil? || value.is_a?(String) || value.is_a?(Symbol)
|
264
|
+
tools.assertions.validate_name(value, as: 'entity class')
|
265
|
+
|
266
|
+
return
|
267
|
+
end
|
268
|
+
|
269
|
+
raise ArgumentError,
|
270
|
+
'entity class is not a Class, a String or a Symbol'
|
271
|
+
end
|
272
|
+
|
273
|
+
def validate_parameter(value, as:)
|
274
|
+
tools.assertions.validate_name(value, as: as)
|
275
|
+
|
276
|
+
value.to_s
|
277
|
+
end
|
278
|
+
|
279
|
+
def validate_parameter_keys(params)
|
280
|
+
return if PARAMETER_KEYS.any? { |key| has_key?(params, key) }
|
281
|
+
|
282
|
+
raise ArgumentError, "name or entity class can't be blank"
|
283
|
+
end
|
284
|
+
|
285
|
+
def validate_parameters(**params) # rubocop:disable Metrics/MethodLength
|
286
|
+
validate_parameter_keys(params)
|
287
|
+
|
288
|
+
if has_key?(params, :entity_class)
|
289
|
+
validate_entity_class(params[:entity_class])
|
290
|
+
end
|
291
|
+
|
292
|
+
if has_key?(params, :name)
|
293
|
+
validate_parameter(params[:name], as: 'name')
|
294
|
+
end
|
295
|
+
|
296
|
+
if has_key?(params, :plural_name)
|
297
|
+
validate_parameter(params[:plural_name], as: 'plural name')
|
298
|
+
end
|
299
|
+
|
300
|
+
if has_key?(params, :qualified_name)
|
301
|
+
validate_parameter(params[:qualified_name], as: 'qualified name')
|
302
|
+
end
|
303
|
+
|
304
|
+
if has_key?(params, :singular_name) # rubocop:disable Style/GuardClause
|
305
|
+
validate_parameter(params[:singular_name], as: 'singular name')
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
# @return [String] the name of the relation.
|
311
|
+
attr_reader :name
|
312
|
+
|
313
|
+
# @return [String] the pluralized name of the relation.
|
314
|
+
attr_reader :plural_name
|
315
|
+
|
316
|
+
# @return [String] a scoped name for the relation.
|
317
|
+
attr_reader :qualified_name
|
318
|
+
|
319
|
+
# @return [String] the name of an entity in the relation.
|
320
|
+
attr_reader :singular_name
|
321
|
+
|
322
|
+
# @return [Class] the class of entity represented by the relation.
|
323
|
+
def entity_class
|
324
|
+
return @entity_class if @entity_class.is_a?(Class)
|
325
|
+
|
326
|
+
@entity_class = Object.const_get(@entity_class)
|
327
|
+
end
|
328
|
+
|
329
|
+
# (see Cuprum::Collections::Relation::Parameters.resolve_parameters)
|
330
|
+
def resolve_parameters(parameters)
|
331
|
+
Parameters.resolve_parameters(parameters)
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
# Methods for specifying a relation's primary key.
|
336
|
+
module PrimaryKeys
|
337
|
+
# @return [String] the name of the primary key attribute. Defaults to
|
338
|
+
# 'id'.
|
339
|
+
def primary_key_name
|
340
|
+
@primary_key_name ||= options.fetch(:primary_key_name, 'id').to_s
|
341
|
+
end
|
342
|
+
|
343
|
+
# @return [Class, Stannum::Constraint] the type of the primary key
|
344
|
+
# attribute. Defaults to Integer.
|
345
|
+
def primary_key_type
|
346
|
+
@primary_key_type ||=
|
347
|
+
options
|
348
|
+
.fetch(:primary_key_type, Integer)
|
349
|
+
.then { |obj| obj.is_a?(String) ? Object.const_get(obj) : obj }
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
IGNORED_PARAMETERS = %i[
|
354
|
+
entity_class
|
355
|
+
name
|
356
|
+
qualified_name
|
357
|
+
singular_name
|
358
|
+
].freeze
|
359
|
+
private_constant :IGNORED_PARAMETERS
|
360
|
+
|
361
|
+
include Cuprum::Collections::Relation::Parameters
|
362
|
+
|
363
|
+
# @overload initialize(entity_class: nil, name: nil, qualified_name: nil, singular_name: nil, **options)
|
364
|
+
# @param entity_class [Class, String] the class of entity represented by
|
365
|
+
# the relation.
|
366
|
+
# @param name [String] the name of the relation.
|
367
|
+
# @param qualified_name [String] a scoped name for the relation.
|
368
|
+
# @param singular_name [String] the name of an entity in the relation.
|
369
|
+
# @param options [Hash] additional options for the relation.
|
370
|
+
def initialize(**parameters)
|
371
|
+
relation_params = resolve_parameters(parameters)
|
372
|
+
|
373
|
+
@entity_class = relation_params[:entity_class]
|
374
|
+
@name = relation_params[:name]
|
375
|
+
@plural_name = relation_params[:plural_name]
|
376
|
+
@qualified_name = relation_params[:qualified_name]
|
377
|
+
@singular_name = relation_params[:singular_name]
|
378
|
+
|
379
|
+
@options = ignore_parameters(**parameters)
|
380
|
+
end
|
381
|
+
|
382
|
+
# @return [Hash] additional options for the relation.
|
383
|
+
attr_reader :options
|
384
|
+
|
385
|
+
private
|
386
|
+
|
387
|
+
def ignore_parameters(**parameters)
|
388
|
+
parameters
|
389
|
+
.reject { |key, _| ignored_parameters.include?(key) }
|
390
|
+
.to_h
|
391
|
+
end
|
392
|
+
|
393
|
+
def ignored_parameters
|
394
|
+
@ignored_parameters ||= Set.new(IGNORED_PARAMETERS)
|
395
|
+
end
|
396
|
+
|
397
|
+
def tools
|
398
|
+
SleepingKingStudios::Tools::Toolbelt.instance
|
399
|
+
end
|
400
|
+
end
|
401
|
+
end
|
@@ -3,6 +3,7 @@
|
|
3
3
|
require 'forwardable'
|
4
4
|
|
5
5
|
require 'cuprum/collections'
|
6
|
+
require 'cuprum/collections/relation'
|
6
7
|
|
7
8
|
module Cuprum::Collections
|
8
9
|
# A repository represents a group of collections.
|
@@ -16,6 +17,9 @@ module Cuprum::Collections
|
|
16
17
|
class Repository
|
17
18
|
extend Forwardable
|
18
19
|
|
20
|
+
# Error raised when trying to call an abstract repository method.
|
21
|
+
class AbstractRepositoryError < StandardError; end
|
22
|
+
|
19
23
|
# Error raised when trying to add an existing collection to the repository.
|
20
24
|
class DuplicateCollectionError < StandardError; end
|
21
25
|
|
@@ -57,12 +61,12 @@ module Cuprum::Collections
|
|
57
61
|
# The collection must implement the #collection_name property. Repository
|
58
62
|
# subclasses may enforce additional requirements.
|
59
63
|
#
|
60
|
-
# @param collection [
|
61
|
-
# repository.
|
62
|
-
# @param force [true, false]
|
64
|
+
# @param collection [Cuprum::Collections::Collection] the collection to add
|
65
|
+
# to the repository.
|
66
|
+
# @param force [true, false] if true, override an existing collection with
|
63
67
|
# the same name.
|
64
68
|
#
|
65
|
-
# @return [Cuprum::
|
69
|
+
# @return [Cuprum::Collections::Repository] the repository.
|
66
70
|
#
|
67
71
|
# @raise [DuplicateCollectionError] if a collection with the same name
|
68
72
|
# already exists in the repository.
|
@@ -80,6 +84,57 @@ module Cuprum::Collections
|
|
80
84
|
end
|
81
85
|
alias << add
|
82
86
|
|
87
|
+
# @overload create(collection_name: nil, entity_class: nil, force: false, **options)
|
88
|
+
# Adds a new collection with the given name to the repository.
|
89
|
+
#
|
90
|
+
# @param collection_name [String] the name of the new collection.
|
91
|
+
# @param entity_class [Class, String] the class of entity represented in
|
92
|
+
# the collection.
|
93
|
+
# @param force [true, false] if true, override an existing collection with
|
94
|
+
# the same name.
|
95
|
+
# @param options [Hash] additional options to pass to Collection.new.
|
96
|
+
#
|
97
|
+
# @return [Cuprum::Collections::Collection] the created collection.
|
98
|
+
#
|
99
|
+
# @raise [DuplicateCollectionError] if a collection with the same name
|
100
|
+
# already exists in the repository.
|
101
|
+
def create(force: false, **options)
|
102
|
+
collection = build_collection(**options)
|
103
|
+
|
104
|
+
add(collection, force: force)
|
105
|
+
|
106
|
+
collection
|
107
|
+
end
|
108
|
+
|
109
|
+
# @overload find_or_create(collection_name: nil, entity_class: nil, **options)
|
110
|
+
# Finds or creates a new collection with the given name.
|
111
|
+
#
|
112
|
+
# @param collection_name [String] the name of the new collection.
|
113
|
+
# @param entity_class [Class, String] the class of entity represented in
|
114
|
+
# the collection.
|
115
|
+
# @param options [Hash] additional options to pass to Collection.new.
|
116
|
+
#
|
117
|
+
# @return [Cuprum::Collections::Collection] the created collection.
|
118
|
+
#
|
119
|
+
# @raise [DuplicateCollectionError] if a collection with the same name
|
120
|
+
# but different parameters already exists in the repository.
|
121
|
+
def find_or_create(**parameters)
|
122
|
+
qualified_name = qualified_name_for(**parameters)
|
123
|
+
|
124
|
+
unless key?(qualified_name)
|
125
|
+
create(**parameters)
|
126
|
+
|
127
|
+
return @collections[qualified_name]
|
128
|
+
end
|
129
|
+
|
130
|
+
collection = @collections[qualified_name]
|
131
|
+
|
132
|
+
return collection if collection.matches?(**parameters)
|
133
|
+
|
134
|
+
raise DuplicateCollectionError,
|
135
|
+
"collection #{qualified_name} already exists"
|
136
|
+
end
|
137
|
+
|
83
138
|
# Checks if a collection with the given name exists in the repository.
|
84
139
|
#
|
85
140
|
# @param qualified_name [String, Symbol] The name to check for.
|
@@ -91,6 +146,18 @@ module Cuprum::Collections
|
|
91
146
|
|
92
147
|
private
|
93
148
|
|
149
|
+
def build_collection(**)
|
150
|
+
raise AbstractRepositoryError,
|
151
|
+
"#{self.class.name} is an abstract class. Define a repository " \
|
152
|
+
'subclass and implement the #build_collection method.'
|
153
|
+
end
|
154
|
+
|
155
|
+
def qualified_name_for(**parameters)
|
156
|
+
Cuprum::Collections::Relation::Disambiguation
|
157
|
+
.resolve_parameters(parameters, name: :collection_name)
|
158
|
+
.fetch(:qualified_name)
|
159
|
+
end
|
160
|
+
|
94
161
|
def valid_collection?(collection)
|
95
162
|
collection.respond_to?(:collection_name)
|
96
163
|
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections'
|
4
|
+
require 'cuprum/collections/relation'
|
5
|
+
|
6
|
+
module Cuprum::Collections
|
7
|
+
# Class representing a singular or plural resource of entities.
|
8
|
+
class Resource < Cuprum::Collections::Relation
|
9
|
+
include Cuprum::Collections::Relation::Cardinality
|
10
|
+
include Cuprum::Collections::Relation::Disambiguation
|
11
|
+
include Cuprum::Collections::Relation::PrimaryKeys
|
12
|
+
|
13
|
+
# @overload initialize(entity_class: nil, name: nil, qualified_name: nil, singular_name: nil, **options)
|
14
|
+
# @param entity_class [Class, String] the class of entity represented by
|
15
|
+
# the resource.
|
16
|
+
# @param name [String] the name of the resource. Aliased as
|
17
|
+
# :resource_name.
|
18
|
+
# @param qualified_name [String] a scoped name for the resource.
|
19
|
+
# @param singular_name [String] the name of an entity in the resource.
|
20
|
+
# @param options [Hash] additional options for the resource.
|
21
|
+
#
|
22
|
+
# @option options plural [Boolean] if true, the resource represents a
|
23
|
+
# plural resource. Defaults to true. Can also be specified as :singular.
|
24
|
+
# @option options primary_key_name [String] the name of the primary key
|
25
|
+
# attribute. Defaults to 'id'.
|
26
|
+
# @option primary_key_type [Class, Stannum::Constraint] the type of
|
27
|
+
# the primary key attribute. Defaults to Integer.
|
28
|
+
def initialize(**params)
|
29
|
+
params = disambiguate_keyword(params, :entity_class, :resource_class)
|
30
|
+
params = disambiguate_keyword(params, :name, :resource_name)
|
31
|
+
params = disambiguate_keyword(
|
32
|
+
params,
|
33
|
+
:singular_name,
|
34
|
+
:singular_resource_name
|
35
|
+
)
|
36
|
+
@plural = resolve_plurality(**params)
|
37
|
+
|
38
|
+
super(**params)
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [Class] the class of entity represented by the resource.
|
42
|
+
def resource_class
|
43
|
+
tools.core_tools.deprecate '#resource_class method',
|
44
|
+
message: 'Use #entity_class instead'
|
45
|
+
|
46
|
+
entity_class
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [String] the name of the resource.
|
50
|
+
def resource_name
|
51
|
+
tools.core_tools.deprecate '#resource_name method',
|
52
|
+
message: 'Use #name instead'
|
53
|
+
|
54
|
+
name
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return[String] the name of an entity in the resource.
|
58
|
+
def singular_resource_name
|
59
|
+
tools.core_tools.deprecate '#singular_resource_name method',
|
60
|
+
message: 'Use #singular_name instead'
|
61
|
+
|
62
|
+
singular_name
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|