lotus-model 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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