cuprum-collections 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 53ab28a09a25a4de8a7ca405a3f59b45b5b9cec3fc0acd243a8815bfdfee80ae
4
- data.tar.gz: aef7d1257fd0997c284a1cad91118086ae2672789fe40d9648fc6a553fb70abd
3
+ metadata.gz: 78622fdeb8107cc8df27120e104a7b4ab1239569f8677b648b3462b36b4c008c
4
+ data.tar.gz: 0ca7eeb33a33d477888851d1a515b9e0c7cae3dc80a7a85f5455747ea1275ddc
5
5
  SHA512:
6
- metadata.gz: 2f9544cd606500cca431300aa4432ab58a475fbb2ee6c813bbdef36d8a10a5cec3eea7b219eb16dfcf4a810fa022154e554e61373ad47e080818933f97f76d74
7
- data.tar.gz: 46f80cf9f3d575e564a46230fc65c984d4886f7f23ca62cdd2454f528a9d2f96fc70e31d23f222c7acd944ab5f5a9a468f94ed3a928a191d5bdaf0057219288a
6
+ metadata.gz: 40a8281d4232bf7e8e78450d99dde47089dbbc039c7f7c689c579d98bf22d3069e3e9c0f3066ebca1d4ce292fb170738f5ed71afb8b49a875648333d81523f08
7
+ data.tar.gz: fc9c9d23db953377b34397a89fdd4098e170b1b2db3f84261562c473d932e881dbcc92237d5fb53a8b819a722bef5d89f545418fca9e527e8dfc0271c4aacd3c
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0
4
+
5
+ Implemented `Cuprum::Collections::Repository`.
6
+
7
+ ### Collections
8
+
9
+ Implemented `Cuprum::Collections::Basic::Repository`.
10
+
11
+ ### Queries
12
+
13
+ Fixed passing an attributes array as a query ordering.
14
+
3
15
  ## 0.1.0
4
16
 
5
17
  Initial version.
data/README.md CHANGED
@@ -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: 'Tammsyn Muir' }
80
+ attributes: { id: 10, title: 'Gideon the Ninth', author: 'Tamsyn Muir' }
81
81
  )
82
82
  end
83
83
 
@@ -116,7 +116,7 @@ Structurally, a collection is a set of commands, which are instances of `Cuprum:
116
116
  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
117
 
118
118
  ```ruby
119
- book = { 'id' => 10, 'title' => 'Gideon the Ninth', 'author' => 'Tammsyn Muir' }
119
+ book = { 'id' => 10, 'title' => 'Gideon the Ninth', 'author' => 'Tamsyn Muir' }
120
120
  attributes = { 'title' => 'Harrow the Ninth', 'published_at' => '2020-08-04' }
121
121
  result = collection.assign_one.call(attributes: attributes, entity: entity)
122
122
 
@@ -124,7 +124,7 @@ result.value
124
124
  #=> {
125
125
  # 'id' => 10,
126
126
  # 'title' => 'Harrow the Ninth',
127
- # 'author' => 'Tammsyn Muir',
127
+ # 'author' => 'Tamsyn Muir',
128
128
  # 'published_at' => '2020-08-04'
129
129
  # }
130
130
  ```
@@ -136,14 +136,14 @@ If the entity class specifies a set of attributes (such as the defined columns i
136
136
  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
137
 
138
138
  ```ruby
139
- attributes = { 'id' => 10, 'title' => 'Gideon the Ninth', 'author' => 'Tammsyn Muir' }
139
+ attributes = { 'id' => 10, 'title' => 'Gideon the Ninth', 'author' => 'Tamsyn Muir' }
140
140
  result = collection.build_one.call(attributes: attributes, entity: entity)
141
141
 
142
142
  result.value
143
143
  #=> {
144
144
  # 'id' => 10,
145
145
  # 'title' => 'Gideon the Ninth',
146
- # 'author' => 'Tammsyn Muir'
146
+ # 'author' => 'Tamsyn Muir'
147
147
  # }
