cuprum-collections 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|