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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/README.md +321 -15
  4. data/lib/cuprum/collections/basic/collection.rb +13 -0
  5. data/lib/cuprum/collections/basic/commands/destroy_one.rb +4 -3
  6. data/lib/cuprum/collections/basic/commands/find_many.rb +1 -1
  7. data/lib/cuprum/collections/basic/commands/insert_one.rb +4 -3
  8. data/lib/cuprum/collections/basic/commands/update_one.rb +4 -3
  9. data/lib/cuprum/collections/basic/query.rb +3 -3
  10. data/lib/cuprum/collections/basic/repository.rb +67 -0
  11. data/lib/cuprum/collections/commands/abstract_find_many.rb +33 -32
  12. data/lib/cuprum/collections/commands/abstract_find_one.rb +4 -3
  13. data/lib/cuprum/collections/commands/create.rb +60 -0
  14. data/lib/cuprum/collections/commands/find_one_matching.rb +134 -0
  15. data/lib/cuprum/collections/commands/update.rb +74 -0
  16. data/lib/cuprum/collections/commands/upsert.rb +162 -0
  17. data/lib/cuprum/collections/commands.rb +7 -2
  18. data/lib/cuprum/collections/errors/abstract_find_error.rb +210 -0
  19. data/lib/cuprum/collections/errors/already_exists.rb +4 -72
  20. data/lib/cuprum/collections/errors/extra_attributes.rb +8 -18
  21. data/lib/cuprum/collections/errors/failed_validation.rb +5 -18
  22. data/lib/cuprum/collections/errors/invalid_parameters.rb +7 -15
  23. data/lib/cuprum/collections/errors/invalid_query.rb +5 -15
  24. data/lib/cuprum/collections/errors/missing_default_contract.rb +5 -17
  25. data/lib/cuprum/collections/errors/not_found.rb +4 -67
  26. data/lib/cuprum/collections/errors/not_unique.rb +18 -0
  27. data/lib/cuprum/collections/errors/unknown_operator.rb +7 -17
  28. data/lib/cuprum/collections/errors.rb +13 -1
  29. data/lib/cuprum/collections/queries/ordering.rb +4 -2
  30. data/lib/cuprum/collections/repository.rb +105 -0
  31. data/lib/cuprum/collections/rspec/assign_one_command_contract.rb +2 -2
  32. data/lib/cuprum/collections/rspec/build_one_command_contract.rb +1 -1
  33. data/lib/cuprum/collections/rspec/collection_contract.rb +140 -103
  34. data/lib/cuprum/collections/rspec/destroy_one_command_contract.rb +8 -6
  35. data/lib/cuprum/collections/rspec/find_many_command_contract.rb +114 -34
  36. data/lib/cuprum/collections/rspec/find_one_command_contract.rb +12 -9
  37. data/lib/cuprum/collections/rspec/insert_one_command_contract.rb +4 -3
  38. data/lib/cuprum/collections/rspec/query_contract.rb +3 -3
  39. data/lib/cuprum/collections/rspec/querying_contract.rb +2 -2
  40. data/lib/cuprum/collections/rspec/repository_contract.rb +235 -0
  41. data/lib/cuprum/collections/rspec/update_one_command_contract.rb +4 -3
  42. data/lib/cuprum/collections/version.rb +3 -3
  43. data/lib/cuprum/collections.rb +1 -0
  44. metadata +25 -91