148
148
  ```
149
149
 
@@ -298,14 +298,14 @@ If the collection does not include an entity with the specified primary key, the
298
298
  The `InsertOne` command takes an entity and inserts that entity into the collection.
299
299
 
300
300
  ```ruby
301
- book = { 'id' => 10, 'title' => 'Gideon the Ninth', 'author' => 'Tammsyn Muir' }
301
+ book = { 'id' => 10, 'title' => 'Gideon the Ninth', 'author' => 'Tamsyn Muir' }
302
302
  result = collection.insert_one.call(entity: entity)
303
303
 
304
304
  result.value
305
305
  #=> {
306
306
  # 'id' => 10,
307
307
  # 'title' => 'Gideon the Ninth',
308
- # 'author' => 'Tammsyn Muir'
308
+ # 'author' => 'Tamsyn Muir'
309
309
  # }
310
310
 
311
311
  collection.query.where(id: 10).exists?
@@ -351,7 +351,7 @@ contract = Stannum::Contract.new do
351
351
  property :title, Stannum::Constraints::Presence.new
352
352
  end
353
353
 
354
- book = { 'id' => 10, 'title' => 'Gideon the Ninth', 'author' => 'Tammsyn Muir' }
354
+ book = { 'id' => 10, 'title' => 'Gideon the Ninth', 'author' => 'Tamsyn Muir' }
355
355
  result = collection.validate_one.call(contract: contract, entity: book)
356
356
  result.success?
357
357
  #=> true
@@ -361,9 +361,34 @@ If the contract does not match the entity, the `#validate_one` command will retu
361
361
 
362
362
  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
363
 
364
- #### Basic Collection
364
+ <a id="repositories"></a>
365
+
366
+ #### Repositories
367
+
368
+ ```ruby
369
+ require 'cuprum/collections/repository'
370
+ ```
371
+
372
+ 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.
373
+
374
+ ```ruby
375
+ repository = Cuprum::Collections::Repository.new
376
+ repository.key?('book')
377
+ #=> false
365
378
 
379
+ repository.add(books_collection)
380
+
381
+ repository.key?('book')
382
+ #=> true
383
+ repository.keys
384
+ #=> ['books']
385
+ repository['books']
386
+ #=> the books collection
366
387
  ```
388
+
389
+ #### Basic Collection
390
+
391
+ ```ruby
367
392
  require 'cuprum/collections/basic'
368
393
  ```
369
394
 
@@ -388,6 +413,40 @@ You can also specify some optional keywords:
388
413
  - The `:primary_key_name` parameter specifies the attribute that serves as the primary key for the collection entities. The default value is `:id`.
389
414
  - The `:primary_key_type` parameter specifies the type of the primary key attribute. The default value is `Integer`.
390
415
 
416
+ ##### Basic Repositories
417
+
418
+ ```ruby
419
+ require 'cuprum/collections/basic/repository'
420
+ ```
421
+
422
+ 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.
423
+
424
+ ```ruby
425
+ data = {
426
+ 'books' => [
427
+ {
428
+ 'name' => 'Gideon the Ninth',
429
+ 'author' => 'Tamsyn Muir'
430
+ }
431
+ ]
432
+ }
433
+ repository = Cuprum::Collections::Basic::Repository.new(data: data)
434
+ repository.keys
435
+ #=> []
436
+
437
+ repository.build(collection_name: 'books')
438
+ #=> an instance of Cuprum::Collections::Basic::Collection
439
+ repository.keys
440
+ #=> ['books']
441
+ repository['books'].query.to_a
442
+ #=> [
443
+ # {
444
+ # 'name' => 'Gideon the Ninth',
445
+ # 'author' => 'Tamsyn Muir'
446
+ # }
447
+ # ]
448
+ ```
449
+
391
450
  <a id="constraints"></a>
392
451
 
393
452
  ### Constraints
