cuprum-collections 0.3.0 → 0.4.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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +48 -0
  3. data/DEVELOPMENT.md +2 -2
  4. data/README.md +13 -11
  5. data/lib/cuprum/collections/association.rb +256 -0
  6. data/lib/cuprum/collections/associations/belongs_to.rb +32 -0
  7. data/lib/cuprum/collections/associations/has_many.rb +23 -0
  8. data/lib/cuprum/collections/associations/has_one.rb +23 -0
  9. data/lib/cuprum/collections/associations.rb +10 -0
  10. data/lib/cuprum/collections/basic/collection.rb +39 -74
  11. data/lib/cuprum/collections/basic/commands/find_many.rb +1 -1
  12. data/lib/cuprum/collections/basic/commands/find_matching.rb +1 -1
  13. data/lib/cuprum/collections/basic/repository.rb +9 -33
  14. data/lib/cuprum/collections/basic.rb +1 -0
  15. data/lib/cuprum/collections/collection.rb +154 -0
  16. data/lib/cuprum/collections/commands/associations/find_many.rb +161 -0
  17. data/lib/cuprum/collections/commands/associations/require_many.rb +48 -0
  18. data/lib/cuprum/collections/commands/associations.rb +13 -0
  19. data/lib/cuprum/collections/commands/find_one_matching.rb +1 -1
  20. data/lib/cuprum/collections/commands.rb +1 -0
  21. data/lib/cuprum/collections/errors/abstract_find_error.rb +1 -1
  22. data/lib/cuprum/collections/relation.rb +401 -0
  23. data/lib/cuprum/collections/repository.rb +71 -4
  24. data/lib/cuprum/collections/resource.rb +65 -0
  25. data/lib/cuprum/collections/rspec/contracts/association_contracts.rb +2137 -0
  26. data/lib/cuprum/collections/rspec/contracts/basic/command_contracts.rb +484 -0
  27. data/lib/cuprum/collections/rspec/contracts/basic.rb +11 -0
  28. data/lib/cuprum/collections/rspec/contracts/collection_contracts.rb +429 -0
  29. data/lib/cuprum/collections/rspec/contracts/command_contracts.rb +1462 -0
  30. data/lib/cuprum/collections/rspec/contracts/query_contracts.rb +1093 -0
  31. data/lib/cuprum/collections/rspec/contracts/relation_contracts.rb +1381 -0
  32. data/lib/cuprum/collections/rspec/contracts/repository_contracts.rb +605 -0
  33. data/lib/cuprum/collections/rspec/contracts.rb +23 -0
  34. data/lib/cuprum/collections/rspec/fixtures.rb +85 -82
  35. data/lib/cuprum/collections/rspec.rb +4 -1
  36. data/lib/cuprum/collections/version.rb +1 -1
  37. data/lib/cuprum/collections.rb +9 -4
  38. metadata +23 -19
  39. data/lib/cuprum/collections/base.rb +0 -11
  40. data/lib/cuprum/collections/basic/rspec/command_contract.rb +0 -392
  41. data/lib/cuprum/collections/rspec/assign_one_command_contract.rb +0 -168
  42. data/lib/cuprum/collections/rspec/build_one_command_contract.rb +0 -93
  43. data/lib/cuprum/collections/rspec/collection_contract.rb +0 -190
  44. data/lib/cuprum/collections/rspec/destroy_one_command_contract.rb +0 -108
  45. data/lib/cuprum/collections/rspec/find_many_command_contract.rb +0 -407
  46. data/lib/cuprum/collections/rspec/find_matching_command_contract.rb +0 -194
  47. data/lib/cuprum/collections/rspec/find_one_command_contract.rb +0 -157
  48. data/lib/cuprum/collections/rspec/insert_one_command_contract.rb +0 -84
  49. data/lib/cuprum/collections/rspec/query_builder_contract.rb +0 -92
  50. data/lib/cuprum/collections/rspec/query_contract.rb +0 -650
  51. data/lib/cuprum/collections/rspec/querying_contract.rb +0 -298
  52. data/lib/cuprum/collections/rspec/repository_contract.rb +0 -235
  53. data/lib/cuprum/collections/rspec/update_one_command_contract.rb +0 -80
  54. data/lib/cuprum/collections/rspec/validate_one_command_contract.rb +0 -96
