cuprum-collections 0.1.0 → 0.3.0.rc.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 +26 -0
- data/README.md +321 -15
- data/lib/cuprum/collections/basic/collection.rb +13 -0
- data/lib/cuprum/collections/basic/commands/destroy_one.rb +4 -3
- data/lib/cuprum/collections/basic/commands/find_many.rb +1 -1
- data/lib/cuprum/collections/basic/commands/insert_one.rb +4 -3
- data/lib/cuprum/collections/basic/commands/update_one.rb +4 -3
- data/lib/cuprum/collections/basic/query.rb +3 -3
- data/lib/cuprum/collections/basic/repository.rb +67 -0
- data/lib/cuprum/collections/commands/abstract_find_many.rb +33 -32
- data/lib/cuprum/collections/commands/abstract_find_one.rb +4 -3
- data/lib/cuprum/collections/commands/create.rb +60 -0
- data/lib/cuprum/collections/commands/find_one_matching.rb +134 -0
- data/lib/cuprum/collections/commands/update.rb +74 -0
- data/lib/cuprum/collections/commands/upsert.rb +162 -0
- data/lib/cuprum/collections/commands.rb +7 -2
- data/lib/cuprum/collections/errors/abstract_find_error.rb +210 -0
- data/lib/cuprum/collections/errors/already_exists.rb +4 -72
- data/lib/cuprum/collections/errors/extra_attributes.rb +8 -18
- data/lib/cuprum/collections/errors/failed_validation.rb +5 -18
- data/lib/cuprum/collections/errors/invalid_parameters.rb +7 -15
- data/lib/cuprum/collections/errors/invalid_query.rb +5 -15
- data/lib/cuprum/collections/errors/missing_default_contract.rb +5 -17
- data/lib/cuprum/collections/errors/not_found.rb +4 -67
- data/lib/cuprum/collections/errors/not_unique.rb +18 -0
- data/lib/cuprum/collections/errors/unknown_operator.rb +7 -17
- data/lib/cuprum/collections/errors.rb +13 -1
- data/lib/cuprum/collections/queries/ordering.rb +4 -2
- data/lib/cuprum/collections/repository.rb +105 -0
- data/lib/cuprum/collections/rspec/assign_one_command_contract.rb +2 -2
- data/lib/cuprum/collections/rspec/build_one_command_contract.rb +1 -1
- data/lib/cuprum/collections/rspec/collection_contract.rb +140 -103
- data/lib/cuprum/collections/rspec/destroy_one_command_contract.rb +8 -6
- data/lib/cuprum/collections/rspec/find_many_command_contract.rb +114 -34
- data/lib/cuprum/collections/rspec/find_one_command_contract.rb +12 -9
- data/lib/cuprum/collections/rspec/insert_one_command_contract.rb +4 -3
- data/lib/cuprum/collections/rspec/query_contract.rb +3 -3
- data/lib/cuprum/collections/rspec/querying_contract.rb +2 -2
- data/lib/cuprum/collections/rspec/repository_contract.rb +235 -0
- data/lib/cuprum/collections/rspec/update_one_command_contract.rb +4 -3
- data/lib/cuprum/collections/version.rb +3 -3
- data/lib/cuprum/collections.rb +1 -0
- metadata +25 -91
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 077ded1de1d41e29689ed46422c0ba4b367d333a495f568f461c9a5a0d8c5278
|
4
|
+
data.tar.gz: 82e9ec27fcdf2975f37f24f680cd2a0d653ec529c6884a6e9c8ad8cd33a5f4ed
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 623403206ee2a4ed953069e67e04f6c06038157fa3ac2701e65cd3e2739b49e4579a1379f2f04b7324df646de1b2c667749c8a9ae5c576bdbc587f88bcd7be7c
|
7
|
+
data.tar.gz: 5cc578f36e263ed5b83c46e902222286f47a1d2588aad8b43817686cd814242205c81d02591921895510ab551a8afdd96384c1a7fe7e0a4557dd991b5a641d0e
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,31 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 0.2.0
|
4
|
+
|
5
|
+
Implemented `Cuprum::Collections::Repository`.
|
6
|
+
|
7
|
+
### Collections
|
8
|
+
|
9
|
+
Updated `Cuprum::Collections::Basic::Collection`.
|
10
|
+
|
11
|
+
- Implemented `#count` method.
|
12
|
+
- Implemented `#qualified_name` method.
|
13
|
+
|
14
|
+
Implemented `Cuprum::Collections::Basic::Repository`.
|
15
|
+
|
16
|
+
### Commands
|
17
|
+
|
18
|
+
Implemented built-in Commands, which take a `:collection` parameter:
|
19
|
+
|
20
|
+
- `Commands::Create`
|
21
|
+
- `Commands::FindOneMatching`
|
22
|
+
- `Commands::Update`
|
23
|
+
- `Commands::Upsert`
|
24
|
+
|
25
|
+
### Queries
|
26
|
+
|
27
|
+
Fixed passing an attributes array as a query ordering.
|
28
|
+
|
3
29
|
## 0.1.0
|
4
30
|
|
5
31
|
Initial version.
|
data/README.md
CHANGED
@@ -26,7 +26,7 @@ The Ruby ecosystem has a wide variety of tools and libraries for managing data a
|
|
26
26
|
|
27
27
|
### Compatibility
|
28
28
|
|
29
|
-
Cuprum::Collections is tested against Ruby (MRI) 2.
|
29
|
+
Cuprum::Collections is tested against Ruby (MRI) 2.7 through 3.2.
|
30
30
|
|
31
31
|
### Documentation
|
32
32
|
|
@@ -34,9 +34,9 @@ Documentation is generated using [YARD](https://yardoc.org/), and can be generat
|
|
34
34
|
|
35
35
|
### License
|
36
36
|
|
37
|
-
Copyright (c) 2020-
|
37
|
+
Copyright (c) 2020-2023 Rob Smith
|
38
38
|
|
39
|
-
|
39
|
+
Cuprum::Collections is released under the [MIT License](https://opensource.org/licenses/MIT).
|
40
40
|
|
41
41
|
### Contribute
|
42
42
|
|
@@ -77,7 +77,7 @@ steps do
|
|
77
77
|
# Build the book from attributes.
|
78
78
|
book = step do
|
79
79
|
collection.build_one.call(
|
80
|
-
attributes: { id: 10, title: 'Gideon the Ninth', author: '
|
80
|
+
attributes: { id: 10, title: 'Gideon the Ninth', author: 'Tamsyn Muir' }
|
81
81
|
)
|
82
82
|
end
|
83
83
|
|
@@ -105,6 +105,13 @@ end
|
|
105
105
|
|
106
106
|
Because a collection can represent any sort of data, from a raw Ruby Hash to an ORM record, the term used to indicate "one item in the collection" is an *entity*. Likewise, the class of the items in the collection is the *entity_class*. In our example above, our entities are books, and the entity class is Hash.
|
107
107
|
|
108
|
+
Each collection also defines the following methods:
|
109
|
+
|
110
|
+
- `#collection_name`: The collection name is a short description of what the collection contains. For example, a collection of `Book` objects might have a collection name of `'books'`, while a collection of `Authorization::Credentials::ApiKey` objects might have a collection name of `'api_keys'`.
|
111
|
+
- `#qualified_name`: The qualified name is a full description of the collection, and should be unique. For example, a collection of `Book` objects might have a qualified name of `'books'`, while a collection of `Authorization::Credentials::ApiKey` objects might have a qualified name of `'authorization/credentials/api_keys'`.
|
112
|
+
|
113
|
+
As a general rule, the `#collection_name` is used when displaying information to the user, while the `#qualified_name` is used to uniquely identify the collection (such as when adding to or retrieving from a [Repository](#repositories)).
|
114
|
+
|
108
115
|
<a id="commands"></a>
|
109
116
|
|
110
117
|
#### Commands
|
@@ -116,7 +123,7 @@ Structurally, a collection is a set of commands, which are instances of `Cuprum:
|
|
116
123
|
The `AssignOne` command takes an attributes hash and an entity, and returns an instance of the entity class whose attributes are equal to the attributes hash merged into original entities attributes. Depending on the collection, `#assign_one` may or may not modify or return the original entity.
|
117
124
|
|
118
125
|
```ruby
|
119
|
-
book = { 'id' => 10, 'title' => 'Gideon the Ninth', 'author' => '
|
126
|
+
book = { 'id' => 10, 'title' => 'Gideon the Ninth', 'author' => 'Tamsyn Muir' }
|
120
127
|
attributes = { 'title' => 'Harrow the Ninth', 'published_at' => '2020-08-04' }
|
121
128
|
result = collection.assign_one.call(attributes: attributes, entity: entity)
|
122
129
|
|
@@ -124,7 +131,7 @@ result.value
|
|
124
131
|
#=> {
|
125
132
|
# 'id' => 10,
|
126
133
|
# 'title' => 'Harrow the Ninth',
|
127
|
-
# 'author' => '
|
134
|
+
# 'author' => 'Tamsyn Muir',
|
128
135
|
# 'published_at' => '2020-08-04'
|
129
136
|
# }
|
130
137
|
```
|
@@ -136,14 +143,14 @@ If the entity class specifies a set of attributes (such as the defined columns i
|
|
136
143
|
The `BuildOne` command takes an attributes hash and returns a new instance of the entity class whose attributes are equal to the given attributes. This does not validate or persist the entity; it is equivalent to calling `entity_class.new` with the attributes.
|
137
144
|
|
138
145
|
```ruby
|
139
|
-
attributes = { 'id' => 10, 'title' => 'Gideon the Ninth', 'author' => '
|
146
|
+
attributes = { 'id' => 10, 'title' => 'Gideon the Ninth', 'author' => 'Tamsyn Muir' }
|
140
147
|
result = collection.build_one.call(attributes: attributes, entity: entity)
|
141
148
|
|
142
149
|
result.value
|
143
150
|
#=> {
|
144
151
|
# 'id' => 10,
|
145
152
|
# 'title' => 'Gideon the Ninth',
|
146
|
-
# 'author' => '
|
153
|
+
# 'author' => 'Tamsyn Muir'
|
147
154
|
# }
|
148
155
|
```
|
149
156
|
|
@@ -214,7 +221,7 @@ If the collection does not include an entity with each of the specified primary
|
|
214
221
|
|
215
222
|
##### Find Matching
|
216
223
|
|
217
|
-
The `FindMatching` command takes a set of query parameters and queries data from the collection. You can specify filters using the `:where` keyword or by passing a block, sort the results using the `:order` keyword, or return a subset of the results using the `:limit` and `:offset` keywords.
|
224
|
+
The `FindMatching` command takes a set of query parameters and queries data from the collection. You can specify filters using the `:where` keyword or by passing a block, sort the results using the `:order` keyword, or return a subset of the results using the `:limit` and `:offset` keywords.
|
218
225
|
|
219
226
|
```ruby
|
220
227
|
result =
|
@@ -260,7 +267,11 @@ The `FindMatching` command has several options:
|
|
260
267
|
#=> { books: [{ ... }, { ... }, { ... }] }
|
261
268
|
```
|
262
269
|
|
270
|
+
- The `:limit` keyword caps the number of results returned.
|
271
|
+
- The `:offset` keyword skips the specified number of results.
|
272
|
+
- The `:order` keyword specifies the order of results.
|
263
273
|
- The `:scope` keyword allows you to pass a query to the command. Only entities that match the given scope will be found and returned by `#find_matching`.
|
274
|
+
- The `:where` keyword defines filters for which results are to be returned.
|
264
275
|
|
265
276
|
##### Find One
|
266
277
|
|
@@ -298,14 +309,14 @@ If the collection does not include an entity with the specified primary key, the
|
|
298
309
|
The `InsertOne` command takes an entity and inserts that entity into the collection.
|
299
310
|
|
300
311
|
```ruby
|
301
|
-
book = { 'id' => 10, 'title' => 'Gideon the Ninth', 'author' => '
|
302
|
-
result = collection.insert_one.call(entity:
|
312
|
+
book = { 'id' => 10, 'title' => 'Gideon the Ninth', 'author' => 'Tamsyn Muir' }
|
313
|
+
result = collection.insert_one.call(entity: book)
|
303
314
|
|
304
315
|
result.value
|
305
316
|
#=> {
|
306
317
|
# 'id' => 10,
|
307
318
|
# 'title' => 'Gideon the Ninth',
|
308
|
-
# 'author' => '
|
319
|
+
# 'author' => 'Tamsyn Muir'
|
309
320
|
# }
|
310
321
|
|
311
322
|
collection.query.where(id: 10).exists?
|
@@ -351,7 +362,7 @@ contract = Stannum::Contract.new do
|
|
351
362
|
property :title, Stannum::Constraints::Presence.new
|
352
363
|
end
|
353
364
|
|
354
|
-
book = { 'id' => 10, 'title' => 'Gideon the Ninth', 'author' => '
|
365
|
+
book = { 'id' => 10, 'title' => 'Gideon the Ninth', 'author' => 'Tamsyn Muir' }
|
355
366
|
result = collection.validate_one.call(contract: contract, entity: book)
|
356
367
|
result.success?
|
357
368
|
#=> true
|
@@ -361,9 +372,59 @@ If the contract does not match the entity, the `#validate_one` command will retu
|
|
361
372
|
|
362
373
|
If the collection does not specify a default contract and no `:contract` keyword is provided, the `#validate_one` command will return a failing result with a `MissingDefaultContract` error.
|
363
374
|
|
364
|
-
|
375
|
+
<a id="repositories"></a>
|
376
|
+
|
377
|
+
#### Repositories
|
378
|
+
|
379
|
+
```ruby
|
380
|
+
require 'cuprum/collections/repository'
|
381
|
+
```
|
382
|
+
|
383
|
+
A repository is a group of collections. While a collection might be be a single data set, such as the records in a table, the repository represents all of the data sets in a data source, such as the tables in a database. Each repository uses the `#qualified_name` of its collections as a unique key.
|
384
|
+
|
385
|
+
```ruby
|
386
|
+
repository = Cuprum::Collections::Repository.new
|
387
|
+
repository.key?('books')
|
388
|
+
#=> false
|
389
|
+
|
390
|
+
repository.add(books_collection)
|
391
|
+
|
392
|
+
repository.key?('books')
|
393
|
+
#=> true
|
394
|
+
repository.keys
|
395
|
+
#=> ['books']
|
396
|
+
repository['books']
|
397
|
+
#=> the books collection
|
398
|
+
```
|
399
|
+
|
400
|
+
When accessing a collection with a qualified name, you must pass the qualified name to `#[]` or `#key?`, rather than the collection name.
|
401
|
+
|
402
|
+
```ruby
|
403
|
+
repository = Cuprum::Collections::Repository.new
|
404
|
+
repository.key?('api_keys')
|
405
|
+
#=> false
|
406
|
+
repository.key?('authorization/credentials/api_keys')
|
407
|
+
#=> false
|
365
408
|
|
409
|
+
api_keys_collection.collection_name
|
410
|
+
#=> 'api_keys'
|
411
|
+
api_keys_collection.qualified_name
|
412
|
+
#=> 'authorization/credentials/api_keys'
|
413
|
+
repository.add(api_keys_collection)
|
414
|
+
|
415
|
+
repository.key?('api_keys')
|
416
|
+
#=> false
|
417
|
+
repository.key?('authorization/credentials/api_keys')
|
418
|
+
#=> true
|
419
|
+
repository.keys
|
420
|
+
#=> ['authorization/credentials/api_keys']
|
421
|
+
repository['authorization/credentials/api_keys']
|
422
|
+
#=> the api keys collection
|
366
423
|
```
|
424
|
+
|
425
|
+
#### Basic Collection
|
426
|
+
|
427
|
+
```ruby
|
367
428
|
require 'cuprum/collections/basic'
|
368
429
|
```
|
369
430
|
|
@@ -387,6 +448,41 @@ You can also specify some optional keywords:
|
|
387
448
|
- The `:member_name` parameter is used to create an envelope for singular query commands such as the `FindOne` command. If not given, the member name will be generated automatically as a singular form of the collection name.
|
388
449
|
- The `:primary_key_name` parameter specifies the attribute that serves as the primary key for the collection entities. The default value is `:id`.
|
389
450
|
- The `:primary_key_type` parameter specifies the type of the primary key attribute. The default value is `Integer`.
|
451
|
+
- The `:qualified_name` parameter sets the qualified name for the collection. It is used to uniquely identify the collection in a repository.
|
452
|
+
|
453
|
+
##### Basic Repositories
|
454
|
+
|
455
|
+
```ruby
|
456
|
+
require 'cuprum/collections/basic/repository'
|
457
|
+
```
|
458
|
+
|
459
|
+
A `Basic::Repository` is a collection of `Basic::Collection`s. In addition to implementing the Repository methods (see [Repositories](#repositories), above), a basic repository can be initialized with a data set and used to build new collections directly.
|
460
|
+
|
461
|
+
```ruby
|
462
|
+
data = {
|
463
|
+
'books' => [
|
464
|
+
{
|
465
|
+
'name' => 'Gideon the Ninth',
|
466
|
+
'author' => 'Tamsyn Muir'
|
467
|
+
}
|
468
|
+
]
|
469
|
+
}
|
470
|
+
repository = Cuprum::Collections::Basic::Repository.new(data: data)
|
471
|
+
repository.keys
|
472
|
+
#=> []
|
473
|
+
|
474
|
+
repository.build(collection_name: 'books')
|
475
|
+
#=> an instance of Cuprum::Collections::Basic::Collection
|
476
|
+
repository.keys
|
477
|
+
#=> ['books']
|
478
|
+
repository['books'].query.to_a
|
479
|
+
#=> [
|
480
|
+
# {
|
481
|
+
# 'name' => 'Gideon the Ninth',
|
482
|
+
# 'author' => 'Tamsyn Muir'
|
483
|
+
# }
|
484
|
+
# ]
|
485
|
+
```
|
390
486
|
|
391
487
|
<a id="constraints"></a>
|
392
488
|
|
@@ -686,7 +782,7 @@ The `#reset` method takes no parameters and returns the query. By default, a `Qu
|
|
686
782
|
query.count
|
687
783
|
#=> 10
|
688
784
|
|
689
|
-
book = { id: 10, title: 'Gideon the Ninth', author: '
|
785
|
+
book = { id: 10, title: 'Gideon the Ninth', author: 'Tamsyn Muir' }
|
690
786
|
collection.insert_one.call(entity: book)
|
691
787
|
|
692
788
|
query.count
|
@@ -948,3 +1044,213 @@ query.each.map(&:title)
|
|
948
1044
|
# 'The Farthest Shore'
|
949
1045
|
# ]
|
950
1046
|
```
|
1047
|
+
|
1048
|
+
<a id="builtin-commands"></a>
|
1049
|
+
|
1050
|
+
### Built In Commands
|
1051
|
+
|
1052
|
+
`Cuprum::Collections` defines some basic commands. Each command takes a `:collection` parameter, which is used for performing the data operations.
|
1053
|
+
|
1054
|
+
#### Create
|
1055
|
+
|
1056
|
+
```ruby
|
1057
|
+
require 'cuprum/collections/commands/create'
|
1058
|
+
```
|
1059
|
+
|
1060
|
+
The `Create` command takes an attributes Hash and adds an entity with those attributes to the collection. Internally, it calls the `#build_one`, `#validate_one`, and `#insert_one` commands on the collection.
|
1061
|
+
|
1062
|
+
```ruby
|
1063
|
+
command = Cuprum::Collections::Commands::Create.new(collection: books_collection)
|
1064
|
+
|
1065
|
+
books_collection.count
|
1066
|
+
#=> 0
|
1067
|
+
result = command.call(attributes: { 'title' => 'Gideon the Ninth' })
|
1068
|
+
result.value
|
1069
|
+
#=> a Book with title "Gideon the Ninth"
|
1070
|
+
books_collection.count
|
1071
|
+
#=> 1
|
1072
|
+
|
1073
|
+
books_collection.find_matching.call { { 'title' => 'Gideon the Ninth' } }.value.first
|
1074
|
+
#=> a Book with title "Gideon the Ninth"
|
1075
|
+
```
|
1076
|
+
|
1077
|
+
If the contract does not match the entity, the `Create` command will return a failing result with a `ValidationFailed` error.
|
1078
|
+
|
1079
|
+
If the collection does not specify a default contract and no :contract keyword is provided, the `Create` command will return a failing result with a `MissingDefaultContract` error.
|
1080
|
+
|
1081
|
+
If the collection already includes an entity with the specified primary key, the `Create` command will return a failing result with an `AlreadyExists` error.
|
1082
|
+
|
1083
|
+
#### FindOneMatching
|
1084
|
+
|
1085
|
+
```ruby
|
1086
|
+
require 'cuprum/collections/commands/find_one_matching'
|
1087
|
+
```
|
1088
|
+
|
1089
|
+
The `FindOneMatching` command takes either an attributes hash or a [Query](#queries) block, and returns the unique entity from the collection with those entities or matching that query.
|
1090
|
+
|
1091
|
+
```ruby
|
1092
|
+
command = Cuprum::Collections::Commands::FindOneMatching.new(collection: books_collection)
|
1093
|
+
|
1094
|
+
result = command.call(attributes: { 'title' => 'Gideon the Ninth'})
|
1095
|
+
result.success?
|
1096
|
+
#=> true
|
1097
|
+
result.value
|
1098
|
+
#=> the unique Book with title "Gideon the Ninth"
|
1099
|
+
|
1100
|
+
result = command.call do
|
1101
|
+
{
|
1102
|
+
'series' => 'The Locked Tomb',
|
1103
|
+
'published_at' => less_than('2020-01-01')
|
1104
|
+
}
|
1105
|
+
end
|
1106
|
+
result.success?
|
1107
|
+
#=> true
|
1108
|
+
result.value
|
1109
|
+
#=> the unique Book with the given series and published before the given date
|
1110
|
+
```
|
1111
|
+
|
1112
|
+
If there are no entities in the collection matching the attributes or query, the `FindOneMatching` command returns a failing result with a `Cuprum::Collections::Errors::NotFound` error.
|
1113
|
+
|
1114
|
+
```ruby
|
1115
|
+
result = command.call(attributes: { 'title' => 'Gideon the Eleventh'})
|
1116
|
+
result.success?
|
1117
|
+
#=> false
|
1118
|
+
result.error
|
1119
|
+
#=> an instance of Cuprum::Collections::Errors::NotFound
|
1120
|
+
```
|
1121
|
+
|
1122
|
+
If there are two or more entities in the collection matching the attributes or query, the `FindOneMatching` command returns a failing result with a `Cuprum::Collections::Errors::NotUnique` error.
|
1123
|
+
|
1124
|
+
```ruby
|
1125
|
+
result = command.call(attributes: { 'author' => 'Tamsyn Muir'})
|
1126
|
+
result.success?
|
1127
|
+
#=> false
|
1128
|
+
result.error
|
1129
|
+
#=> an instance of Cuprum::Collections::Errors::NotUnique
|
1130
|
+
```
|
1131
|
+
|
1132
|
+
#### Update
|
1133
|
+
|
1134
|
+
```ruby
|
1135
|
+
require 'cuprum/collections/commands/update'
|
1136
|
+
```
|
1137
|
+
|
1138
|
+
The `Update` command takes an attributes Hash and an entity and updates the corresponding entity in the collection with those attributes. Internally, it calls the `#assign_one`, `#validate_one`, and `#update_one` commands on the collection.
|
1139
|
+
|
1140
|
+
```ruby
|
1141
|
+
command = Cuprum::Collections::Commands::Update.new(collection: books_collection)
|
1142
|
+
|
1143
|
+
entity = books_collection.find_matching.call { { 'title' => 'Gideon the Ninth' } }.value.first
|
1144
|
+
#=> a Book with title "Gideon the Ninth" and series "The Locked Tomb"
|
1145
|
+
|
1146
|
+
result = command.call(
|
1147
|
+
attributes: { 'series' => 'Space Necromancers' },
|
1148
|
+
entity: entity
|
1149
|
+
)
|
1150
|
+
result.value
|
1151
|
+
#=> a Book with title "Gideon the Ninth" and series "Space Necromancers"
|
1152
|
+
|
1153
|
+
books_collection.find_matching.call { { 'title' => 'Gideon the Ninth' } }.value.first
|
1154
|
+
#=> a Book with title "Gideon the Ninth" and series "Space Necromancers"
|
1155
|
+
```
|
1156
|
+
|
1157
|
+
If the contract does not match the entity, the `Update` command will return a failing result with a `ValidationFailed` error.
|
1158
|
+
|
1159
|
+
If the collection does not specify a default contract and no :contract keyword is provided, the `Update` command will return a failing result with a `MissingDefaultContract` error.
|
1160
|
+
|
1161
|
+
If the collection does not include an entity with the specified entity's primary key, the `Update` command will return a failing result with a `NotFound` error.
|
1162
|
+
|
1163
|
+
#### Upsert
|
1164
|
+
|
1165
|
+
```ruby
|
1166
|
+
require 'cuprum/collections/commands/upsert'
|
1167
|
+
```
|
1168
|
+
|
1169
|
+
The `Upsert` command takes an attributes Hash and checks the collection for an entity with a matching primary key. If an entity is found, it updates the entity with the given attributes, as per the `Update` command (see above); if an entity is not found, it creates a new entity with the given attributes, as per the `Create` command.
|
1170
|
+
|
1171
|
+
```ruby
|
1172
|
+
command = Cuprum::Collections::Commands::Upsert.new(collection: books_collection)
|
1173
|
+
|
1174
|
+
# Creating An Entity
|
1175
|
+
books_collection.count
|
1176
|
+
#=> 0
|
1177
|
+
result = command.call(attributes: { 'id' => 0, 'title' => 'Gideon the Ninth' })
|
1178
|
+
result.value
|
1179
|
+
#=> a Book with id 0 and title "Gideon the Ninth"
|
1180
|
+
books_collection.count
|
1181
|
+
#=> 1
|
1182
|
+
|
1183
|
+
books_collection.find_matching.call { { 'title' => 'Gideon the Ninth' } }.value.first
|
1184
|
+
#=> a Book with id 0 and title "Gideon the Ninth"
|
1185
|
+
|
1186
|
+
# Updating An Entity
|
1187
|
+
result = command.call(attributes: { 'id' => 0, 'author' => 'Tamsyn Muir' })
|
1188
|
+
result.value
|
1189
|
+
#=> a Book with id 0, title "Gideon the Ninth", and author "Tamsyn Muir"
|
1190
|
+
|
1191
|
+
books_collection.find_matching.call { { 'title' => 'Gideon the Ninth' } }.value.first
|
1192
|
+
#=> a Book with id 0, title "Gideon the Ninth", and author "Tamsyn Muir"
|
1193
|
+
```
|
1194
|
+
|
1195
|
+
The `Upsert` command can also be configured with an attribute name or list of attribute names; the command will then search for an entity in the collection matching those attributes, rather than by primary key.
|
1196
|
+
|
1197
|
+
```ruby
|
1198
|
+
command =
|
1199
|
+
Cuprum::Collections::Commands::Upsert
|
1200
|
+
.new(attribute_names: %w[title author], collection: books_collection)
|
1201
|
+
other_entity = books_collection.build_one.call(
|
1202
|
+
attributes: {
|
1203
|
+
'id' => 0,
|
1204
|
+
'title' => 'Gideon the Ninth',
|
1205
|
+
'author' => 'T. M.'
|
1206
|
+
}
|
1207
|
+
)
|
1208
|
+
books_collection.insert_one.call(entity: entity)
|
1209
|
+
|
1210
|
+
# Creating An Entity
|
1211
|
+
books_collection.count
|
1212
|
+
#=> 1
|
1213
|
+
result = command.call(
|
1214
|
+
attributes: {
|
1215
|
+
'id' => 1,
|
1216
|
+
'title' => 'Gideon the Ninth',
|
1217
|
+
'author' => 'Tamsyn Muir'
|
1218
|
+
}
|
1219
|
+
)
|
1220
|
+
result.value
|
1221
|
+
#=> a Book with id 1, title "Gideon the Ninth", and author "Tamsyn Muir"
|
1222
|
+
books_collection.count
|
1223
|
+
#=> 1
|
1224
|
+
|
1225
|
+
books_collection.find_matching.call { { 'title' => 'Gideon the Ninth' } }.value.count
|
1226
|
+
#=> 2
|
1227
|
+
books_collection.find_matching.call { { 'title' => 'Gideon the Ninth' } }.value.map(&:author)
|
1228
|
+
#=> ['T. M.', 'Tamsyn Muir']
|
1229
|
+
|
1230
|
+
# Updating An Entity
|
1231
|
+
result = command.call(
|
1232
|
+
attributes: {
|
1233
|
+
'title' => 'Gideon the Ninth',
|
1234
|
+
'author' => 'Tamsyn Muir',
|
1235
|
+
'series' => 'The Locked Tomb'
|
1236
|
+
}
|
1237
|
+
)
|
1238
|
+
result.value
|
1239
|
+
#=> a Book with id 1, title "Gideon the Ninth", author "Tamsyn Muir", and series
|
1240
|
+
# "The Locked Tomb"
|
1241
|
+
|
1242
|
+
books_collection.find_matching.call do
|
1243
|
+
{
|
1244
|
+
'title' => 'Gideon the Ninth',
|
1245
|
+
'author' => 'Tamsyn Muir'
|
1246
|
+
}
|
1247
|
+
end.value.first
|
1248
|
+
#=> a Book with id 1, title "Gideon the Ninth", author "Tamsyn Muir", and series
|
1249
|
+
# "The Locked Tomb"
|
1250
|
+
```
|
1251
|
+
|
1252
|
+
If there are two or more entities in the collection matching the attributes or query, the `Upsert` command returns a failing result with a `Cuprum::Collections::Errors::NotUnique` error.
|
1253
|
+
|
1254
|
+
If the contract does not match the entity, the `Upsert` command will return a failing result with a `ValidationFailed` error.
|
1255
|
+
|
1256
|
+
If the collection does not specify a default contract and no :contract keyword is provided, the `Upsert` command will return a failing result with a `MissingDefaultContract` error.
|
@@ -17,6 +17,8 @@ module Cuprum::Collections::Basic
|
|
17
17
|
# Defaults to :id.
|
18
18
|
# @param primary_key_type [Class, Stannum::Constraint] The type of the
|
19
19
|
# primary key attribute. Defaults to Integer.
|
20
|
+
# @param qualified_name [String] The qualified name of the collection, which
|
21
|
+
# should be unique. Defaults to the collection name.
|
20
22
|
# @param options [Hash<Symbol>] Additional options for the command.
|
21
23
|
def initialize( # rubocop:disable Metrics/ParameterLists
|
22
24
|
collection_name:,
|
@@ -25,6 +27,7 @@ module Cuprum::Collections::Basic
|
|
25
27
|
member_name: nil,
|
26
28
|
primary_key_name: :id,
|
27
29
|
primary_key_type: Integer,
|
30
|
+
qualified_name: nil,
|
28
31
|
**options
|
29
32
|
)
|
30
33
|
super()
|
@@ -37,6 +40,7 @@ module Cuprum::Collections::Basic
|
|
37
40
|
@options = options
|
38
41
|
@primary_key_name = primary_key_name
|
39
42
|
@primary_key_type = primary_key_type
|
43
|
+
@qualified_name = qualified_name || @collection_name
|
40
44
|
end
|
41
45
|
|
42
46
|
# @return [String] the name of the collection.
|
@@ -62,6 +66,9 @@ module Cuprum::Collections::Basic
|
|
62
66
|
# attribute.
|
63
67
|
attr_reader :primary_key_type
|
64
68
|
|
69
|
+
# @return [String] the qualified name of the collection.
|
70
|
+
attr_reader :qualified_name
|
71
|
+
|
65
72
|
command_class :assign_one do
|
66
73
|
Cuprum::Collections::Basic::Commands::AssignOne
|
67
74
|
.subclass(**command_options)
|
@@ -107,6 +114,12 @@ module Cuprum::Collections::Basic
|
|
107
114
|
.subclass(**command_options)
|
108
115
|
end
|
109
116
|
|
117
|
+
# @return [Integer] the count of items in the collection.
|
118
|
+
def count
|
119
|
+
query.count
|
120
|
+
end
|
121
|
+
alias size count
|
122
|
+
|
110
123
|
# A new Query instance, used for querying against the collection data.
|
111
124
|
#
|
112
125
|
# @return [Cuprum::Collections::Basic::Query] the query.
|
@@ -28,9 +28,10 @@ module Cuprum::Collections::Basic::Commands
|
|
28
28
|
return if index
|
29
29
|
|
30
30
|
error = Cuprum::Collections::Errors::NotFound.new(
|
31
|
-
|
32
|
-
|
33
|
-
|
31
|
+
attribute_name: primary_key_name,
|
32
|
+
attribute_value: primary_key,
|
33
|
+
collection_name: collection_name,
|
34
|
+
primary_key: true
|
34
35
|
)
|
35
36
|
Cuprum::Result.new(error: error)
|
36
37
|
end
|
@@ -32,9 +32,10 @@ module Cuprum::Collections::Basic::Commands
|
|
32
32
|
return if index.nil?
|
33
33
|
|
34
34
|
error = Cuprum::Collections::Errors::AlreadyExists.new(
|
35
|
-
|
36
|
-
|
37
|
-
|
35
|
+
attribute_name: primary_key_name,
|
36
|
+
attribute_value: value,
|
37
|
+
collection_name: collection_name,
|
38
|
+
primary_key: true
|
38
39
|
)
|
39
40
|
failure(error)
|
40
41
|
end
|
@@ -32,9 +32,10 @@ module Cuprum::Collections::Basic::Commands
|
|
32
32
|
return index unless index.nil?
|
33
33
|
|
34
34
|
error = Cuprum::Collections::Errors::NotFound.new(
|
35
|
-
|
36
|
-
|
37
|
-
|
35
|
+
attribute_name: primary_key_name,
|
36
|
+
attribute_value: entity[primary_key_name.to_s],
|
37
|
+
collection_name: collection_name,
|
38
|
+
primary_key: true
|
38
39
|
)
|
39
40
|
failure(error)
|
40
41
|
end
|
@@ -151,9 +151,9 @@ module Cuprum::Collections::Basic
|
|
151
151
|
def filtered_data
|
152
152
|
@filtered_data ||=
|
153
153
|
data
|
154
|
-
.
|
155
|
-
.
|
156
|
-
.
|
154
|
+
.then { |ary| apply_filters(ary) }
|
155
|
+
.then { |ary| apply_order(ary) }
|
156
|
+
.then { |ary| apply_limit_offset(ary) }
|
157
157
|
.map(&:dup)
|
158
158
|
end
|
159
159
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections/basic'
|
4
|
+
require 'cuprum/collections/basic/collection'
|
5
|
+
require 'cuprum/collections/repository'
|
6
|
+
|
7
|
+
module Cuprum::Collections::Basic
|
8
|
+
# A repository represents a group of Basic collections.
|
9
|
+
class Repository < Cuprum::Collections::Repository
|
10
|
+
# @param data [Hash<String, Object>] Seed data to use when building
|
11
|
+
# collections.
|
12
|
+
def initialize(data: {})
|
13
|
+
super()
|
14
|
+
|
15
|
+
@data = data
|
16
|
+
end
|
17
|
+
|
18
|
+
# Adds a new collection with the given name to the repository.
|
19
|
+
#
|
20
|
+
# @param collection_name [String] The name of the new collection.
|
21
|
+
# @param data [Hash<String, Object>] The inital data for the collection. If
|
22
|
+
# not specified, defaults to the data used to initialize the repository.
|
23
|
+
# @param options [Hash] Additional options to pass to Collection.new
|
24
|
+
#
|
25
|
+
# @return [Cuprum::Collections::Basic::Collection] the created collection.
|
26
|
+
#
|
27
|
+
# @see Cuprum::Collections::Basic::Collection#initialize.
|
28
|
+
def build(collection_name:, data: nil, **options)
|
29
|
+
validate_collection_name!(collection_name)
|
30
|
+
validate_data!(data)
|
31
|
+
|
32
|
+
collection = Cuprum::Collections::Basic.new(
|
33
|
+
collection_name: collection_name,
|
34
|
+
data: data || @data.fetch(collection_name.to_s, []),
|
35
|
+
**options
|
36
|
+
)
|
37
|
+
|
38
|
+
add(collection)
|
39
|
+
|
40
|
+
collection
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def valid_collection?(collection)
|
46
|
+
collection.is_a?(Cuprum::Collections::Basic::Collection)
|
47
|
+
end
|
48
|
+
|
49
|
+
def validate_collection_name!(name)
|
50
|
+
raise ArgumentError, "collection name can't be blank" if name.nil?
|
51
|
+
|
52
|
+
unless name.is_a?(String) || name.is_a?(Symbol)
|
53
|
+
raise ArgumentError, 'collection name must be a String or Symbol'
|
54
|
+
end
|
55
|
+
|
56
|
+
return unless name.empty?
|
57
|
+
|
58
|
+
raise ArgumentError, "collection name can't be blank"
|
59
|
+
end
|
60
|
+
|
61
|
+
def validate_data!(data)
|
62
|
+
return if data.nil? || data.is_a?(Array)
|
63
|
+
|
64
|
+
raise ArgumentError, 'data must be an Array of Hashes'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|