@@ -686,7 +745,7 @@ The `#reset` method takes no parameters and returns the query. By default, a `Qu
686
745
  query.count
687
746
  #=> 10
688
747
 
689
- book = { id: 10, title: 'Gideon the Ninth', author: 'Tammsyn Muir' }
748
+ book = { id: 10, title: 'Gideon the Ninth', author: 'Tamsyn Muir' }
690
749
  collection.insert_one.call(entity: book)
691
750
 
692
751
  query.count
@@ -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
@@ -33,6 +33,8 @@ module Cuprum::Collections::Queries
33
33
  # @raise InvalidOrderError if any of the hash keys are invalid attribute
34
34
  # names, or any of the hash values are invalid sort directions.
35
35
  def normalize(*attributes)
36
+ attributes = attributes.flatten if attributes.first.is_a?(Array)
37
+
36
38
  validate_ordering!(attributes)
37
39
 
38
40
  qualified = attributes.last.is_a?(Hash) ? attributes.pop : {}
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ require 'cuprum/collections'
6
+
7
+ module Cuprum::Collections
8
+ # A repository represents a group of collections.
9
+ #
10
+ # Conceptually, a repository represents one or more underlying data stores. An
11
+ # application might have one repository for each data store, e.g. one
12
+ # repository for relational data, a second repository for document-based data,
13
+ # and so on. The application may instead aggregate all of its collections into
14
+ # a single repository, relying on the shared interface of all Collection
15
+ # implementations.
16
+ class Repository
17
+ extend Forwardable
18
+
19
+ # Error raised when trying to add an invalid collection to the repository.
20
+ class InvalidCollectionError < StandardError; end
21
+
22
+ # Error raised when trying to access a collection that is not defined.
23
+ class UndefinedCollectionError < StandardError; end
24
+
25
+ def initialize
26
+ @collections = {}
27
+ end
28
+
29
+ # @!method keys
30
+ # Returns the names of the collections in the repository.
31
+ #
32
+ # @return [Array<String>] the collection names.
33
+
34
+ def_delegators :@collections, :keys
35
+
36
+ # Finds and returns the collection with the given name.
37
+ #
38
+ # @param collection_name [String, Symbol] The name of the collection to
39
+ # return.
40
+ #
41
+ # @return [Object] the requested collection.
42
+ #
43
+ # @raise [Cuprum::Collection::Repository::UndefinedCOllectionError] if the
44
+ # requested collection is not in the repository.
45
+ def [](collection_name)
46
+ @collections.fetch(collection_name.to_s) do
47
+ raise UndefinedCollectionError,
48
+ "repository does not define collection #{collection_name.inspect}"
49
+ end
50
+ end
51
+
52
+ # Adds the collection to the repository.
53
+ #
54
+ # The collection must implement the #collection_name property. Repository
55
+ # subclasses may enforce additional requirements.
56
+ #
57
+ # @param collection [#collection_name] The collection to add to the
58
+ # repository.
59
+ #
60
+ # @return [Cuprum::Rails::Repository] the repository.
61
+ def add(collection)
62
+ validate_collection!(collection)
63
+
64
+ @collections[collection.collection_name.to_s] = collection
65
+
66
+ self
67
+ end
68
+ alias << add
69
+
70
+ # Checks if a collection with the given name exists in the repository.
71
+ #
72
+ # @param collection_name [String, Symbol] The name to check for.
73
+ #
74
+ # @return [true, false] true if the key exists, otherwise false.
75
+ def key?(collection_name)
76
+ @collections.key?(collection_name.to_s)
77
+ end
78
+
79
+ private
80
+
81
+ def valid_collection?(collection)
82
+ collection.respond_to?(:collection_name)
83
+ end
84
+
85
+ def validate_collection!(collection)
86
+ return if valid_collection?(collection)
87
+
88
+ raise InvalidCollectionError,
89
+ "#{collection.inspect} is not a valid collection"
90
+ end
91
+ end
92
+ end
@@ -58,7 +58,7 @@ module Cuprum::Collections::RSpec
58
58
  let(:attributes) do
59
59
  {
60
60
  title: 'Gideon the Ninth',
61
- author: 'Tammsyn Muir',
61
+ author: 'Tamsyn Muir',
62
62
  series: 'The Locked Tomb',
63
63
  category: 'Horror'
64
64
  }
@@ -124,7 +124,7 @@ module Cuprum::Collections::RSpec
124
124
  let(:attributes) do
125
125
  {
126
126
  title: 'Gideon the Ninth',
127
- author: 'Tammsyn Muir',
127
+ author: 'Tamsyn Muir',
128
128
  series: 'The Locked Tomb',
129
129
  category: 'Horror'
130
130
  }
@@ -50,7 +50,7 @@ module Cuprum::Collections::RSpec
50
50
  let(:attributes) do
51
51
  {
52
52
  title: 'Gideon the Ninth',
53
- author: 'Tammsyn Muir',
53
+ author: 'Tamsyn Muir',
54
54
  series: 'The Locked Tomb',
55
55
  category: 'Horror'
56
56
  }
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/collections/rspec'
4
+
5
+ module Cuprum::Collections::RSpec
6
+ # Contract validating the behavior of a Repository.
7
+ REPOSITORY_CONTRACT = lambda do
8
+ describe '#[]' do
9
+ let(:error_class) do
10
+ described_class::UndefinedCollectionError
11
+ end
12
+ let(:error_message) do
13
+ "repository does not define collection #{collection_name.inspect}"
14
+ end
15
+
16
+ it { expect(repository).to respond_to(:[]).with(1).argument }
17
+
18
+ describe 'with nil' do
19
+ let(:collection_name) { nil }
20
+
21
+ it 'should raise an exception' do
22
+ expect { repository[collection_name] }
23
+ .to raise_error(error_class, error_message)
24
+ end
25
+ end
26
+
27
+ describe 'with an object' do
28
+ let(:collection_name) { Object.new.freeze }
29
+
30
+ it 'should raise an exception' do
31
+ expect { repository[collection_name] }
32
+ .to raise_error(error_class, error_message)
33
+ end
34
+ end
35
+
36
+ describe 'with an invalid string' do
37
+ let(:collection_name) { 'invalid_name' }
38
+
39
+ it 'should raise an exception' do
40
+ expect { repository[collection_name] }
41
+ .to raise_error(error_class, error_message)
42
+ end
43
+ end
44
+
45
+ describe 'with an invalid symbol' do
46
+ let(:collection_name) { :invalid_name }
47
+
48
+ it 'should raise an exception' do
49
+ expect { repository[collection_name] }
50
+ .to raise_error(error_class, error_message)
51
+ end
52
+ end
53
+
54
+ wrap_context 'when the repository has many collections' do
55
+ describe 'with an invalid string' do
56
+ let(:collection_name) { 'invalid_name' }
57
+
58
+ it 'should raise an exception' do
59
+ expect { repository[collection_name] }
60
+ .to raise_error(error_class, error_message)
61
+ end
62
+ end
63
+
64
+ describe 'with an invalid symbol' do
65
+ let(:collection_name) { :invalid_name }
66
+
67
+ it 'should raise an exception' do
68
+ expect { repository[collection_name] }
69
+ .to raise_error(error_class, error_message)
70
+ end
71
+ end
72
+
73
+ describe 'with a valid string' do
74
+ let(:collection_name) { collection_names.first }
75
+
76
+ it { expect(repository[collection_name]).to be books_collection }
77
+ end
78
+
79
+ describe 'with a valid symbol' do
80
+ let(:collection_name) { collection_names.first.intern }
81
+
82
+ it { expect(repository[collection_name]).to be books_collection }
83
+ end
84
+ end
85
+ end
86
+
87
+ describe '#add' do
88
+ let(:error_class) do
89
+ described_class::InvalidCollectionError
90
+ end
91
+ let(:error_message) do
92
+ "#{collection.inspect} is not a valid collection"
93
+ end
94
+
95
+ it { expect(repository).to respond_to(:add).with(1).argument }
96
+
97
+ it 'should alias #add as #<<' do
98
+ expect(repository.method(:<<)).to be == repository.method(:add)
99
+ end
100
+
101
+ describe 'with nil' do
102
+ let(:collection) { nil }
103
+
104
+ it 'should raise an exception' do
105
+ expect { repository.add(collection) }
106
+ .to raise_error(error_class, error_message)
107
+ end
108
+ end
109
+
110
+ describe 'with an object' do
111
+ let(:collection) { Object.new.freeze }
112
+
113
+ it 'should raise an exception' do
114
+ expect { repository.add(collection) }
115
+ .to raise_error(error_class, error_message)
116
+ end
117
+ end
118
+
119
+ describe 'with a collection' do
120
+ it { expect(repository.add(example_collection)).to be repository }
121
+
122
+ it 'should add the collection to the repository' do
123
+ repository.add(example_collection)
124
+
125
+ expect(repository[example_collection.collection_name])
126
+ .to be example_collection
127
+ end
128
+ end
129
+ end
130
+
131
+ describe '#key?' do
132
+ it { expect(repository).to respond_to(:key?).with(1).argument }
133
+
134
+ it { expect(repository.key? nil).to be false }
135
+
136
+ it { expect(repository.key? Object.new.freeze).to be false }
137
+
138
+ it { expect(repository.key? 'invalid_name').to be false }
139
+
140
+ it { expect(repository.key? :invalid_name).to be false }
141
+
142
+ wrap_context 'when the repository has many collections' do
143
+ it { expect(repository.key? 'invalid_name').to be false }
144
+
145
+ it { expect(repository.key? :invalid_name).to be false }
146
+
147
+ it { expect(repository.key? collection_names.first).to be true }
148
+
149
+ it { expect(repository.key? collection_names.first.intern).to be true }
150
+ end
151
+ end
152
+
153
+ describe '#keys' do
154
+ include_examples 'should define reader', :keys, []
155
+
156
+ wrap_context 'when the repository has many collections' do
157
+ it { expect(repository.keys).to be == collection_names }
158
+ end
159
+ end
160
+ end
161
+ end
@@ -11,7 +11,7 @@ module Cuprum
11
11
  # Major version.
12
12
  MAJOR = 0
13
13
  # Minor version.
14
- MINOR = 1
14
+ MINOR = 2
15
15
  # Patch version.
16
16
  PATCH = 0
17
17
  # Prerelease version.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cuprum-collections
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rob "Merlin" Smith
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-19 00:00:00.000000000 Z
11
+ date: 2021-10-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cuprum
@@ -143,6 +143,7 @@ files:
143
143
  - lib/cuprum/collections/basic/commands/validate_one.rb
144
144
  - lib/cuprum/collections/basic/query.rb
145
145
  - lib/cuprum/collections/basic/query_builder.rb
146
+ - lib/cuprum/collections/basic/repository.rb
146
147
  - lib/cuprum/collections/basic/rspec.rb
147
148
  - lib/cuprum/collections/basic/rspec/command_contract.rb
148
149
  - lib/cuprum/collections/command.rb
@@ -175,6 +176,7 @@ files:
175
176
  - lib/cuprum/collections/queries/parse_strategy.rb
176
177
  - lib/cuprum/collections/query.rb
177
178
  - lib/cuprum/collections/query_builder.rb
179
+ - lib/cuprum/collections/repository.rb
178
180
  - lib/cuprum/collections/rspec.rb
179
181
  - lib/cuprum/collections/rspec/assign_one_command_contract.rb
180
182
  - lib/cuprum/collections/rspec/build_one_command_contract.rb
@@ -188,6 +190,7 @@ files:
188
190
  - lib/cuprum/collections/rspec/query_builder_contract.rb
189
191
  - lib/cuprum/collections/rspec/query_contract.rb
190
192
  - lib/cuprum/collections/rspec/querying_contract.rb
193
+ - lib/cuprum/collections/rspec/repository_contract.rb
191
194
  - lib/cuprum/collections/rspec/update_one_command_contract.rb
192
195
  - lib/cuprum/collections/rspec/validate_one_command_contract.rb
193
196
  - lib/cuprum/collections/version.rb