lotus-model 0.0.0 → 0.1.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +6 -0
  4. data/.yardopts +5 -0
  5. data/EXAMPLE.md +217 -0
  6. data/Gemfile +14 -2
  7. data/README.md +303 -3
  8. data/Rakefile +17 -1
  9. data/lib/lotus-model.rb +1 -0
  10. data/lib/lotus/entity.rb +157 -0
  11. data/lib/lotus/model.rb +23 -2
  12. data/lib/lotus/model/adapters/abstract.rb +167 -0
  13. data/lib/lotus/model/adapters/implementation.rb +111 -0
  14. data/lib/lotus/model/adapters/memory/collection.rb +132 -0
  15. data/lib/lotus/model/adapters/memory/command.rb +90 -0
  16. data/lib/lotus/model/adapters/memory/query.rb +457 -0
  17. data/lib/lotus/model/adapters/memory_adapter.rb +149 -0
  18. data/lib/lotus/model/adapters/sql/collection.rb +209 -0
  19. data/lib/lotus/model/adapters/sql/command.rb +67 -0
  20. data/lib/lotus/model/adapters/sql/query.rb +615 -0
  21. data/lib/lotus/model/adapters/sql_adapter.rb +154 -0
  22. data/lib/lotus/model/mapper.rb +101 -0
  23. data/lib/lotus/model/mapping.rb +23 -0
  24. data/lib/lotus/model/mapping/coercer.rb +80 -0
  25. data/lib/lotus/model/mapping/collection.rb +336 -0
  26. data/lib/lotus/model/version.rb +4 -1
  27. data/lib/lotus/repository.rb +620 -0
  28. data/lotus-model.gemspec +15 -11
  29. data/test/entity_test.rb +126 -0
  30. data/test/fixtures.rb +81 -0
  31. data/test/model/adapters/abstract_test.rb +75 -0
  32. data/test/model/adapters/implementation_test.rb +22 -0
  33. data/test/model/adapters/memory/query_test.rb +91 -0
  34. data/test/model/adapters/memory_adapter_test.rb +1044 -0
  35. data/test/model/adapters/sql/query_test.rb +121 -0
  36. data/test/model/adapters/sql_adapter_test.rb +1078 -0
  37. data/test/model/mapper_test.rb +94 -0
  38. data/test/model/mapping/coercer_test.rb +27 -0
  39. data/test/model/mapping/collection_test.rb +82 -0
  40. data/test/repository_test.rb +283 -0
  41. data/test/test_helper.rb +30 -0
  42. data/test/version_test.rb +7 -0
  43. metadata +109 -11
data/lotus-model.gemspec CHANGED
@@ -4,20 +4,24 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'lotus/model/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "lotus-model"
7
+ spec.name = 'lotus-model'
8
8
  spec.version = Lotus::Model::VERSION
9
- spec.authors = ["Luca Guidi"]
10
- spec.email = ["me@lucaguidi.com"]
11
- spec.summary = %q{Model layer for Lotus}
12
- spec.description = %q{Model layer for Lotus}
13
- spec.homepage = ""
14
- spec.license = "MIT"
9
+ spec.authors = ['Luca Guidi']
10
+ spec.email = ['me@lucaguidi.com']
11
+ spec.summary = %q{A persistence layer for Lotus}
12
+ spec.description = %q{A persistence framework with entities, repositories, data mapper and query objects}
13
+ spec.homepage = 'http://lotusrb.org'
14
+ spec.license = 'MIT'
15
15
 
16
- spec.files = `git ls-files`.split($/)
16
+ spec.files = `git ls-files -z`.split("\x0")
17
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
- spec.require_paths = ["lib"]
19
+ spec.require_paths = ['lib']
20
20
 
21
- spec.add_development_dependency "bundler", "~> 1.5"
22
- spec.add_development_dependency "rake"
21
+ spec.add_runtime_dependency 'lotus-utils', '~> 0.1', '> 0.1.0'
22
+ spec.add_runtime_dependency 'sequel', '~> 4.9'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.5'
25
+ spec.add_development_dependency 'minitest', '~> 5'
26
+ spec.add_development_dependency 'rake', '~> 10'
23
27
  end