@@ -0,0 +1,105 @@
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 existing collection to the repository.
20
+ class DuplicateCollectionError < StandardError; end
21
+
22
+ # Error raised when trying to add an invalid collection to the repository.
23
+ class InvalidCollectionError < StandardError; end
24
+
25
+ # Error raised when trying to access a collection that is not defined.
26
+ class UndefinedCollectionError < StandardError; end
27
+
28
+ def initialize
29
+ @collections = {}
30
+ end
31
+
32
+ # @!method keys
33
+ # Returns the names of the collections in the repository.
34
+ #
35
+ # @return [Array<String>] the collection names.
36
+
37
+ def_delegators :@collections, :keys
38
+
39
+ # Finds and returns the collection with the given name.
40
+ #
41
+ # @param qualified_name [String, Symbol] The qualified name of the
42
+ # collection to return.
43
+ #
44
+ # @return [Object] the requested collection.
45
+ #
46
+ # @raise [Cuprum::Collection::Repository::UndefinedCOllectionError] if the
47
+ # requested collection is not in the repository.
48
+ def [](qualified_name)
49
+ @collections.fetch(qualified_name.to_s) do
50
+ raise UndefinedCollectionError,
51
+ "repository does not define collection #{qualified_name.inspect}"
52
+ end
53
+ end
54
+
55
+ # Adds the collection to the repository.
56
+ #
57
+ # The collection must implement the #collection_name property. Repository
58
+ # subclasses may enforce additional requirements.
59
+ #
60
+ # @param collection [#collection_name] The collection to add to the
61
+ # repository.
62
+ # @param force [true, false] If true, override an existing collection with
63
+ # the same name.
64
+ #
65
+ # @return [Cuprum::Rails::Repository] the repository.
66
+ #
67
+ # @raise [DuplicateCollectionError] if a collection with the same name
68
+ # already exists in the repository.
69
+ def add(collection, force: false)
70
+ validate_collection!(collection)
71
+
72
+ if !force && key?(collection.qualified_name.to_s)
73
+ raise DuplicateCollectionError,
74
+ "collection #{collection.qualified_name} already exists"
75
+ end
76
+
77
+ @collections[collection.qualified_name.to_s] = collection
78
+
79
+ self
80
+ end
81
+ alias << add
82
+
83
+ # Checks if a collection with the given name exists in the repository.
84
+ #
85
+ # @param qualified_name [String, Symbol] The name to check for.
86
+ #
87
+ # @return [true, false] true if the key exists, otherwise false.
88
+ def key?(qualified_name)
89
+ @collections.key?(qualified_name.to_s)
90
+ end
91
+
92
+ private
93
+
94
+ def valid_collection?(collection)
95
+ collection.respond_to?(:collection_name)
96
+ end
97
+
98
+ def validate_collection!(collection)
99
+ return if valid_collection?(collection)
100
+
101
+ raise InvalidCollectionError,
102
+ "#{collection.inspect} is not a valid collection"
103
+ end
104
+ end
105
+ 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
  }
@@ -1,153 +1,190 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'rspec/sleeping_king_studios/contract'
4
+
3
5
  require 'cuprum/collections/rspec'
4
6
 
5
7
  module Cuprum::Collections::RSpec
6
8
  # Contract validating the behavior of a Collection.
7
- COLLECTION_CONTRACT = lambda do
8
- shared_examples 'should define the command' do |command_name, command_class|
9
- tools = SleepingKingStudios::Tools::Toolbelt.instance
10
- class_name = tools.str.camelize(command_name)
11
-
12
- describe "::#{class_name}" do
13
- let(:constructor_options) { {} }
14
- let(:command) do
15
- collection.const_get(class_name).new(**constructor_options)
16
- end
9
+ module CollectionContract
10
+ extend RSpec::SleepingKingStudios::Contract
11
+
12
+ # @!method apply(example_group)
13
+ # Adds the contract to the example group.
14
+ #
15
+ # @param example_group [RSpec::Core::ExampleGroup] The example group to
16
+ # which the contract is applied.
17
+
18
+ contract do
19
+ shared_examples 'should define the command' \
20
+ do |command_name, command_class|
21
+ tools = SleepingKingStudios::Tools::Toolbelt.instance
22
+ class_name = tools.str.camelize(command_name)
23
+
24
+ describe "::#{class_name}" do
25
+ let(:constructor_options) { {} }
26
+ let(:command) do
27
+ collection.const_get(class_name).new(**constructor_options)
28
+ end
17
29
 
18
- it { expect(collection).to define_constant(class_name) }
30
+ it { expect(collection).to define_constant(class_name) }
19
31
 
20
- it { expect(collection.const_get(class_name)).to be_a Class }
32
+ it { expect(collection.const_get(class_name)).to be_a Class }
21
33
 
22
- it { expect(collection.const_get(class_name)).to be < command_class }
23
-
24
- command_options.each do |option_name|
25
- it "should set the ##{option_name}" do
26
- expect(command.send(option_name))
27
- .to be == collection.send(option_name)
28
- end
29
- end
30
-
31
- describe 'with options' do
32
- let(:constructor_options) do
33
- {
34
- data: [],
35
- member_name: 'tome'
36
- }
37
- end
34
+ it { expect(collection.const_get(class_name)).to be < command_class }
38
35
 
