cuprum-collections 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +73 -0
- data/README.md +5 -5
- data/lib/cuprum/collections/association.rb +9 -28
- data/lib/cuprum/collections/associations/belongs_to.rb +1 -8
- data/lib/cuprum/collections/associations/has_many.rb +1 -10
- data/lib/cuprum/collections/associations/has_one.rb +1 -10
- data/lib/cuprum/collections/basic/collection.rb +56 -49
- data/lib/cuprum/collections/basic/command.rb +22 -88
- data/lib/cuprum/collections/basic/commands/assign_one.rb +2 -6
- data/lib/cuprum/collections/basic/commands/build_one.rb +1 -4
- data/lib/cuprum/collections/basic/commands/destroy_one.rb +4 -8
- data/lib/cuprum/collections/basic/commands/find_many.rb +4 -24
- data/lib/cuprum/collections/basic/commands/find_matching.rb +5 -21
- data/lib/cuprum/collections/basic/commands/find_one.rb +3 -20
- data/lib/cuprum/collections/basic/commands/insert_one.rb +3 -6
- data/lib/cuprum/collections/basic/commands/update_one.rb +3 -6
- data/lib/cuprum/collections/basic/commands/validate_one.rb +13 -18
- data/lib/cuprum/collections/basic/query.rb +26 -40
- data/lib/cuprum/collections/basic/repository.rb +4 -3
- data/lib/cuprum/collections/basic/scopes/all_scope.rb +25 -0
- data/lib/cuprum/collections/basic/scopes/base.rb +32 -0
- data/lib/cuprum/collections/basic/scopes/builder.rb +39 -0
- data/lib/cuprum/collections/basic/scopes/conjunction_scope.rb +20 -0
- data/lib/cuprum/collections/basic/scopes/criteria_scope.rb +62 -0
- data/lib/cuprum/collections/basic/scopes/disjunction_scope.rb +20 -0
- data/lib/cuprum/collections/basic/scopes/none_scope.rb +33 -0
- data/lib/cuprum/collections/basic/scopes.rb +23 -0
- data/lib/cuprum/collections/basic.rb +1 -0
- data/lib/cuprum/collections/collection.rb +24 -82
- data/lib/cuprum/collections/collection_command.rb +116 -0
- data/lib/cuprum/collections/commands/abstract_find_many.rb +11 -21
- data/lib/cuprum/collections/commands/abstract_find_matching.rb +43 -24
- data/lib/cuprum/collections/commands/abstract_find_one.rb +7 -10
- data/lib/cuprum/collections/commands/associations/find_many.rb +3 -8
- data/lib/cuprum/collections/commands/associations/require_many.rb +5 -5
- data/lib/cuprum/collections/commands/create.rb +3 -3
- data/lib/cuprum/collections/commands/find_one_matching.rb +6 -6
- data/lib/cuprum/collections/commands/query_command.rb +19 -0
- data/lib/cuprum/collections/commands/update.rb +3 -3
- data/lib/cuprum/collections/commands/upsert.rb +10 -10
- data/lib/cuprum/collections/commands.rb +1 -0
- data/lib/cuprum/collections/constraints/ordering.rb +2 -2
- data/lib/cuprum/collections/errors/abstract_find_error.rb +25 -42
- data/lib/cuprum/collections/errors/extra_attributes.rb +3 -3
- data/lib/cuprum/collections/errors/failed_validation.rb +2 -2
- data/lib/cuprum/collections/errors/invalid_parameters.rb +2 -2
- data/lib/cuprum/collections/errors/invalid_query.rb +10 -16
- data/lib/cuprum/collections/errors/missing_default_contract.rb +1 -1
- data/lib/cuprum/collections/errors/unknown_operator.rb +1 -1
- data/lib/cuprum/collections/queries.rb +31 -0
- data/lib/cuprum/collections/query.rb +50 -62
- data/lib/cuprum/collections/relation.rb +5 -383
- data/lib/cuprum/collections/relations/cardinality.rb +66 -0
- data/lib/cuprum/collections/relations/options.rb +18 -0
- data/lib/cuprum/collections/relations/parameters.rb +217 -0
- data/lib/cuprum/collections/relations/primary_keys.rb +23 -0
- data/lib/cuprum/collections/relations/scope.rb +65 -0
- data/lib/cuprum/collections/relations.rb +14 -0
- data/lib/cuprum/collections/repository.rb +5 -5
- data/lib/cuprum/collections/resource.rb +10 -41
- data/lib/cuprum/collections/rspec/contracts/association_contracts.rb +80 -90
- data/lib/cuprum/collections/rspec/contracts/collection_contracts.rb +69 -111
- data/lib/cuprum/collections/rspec/contracts/command_contracts.rb +42 -1335
- data/lib/cuprum/collections/rspec/contracts/query_contracts.rb +352 -531
- data/lib/cuprum/collections/rspec/contracts/relation_contracts.rb +74 -191
- data/lib/cuprum/collections/rspec/contracts/repository_contracts.rb +13 -13
- data/lib/cuprum/collections/rspec/contracts/scope_contracts.rb +1029 -0
- data/lib/cuprum/collections/rspec/contracts/scopes/builder_contracts.rb +856 -0
- data/lib/cuprum/collections/rspec/contracts/scopes/composition_contracts.rb +1430 -0
- data/lib/cuprum/collections/rspec/contracts/scopes/criteria_contracts.rb +2217 -0
- data/lib/cuprum/collections/rspec/contracts/scopes/logical_contracts.rb +297 -0
- data/lib/cuprum/collections/rspec/contracts/scopes.rb +13 -0
- data/lib/cuprum/collections/rspec/contracts.rb +2 -0
- data/lib/cuprum/collections/rspec/deferred/association_examples.rb +2098 -0
- data/lib/cuprum/collections/rspec/deferred/collection_examples.rb +338 -0
- data/lib/cuprum/collections/rspec/deferred/command_examples.rb +160 -0
- data/lib/cuprum/collections/rspec/deferred/commands/assign_one_examples.rb +178 -0
- data/lib/cuprum/collections/rspec/deferred/commands/build_one_examples.rb +94 -0
- data/lib/cuprum/collections/rspec/deferred/commands/destroy_one_examples.rb +118 -0
- data/lib/cuprum/collections/rspec/deferred/commands/find_many_examples.rb +307 -0
- data/lib/cuprum/collections/rspec/deferred/commands/find_matching_examples.rb +143 -0
- data/lib/cuprum/collections/rspec/deferred/commands/find_one_examples.rb +116 -0
- data/lib/cuprum/collections/rspec/deferred/commands/insert_one_examples.rb +103 -0
- data/lib/cuprum/collections/rspec/deferred/commands/update_one_examples.rb +99 -0
- data/lib/cuprum/collections/rspec/deferred/commands/validate_one_examples.rb +117 -0
- data/lib/cuprum/collections/rspec/deferred/commands.rb +8 -0
- data/lib/cuprum/collections/rspec/deferred/relation_examples.rb +1437 -0
- data/lib/cuprum/collections/rspec/deferred/resource_examples.rb +26 -0
- data/lib/cuprum/collections/rspec/deferred.rb +8 -0
- data/lib/cuprum/collections/scope.rb +29 -0
- data/lib/cuprum/collections/scopes/all.rb +51 -0
- data/lib/cuprum/collections/scopes/all_scope.rb +18 -0
- data/lib/cuprum/collections/scopes/base.rb +79 -0
- data/lib/cuprum/collections/scopes/builder.rb +39 -0
- data/lib/cuprum/collections/scopes/building.rb +221 -0
- data/lib/cuprum/collections/scopes/composition.rb +162 -0
- data/lib/cuprum/collections/scopes/conjunction.rb +44 -0
- data/lib/cuprum/collections/scopes/conjunction_scope.rb +12 -0
- data/lib/cuprum/collections/scopes/container.rb +65 -0
- data/lib/cuprum/collections/scopes/criteria/parser.rb +241 -0
- data/lib/cuprum/collections/scopes/criteria.rb +206 -0
- data/lib/cuprum/collections/scopes/criteria_scope.rb +12 -0
- data/lib/cuprum/collections/scopes/disjunction.rb +45 -0
- data/lib/cuprum/collections/scopes/disjunction_scope.rb +12 -0
- data/lib/cuprum/collections/scopes/none.rb +62 -0
- data/lib/cuprum/collections/scopes/none_scope.rb +18 -0
- data/lib/cuprum/collections/scopes.rb +23 -0
- data/lib/cuprum/collections/version.rb +2 -2
- data/lib/cuprum/collections.rb +14 -9
- metadata +61 -15
- data/lib/cuprum/collections/basic/query_builder.rb +0 -69
- data/lib/cuprum/collections/command.rb +0 -26
- data/lib/cuprum/collections/queries/parse.rb +0 -22
- data/lib/cuprum/collections/queries/parse_block.rb +0 -206
- data/lib/cuprum/collections/queries/parse_strategy.rb +0 -91
- data/lib/cuprum/collections/query_builder.rb +0 -61
- data/lib/cuprum/collections/rspec/contracts/basic/command_contracts.rb +0 -484
@@ -3,399 +3,21 @@
|
|
3
3
|
require 'set'
|
4
4
|
|
5
5
|
require 'cuprum/collections'
|
6
|
+
require 'cuprum/collections/relations/options'
|
7
|
+
require 'cuprum/collections/relations/parameters'
|
6
8
|
|
7
9
|
module Cuprum::Collections
|
8
10
|
# Abstract class representing a group or view of entities.
|
9
11
|
class Relation
|
10
|
-
|
11
|
-
|
12
|
-
# @return [Boolean] true if the relation is plural; otherwise false.
|
13
|
-
def plural?
|
14
|
-
@plural
|
15
|
-
end
|
12
|
+
include Cuprum::Collections::Relations::Options
|
13
|
+
include Cuprum::Collections::Relations::Parameters
|
16
14
|
|
17
|
-
|
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)
|
15
|
+
# @!method initialize(entity_class: nil, name: nil, qualified_name: nil, singular_name: nil, **options)
|
364
16
|
# @param entity_class [Class, String] the class of entity represented by
|
365
17
|
# the relation.
|
366
18
|
# @param name [String] the name of the relation.
|
367
19
|
# @param qualified_name [String] a scoped name for the relation.
|
368
20
|
# @param singular_name [String] the name of an entity in the relation.
|
369
21
|
# @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
22
|
end
|
401
23
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections/relations'
|
4
|
+
|
5
|
+
module Cuprum::Collections::Relations
|
6
|
+
# Methods for resolving a singular or plural relation.
|
7
|
+
module Cardinality
|
8
|
+
IGNORED_PARAMETERS = %i[
|
9
|
+
plural
|
10
|
+
singular
|
11
|
+
].freeze
|
12
|
+
private_constant :IGNORED_PARAMETERS
|
13
|
+
|
14
|
+
# @overload initialize(plural: true, **)
|
15
|
+
# @param plural [Boolean] if true, the resource represents a plural
|
16
|
+
# resource. Defaults to true. Can also be specified as :singular.
|
17
|
+
def initialize(**parameters)
|
18
|
+
super(**parameters.except(*IGNORED_PARAMETERS))
|
19
|
+
|
20
|
+
@plural = resolve_plurality(**parameters)
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [Boolean] true if the relation is plural; otherwise false.
|
24
|
+
def plural?
|
25
|
+
@plural
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [Boolean] true if the relation is singular; otherwise false.
|
29
|
+
def singular?
|
30
|
+
!@plural
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def resolve_plurality(**params) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
36
|
+
if params.key?(:plural) && !params[:plural].nil?
|
37
|
+
if params.key?(:singular) && !params[:singular].nil?
|
38
|
+
message =
|
39
|
+
'ambiguous cardinality: initialized with parameters ' \
|
40
|
+
"plural: #{params[:plural].inspect} and singular: " \
|
41
|
+
"#{params[:singular].inspect}"
|
42
|
+
|
43
|
+
raise ArgumentError, message
|
44
|
+
end
|
45
|
+
|
46
|
+
validate_cardinality(params[:plural], as: 'plural')
|
47
|
+
|
48
|
+
return params[:plural]
|
49
|
+
end
|
50
|
+
|
51
|
+
if params.key?(:singular) && !params[:singular].nil?
|
52
|
+
validate_cardinality(params[:singular], as: 'singular')
|
53
|
+
|
54
|
+
return !params[:singular]
|
55
|
+
end
|
56
|
+
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
60
|
+
def validate_cardinality(value, as:)
|
61
|
+
return if value == true || value == false # rubocop:disable Style/MultipleComparison
|
62
|
+
|
63
|
+
raise ArgumentError, "#{as} must be true or false"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections/relations'
|
4
|
+
|
5
|
+
module Cuprum::Collections::Relations
|
6
|
+
# Methods for storing arbitrary options for a relation.
|
7
|
+
module Options
|
8
|
+
# @param options [Hash] additional options for the relation.
|
9
|
+
def initialize(**options)
|
10
|
+
super()
|
11
|
+
|
12
|
+
@options = options
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [Hash] additional options for the relation.
|
16
|
+
attr_reader :options
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,217 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections/relations'
|
4
|
+
|
5
|
+
module Cuprum::Collections::Relations
|
6
|
+
# Methods for resolving a relations's naming and entity class from options.
|
7
|
+
module Parameters # rubocop:disable Metrics/ModuleLength
|
8
|
+
IGNORED_PARAMETERS = %i[
|
9
|
+
entity_class
|
10
|
+
name
|
11
|
+
qualified_name
|
12
|
+
singular_name
|
13
|
+
].freeze
|
14
|
+
private_constant :IGNORED_PARAMETERS
|
15
|
+
|
16
|
+
PARAMETER_KEYS = %i[entity_class name qualified_name].freeze
|
17
|
+
private_constant :PARAMETER_KEYS
|
18
|
+
|
19
|
+
class << self # rubocop:disable Metrics/ClassLength
|
20
|
+
# @overload resolve_parameters(entity_class: nil, singular_name: nil, name: nil, qualified_name: nil)
|
21
|
+
# Helper method for resolving a Relation's required parameters.
|
22
|
+
#
|
23
|
+
# The returned Hash will define the :entity_class, :singular_name,
|
24
|
+
# :name, and :qualified_name keys.
|
25
|
+
#
|
26
|
+
# @param entity_class [Class, String] the class of entity represented
|
27
|
+
# by the relation.
|
28
|
+
# @param singular_name [String] the name of an entity in the relation.
|
29
|
+
# @param name [String] the name of the relation.
|
30
|
+
# @param qualified_name [String] a scoped name for the relation.
|
31
|
+
#
|
32
|
+
# @return [Hash] the resolved parameters.
|
33
|
+
def resolve_parameters(params) # rubocop:disable Metrics/MethodLength
|
34
|
+
validate_parameters(**params)
|
35
|
+
|
36
|
+
entity_class = entity_class_from(**params)
|
37
|
+
class_name = entity_class_name(entity_class)
|
38
|
+
name = relation_name_from(**params, class_name:)
|
39
|
+
plural_name = plural_name_from(**params, name:)
|
40
|
+
qualified_name = qualified_name_from(**params, class_name:)
|
41
|
+
singular_name = singular_name_from(**params, name:)
|
42
|
+
|
43
|
+
{
|
44
|
+
entity_class:,
|
45
|
+
name:,
|
46
|
+
plural_name:,
|
47
|
+
qualified_name:,
|
48
|
+
singular_name:
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def classify(raw)
|
55
|
+
raw
|
56
|
+
.then { |str| tools.string_tools.singularize(str).to_s }
|
57
|
+
.split('/')
|
58
|
+
.map { |str| tools.string_tools.camelize(str) }
|
59
|
+
.join('::')
|
60
|
+
end
|
61
|
+
|
62
|
+
def entity_class_from(**params)
|
63
|
+
if has_key?(params, :entity_class)
|
64
|
+
entity_class = params[:entity_class]
|
65
|
+
|
66
|
+
return entity_class.is_a?(Class) ? entity_class : entity_class.to_s
|
67
|
+
end
|
68
|
+
|
69
|
+
if has_key?(params, :qualified_name)
|
70
|
+
return classify(params[:qualified_name])
|
71
|
+
end
|
72
|
+
|
73
|
+
classify(params[:name])
|
74
|
+
end
|
75
|
+
|
76
|
+
def entity_class_name(entity_class, scoped: true)
|
77
|
+
(entity_class.is_a?(Class) ? entity_class.name : entity_class)
|
78
|
+
.split('::')
|
79
|
+
.map { |str| tools.string_tools.underscore(str) }
|
80
|
+
.then { |ary| scoped ? ary.join('/') : ary.last }
|
81
|
+
end
|
82
|
+
|
83
|
+
def has_key?(params, key) # rubocop:disable Naming/PredicatePrefix
|
84
|
+
return false unless params.key?(key)
|
85
|
+
|
86
|
+
!params[key].nil?
|
87
|
+
end
|
88
|
+
|
89
|
+
def plural_name_from(name:, **parameters)
|
90
|
+
if parameters.key?(:plural_name) && !parameters[:plural_name].nil?
|
91
|
+
return validate_parameter(
|
92
|
+
parameters[:plural_name],
|
93
|
+
as: 'plural name'
|
94
|
+
)
|
95
|
+
end
|
96
|
+
|
97
|
+
tools.string_tools.pluralize(name)
|
98
|
+
end
|
99
|
+
|
100
|
+
def qualified_name_from(class_name:, **params)
|
101
|
+
return params[:qualified_name].to_s if has_key?(params, :qualified_name)
|
102
|
+
|
103
|
+
tools.string_tools.pluralize(class_name)
|
104
|
+
end
|
105
|
+
|
106
|
+
def relation_name_from(class_name:, **params)
|
107
|
+
return params[:name].to_s if has_key?(params, :name)
|
108
|
+
|
109
|
+
tools.string_tools.pluralize(class_name.split('/').last)
|
110
|
+
end
|
111
|
+
|
112
|
+
def singular_name_from(name:, **parameters)
|
113
|
+
if parameters.key?(:singular_name) && !parameters[:singular_name].nil?
|
114
|
+
return validate_parameter(
|
115
|
+
parameters[:singular_name],
|
116
|
+
as: 'singular name'
|
117
|
+
)
|
118
|
+
end
|
119
|
+
|
120
|
+
tools.string_tools.singularize(name)
|
121
|
+
end
|
122
|
+
|
123
|
+
def tools
|
124
|
+
SleepingKingStudios::Tools::Toolbelt.instance
|
125
|
+
end
|
126
|
+
|
127
|
+
def validate_entity_class(value)
|
128
|
+
return if value.is_a?(Class)
|
129
|
+
|
130
|
+
if value.nil? || value.is_a?(String) || value.is_a?(Symbol)
|
131
|
+
tools.assertions.validate_name(value, as: 'entity class')
|
132
|
+
|
133
|
+
return
|
134
|
+
end
|
135
|
+
|
136
|
+
raise ArgumentError,
|
137
|
+
'entity class is not a Class, a String or a Symbol'
|
138
|
+
end
|
139
|
+
|
140
|
+
def validate_parameter(value, as:)
|
141
|
+
tools.assertions.validate_name(value, as:)
|
142
|
+
|
143
|
+
value.to_s
|
144
|
+
end
|
145
|
+
|
146
|
+
def validate_parameter_keys(params)
|
147
|
+
return if PARAMETER_KEYS.any? { |key| has_key?(params, key) }
|
148
|
+
|
149
|
+
raise ArgumentError, "name or entity class can't be blank"
|
150
|
+
end
|
151
|
+
|
152
|
+
def validate_parameters(**params) # rubocop:disable Metrics/MethodLength
|
153
|
+
validate_parameter_keys(params)
|
154
|
+
|
155
|
+
if has_key?(params, :entity_class)
|
156
|
+
validate_entity_class(params[:entity_class])
|
157
|
+
end
|
158
|
+
|
159
|
+
validate_parameter(params[:name], as: 'name') if has_key?(params, :name)
|
160
|
+
|
161
|
+
if has_key?(params, :plural_name)
|
162
|
+
validate_parameter(params[:plural_name], as: 'plural name')
|
163
|
+
end
|
164
|
+
|
165
|
+
if has_key?(params, :qualified_name)
|
166
|
+
validate_parameter(params[:qualified_name], as: 'qualified name')
|
167
|
+
end
|
168
|
+
|
169
|
+
if has_key?(params, :singular_name) # rubocop:disable Style/GuardClause
|
170
|
+
validate_parameter(params[:singular_name], as: 'singular name')
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# @overload initialize(entity_class: nil, name: nil, qualified_name: nil, singular_name: nil, **)
|
176
|
+
# @param entity_class [Class, String] the class of entity represented by
|
177
|
+
# the relation.
|
178
|
+
# @param name [String] the name of the relation.
|
179
|
+
# @param qualified_name [String] a scoped name for the relation.
|
180
|
+
# @param singular_name [String] the name of an entity in the relation.
|
181
|
+
def initialize(**parameters)
|
182
|
+
super(**parameters.except(*IGNORED_PARAMETERS))
|
183
|
+
|
184
|
+
relation_params = resolve_parameters(parameters)
|
185
|
+
|
186
|
+
@entity_class = relation_params[:entity_class]
|
187
|
+
@name = relation_params[:name]
|
188
|
+
@plural_name = relation_params[:plural_name]
|
189
|
+
@qualified_name = relation_params[:qualified_name]
|
190
|
+
@singular_name = relation_params[:singular_name]
|
191
|
+
end
|
192
|
+
|
193
|
+
# @return [String] the name of the relation.
|
194
|
+
attr_reader :name
|
195
|
+
|
196
|
+
# @return [String] the pluralized name of the relation.
|
197
|
+
attr_reader :plural_name
|
198
|
+
|
199
|
+
# @return [String] a scoped name for the relation.
|
200
|
+
attr_reader :qualified_name
|
201
|
+
|
202
|
+
# @return [String] the name of an entity in the relation.
|
203
|
+
attr_reader :singular_name
|
204
|
+
|
205
|
+
# @return [Class] the class of entity represented by the relation.
|
206
|
+
def entity_class
|
207
|
+
return @entity_class if @entity_class.is_a?(Class)
|
208
|
+
|
209
|
+
@entity_class = Object.const_get(@entity_class)
|
210
|
+
end
|
211
|
+
|
212
|
+
# (see Cuprum::Collections::Relations::Parameters.resolve_parameters)
|
213
|
+
def resolve_parameters(parameters)
|
214
|
+
Parameters.resolve_parameters(parameters)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|