@@ -0,0 +1,126 @@
1
+ require 'test_helper'
2
+
3
+ describe Lotus::Entity do
4
+ before do
5
+ class Car
6
+ include Lotus::Entity
7
+ end
8
+
9
+ class Book
10
+ include Lotus::Entity
11
+ self.attributes = :title, :author
12
+ end
13
+
14
+ class NonFinctionBook < Book
15
+ end
16
+
17
+ class Camera
18
+ include Lotus::Entity
19
+ attr_accessor :analog
20
+ end
21
+ end
22
+
23
+ after do
24
+ [:Car, :Book, :NonFinctionBook, :Camera].each do |const|
25
+ Object.send(:remove_const, const)
26
+ end
27
+ end
28
+
29
+ describe 'attributes' do
30
+ let(:attributes) { [:id, :model] }
31
+
32
+ it 'defines attributes' do
33
+ Car.send(:attributes=, attributes)
34
+ Car.send(:attributes).must_equal attributes
35
+ end
36
+ end
37
+
38
+ describe '#initialize' do
39
+ describe 'with defined attributes' do
40
+ it 'accepts given attributes' do
41
+ book = Book.new(title: "A Lover's Discourse: Fragments", author: 'Roland Barthes')
42
+
43
+ book.instance_variable_get(:@title).must_equal "A Lover's Discourse: Fragments"
44
+ book.instance_variable_get(:@author).must_equal 'Roland Barthes'
45
+ end
46
+
47
+ it 'ignores unknown attributes' do
48
+ book = Book.new(unknown: 'x')
49
+
50
+ book.instance_variable_get(:@book).must_be_nil
51
+ end
52
+
53
+ it 'accepts given attributes for subclass' do
54
+ book = NonFinctionBook.new(title: 'Refactoring', author: 'Martin Fowler')
55
+
56
+ book.instance_variable_get(:@title).must_equal 'Refactoring'
57
+ book.instance_variable_get(:@author).must_equal 'Martin Fowler'
58
+ end
59
+ end
60
+
61
+ describe 'with undefined attributes' do
62
+ it 'is able to initialize an entity without given attributes' do
63
+ camera = Camera.new
64
+ camera.analog.must_be_nil
65
+ end
66
+
67
+ it 'is able to initialize an entity if it has the right accessors' do
68
+ camera = Camera.new(analog: true)
69
+ camera.analog.must_equal(true)
70
+ end
71
+
72
+ it "raises an error when the given attributes don't correspond to a known accessor" do
73
+ -> { Camera.new(digital: true) }.must_raise(NoMethodError)
74
+ end
75
+ end
76
+ end
77
+
78
+ describe 'accessors' do
79
+ it 'exposes getters for attributes' do
80
+ book = Book.new(title: 'High Fidelity')
81
+
82
+ book.title.must_equal 'High Fidelity'
83
+ end
84
+
85
+ it 'exposes setters for attributes' do
86
+ book = Book.new
87
+ book.title = 'A Man'
88
+
89
+ book.instance_variable_get(:@title).must_equal 'A Man'
90
+ book.title.must_equal 'A Man'
91
+ end
92
+
93
+ it 'exposes accessor for id' do
94
+ book = Book.new
95
+ book.id.must_be_nil
96
+
97
+ book.id = 23
98
+ book.id.must_equal 23
99
+ end
100
+ end
101
+
102
+ describe '#==' do
103
+ before do
104
+ @book1 = Book.new
105
+ @book1.id = 23
106
+
107
+ @book2 = Book.new
108
+ @book2.id = 23
109
+
110
+ @book3 = Book.new
111
+ @car = Car.new
112
+ end
113
+
114
+ it 'returns true if they have the same class and id' do
115
+ @book1.must_equal @book2
116
+ end
117
+
118
+ it 'returns false if they have the same class but different id' do
119
+ @book1.wont_equal @book3
120
+ end
121
+
122
+ it 'returns false if they have different class' do
123
+ @book1.wont_equal @car
124
+ end
125
+ end
126
+ end
data/test/fixtures.rb ADDED
@@ -0,0 +1,81 @@
1
+ class User
2
+ include Lotus::Entity
3
+ self.attributes = :name, :age
4
+ end
5
+
6
+ class Article
7
+ include Lotus::Entity
8
+ self.attributes = :user_id, :unmapped_attribute, :title, :comments_count
9
+ end
10
+
11
+ class UserRepository
12
+ include Lotus::Repository
13
+ end
14
+
15
+ class ArticleRepository
16
+ include Lotus::Repository
17
+
18
+ def self.rank
19
+ query do
20
+ desc(:comments_count)
21
+ end
22
+ end
23
+
24
+ def self.by_user(user)
25
+ query do
26
+ where(user_id: user.id)
27
+ end
28
+ end
29
+
30
+ def self.not_by_user(user)
31
+ exclude by_user(user)
32
+ end
33
+
34
+ def self.rank_by_user(user)
35
+ rank.by_user(user)
36
+ end
37
+ end
38
+
39
+ DB = Sequel.connect(SQLITE_CONNECTION_STRING)
40
+
41
+ DB.create_table :users do
42
+ primary_key :id
43
+ String :name
44
+ Integer :age
45
+ end
46
+
47
+ DB.create_table :articles do
48
+ primary_key :_id
49
+ Integer :user_id
50
+ String :s_title
51
+ String :comments_count # Not an error: we're testing String => Integer coercion
52
+ String :umapped_column
53
+ end
54
+
55
+ DB.create_table :devices do
56
+ primary_key :id
57
+ end
58
+
59
+ # DB.dataset_class = Class.new(Sequel::Dataset)
60
+
61
+ #FIXME this should be passed by the framework internals.
62
+ MAPPER = Lotus::Model::Mapper.new do
63
+ collection :users do
64
+ entity User
65
+
66
+ attribute :id, Integer
67
+ attribute :name, String
68
+ attribute :age, Integer
69
+ end
70
+
71
+ collection :articles do
72
+ entity Article
73
+
74
+ attribute :id, Integer, as: :_id
75
+ attribute :user_id, Integer
76
+ attribute :title, String, as: 's_title'
77
+ attribute :comments_count, Integer
78
+
79
+ identity :_id
80
+ end
81
+ end.load!
@@ -0,0 +1,75 @@
1
+ require 'test_helper'
2
+
3
+ describe Lotus::Model::Adapters::Abstract do
4
+ let(:adapter) { Lotus::Model::Adapters::Abstract.new(mapper) }
5
+ let(:mapper) { Object.new }
6
+ let(:entity) { Object.new }
7
+ let(:query) { Object.new }
8
+ let(:collection) { :collection }
9
+
10
+ describe '#persist' do
11
+ it 'raises error' do
12
+ ->{ adapter.persist(collection, entity) }.must_raise NotImplementedError
13
+ end
14
+ end
15
+
16
+ describe '#create' do
17
+ it 'raises error' do
18
+ ->{ adapter.create(collection, entity) }.must_raise NotImplementedError
19
+ end
20
+ end
21
+
22
+ describe '#update' do
23
+ it 'raises error' do
24
+ ->{ adapter.update(collection, entity) }.must_raise NotImplementedError
25
+ end
26
+ end
27
+
28
+ describe '#delete' do
29
+ it 'raises error' do
30
+ ->{ adapter.delete(collection, entity) }.must_raise NotImplementedError
31
+ end
32
+ end
33
+
34
+ describe '#all' do
35
+ it 'raises error' do
36
+ ->{ adapter.all(collection) }.must_raise NotImplementedError
37
+ end
38
+ end
39
+
40
+ describe '#find' do
41
+ it 'raises error' do
42
+ ->{ adapter.find(collection, 1) }.must_raise NotImplementedError
43
+ end
44
+ end
45
+
46
+ describe '#first' do
47
+ it 'raises error' do
48
+ ->{ adapter.first(collection) }.must_raise NotImplementedError
49
+ end
50
+ end
51
+
52
+ describe '#last' do
53
+ it 'raises error' do
54
+ ->{ adapter.last(collection) }.must_raise NotImplementedError
55
+ end
56
+ end
57
+
58
+ describe '#clear' do
59
+ it 'raises error' do
60
+ ->{ adapter.clear(collection) }.must_raise NotImplementedError
61
+ end
62
+ end
63
+
64
+ describe '#command' do
65
+ it 'raises error' do
66
+ ->{ adapter.command(query) }.must_raise NotImplementedError
67
+ end
68
+ end
69
+
70
+ describe '#query' do
71
+ it 'raises error' do
72
+ ->{ adapter.query(collection) }.must_raise NotImplementedError
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,22 @@
1
+ require 'test_helper'
2
+
3
+ describe Lotus::Model::Adapters::Implementation do
4
+ before do
5
+ TestAdapter = Class.new(Lotus::Model::Adapters::Abstract) do
6
+ include Lotus::Model::Adapters::Implementation
7
+ end
8
+
9
+ mapper = Object.new
10
+ @adapter = TestAdapter.new(mapper)
11
+ end
12
+
13
+ after do
14
+ Object.send(:remove_const, :TestAdapter)
15
+ end
16
+
17
+ it 'must implement #_collection' do
18
+ -> {
19
+ @adapter.send(:_collection, :x)
20
+ }.must_raise NotImplementedError
21
+ end
22
+ end
@@ -0,0 +1,91 @@
1
+ require 'test_helper'
2
+
3
+ describe Lotus::Model::Adapters::Memory::Query do
4
+ before do
5
+ MockDataset = Struct.new(:records) do
6
+ def all
7
+ records
8
+ end
9
+
10
+ def to_s
11
+ records.to_s
12
+ end
13
+ end
14
+
15
+ class MockCollection
16
+ def deserialize(array)
17
+ array
18
+ end
19
+ end
20
+
21
+ collection = MockCollection.new
22
+ @query = Lotus::Model::Adapters::Memory::Query.new(dataset, collection)
23
+ end
24
+
25
+ after do
26
+ Object.send(:remove_const, :MockDataset)
27
+ Object.send(:remove_const, :MockCollection)
28
+ end
29
+
30
+ let(:dataset) { MockDataset.new([]) }
31
+
32
+ describe '#negate!' do
33
+ it 'raises an error' do
34
+ -> { @query.negate! }.must_raise NotImplementedError
35
+ end
36
+ end
37
+
38
+ describe '#to_s' do
39
+ let(:dataset) { MockDataset.new([1, 2, 3]) }
40
+
41
+ it 'delegates to the wrapped dataset' do
42
+ @query.to_s.must_equal dataset.to_s
43
+ end
44
+ end
45
+
46
+ describe '#empty?' do
47
+ describe "when it's empty" do
48
+ it 'returns true' do
49
+ @query.must_be_empty
50
+ end
51
+ end
52
+
53
+ describe "when it's filled with elements" do
54
+ let(:dataset) { MockDataset.new([1, 2, 3]) }
55
+
56
+ it 'returns false' do
57
+ @query.wont_be_empty
58
+ end
59
+ end
60
+ end
61
+
62
+ describe '#any?' do
63
+ describe "when it's empty" do
64
+ it 'returns false' do
65
+ assert !@query.any?
66
+ end
67
+ end
68
+
69
+ describe "when it's filled with elements" do
70
+ let(:dataset) { MockDataset.new([1, 2, 3]) }
71
+
72
+ it 'returns true' do
73
+ assert @query.any?
74
+ end
75
+
76
+ describe "when a block is passed" do
77
+ describe "and it doesn't match elements" do
78
+ it 'returns false' do
79
+ assert !@query.any? {|e| e > 100 }
80
+ end
81
+ end
82
+
83
+ describe "and it matches elements" do
84
+ it 'returns true' do
85
+ assert @query.any? {|e| e % 2 == 0 }
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,1044 @@
1
+ require 'test_helper'
2
+
3
+ describe Lotus::Model::Adapters::MemoryAdapter do
4
+ before do
5
+ TestUser = Struct.new(:id, :name, :age) do
6
+ include Lotus::Entity
7
+ end
8
+
9
+ TestDevice = Struct.new(:id) do
10
+ include Lotus::Entity
11
+ end
12
+
13
+ @mapper = Lotus::Model::Mapper.new do
14
+ collection :users do
15
+ entity TestUser
16
+
17
+ attribute :id, Integer
18
+ attribute :name, String
19
+ attribute :age, Integer
20
+ end
21
+
22
+ collection :devices do
23
+ entity TestDevice
24
+
25
+ attribute :id, Integer
26
+ end
27
+ end.load!
28
+
29
+ @adapter = Lotus::Model::Adapters::MemoryAdapter.new(@mapper)
30
+ end
31
+
32
+ after do
33
+ Object.send(:remove_const, :TestUser)
34
+ Object.send(:remove_const, :TestDevice)
35
+ end
36
+
37
+ let(:collection) { :users }
38
+
39
+ describe 'multiple collections' do
40
+ it 'create records' do
41
+ user = TestUser.new
42
+ device = TestDevice.new
43
+
44
+ @adapter.create(:users, user)
45
+ @adapter.create(:devices, device)
46
+
47
+ @adapter.all(:users).must_equal [user]
48
+ @adapter.all(:devices).must_equal [device]
49
+ end
50
+ end
51
+
52
+ describe '#persist' do
53
+ describe 'when the given entity is not persisted' do
54
+ let(:entity) { TestUser.new }
55
+
56
+ it 'stores the record and assigns an id' do
57
+ @adapter.persist(collection, entity)
58
+
59
+ entity.id.wont_be_nil
60
+ @adapter.find(collection, entity.id).must_equal entity
61
+ end
62
+ end
63
+
64
+ describe 'when the given entity is persisted' do
65
+ before do
66
+ @adapter.create(collection, entity)
67
+ end
68
+
69
+ let(:entity) { TestUser.new }
70
+
71
+ it 'updates the record and leaves untouched the id' do
72
+ id = entity.id
73
+ id.wont_be_nil
74
+
75
+ entity.name = 'L'
76
+ @adapter.persist(collection, entity)
77
+
78
+ entity.id.must_equal(id)
79
+ @adapter.find(collection, entity.id).must_equal entity
80
+ end
81
+ end
82
+ end
83
+
84
+ describe '#create' do
85
+ let(:entity) { TestUser.new }
86
+
87
+ it 'stores the record and assigns an id' do
88
+ @adapter.create(collection, entity)
89
+
90
+ entity.id.wont_be_nil
91
+ @adapter.find(collection, entity.id).must_equal entity
92
+ end
93
+ end
94
+
95
+ describe '#update' do
96
+ before do
97
+ @adapter.create(collection, entity)
98
+ end
99
+
100
+ let(:entity) { TestUser.new(id: nil, name: 'L') }
101
+
102
+ it 'stores the changes and leave the id untouched' do
103
+ id = entity.id
104
+
105
+ entity.name = 'MG'
106
+ @adapter.update(collection, entity)
107
+
108
+ entity.id.must_equal id
109
+ @adapter.find(collection, entity.id).must_equal entity
110
+ end
111
+ end
112
+
113
+ describe '#delete' do
114
+ before do
115
+ @adapter.create(collection, entity)
116
+ end
117
+
118
+ let(:entity) { TestUser.new }
119
+
120
+ it 'removes the given identity' do
121
+ @adapter.delete(collection, entity)
122
+ @adapter.find(collection, entity.id).must_be_nil
123
+ end
124
+ end
125
+
126
+ describe '#all' do
127
+ describe 'when no records are persisted' do
128
+ before do
129
+ @adapter.clear(collection)
130
+ end
131
+
132
+ it 'returns an empty collection' do
133
+ @adapter.all(collection).must_be_empty
134
+ end
135
+ end
136
+
137
+ describe 'when some records are persisted' do
138
+ before do
139
+ @adapter.create(collection, entity)
140
+ end
141
+
142
+ let(:entity) { TestUser.new }
143
+
144
+ it 'returns all of them' do
145
+ @adapter.all(collection).must_equal [entity]
146
+ end
147
+ end
148
+ end
149
+
150
+ describe '#find' do
151
+ before do
152
+ @adapter.create(collection, entity)
153
+ @adapter.instance_variable_get(:@collections).fetch(collection).records.store(nil, nil_entity)
154
+ end
155
+
156
+ let(:entity) { TestUser.new }
157
+ let(:nil_entity) { {id: 0} }
158
+
159
+ it 'returns the record by id' do
160
+ @adapter.find(collection, entity.id).must_equal entity
161
+ end
162
+
163
+ it 'returns nil when the record cannot be found' do
164
+ @adapter.find(collection, 1_000_000).must_be_nil
165
+ end
166
+
167
+ it 'returns nil when the given id is nil' do
168
+ @adapter.find(collection, nil).must_be_nil
169
+ end
170
+ end
171
+
172
+ describe '#first' do
173
+ describe 'when no records are peristed' do
174
+ before do
175
+ @adapter.clear(collection)
176
+ end
177
+
178
+ it 'returns nil' do
179
+ @adapter.first(collection).must_be_nil
180
+ end
181
+ end
182
+
183
+ describe 'when some records are persisted' do
184
+ before do
185
+ @adapter.create(collection, entity1)
186
+ @adapter.create(collection, entity2)
187
+ end
188
+
189
+ let(:entity1) { TestUser.new }
190
+ let(:entity2) { TestUser.new }
191
+
192
+ it 'returns the first record' do
193
+ @adapter.first(collection).must_equal entity1
194
+ end
195
+ end
196
+ end
197
+
198
+ describe '#last' do
199
+ describe 'when no records are peristed' do
200
+ before do
201
+ @adapter.clear(collection)
202
+ end
203
+
204
+ it 'returns nil' do
205
+ @adapter.last(collection).must_be_nil
206
+ end
207
+ end
208
+
209
+ describe 'when some records are persisted' do
210
+ before do
211
+ @adapter.create(collection, entity1)
212
+ @adapter.create(collection, entity2)
213
+ end
214
+
215
+ let(:entity1) { TestUser.new }
216
+ let(:entity2) { TestUser.new }
217
+
218
+ it 'returns the last record' do
219
+ @adapter.last(collection).must_equal entity2
220
+ end
221
+ end
222
+ end
223
+
224
+ describe '#clear' do
225
+ before do
226
+ @adapter.create(collection, entity)
227
+ end
228
+
229
+ let(:entity) { TestUser.new }
230
+
231
+ it 'removes all the records' do
232
+ @adapter.clear(collection)
233
+ @adapter.all(collection).must_be_empty
234
+ end
235
+
236
+ it 'resets the id counter' do
237
+ @adapter.clear(collection)
238
+
239
+ @adapter.create(collection, entity)
240
+ entity.id.must_equal 1
241
+ end
242
+ end
243
+
244
+ describe '#query' do
245
+ before do
246
+ @adapter.clear(collection)
247
+ end
248
+
249
+ let(:user1) { TestUser.new(name: 'L', age: '32') }
250
+ let(:user2) { TestUser.new(name: 'MG', age: 31) }
251
+
252
+ describe 'where' do
253
+ describe 'with an empty collection' do
254
+ it 'returns an empty result set' do
255
+ result = @adapter.query(collection) do
256
+ where(id: 23)
257
+ end.all
258
+
259
+ result.must_be_empty
260
+ end
261
+ end
262
+
263
+ describe 'with a filled collection' do
264
+ before do
265
+ @adapter.create(collection, user1)
266
+ @adapter.create(collection, user2)
267
+ end
268
+
269
+ it 'returns selected records' do
270
+ id = user1.id
271
+
272
+ query = Proc.new {
273
+ where(id: id)
274
+ }
275
+
276
+ result = @adapter.query(collection, &query).all
277
+ result.must_equal [user1]
278
+ end
279
+
280
+ it 'can use multiple where conditions' do
281
+ id = user1.id
282
+ name = user1.name
283
+
284
+ query = Proc.new {
285
+ where(id: id).where(name: name)
286
+ }
287
+
288
+ result = @adapter.query(collection, &query).all
289
+ result.must_equal [user1]
290
+ end
291
+
292
+ it 'can use multiple where conditions with "and" alias' do
293
+ id = user1.id
294
+ name = user1.name
295
+
296
+ query = Proc.new {
297
+ where(id: id).and(name: name)
298
+ }
299
+
300
+ result = @adapter.query(collection, &query).all
301
+ result.must_equal [user1]
302
+ end
303
+ end
304
+ end
305
+
306
+ describe 'exclude' do
307
+ describe 'with an empty collection' do
308
+ it 'returns an empty result set' do
309
+ result = @adapter.query(collection) do
310
+ exclude(id: 23)
311
+ end.all
312
+
313
+ result.must_be_empty
314
+ end
315
+ end
316
+
317
+ describe 'with a filled collection' do
318
+ before do
319
+ @adapter.create(collection, user1)
320
+ @adapter.create(collection, user2)
321
+ @adapter.create(collection, user3)
322
+ end
323
+
324
+ let(:user3) { TestUser.new(name: 'S', age: 2) }
325
+
326
+ it 'returns selected records' do
327
+ id = user1.id
328
+
329
+ query = Proc.new {
330
+ exclude(id: id)
331
+ }
332
+
333
+ result = @adapter.query(collection, &query).all
334
+ result.must_equal [user2, user3]
335
+ end
336
+
337
+ it 'can use multiple exclude conditions' do
338
+ id = user1.id
339
+ name = user2.name
340
+
341
+ query = Proc.new {
342
+ exclude(id: id).exclude(name: name)
343
+ }
344
+
345
+ $debug = true
346
+ result = @adapter.query(collection, &query).all
347
+ result.must_equal [user3]
348
+ end
349
+
350
+ it 'can use multiple exclude conditions with "not" alias' do
351
+ id = user1.id
352
+ name = user2.name
353
+
354
+ query = Proc.new {
355
+ self.not(id: id).not(name: name)
356
+ }
357
+
358
+ result = @adapter.query(collection, &query).all
359
+ result.must_equal [user3]
360
+ end
361
+ end
362
+ end
363
+
364
+ describe 'or' do
365
+ describe 'with an empty collection' do
366
+ it 'returns an empty result set' do
367
+ result = @adapter.query(collection) do
368
+ where(name: 'L').or(name: 'MG')
369
+ end.all
370
+
371
+ result.must_be_empty
372
+ end
373
+ end
374
+
375
+ describe 'with a filled collection' do
376
+ before do
377
+ @adapter.create(collection, user1)
378
+ @adapter.create(collection, user2)
379
+ end
380
+
381
+ it 'returns selected records' do
382
+ name1 = user1.name
383
+ name2 = user2.name
384
+
385
+ query = Proc.new {
386
+ where(name: name1).or(name: name2)
387
+ }
388
+
389
+ result = @adapter.query(collection, &query).all
390
+ result.must_equal [user1, user2]
391
+ end
392
+
393
+ it 'returns selected records only from the "or" condition' do
394
+ name2 = user2.name
395
+
396
+ query = Proc.new {
397
+ where(name: 'unknown').or(name: name2)
398
+ }
399
+
400
+ result = @adapter.query(collection, &query).all
401
+ result.must_equal [user2]
402
+ end
403
+ end
404
+ end
405
+
406
+ describe 'select' do
407
+ describe 'with an empty collection' do
408
+ it 'returns an empty result' do
409
+ result = @adapter.query(collection) do
410
+ select(:age)
411
+ end.all
412
+
413
+ result.must_be_empty
414
+ end
415
+ end
416
+
417
+ describe 'with a filled collection' do
418
+ before do
419
+ @adapter.create(collection, user1)
420
+ @adapter.create(collection, user2)
421
+ @adapter.create(collection, user3)
422
+ end
423
+
424
+ let(:user1) { TestUser.new(name: 'L', age: 32) }
425
+ let(:user3) { TestUser.new(name: 'S') }
426
+ let(:users) { [user1, user2, user3] }
427
+
428
+ it 'returns the selected columnts from all the records' do
429
+ query = Proc.new {
430
+ select(:age)
431
+ }
432
+
433
+ result = @adapter.query(collection, &query).all
434
+
435
+ users.each do |user|
436
+ record = result.find {|r| r.age == user.age }
437
+ record.wont_be_nil
438
+ record.name.must_be_nil
439
+ end
440
+ end
441
+
442
+ it 'returns only the select of requested records' do
443
+ name = user2.name
444
+
445
+ query = Proc.new {
446
+ where(name: name).select(:age)
447
+ }
448
+
449
+ result = @adapter.query(collection, &query).all
450
+
451
+ record = result.first
452
+ record.age.must_equal(user2.age)
453
+ record.name.must_be_nil
454
+ end
455
+
456
+ it 'returns only the multiple select of requested records' do
457
+ name = user2.name
458
+
459
+ query = Proc.new {
460
+ where(name: name).select(:name, :age)
461
+ }
462
+
463
+ result = @adapter.query(collection, &query).all
464
+
465
+ record = result.first
466
+ record.name.must_equal(user2.name)
467
+ record.age.must_equal(user2.age)
468
+ record.id.must_be_nil
469
+ end
470
+ end
471
+ end
472
+
473
+ describe 'order' do
474
+ describe 'with an empty collection' do
475
+ it 'returns an empty result set' do
476
+ result = @adapter.query(collection) do
477
+ order(:id)
478
+ end.all
479
+
480
+ result.must_be_empty
481
+ end
482
+ end
483
+
484
+ describe 'with a filled collection' do
485
+ before do
486
+ @adapter.create(collection, user1)
487
+ @adapter.create(collection, user2)
488
+ end
489
+
490
+ it 'returns sorted records' do
491
+ query = Proc.new {
492
+ order(:id)
493
+ }
494
+
495
+ result = @adapter.query(collection, &query).all
496
+ result.must_equal [user1, user2]
497
+ end
498
+ end
499
+ end
500
+
501
+ describe 'asc' do
502
+ describe 'with an empty collection' do
503
+ it 'returns an empty result set' do
504
+ result = @adapter.query(collection) do
505
+ asc(:id)
506
+ end.all
507
+
508
+ result.must_be_empty
509
+ end
510
+ end
511
+
512
+ describe 'with a filled collection' do
513
+ before do
514
+ @adapter.create(collection, user1)
515
+ @adapter.create(collection, user2)
516
+ end
517
+
518
+ it 'returns sorted records' do
519
+ query = Proc.new {
520
+ asc(:id)
521
+ }
522
+
523
+ result = @adapter.query(collection, &query).all
524
+ result.must_equal [user1, user2]
525
+ end
526
+ end
527
+ end
528
+
529
+ describe 'desc' do
530
+ describe 'with an empty collection' do
531
+ it 'returns an empty result set' do
532
+ result = @adapter.query(collection) do
533
+ desc(:id)
534
+ end.all
535
+
536
+ result.must_be_empty
537
+ end
538
+ end
539
+
540
+ describe 'with a filled collection' do
541
+ before do
542
+ @adapter.create(collection, user1)
543
+ @adapter.create(collection, user2)
544
+ end
545
+
546
+ it 'returns reverse sorted records' do
547
+ query = Proc.new {
548
+ desc(:id)
549
+ }
550
+
551
+ result = @adapter.query(collection, &query).all
552
+ result.must_equal [user2, user1]
553
+ end
554
+ end
555
+ end
556
+
557
+ describe 'limit' do
558
+ describe 'with an empty collection' do
559
+ it 'returns an empty result set' do
560
+ result = @adapter.query(collection) do
561
+ limit(1)
562
+ end.all
563
+
564
+ result.must_be_empty
565
+ end
566
+ end
567
+
568
+ describe 'with a filled collection' do
569
+ before do
570
+ @adapter.create(collection, user1)
571
+ @adapter.create(collection, user2)
572
+ @adapter.create(collection, TestUser.new(name: user2.name))
573
+ end
574
+
575
+ it 'returns only the number of requested records' do
576
+ name = user2.name
577
+
578
+ query = Proc.new {
579
+ where(name: name).limit(1)
580
+ }
581
+
582
+ result = @adapter.query(collection, &query).all
583
+ result.must_equal [user2]
584
+ end
585
+ end
586
+ end
587
+
588
+ describe 'offset' do
589
+ describe 'with an empty collection' do
590
+ it 'returns an empty result set' do
591
+ result = @adapter.query(collection) do
592
+ limit(1).offset(1)
593
+ end.all
594
+
595
+ result.must_be_empty
596
+ end
597
+ end
598
+
599
+ describe 'with a filled collection' do
600
+ before do
601
+ @adapter.create(collection, user1)
602
+ @adapter.create(collection, user2)
603
+ @adapter.create(collection, user3)
604
+ end
605
+
606
+ let(:user3) { TestUser.new(name: user2.name) }
607
+
608
+ it 'returns only the number of requested records' do
609
+ name = user2.name
610
+
611
+ query = Proc.new {
612
+ where(name: name).limit(1).offset(1)
613
+ }
614
+
615
+ result = @adapter.query(collection, &query).all
616
+ result.must_equal [user3]
617
+ end
618
+ end
619
+ end
620
+
621
+ describe 'exist?' do
622
+ describe 'with an empty collection' do
623
+ it 'returns false' do
624
+ result = @adapter.query(collection) do
625
+ where(id: 23)
626
+ end.exist?
627
+
628
+ result.must_equal false
629
+ end
630
+ end
631
+
632
+ describe 'with a filled collection' do
633
+ before do
634
+ @adapter.create(collection, user1)
635
+ @adapter.create(collection, user2)
636
+ end
637
+
638
+ it 'returns true when there are matched records' do
639
+ id = user1.id
640
+
641
+ query = Proc.new {
642
+ where(id: id)
643
+ }
644
+
645
+ result = @adapter.query(collection, &query).exist?
646
+ result.must_equal true
647
+ end
648
+
649
+ it 'returns false when there are matched records' do
650
+ query = Proc.new {
651
+ where(id: 'unknown')
652
+ }
653
+
654
+ result = @adapter.query(collection, &query).exist?
655
+ result.must_equal false
656
+ end
657
+ end
658
+ end
659
+
660
+
661
+ describe 'count' do
662
+ describe 'with an empty collection' do
663
+ it 'returns 0' do
664
+ result = @adapter.query(collection) do
665
+ all
666
+ end.count
667
+
668
+ result.must_equal 0
669
+ end
670
+ end
671
+
672
+ describe 'with a filled collection' do
673
+ before do
674
+ @adapter.create(collection, user1)
675
+ @adapter.create(collection, user2)
676
+ end
677
+
678
+ it 'returns the count of all the records' do
679
+ query = Proc.new {
680
+ all
681
+ }
682
+
683
+ result = @adapter.query(collection, &query).count
684
+ result.must_equal 2
685
+ end
686
+
687
+ it 'returns the count from an empty query block' do
688
+ query = Proc.new {
689
+ }
690
+
691
+ result = @adapter.query(collection, &query).count
692
+ result.must_equal 2
693
+ end
694
+
695
+ it 'returns only the count of requested records' do
696
+ name = user2.name
697
+
698
+ query = Proc.new {
699
+ where(name: name)
700
+ }
701
+
702
+ result = @adapter.query(collection, &query).count
703
+ result.must_equal 1
704
+ end
705
+ end
706
+ end
707
+
708
+ describe 'sum' do
709
+ describe 'with an empty collection' do
710
+ it 'returns nil' do
711
+ result = @adapter.query(collection) do
712
+ all
713
+ end.sum(:age)
714
+
715
+ result.must_be_nil
716
+ end
717
+ end
718
+
719
+ describe 'with a filled collection' do
720
+ before do
721
+ @adapter.create(collection, user1)
722
+ @adapter.create(collection, user2)
723
+ @adapter.create(collection, TestUser.new(name: 'S'))
724
+ end
725
+
726
+ it 'returns the sum of all the records' do
727
+ query = Proc.new {
728
+ all
729
+ }
730
+
731
+ result = @adapter.query(collection, &query).sum(:age)
732
+ result.must_equal 63
733
+ end
734
+
735
+ it 'returns the sum from an empty query block' do
736
+ query = Proc.new {
737
+ }
738
+
739
+ result = @adapter.query(collection, &query).sum(:age)
740
+ result.must_equal 63
741
+ end
742
+
743
+ it 'returns only the sum of requested records' do
744
+ name = user2.name
745
+
746
+ query = Proc.new {
747
+ where(name: name)
748
+ }
749
+
750
+ result = @adapter.query(collection, &query).sum(:age)
751
+ result.must_equal 31
752
+ end
753
+ end
754
+ end
755
+
756
+ describe 'average' do
757
+ describe 'with an empty collection' do
758
+ it 'returns nil' do
759
+ result = @adapter.query(collection) do
760
+ all
761
+ end.average(:age)
762
+
763
+ result.must_be_nil
764
+ end
765
+ end
766
+
767
+ describe 'with a filled collection' do
768
+ before do
769
+ @adapter.create(collection, user1)
770
+ @adapter.create(collection, user2)
771
+ @adapter.create(collection, TestUser.new(name: 'S'))
772
+ end
773
+
774
+ it 'returns the average of all the records' do
775
+ query = Proc.new {
776
+ all
777
+ }
778
+
779
+ result = @adapter.query(collection, &query).average(:age)
780
+ result.must_equal 31.5
781
+ end
782
+
783
+ it 'returns the average from an empty query block' do
784
+ query = Proc.new {
785
+ }
786
+
787
+ result = @adapter.query(collection, &query).average(:age)
788
+ result.must_equal 31.5
789
+ end
790
+
791
+ it 'returns only the average of requested records' do
792
+ name = user2.name
793
+
794
+ query = Proc.new {
795
+ where(name: name)
796
+ }
797
+
798
+ result = @adapter.query(collection, &query).average(:age)
799
+ result.must_equal 31.0
800
+ end
801
+ end
802
+ end
803
+
804
+ describe 'avg' do
805
+ describe 'with an empty collection' do
806
+ it 'returns nil' do
807
+ result = @adapter.query(collection) do
808
+ all
809
+ end.avg(:age)
810
+
811
+ result.must_be_nil
812
+ end
813
+ end
814
+
815
+ describe 'with a filled collection' do
816
+ before do
817
+ @adapter.create(collection, user1)
818
+ @adapter.create(collection, user2)
819
+ @adapter.create(collection, TestUser.new(name: 'S'))
820
+ end
821
+
822
+ it 'returns the average of all the records' do
823
+ query = Proc.new {
824
+ all
825
+ }
826
+
827
+ result = @adapter.query(collection, &query).avg(:age)
828
+ result.must_equal 31.5
829
+ end
830
+
831
+ it 'returns the average from an empty query block' do
832
+ query = Proc.new {
833
+ }
834
+
835
+ result = @adapter.query(collection, &query).avg(:age)
836
+ result.must_equal 31.5
837
+ end
838
+
839
+ it 'returns only the average of requested records' do
840
+ name = user2.name
841
+
842
+ query = Proc.new {
843
+ where(name: name)
844
+ }
845
+
846
+ result = @adapter.query(collection, &query).avg(:age)
847
+ result.must_equal 31.0
848
+ end
849
+ end
850
+ end
851
+
852
+ describe 'max' do
853
+ describe 'with an empty collection' do
854
+ it 'returns nil' do
855
+ result = @adapter.query(collection) do
856
+ all
857
+ end.max(:age)
858
+
859
+ result.must_be_nil
860
+ end
861
+ end
862
+
863
+ describe 'with a filled collection' do
864
+ before do
865
+ @adapter.create(collection, user1)
866
+ @adapter.create(collection, user2)
867
+ @adapter.create(collection, TestUser.new(name: 'S'))
868
+ end
869
+
870
+ it 'returns the maximum of all the records' do
871
+ query = Proc.new {
872
+ all
873
+ }
874
+
875
+ result = @adapter.query(collection, &query).max(:age)
876
+ result.must_equal 32
877
+ end
878
+
879
+ it 'returns the maximum from an empty query block' do
880
+ query = Proc.new {
881
+ }
882
+
883
+ result = @adapter.query(collection, &query).max(:age)
884
+ result.must_equal 32
885
+ end
886
+
887
+ it 'returns only the maximum of requested records' do
888
+ name = user2.name
889
+
890
+ query = Proc.new {
891
+ where(name: name)
892
+ }
893
+
894
+ result = @adapter.query(collection, &query).max(:age)
895
+ result.must_equal 31
896
+ end
897
+ end
898
+ end
899
+
900
+ describe 'min' do
901
+ describe 'with an empty collection' do
902
+ it 'returns nil' do
903
+ result = @adapter.query(collection) do
904
+ all
905
+ end.min(:age)
906
+
907
+ result.must_be_nil
908
+ end
909
+ end
910
+
911
+ describe 'with a filled collection' do
912
+ before do
913
+ @adapter.create(collection, user1)
914
+ @adapter.create(collection, user2)
915
+ @adapter.create(collection, TestUser.new(name: 'S'))
916
+ end
917
+
918
+ it 'returns the minimum of all the records' do
919
+ query = Proc.new {
920
+ all
921
+ }
922
+
923
+ result = @adapter.query(collection, &query).min(:age)
924
+ result.must_equal 31
925
+ end
926
+
927
+ it 'returns the minimum from an empty query block' do
928
+ query = Proc.new {
929
+ }
930
+
931
+ result = @adapter.query(collection, &query).min(:age)
932
+ result.must_equal 31
933
+ end
934
+
935
+ it 'returns only the minimum of requested records' do
936
+ name = user1.name
937
+
938
+ query = Proc.new {
939
+ where(name: name)
940
+ }
941
+
942
+ result = @adapter.query(collection, &query).min(:age)
943
+ result.must_equal 32
944
+ end
945
+ end
946
+ end
947
+
948
+ describe 'interval' do
949
+ describe 'with an empty collection' do
950
+ it 'returns nil' do
951
+ result = @adapter.query(collection) do
952
+ all
953
+ end.interval(:age)
954
+
955
+ result.must_be_nil
956
+ end
957
+ end
958
+
959
+ describe 'with a filled collection' do
960
+ before do
961
+ @adapter.create(collection, user1)
962
+ @adapter.create(collection, user2)
963
+ @adapter.create(collection, TestUser.new(name: 'S'))
964
+ end
965
+
966
+ it 'returns the interval of all the records' do
967
+ query = Proc.new {
968
+ all
969
+ }
970
+
971
+ result = @adapter.query(collection, &query).interval(:age)
972
+ result.must_equal 1
973
+ end
974
+
975
+ it 'returns the interval from an empty query block' do
976
+ query = Proc.new {
977
+ }
978
+
979
+ result = @adapter.query(collection, &query).interval(:age)
980
+ result.must_equal 1
981
+ end
982
+
983
+ it 'returns only the interval of requested records' do
984
+ name = user1.name
985
+
986
+ query = Proc.new {
987
+ where(name: name)
988
+ }
989
+
990
+ result = @adapter.query(collection, &query).interval(:age)
991
+ result.must_equal 0
992
+ end
993
+ end
994
+ end
995
+
996
+ describe 'range' do
997
+ describe 'with an empty collection' do
998
+ it 'returns nil' do
999
+ result = @adapter.query(collection) do
1000
+ all
1001
+ end.range(:age)
1002
+
1003
+ result.must_equal nil..nil
1004
+ end
1005
+ end
1006
+
1007
+ describe 'with a filled collection' do
1008
+ before do
1009
+ @adapter.create(collection, user1)
1010
+ @adapter.create(collection, user2)
1011
+ @adapter.create(collection, TestUser.new(name: 'S'))
1012
+ end
1013
+
1014
+ it 'returns the range of all the records' do
1015
+ query = Proc.new {
1016
+ all
1017
+ }
1018
+
1019
+ result = @adapter.query(collection, &query).range(:age)
1020
+ result.must_equal 31..32
1021
+ end
1022
+
1023
+ it 'returns the range from an empty query block' do
1024
+ query = Proc.new {
1025
+ }
1026
+
1027
+ result = @adapter.query(collection, &query).range(:age)
1028
+ result.must_equal 31..32
1029
+ end
1030
+
1031
+ it 'returns only the range of requested records' do
1032
+ name = user2.name
1033
+
1034
+ query = Proc.new {
1035
+ where(name: name)
1036
+ }
1037
+
1038
+ result = @adapter.query(collection, &query).range(:age)
1039
+ result.must_equal 31..31
1040
+ end
1041
+ end
1042
+ end
1043
+ end
1044
+ end