39
36
  command_options.each do |option_name|
40
37
  it "should set the ##{option_name}" do
41
- expect(command.send(option_name)).to(
42
- be == constructor_options.fetch(option_name) do
43
- collection.send(option_name)
44
- end
45
- )
38
+ expect(command.send(option_name))
39
+ .to be == collection.send(option_name)
46
40
  end
47
41
  end
48
- end
49
- end
50
42
 
51
- describe "##{command_name}" do
52
- let(:constructor_options) { {} }
53
- let(:command) do
54
- collection.send(command_name, **constructor_options)
55
- end
43
+ describe 'with options' do
44
+ let(:constructor_options) do
45
+ {
46
+ data: [],
47
+ member_name: 'tome'
48
+ }
49
+ end
56
50
 
57
- it 'should define the command' do
58
- expect(collection)
59
- .to respond_to(command_name)
60
- .with(0).arguments
61
- .and_any_keywords
51
+ command_options.each do |option_name|
52
+ it "should set the ##{option_name}" do
53
+ expect(command.send(option_name)).to(
54
+ be == constructor_options.fetch(option_name) do
55
+ collection.send(option_name)
56
+ end
57
+ )
58
+ end
59
+ end
60
+ end
62
61
  end
63
62
 
64
- it { expect(command).to be_a collection.const_get(class_name) }
65
-
66
- command_options.each do |option_name|
67
- it "should set the ##{option_name}" do
68
- expect(command.send(option_name))
69
- .to be == collection.send(option_name)
63
+ describe "##{command_name}" do
64
+ let(:constructor_options) { {} }
65
+ let(:command) do
66
+ collection.send(command_name, **constructor_options)
70
67
  end
71
- end
72
68
 
73
- describe 'with options' do
74
- let(:constructor_options) do
75
- {
76
- data: [],
77
- member_name: 'tome'
78
- }
69
+ it 'should define the command' do
70
+ expect(collection)
71
+ .to respond_to(command_name)
72
+ .with(0).arguments
73
+ .and_any_keywords
79
74
  end
80
75
 
76
+ it { expect(command).to be_a collection.const_get(class_name) }
77
+
81
78
  command_options.each do |option_name|
82
79
  it "should set the ##{option_name}" do
83
- expect(command.send(option_name)).to(
84
- be == constructor_options.fetch(option_name) do
85
- collection.send(option_name)
86
- end
87
- )
80
+ expect(command.send(option_name))
81
+ .to be == collection.send(option_name)
82
+ end
83
+ end
84
+
85
+ describe 'with options' do
86
+ let(:constructor_options) do
87
+ {
88
+ data: [],
89
+ member_name: 'tome'
90
+ }
91
+ end
92
+
93
+ command_options.each do |option_name|
94
+ it "should set the ##{option_name}" do
95
+ expect(command.send(option_name)).to(
96
+ be == constructor_options.fetch(option_name) do
97
+ collection.send(option_name)
98
+ end
99
+ )
100
+ end
88
101
  end
89
102
  end
90
103
  end
91
104
  end
92
- end
93
105
 
94
- include_examples 'should define the command',
95
- :assign_one,
96
- commands_namespace::AssignOne
106
+ include_examples 'should define the command',
107
+ :assign_one,
108
+ commands_namespace::AssignOne
109
+
110
+ include_examples 'should define the command',
111
+ :build_one,
112
+ commands_namespace::BuildOne
97
113
 
98
- include_examples 'should define the command',
99
- :build_one,
100
- commands_namespace::BuildOne
114
+ include_examples 'should define the command',
115
+ :destroy_one,
116
+ commands_namespace::DestroyOne
101
117
 
102
- include_examples 'should define the command',
103
- :destroy_one,
104
- commands_namespace::DestroyOne
118
+ include_examples 'should define the command',
119
+ :find_many,
120
+ commands_namespace::FindMany
105
121
 
106
- include_examples 'should define the command',
107
- :find_many,
108
- commands_namespace::FindMany
122
+ include_examples 'should define the command',
123
+ :find_matching,
124
+ commands_namespace::FindMatching
109
125
 