@@ -1,194 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'stannum/rspec/validate_parameter'
4
-
5
- require 'cuprum/collections/constraints/ordering'
6
- require 'cuprum/collections/rspec'
7
- require 'cuprum/collections/rspec/querying_contract'
8
-
9
- module Cuprum::Collections::RSpec
10
- # Contract validating the behavior of a FindMatching command implementation.
11
- FIND_MATCHING_COMMAND_CONTRACT = lambda do
12
- include Stannum::RSpec::Matchers
13
-
14
- describe '#call' do
15
- shared_examples 'should return the matching items' do
16
- it { expect(result).to be_a_passing_result }
17
-
18
- it { expect(result.value).to be_a Enumerator }
19
-
20
- it { expect(result.value.to_a).to be == expected_data }
21
- end
22
-
23
- shared_examples 'should return the wrapped items' do
24
- it { expect(result).to be_a_passing_result }
25
-
26
- it { expect(result.value).to be_a Hash }
27
-
28
- it { expect(result.value.keys).to be == [collection_name] }
29
-
30
- it { expect(result.value[collection_name]).to be == expected_data }
31
- end
32
-
33
- include_contract Cuprum::Collections::RSpec::QUERYING_CONTEXTS
34
-
35
- let(:options) do
36
- opts = {}
37
-
38
- opts[:limit] = limit if limit
39
- opts[:offset] = offset if offset
40
- opts[:order] = order if order
41
- opts[:where] = filter unless filter.nil? || filter.is_a?(Proc)
42
-
43
- opts
44
- end
45
- let(:block) { filter.is_a?(Proc) ? filter : nil }
46
- let(:result) { command.call(**options, &block) }
47
- let(:data) { [] }
48
- let(:matching_data) { data }
49
- let(:expected_data) do
50
- defined?(super()) ? super() : matching_data
51
- end
52
-
53
- it 'should validate the :envelope keyword' do
54
- expect(command)
55
- .to validate_parameter(:call, :envelope)
56
- .using_constraint(Stannum::Constraints::Boolean.new)
57
- end
58
-
59
- it 'should validate the :limit keyword' do
60
- expect(command)
61
- .to validate_parameter(:call, :limit)
62
- .with_value(Object.new)
63
- .using_constraint(Integer, required: false)
64
- end
65
-
66
- it 'should validate the :offset keyword' do
67
- expect(command)
68
- .to validate_parameter(:call, :offset)
69
- .with_value(Object.new)
70
- .using_constraint(Integer, required: false)
71
- end
72
-
73
- it 'should validate the :order keyword' do
74
- constraint = Cuprum::Collections::Constraints::Ordering.new
75
-
76
- expect(command)
77
- .to validate_parameter(:call, :order)
78
- .with_value(Object.new)
79
- .using_constraint(constraint, required: false)
80
- end
81
-
82
- it 'should validate the :scope keyword' do
83
- expect(command)
84
- .to validate_parameter(:call, :scope)
85
- .using_constraint(
86
- Stannum::Constraints::Type.new(query.class, optional: true)
87
- )
88
- .with_value(Object.new.freeze)
89
- end
90
-
91
- it 'should validate the :where keyword' do
92
- expect(command).to validate_parameter(:call, :where)
93
- end
94
-
95
- include_examples 'should return the matching items'
96
-
97
- include_contract Cuprum::Collections::RSpec::QUERYING_CONTRACT,
98
- block: lambda {
99
- include_examples 'should return the matching items'
100
- }
101
-
102
- describe 'with an invalid filter block' do
103
- let(:block) { -> {} }
104
- let(:expected_error) do
105
- an_instance_of(Cuprum::Collections::Errors::InvalidQuery)
106
- end
107
-
108
- it 'should return a failing result' do
109
- expect(result).to be_a_failing_result.with_error(expected_error)
110
- end
111
- end
112
-
113
- describe 'with envelope: true' do
114
- let(:options) { super().merge(envelope: true) }
115
-
116
- include_examples 'should return the wrapped items'
117
-
118
- include_contract Cuprum::Collections::RSpec::QUERYING_CONTRACT,
119
- block: lambda {
120
- include_examples 'should return the wrapped items'
121
- }
122
- end
123
-
124
- context 'when the collection has many items' do
125
- let(:data) { fixtures_data }
126
-
127
- include_examples 'should return the matching items'
128
-
129
- include_contract Cuprum::Collections::RSpec::QUERYING_CONTRACT,
130
- block: lambda {
131
- include_examples 'should return the matching items'
132
- }
133
-
134
- describe 'with envelope: true' do
135
- let(:options) { super().merge(envelope: true) }
136
-
137
- include_examples 'should return the wrapped items'
138
-
139
- include_contract Cuprum::Collections::RSpec::QUERYING_CONTRACT,
140
- block: lambda {
141
- include_examples 'should return the wrapped items'
142
- }
143
- end
144
-
145
- describe 'with scope: query' do
146
- let(:scope_filter) { -> { {} } }
147
- let(:options) { super().merge(scope: scope) }
148
-
149
- describe 'with a scope that does not match any values' do
150
- let(:scope_filter) { -> { { series: 'Mistborn' } } }
151
- let(:matching_data) { [] }
152
-
153
- include_examples 'should return the matching items'
154
- end
155
-
156
- describe 'with a scope that matches some values' do
157
- let(:scope_filter) { -> { { series: nil } } }
158
- let(:matching_data) do
159
- super().select { |item| item['series'].nil? }
160
- end
161
-
162
- include_examples 'should return the matching items'
163
-
164
- describe 'with a where filter' do
165
- let(:filter) { -> { { author: 'Ursula K. LeGuin' } } }
166
- let(:options) { super().merge(where: filter) }
167
- let(:matching_data) do
168
- super().select { |item| item['author'] == 'Ursula K. LeGuin' }
169
- end
170
-
171
- include_examples 'should return the matching items'
172
- end
173
- end
174
-
175
- describe 'with a scope that matches all values' do
176
- let(:scope_filter) { -> { { id: not_equal(nil) } } }
177
-
178
- include_examples 'should return the matching items'
179
-
180
- describe 'with a where filter' do
181
- let(:filter) { -> { { author: 'Ursula K. LeGuin' } } }
182
- let(:options) { super().merge(where: filter) }
183
- let(:matching_data) do
184
- super().select { |item| item['author'] == 'Ursula K. LeGuin' }
185
- end
186
-
187
- include_examples 'should return the matching items'
188
- end
189
- end
190
- end
191
- end
192
- end
193
- end
194
- end
@@ -1,157 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'cuprum/collections/rspec'
4
-
5
- module Cuprum::Collections::RSpec
6
- # Contract validating the behavior of a FindOne command implementation.
7
- FIND_ONE_COMMAND_CONTRACT = lambda do
8
- describe '#call' do
9
- let(:mapped_data) do
10
- defined?(super()) ? super() : data
11
- end
12
- let(:primary_key_name) { defined?(super()) ? super() : :id }
13
- let(:primary_key_type) { defined?(super()) ? super() : Integer }
14
- let(:invalid_primary_key_value) do
15
- defined?(super()) ? super() : 100
16
- end
17
- let(:valid_primary_key_value) do
18
- defined?(super()) ? super() : 0
19
- end
20
-
21
- def tools
22
- SleepingKingStudios::Tools::Toolbelt.instance
23
- end
24
-
25
- it 'should validate the :envelope keyword' do
26
- expect(command)
27
- .to validate_parameter(:call, :envelope)
28
- .using_constraint(Stannum::Constraints::Boolean.new)
29
- end
30
-
31
- it 'should validate the :primary_key keyword' do
32
- expect(command)
33
- .to validate_parameter(:call, :primary_key)
34
- .using_constraint(primary_key_type)
35
- end
36
-
37
- it 'should validate the :scope keyword' do
38
- expect(command)
39
- .to validate_parameter(:call, :scope)
40
- .using_constraint(
41
- Stannum::Constraints::Type.new(query.class, optional: true)
42
- )
43
- .with_value(Object.new.freeze)
44
- end
45
-
46
- describe 'with an invalid primary key' do
47
- let(:primary_key) { invalid_primary_key_value }
48
- let(:expected_error) do
49
- Cuprum::Collections::Errors::NotFound.new(
50
- attribute_name: primary_key_name,
51
- attribute_value: primary_key,
52
- collection_name: command.collection_name,
53
- primary_key: true
54
- )
55
- end
56
-
57
- it 'should return a failing result' do
58
- expect(command.call(primary_key: primary_key))
59
- .to be_a_failing_result
60
- .with_error(expected_error)
61
- end
62
- end
63
-
64
- context 'when the collection has many items' do
65
- let(:data) { fixtures_data }
66
- let(:matching_data) do
67
- mapped_data.find { |item| item[primary_key_name.to_s] == primary_key }
68
- end
69
- let(:expected_data) do
70
- defined?(super()) ? super() : matching_data
71
- end
72
-
73
- describe 'with an invalid primary key' do
74
- let(:primary_key) { invalid_primary_key_value }
75
- let(:expected_error) do
76
- Cuprum::Collections::Errors::NotFound.new(
77
- attribute_name: primary_key_name,
78
- attribute_value: primary_key,
79
- collection_name: command.collection_name,
80
- primary_key: true
81
- )
82
- end
83
-
84
- it 'should return a failing result' do
85
- expect(command.call(primary_key: primary_key))
86
- .to be_a_failing_result
87
- .with_error(expected_error)
88
- end
89
- end
90
-
91
- describe 'with a valid primary key' do
92
- let(:primary_key) { valid_primary_key_value }
93
-
94
- it 'should return a passing result' do
95
- expect(command.call(primary_key: primary_key))
96
- .to be_a_passing_result
97
- .with_value(expected_data)
98
- end
99
- end
100
-
101
- describe 'with envelope: true' do
102
- let(:member_name) { tools.str.singularize(collection_name) }
103
-
104
- describe 'with a valid primary key' do
105
- let(:primary_key) { valid_primary_key_value }
106
-
107
- it 'should return a passing result' do
108
- expect(command.call(primary_key: primary_key, envelope: true))
109
- .to be_a_passing_result
110
- .with_value({ member_name => expected_data })
111
- end
112
- end
113
- end
114
-
115
- describe 'with scope: query' do
116
- let(:scope_filter) { -> { {} } }
117
-
118
- describe 'with a scope that does not match the key' do
119
- let(:scope_filter) { -> { { author: 'Ursula K. LeGuin' } } }
120
-
121
- describe 'with an valid primary key' do
122
- let(:primary_key) { valid_primary_key_value }
123
- let(:expected_error) do
124
- Cuprum::Collections::Errors::NotFound.new(
125
- attribute_name: primary_key_name,
126
- attribute_value: primary_key,
127
- collection_name: command.collection_name,
128
- primary_key: true
129
- )
130
- end
131
-
132
- it 'should return a failing result' do
133
- expect(command.call(primary_key: primary_key, scope: scope))
134
- .to be_a_failing_result
135
- .with_error(expected_error)
136
- end
137
- end
138
- end
139
-
140
- describe 'with a scope that matches the key' do
141
- let(:scope_filter) { -> { { author: 'J.R.R. Tolkien' } } }
142
-
143
- describe 'with a valid primary key' do
144
- let(:primary_key) { valid_primary_key_value }
145
-
146
- it 'should return a passing result' do
147
- expect(command.call(primary_key: primary_key))
148
- .to be_a_passing_result
149
- .with_value(expected_data)
150
- end
151
- end
152
- end
153
- end
154
- end
155
- end
156
- end
157
- end
@@ -1,84 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'cuprum/collections/rspec'
4
-
5
- module Cuprum::Collections::RSpec
6
- # Contract validating the behavior of an InsertOne command implementation.
7
- INSERT_ONE_COMMAND_CONTRACT = lambda do
8
- describe '#call' do
9
- let(:matching_data) { attributes }
10
- let(:expected_data) do
11
- defined?(super()) ? super() : matching_data
12
- end
13
- let(:primary_key_name) do
14
- defined?(super()) ? super() : :id
15
- end
16
- let(:primary_key_type) do
17
- defined?(super()) ? super() : Integer
18
- end
19
- let(:scoped) do
20
- key = primary_key_name
21
- value = entity[primary_key_name.to_s]
22
-
23
- query.where { { key => value } }
24
- end
25
-
26
- it 'should validate the :entity keyword' do
27
- expect(command)
28
- .to validate_parameter(:call, :entity)
29
- .using_constraint(entity_type)
30
- end
31
-
32
- context 'when the item does not exist in the collection' do
33
- it 'should return a passing result' do
34
- expect(command.call(entity: entity))
35
- .to be_a_passing_result
36
- .with_value(be == expected_data)
37
- end
38
-
39
- it 'should append an item to the collection' do
40
- expect { command.call(entity: entity) }
41
- .to(
42
- change { query.reset.count }
43
- .by(1)
44
- )
45
- end
46
-
47
- it 'should add the entity to the collection' do
48
- expect { command.call(entity: entity) }
49
- .to change(scoped, :exists?)
50
- .to be true
51
- end
52
-
53
- it 'should set the attributes' do
54
- command.call(entity: entity)
55
-
56
- expect(scoped.to_a.first).to be == expected_data
57
- end
58
- end
59
-
60
- context 'when the item exists in the collection' do
61
- let(:data) { fixtures_data }
62
- let(:expected_error) do
63
- Cuprum::Collections::Errors::AlreadyExists.new(
64
- attribute_name: primary_key_name,
65
- attribute_value: attributes[primary_key_name],
66
- collection_name: collection_name,
67
- primary_key: true
68
- )
69
- end
70
-
71
- it 'should return a failing result' do
72
- expect(command.call(entity: entity))
73
- .to be_a_failing_result
74
- .with_error(expected_error)
75
- end
76
-
77
- it 'should not append an item to the collection' do
78
- expect { command.call(entity: entity) }
79
- .not_to(change { query.reset.count })
80
- end
81
- end
82
- end
83
- end
84
- end
@@ -1,92 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'cuprum/collections/rspec'
4
-
5
- module Cuprum::Collections::RSpec
6
- # Contract validating the behavior of a QueryBuilder implementation.
7
- QUERY_BUILDER_CONTRACT = lambda do
8
- describe '#base_query' do
9
- include_examples 'should define reader', :base_query, -> { base_query }
10
- end
11
-
12
- describe '#call' do
13
- let(:criteria) { [['title', :equal, 'The Naked Sun']] }
14
- let(:expected) { criteria }
15
- let(:filter) { { title: 'The Naked Sun' } }
16
- let(:strategy) { :custom }
17
- let(:parser) do
18
- instance_double(
19
- Cuprum::Collections::Queries::Parse,
20
- call: Cuprum::Result.new(value: criteria)
21
- )
22
- end
23
- let(:query) do
24
- builder.call(strategy: strategy, where: filter)
25
- end
26
-
27
- before(:example) do
28
- allow(Cuprum::Collections::Queries::Parse)
29
- .to receive(:new)
30
- .and_return(parser)
31
- end
32
-
33
- it 'should define the method' do
34
- expect(builder).to respond_to(:call)
35
- .with(0).arguments
36
- .and_keywords(:strategy, :where)
37
- end
38
-
39
- it 'should parse the criteria' do
40
- builder.call(strategy: strategy, where: filter)
41
-
42
- expect(parser)
43
- .to have_received(:call)
44
- .with(strategy: strategy, where: filter)
45
- end
46
-
47
- it { expect(query).to be_a base_query.class }
48
-
49
- it { expect(query).not_to be base_query }
50
-
51
- it { expect(query.criteria).to be == expected }
52
-
53
- describe 'with strategy: :unsafe' do
54
- let(:strategy) { :unsafe }
55
- let(:filter) { criteria }
56
-
57
- it 'should not parse the criteria' do
58
- builder.call(strategy: strategy, where: filter)
59
-
60
- expect(parser).not_to have_received(:call)
61
- end
62
-
63
- it { expect(query.criteria).to be == expected }
64
- end
65
-
66
- context 'when the query has existing criteria' do
67
- let(:old_criteria) { [['genre', :eq, 'Science Fiction']] }
68
- let(:expected) { old_criteria + criteria }
69
- let(:base_query) { super().send(:with_criteria, old_criteria) }
70
-
71
- it { expect(query.criteria).to be == expected }
72
- end
73
-
74
- context 'when the parser is unable to parse the query' do
75
- let(:error) { Cuprum::Error.new(message: 'Something went wrong.') }
76
- let(:result) { Cuprum::Result.new(error: error) }
77
-
78
- before(:example) do
79
- allow(parser).to receive(:call).and_return(result)
80
- end
81
-
82
- it 'should raise an exception' do
83
- expect do
84
- builder.call(strategy: strategy, where: filter)
85
- end
86
- .to raise_error Cuprum::Collections::QueryBuilder::ParseError,
87
- error.message
88
- end
89
- end
90
- end
91
- end
92
- end