schron 0.0.2
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.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/Gemfile +4 -0
- data/Rakefile +19 -0
- data/lib/schron.rb +4 -0
- data/lib/schron/archive.rb +88 -0
- data/lib/schron/archive/interface.rb +52 -0
- data/lib/schron/datastore/interface.rb +60 -0
- data/lib/schron/datastore/memory.rb +152 -0
- data/lib/schron/datastore/mongo.rb +173 -0
- data/lib/schron/datastore/mongo/metadata.rb +49 -0
- data/lib/schron/datastore/mongo/serializer.rb +38 -0
- data/lib/schron/datastore/sequel.rb +137 -0
- data/lib/schron/datastore/serializer.rb +46 -0
- data/lib/schron/dsl.rb +28 -0
- data/lib/schron/error.rb +7 -0
- data/lib/schron/id.rb +22 -0
- data/lib/schron/identity_map.rb +26 -0
- data/lib/schron/paginated_results.rb +28 -0
- data/lib/schron/paging.rb +5 -0
- data/lib/schron/query.rb +122 -0
- data/lib/schron/repository.rb +35 -0
- data/lib/schron/repository/interface.rb +36 -0
- data/lib/schron/test.rb +22 -0
- data/lib/schron/test/archive_examples.rb +133 -0
- data/lib/schron/test/datastore_examples.rb +419 -0
- data/lib/schron/test/entity.rb +21 -0
- data/lib/schron/test/repository_examples.rb +68 -0
- data/lib/schron/util.rb +27 -0
- data/schron.gemspec +24 -0
- data/spec/lib/schron/archive_spec.rb +26 -0
- data/spec/lib/schron/datastore/memory_spec.rb +9 -0
- data/spec/lib/schron/datastore/mongo_spec.rb +23 -0
- data/spec/lib/schron/datastore/sequel_spec.rb +40 -0
- data/spec/lib/schron/dsl_spec.rb +46 -0
- data/spec/lib/schron/identity_map_spec.rb +36 -0
- data/spec/lib/schron/paginaged_results_spec.rb +27 -0
- data/spec/lib/schron/query_spec.rb +46 -0
- data/spec/lib/schron/repository_spec.rb +27 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/support/test_entities.rb +15 -0
- metadata +192 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'schron/query'
|
2
|
+
require 'schron/archive'
|
3
|
+
require 'schron/dsl'
|
4
|
+
require 'schron/repository/interface'
|
5
|
+
|
6
|
+
module Schron
|
7
|
+
module Repository
|
8
|
+
|
9
|
+
include Schron::Archive::Interface
|
10
|
+
include Schron::Repository::Interface
|
11
|
+
|
12
|
+
include Schron::Archive
|
13
|
+
|
14
|
+
def self.included(archive_class)
|
15
|
+
archive_class.extend Schron::DSL
|
16
|
+
end
|
17
|
+
|
18
|
+
def update(object)
|
19
|
+
maybe_load(@datastore.update(@kind, dump(object)))
|
20
|
+
end
|
21
|
+
|
22
|
+
def multi_update(objects)
|
23
|
+
load_all(@datastore.multi_update(@kind, dump_all(objects)))
|
24
|
+
end
|
25
|
+
|
26
|
+
def remove(id)
|
27
|
+
@datastore.remove(@kind, id) && nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def multi_remove(ids)
|
31
|
+
@datastore.multi_remove(@kind, ids) && nil
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'schron/archive/interface'
|
2
|
+
|
3
|
+
module Schron
|
4
|
+
module Repository
|
5
|
+
module Interface
|
6
|
+
|
7
|
+
include Schron::Archive::Interface
|
8
|
+
|
9
|
+
def update(object)
|
10
|
+
raise NotImplementedError
|
11
|
+
end
|
12
|
+
|
13
|
+
def multi_update(objects)
|
14
|
+
raise NotImplementedError
|
15
|
+
end
|
16
|
+
|
17
|
+
def remove(id)
|
18
|
+
raise NotImplementedError
|
19
|
+
end
|
20
|
+
|
21
|
+
def multi_remove(ids)
|
22
|
+
raise NotImplementedError
|
23
|
+
end
|
24
|
+
|
25
|
+
def dump(object)
|
26
|
+
raise NotImplementedError
|
27
|
+
end
|
28
|
+
|
29
|
+
def load(attributes)
|
30
|
+
raise NotImplementedError
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/schron/test.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Dir[File.join(__dir__, 'test', '**', '*.rb')].each do |f|
|
2
|
+
require f
|
3
|
+
end
|
4
|
+
|
5
|
+
module Schron
|
6
|
+
module Test
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def expect_same_ids(entity_set1, entity_set2)
|
10
|
+
ids1 = entity_set1.map(&:id)
|
11
|
+
ids2 = entity_set2.map(&:id)
|
12
|
+
expect(ids1.to_set).to eq(ids2.to_set)
|
13
|
+
end
|
14
|
+
|
15
|
+
def expect_same_entity(e1, e2)
|
16
|
+
expect(e1.id).to eq(e2.id)
|
17
|
+
expect(e1.attributes).to eq(e2.attributes)
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
if defined?(RSpec)
|
2
|
+
|
3
|
+
require 'schron/query'
|
4
|
+
require 'schron/test'
|
5
|
+
|
6
|
+
# Be sure to set the archive before inclusion
|
7
|
+
# let(:archive) { ... }
|
8
|
+
shared_examples "schron archive" do
|
9
|
+
|
10
|
+
include Schron::Test
|
11
|
+
|
12
|
+
let(:entity_class) { archive.entity_class }
|
13
|
+
|
14
|
+
describe 'schron archive behavior' do
|
15
|
+
|
16
|
+
describe 'query' do
|
17
|
+
it 'yields a query with the archives database and kind' do
|
18
|
+
entity = archive.insert(entity_class.new)
|
19
|
+
expect(archive.datastore).to receive(:exec_query).and_call_original
|
20
|
+
results = archive.query do |q|
|
21
|
+
expect(q.kind).to eq(archive.kind)
|
22
|
+
end
|
23
|
+
expect_same_entity results.first, entity
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe 'first' do
|
28
|
+
it 'yields a query, automatically adding limit 1 and returning the first result' do
|
29
|
+
entity = archive.insert(entity_class.new)
|
30
|
+
expect(archive.datastore).to receive(:exec_query).and_call_original
|
31
|
+
result = archive.first do |q|
|
32
|
+
expect(q.kind).to eq(archive.kind)
|
33
|
+
end
|
34
|
+
expect_same_entity result, entity
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'get' do
|
39
|
+
it 'returns nil when the id is not found' do
|
40
|
+
expect(archive.get('123')).to be_nil
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'does not call load if no data is found' do
|
44
|
+
expect(archive).not_to receive(:load)
|
45
|
+
expect(archive.get('123'))
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'returns the loaded object when present' do
|
49
|
+
david = archive.insert(entity_class.new(id: '1', name: 'david'))
|
50
|
+
expect_same_entity archive.get(david.id), david
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe 'multi_get' do
|
55
|
+
it 'returns objects by id' do
|
56
|
+
entities = archive.multi_insert([
|
57
|
+
entity_class.new(a: '1'),
|
58
|
+
entity_class.new(b: '2')
|
59
|
+
])
|
60
|
+
|
61
|
+
results = archive.multi_get(entities.map(&:id))
|
62
|
+
expect_same_ids results, entities
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'returns an empty array when given an empty array' do
|
66
|
+
expect_same_ids archive.multi_get([]), []
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'does not call load if no results are found' do
|
70
|
+
expect(archive).not_to receive(:load)
|
71
|
+
archive.multi_get(['123'])
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'returns an empty array when no ids are found' do
|
75
|
+
expect_same_ids archive.multi_get(['12']), []
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'ignores non existent ids mixed in with existing ones' do
|
79
|
+
a = archive.insert(entity_class.new)
|
80
|
+
results = archive.multi_get([a.id, 'made-up'])
|
81
|
+
expect_same_ids results, [a]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe 'insert' do
|
86
|
+
it 'returns the inserted object with an id set when none is provided' do
|
87
|
+
object = archive.insert(entity_class.new)
|
88
|
+
expect(object.id).not_to be_nil
|
89
|
+
expect_same_entity archive.get(object.id), object
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'returns the inserted object, using the id passed in when provided' do
|
93
|
+
object = archive.insert(entity_class.new(id: '23'))
|
94
|
+
expect(object.id).to eq('23')
|
95
|
+
expect_same_entity archive.get('23'), object
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe 'multi_insert' do
|
100
|
+
it 'returns the inserted objects, with ids set when none are provided' do
|
101
|
+
objects = archive.multi_insert([entity_class.new, entity_class.new])
|
102
|
+
objects.each do |object|
|
103
|
+
expect_same_entity archive.get(object.id), object
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'returns the inserted objects, using the id passed in if provided' do
|
108
|
+
objects = archive.multi_insert([
|
109
|
+
entity_class.new(id: '23'),
|
110
|
+
entity_class.new(id: 'shrike')
|
111
|
+
])
|
112
|
+
expect(objects.map(&:id).to_set).to eq(Set.new(['23', 'shrike']))
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe 'load' do
|
117
|
+
it 'should pass the attributes to the constructor of the entity class' do
|
118
|
+
expect(archive.entity_class).to receive(:new).with({a: 'b', c: 'd'})
|
119
|
+
archive.load(a: 'b', c: 'd')
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe 'dump' do
|
124
|
+
it 'should call attributes on the object' do
|
125
|
+
obj = double(attributes: {id: 3, name: 'david'})
|
126
|
+
hash = archive.dump(obj)
|
127
|
+
expect(hash).to eq(id: 3, name: 'david')
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
@@ -0,0 +1,419 @@
|
|
1
|
+
if defined?(RSpec)
|
2
|
+
|
3
|
+
require 'schron/query'
|
4
|
+
shared_examples 'schron datastore' do
|
5
|
+
let(:entity_kind) { :entities }
|
6
|
+
let(:named_kind) { :named }
|
7
|
+
let(:with_types_kind) { :with_types }
|
8
|
+
|
9
|
+
describe 'datastore interface' do
|
10
|
+
describe 'get' do
|
11
|
+
it 'should return nil if no matching id is found' do
|
12
|
+
expect(ds.get(entity_kind, 'no-data')).to be_nil
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should return the data if present' do
|
16
|
+
data = ds.insert(named_kind, {name: "dave"})
|
17
|
+
expect(ds.get(named_kind, data[:id])).to eq(name: "dave", id: data[:id])
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should return a copy of the data' do
|
21
|
+
data = ds.insert(entity_kind, {})
|
22
|
+
data = ds.get(entity_kind, data[:id])
|
23
|
+
data[:change] = 'true'
|
24
|
+
expect(ds.get(entity_kind, data[:id])[:change]).to be_nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe 'multi get' do
|
29
|
+
it 'should return an empty array if passed an empty array' do
|
30
|
+
expect(ds.multi_get(entity_kind, [])).to eq([])
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should return data by id' do
|
34
|
+
d1 = ds.insert(named_kind, {name: 'a'})
|
35
|
+
d2 = ds.insert(named_kind, {name: 'b'})
|
36
|
+
ids = [d1[:id], d2[:id]]
|
37
|
+
expect(ds.multi_get(named_kind, ids)).to include(d1, d2)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should not return nils when ids dont exist' do
|
41
|
+
d = ds.insert(entity_kind, {})
|
42
|
+
expect(ds.multi_get(entity_kind, ['no-data', d[:id]])).to eq([d])
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should return copies of the data' do
|
46
|
+
data = ds.insert(entity_kind, {})
|
47
|
+
data_list = ds.multi_get(entity_kind, [data[:id]])
|
48
|
+
data_list.first[:changed] = 'true'
|
49
|
+
retrieved = ds.multi_get(entity_kind, data_list.map{ |d| d[:id] })
|
50
|
+
expect(retrieved).to eq([{id: data[:id]}])
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should return the data in the order of the requested ids' do
|
54
|
+
d1 = ds.insert(named_kind, {name: 'a'})
|
55
|
+
d2 = ds.insert(named_kind, {name: 'b'})
|
56
|
+
ids = [d1[:id], d2[:id]]
|
57
|
+
expect(ds.multi_get(named_kind, ids)).to eq([d1, d2])
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
describe 'insert' do
|
63
|
+
it 'should assign an id if none is provided' do
|
64
|
+
data = ds.insert(entity_kind, {})
|
65
|
+
expect(data[:id]).not_to be_nil
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'should use the provided id if present' do
|
69
|
+
data = ds.insert(entity_kind, {id: '123'})
|
70
|
+
expect(data[:id]).to eq('123')
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should return a copy of the data' do
|
74
|
+
data = ds.insert(entity_kind, {})
|
75
|
+
data[:a] = 'b'
|
76
|
+
expect(ds.get(entity_kind, data[:id])[:a]).to be_nil
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
describe 'multi insert' do
|
82
|
+
it 'inserts all the data and returns it' do
|
83
|
+
data = ds.multi_insert(named_kind, [{name: 'a'}, {name: 'b'}])
|
84
|
+
expect(data.map{ |d| d[:name] }).to include('a', 'b')
|
85
|
+
expect(data.size).to eq(2)
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'assigns ids when none are provided' do
|
89
|
+
data = ds.multi_insert(entity_kind, [{}, {}])
|
90
|
+
expect(data.any?{ |d| d[:id].nil? }).to be(false)
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'uses provided ids when present' do
|
94
|
+
data = ds.multi_insert(entity_kind, [{id: '123'}, {id: '456'}])
|
95
|
+
expect(data.map{ |d| d[:id] }).to eq(['123', '456'])
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe 'update' do
|
100
|
+
it 'should raise an error if no id is provided' do
|
101
|
+
expect { ds.update(entity_kind, {}) }.to raise_error
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'should update the data by id' do
|
105
|
+
inserted = ds.insert(named_kind, {})
|
106
|
+
inserted[:name] = 'david'
|
107
|
+
updated = ds.update(named_kind, inserted)
|
108
|
+
expect(updated[:name]).to eq('david')
|
109
|
+
expect(updated[:id]).to eq(inserted[:id])
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'should return a copy of the data' do
|
113
|
+
data = ds.insert(named_kind, {})
|
114
|
+
data = ds.update(named_kind, data.merge(name: 'joe'))
|
115
|
+
data[:c] = 'd'
|
116
|
+
expect(ds.get(named_kind, data[:id])[:c]).to be_nil
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe 'multi update' do
|
121
|
+
it 'raises an error if any ids are missing' do
|
122
|
+
expect do
|
123
|
+
ds.multi_update(entity_kind, [{id: '123'}, {}])
|
124
|
+
end.to raise_error
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'updates the data by ids' do
|
128
|
+
inserted = ds.multi_insert(named_kind, [{}, {}])
|
129
|
+
inserted[0][:name] = 'a'
|
130
|
+
inserted[1][:name] = 'b'
|
131
|
+
updated = ds.multi_update(named_kind, inserted)
|
132
|
+
expect(updated.map{ |d| d[:name] }.to_set).to eq(Set.new(['a', 'b']))
|
133
|
+
expect(updated.map{ |d| d[:id] }.to_set).to eq(Set.new(inserted.map{ |d| d[:id] }))
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'returns copies of the data' do
|
137
|
+
data = ds.insert(named_kind, {})
|
138
|
+
data = ds.multi_update(named_kind, [data.merge(name: 'joe')]).first
|
139
|
+
data[:c] = 'd'
|
140
|
+
expect(ds.get(named_kind, data[:id])[:c]).to be_nil
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe 'remove' do
|
145
|
+
it 'should remove data from the store by id' do
|
146
|
+
data = ds.insert(entity_kind, {})
|
147
|
+
ds.remove(entity_kind, data[:id])
|
148
|
+
expect(ds.get(entity_kind, data[:id])).to be_nil
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'should return nil' do
|
152
|
+
data = ds.insert(entity_kind, {})
|
153
|
+
expect(ds.remove(entity_kind, data[:id])).to be_nil
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe 'multi remove' do
|
158
|
+
it 'should remove the data from the store by ids' do
|
159
|
+
data = ds.multi_insert(entity_kind, [{}, {}])
|
160
|
+
ids = data.map { |d| d[:id] }
|
161
|
+
ds.multi_remove(entity_kind, ids)
|
162
|
+
expect(ds.multi_get(entity_kind, ids)).to be_empty
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'should return nil' do
|
166
|
+
data = ds.insert(entity_kind, {})
|
167
|
+
expect(ds.multi_remove(entity_kind, [data[:id]])).to be_nil
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
describe 'exec query' do
|
172
|
+
let(:query) { Schron::Query.new(named_kind, ds) }
|
173
|
+
let(:no_result_query) { Schron::Query.new(entity_kind, ds) }
|
174
|
+
|
175
|
+
before(:each) do
|
176
|
+
@c = ds.insert(named_kind, name: 'c')
|
177
|
+
@a1 = ds.insert(named_kind, name: 'a')
|
178
|
+
@b = ds.insert(named_kind, name: 'b')
|
179
|
+
@a2 = ds.insert(named_kind, name: 'a')
|
180
|
+
@nil = ds.insert(named_kind, {name: nil})
|
181
|
+
end
|
182
|
+
|
183
|
+
describe 'filters' do
|
184
|
+
it 'filters by equality with a hash' do
|
185
|
+
found = query.filter(name: 'b').all
|
186
|
+
expect(found.size).to eq(1)
|
187
|
+
expect(found.first[:id]).to eq(@b[:id])
|
188
|
+
end
|
189
|
+
|
190
|
+
it 'filters by inclusion with a hash' do
|
191
|
+
found = query.filter(name: ['b', 'c']).all
|
192
|
+
expect(found.size).to eq(2)
|
193
|
+
expect(found.map{ |h| h[:id] }).to include(@b[:id], @c[:id])
|
194
|
+
end
|
195
|
+
|
196
|
+
it 'filters by "=" operator' do
|
197
|
+
found = query.filter(:name, '=', 'b').all
|
198
|
+
expect(found.size).to eq(1)
|
199
|
+
expect(found.first[:id]).to eq(@b[:id])
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'filters by "in" operator' do
|
203
|
+
found = query.filter(:name, 'in', ['a', 'b']).all
|
204
|
+
expect(found.size).to eq(3)
|
205
|
+
expect(found.map{ |h| h[:id] }).to include(@a1[:id], @a2[:id], @b[:id])
|
206
|
+
end
|
207
|
+
|
208
|
+
it 'filters by ">" operator' do
|
209
|
+
found = query.filter(:name, '>', 'a').all
|
210
|
+
expect(found.size).to be(2)
|
211
|
+
expect(found.map{ |h| h[:id] }).to include(@b[:id], @c[:id])
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'filters by ">=" operator' do
|
215
|
+
found = query.filter(:name, '>=', 'a').all
|
216
|
+
expect(found.size).to be(4)
|
217
|
+
expect(found.map{ |h| h[:id] }).to include(@a1[:id], @a2[:id], @b[:id], @c[:id])
|
218
|
+
end
|
219
|
+
|
220
|
+
it 'filters by "<" operator' do
|
221
|
+
found = query.filter(:name, '<', 'b').all
|
222
|
+
expect(found.size).to be(2)
|
223
|
+
expect(found.map{ |h| h[:id] }).to include(@a1[:id], @a2[:id])
|
224
|
+
end
|
225
|
+
|
226
|
+
it 'filters by "<=" operator' do
|
227
|
+
found = query.filter(:name, '<=', 'b').all
|
228
|
+
expect(found.size).to be(3)
|
229
|
+
expect(found.map{ |h| h[:id] }).to include(@a1[:id], @a2[:id], @b[:id])
|
230
|
+
end
|
231
|
+
|
232
|
+
it 'filters by "!=" operator' do
|
233
|
+
found = query.filter(:name, '!=', 'a').all
|
234
|
+
expect(found.size).to eq(3)
|
235
|
+
expect(found.map{ |h| h[:id] }).to include(@c[:id], @b[:id], @nil[:id])
|
236
|
+
end
|
237
|
+
|
238
|
+
it 'returns an empty list if no results match' do
|
239
|
+
expect(query.filter(name: '123').all).to eq([])
|
240
|
+
end
|
241
|
+
|
242
|
+
context 'nil filtering' do
|
243
|
+
it 'filters by = nil' do
|
244
|
+
found = query.filter(name: nil).all
|
245
|
+
expect(found.size).to eq(1)
|
246
|
+
expect(found.first[:id]).to eq(@nil[:id])
|
247
|
+
end
|
248
|
+
|
249
|
+
it 'filters by != nil' do
|
250
|
+
found = query.filter(:name, '!=', nil).all
|
251
|
+
expect(found.size).to eq(4)
|
252
|
+
expect(found.map{ |h| h[:id] }).to include(@a1[:id], @a2[:id], @b[:id], @c[:id])
|
253
|
+
end
|
254
|
+
|
255
|
+
it 'filters by in with nil' do
|
256
|
+
found = query.filter(:name, 'in', [nil, 'b']).all
|
257
|
+
expect(found.size).to eq(2)
|
258
|
+
expect(found.map{ |h| h[:id] }).to include(@b[:id], @nil[:id])
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
describe 'limit' do
|
264
|
+
it 'limits the results' do
|
265
|
+
found = query.limit(1).all
|
266
|
+
expect(found.size).to eq(1)
|
267
|
+
end
|
268
|
+
|
269
|
+
it 'returns an empty list if no results match' do
|
270
|
+
q = no_result_query
|
271
|
+
expect(q.limit(2).all).to eq([])
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
describe 'offset' do
|
276
|
+
it 'offsets the results' do
|
277
|
+
all = 4.times.reduce([]) do |ary, i|
|
278
|
+
ary + query.limit(1).offset(i).all
|
279
|
+
end
|
280
|
+
all_ids = all.map { |h| h[:id] }
|
281
|
+
expect(all_ids).to include(@a1[:id], @a2[:id], @b[:id], @c[:id])
|
282
|
+
end
|
283
|
+
|
284
|
+
it 'returns an empty list if no results match' do
|
285
|
+
expect(query.offset(10).all).to eq([])
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
describe 'sorts' do
|
290
|
+
it 'sorts in asc direction by default' do
|
291
|
+
found = query.sort(:name).all
|
292
|
+
expect(found.map{ |h| h[:name] }).to eq(
|
293
|
+
[nil, 'a', 'a', 'b', 'c']
|
294
|
+
)
|
295
|
+
end
|
296
|
+
|
297
|
+
it 'sorts ascending' do
|
298
|
+
found = query.sort(:name, :asc).all
|
299
|
+
expect(found.map{ |h| h[:name] }).to eq([nil, 'a', 'a', 'b', 'c'])
|
300
|
+
end
|
301
|
+
|
302
|
+
it 'sorts descending' do
|
303
|
+
found = query.sort(:name, :desc).all
|
304
|
+
expect(found.map { |h| h[:name] }).to eq(['c', 'b', 'a', 'a', nil])
|
305
|
+
end
|
306
|
+
|
307
|
+
it 'sorts by multiple fields' do
|
308
|
+
found = query.sort(:name).sort(:id).all
|
309
|
+
expected = [@nil[:id]] + [@a1[:id], @a2[:id]].sort + [@b[:id], @c[:id]]
|
310
|
+
expect(found.map { |h| h[:id] }).to eq(expected)
|
311
|
+
end
|
312
|
+
|
313
|
+
it 'returns an empty list if no results match' do
|
314
|
+
expect(no_result_query.sort(:id).all).to eq([])
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
describe 'datastore type handling' do
|
322
|
+
|
323
|
+
describe 'arrays' do
|
324
|
+
xit 'saves and loads empty arrays' do
|
325
|
+
data = ds.insert(with_types_kind, {
|
326
|
+
array: []
|
327
|
+
})
|
328
|
+
loaded = ds.get(with_types_kind, data[:id])
|
329
|
+
expect(loaded[:array]).to eq([])
|
330
|
+
end
|
331
|
+
xit 'stores and loads arrays of strings, numbers and arrays' do
|
332
|
+
data = ds.insert(with_types_kind, {
|
333
|
+
array: [1, "a", ["b"]]
|
334
|
+
})
|
335
|
+
loaded = ds.get(with_types_kind, data[:id])
|
336
|
+
expect(loaded[:array]).to eq([1, "a", ["b"]])
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
describe 'dates and times' do
|
341
|
+
xit 'saves and loads Date' do
|
342
|
+
data = ds.insert(with_types_kind, {
|
343
|
+
date: Date.new(2010, 10, 6)
|
344
|
+
})
|
345
|
+
loaded = ds.get(with_types_kind, data[:id])
|
346
|
+
expect(loaded[:date]).to eq(Date.new(2010, 10, 6))
|
347
|
+
end
|
348
|
+
|
349
|
+
xit 'saves and loads Time' do
|
350
|
+
data = ds.insert(with_types_kind, {
|
351
|
+
time: Time.new(2013, 10, 6)
|
352
|
+
})
|
353
|
+
loaded = ds.get(with_types_kind, data[:id])
|
354
|
+
expect(loaded[:time]).to eq(Time.new(2013, 10, 6))
|
355
|
+
end
|
356
|
+
|
357
|
+
xit 'saves and loads DateTime' do
|
358
|
+
data = ds.insert(with_types_kind, {
|
359
|
+
date_time: DateTime.new(2013, 10, 6)
|
360
|
+
})
|
361
|
+
loaded = ds.get(with_types_kind, data[:id])
|
362
|
+
expect(loaded[:date_time]).to eq(DateTime.new(2013, 10, 6))
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
describe 'sets' do
|
367
|
+
xit 'saves and loads Sets of numbers, strings and arrays' do
|
368
|
+
data = ds.insert(with_types_kind, {
|
369
|
+
set: Set.new([1, "2", ["a"]])
|
370
|
+
})
|
371
|
+
loaded = ds.get(with_types_kind, data[:id])
|
372
|
+
expect(loaded[:set]).to eq(Set.new([1, "2", ["a"]]))
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
describe 'nested hashes' do
|
377
|
+
xit 'saves and loads nested hashes' do
|
378
|
+
data = ds.insert(with_types_kind, {
|
379
|
+
hash: {a: {b: 'c'}, d: 3}
|
380
|
+
})
|
381
|
+
loaded = ds.get(with_types_kind, data[:id])
|
382
|
+
expect(loaded[:hash]).to eq({a: {b: 'c'}, d: 3})
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
describe 'recursive structure' do
|
387
|
+
xit 'saves and loads' do
|
388
|
+
data = ds.insert(with_types_kind, {
|
389
|
+
name: 'score',
|
390
|
+
children: [
|
391
|
+
{name: 'diet', children: [
|
392
|
+
{name: 'eating'},
|
393
|
+
{name: 'not eating'}
|
394
|
+
]},
|
395
|
+
{name: 'exercise', children: [
|
396
|
+
{name: 'workout'},
|
397
|
+
{name: 'sit'}
|
398
|
+
]}
|
399
|
+
]})
|
400
|
+
loaded = ds.get(with_types_kind, data[:id])
|
401
|
+
loaded.delete(:id)
|
402
|
+
expect(loaded).to eq({
|
403
|
+
name: 'score',
|
404
|
+
children: [
|
405
|
+
{name: 'diet', children: [
|
406
|
+
{name: 'eating'},
|
407
|
+
{name: 'not eating'}
|
408
|
+
]},
|
409
|
+
{name: 'exercise', children: [
|
410
|
+
{name: 'workout'},
|
411
|
+
{name: 'sit'}
|
412
|
+
]}
|
413
|
+
]})
|
414
|
+
end
|
415
|
+
end
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
end
|