110
- include_examples 'should define the command',
111
- :find_matching,
112
- commands_namespace::FindMatching
126
+ include_examples 'should define the command',
127
+ :find_one,
128
+ commands_namespace::FindOne
113
129
 
114
- include_examples 'should define the command',
115
- :find_one,
116
- commands_namespace::FindOne
130
+ include_examples 'should define the command',
131
+ :insert_one,
132
+ commands_namespace::InsertOne
117
133
 
118
- include_examples 'should define the command',
119
- :insert_one,
120
- commands_namespace::InsertOne
134
+ include_examples 'should define the command',
135
+ :update_one,
136
+ commands_namespace::UpdateOne
121
137
 
122
- include_examples 'should define the command',
123
- :update_one,
124
- commands_namespace::UpdateOne
138
+ include_examples 'should define the command',
139
+ :validate_one,
140
+ commands_namespace::ValidateOne
125
141
 
126
- include_examples 'should define the command',
127
- :validate_one,
128
- commands_namespace::ValidateOne
142
+ describe '#collection_name' do
143
+ include_examples 'should define reader',
144
+ :collection_name,
145
+ -> { an_instance_of(String) }
146
+ end
129
147
 
130
- describe '#query' do
131
- let(:default_order) { defined?(super()) ? super() : {} }
132
- let(:query) { collection.query }
148
+ describe '#count' do
149
+ it { expect(collection).to respond_to(:count).with(0).arguments }
133
150
 
134
- it { expect(collection).to respond_to(:query).with(0).arguments }
151
+ it { expect(collection).to have_aliased_method(:count).as(:size) }
135
152
 
136
- it { expect(collection.query).to be_a query_class }
153
+ it { expect(collection.count).to be 0 }
137
154
 
138
- it 'should set the query options' do
139
- query_options.each do |option, value|
140
- expect(collection.query.send option).to be == value
155
+ wrap_context 'when the collection has many items' do
156
+ it { expect(collection.count).to be items.count }
141
157
  end
142
158
  end
143
159
 
144
- it { expect(query.criteria).to be == [] }
160
+ describe '#qualified_name' do
161
+ include_examples 'should define reader',
162
+ :qualified_name,
163
+ -> { an_instance_of(String) }
164
+ end
145
165
 
146
- it { expect(query.limit).to be nil }
166
+ describe '#query' do
167
+ let(:default_order) { defined?(super()) ? super() : {} }
168
+ let(:query) { collection.query }
147
169
 
148
- it { expect(query.offset).to be nil }
170
+ it { expect(collection).to respond_to(:query).with(0).arguments }
149
171
 
150
- it { expect(query.order).to be == default_order }
172
+ it { expect(collection.query).to be_a query_class }
173
+
174
+ it 'should set the query options' do
175
+ query_options.each do |option, value|
176
+ expect(collection.query.send option).to be == value
177
+ end
178
+ end
179
+
180
+ it { expect(query.criteria).to be == [] }
181
+
182
+ it { expect(query.limit).to be nil }
183
+
184
+ it { expect(query.offset).to be nil }
185
+
186
+ it { expect(query.order).to be == default_order }
187
+ end
151
188
  end
152
189
  end
153
190
  end
@@ -28,9 +28,10 @@ module Cuprum::Collections::RSpec
28
28
  let(:primary_key) { invalid_primary_key_value }
29
29
  let(:expected_error) do
30
30
  Cuprum::Collections::Errors::NotFound.new(
31
- collection_name: command.collection_name,
32
- primary_key_name: primary_key_name,
33
- primary_key_values: primary_key
31
+ attribute_name: primary_key_name,
32
+ attribute_value: primary_key,
33
+ collection_name: collection_name,
34
+ primary_key: true
34
35
  )
35
36
  end
36
37
 
@@ -59,9 +60,10 @@ module Cuprum::Collections::RSpec
59
60
  let(:primary_key) { invalid_primary_key_value }
60
61
  let(:expected_error) do
61
62
  Cuprum::Collections::Errors::NotFound.new(
62
- collection_name: command.collection_name,
63
- primary_key_name: primary_key_name,
64
- primary_key_values: primary_key
63
+ attribute_name: primary_key_name,
64
+ attribute_value: primary_key,
65
+ collection_name: collection_name,
66
+ primary_key: true
65
67
  )
66
68
  end
67
69