daodalus 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/.travis.yml +7 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +65 -0
- data/LICENCE.md +9 -0
- data/README.md +316 -0
- data/Rakefile +8 -0
- data/daodalus.gemspec +23 -0
- data/lib/daodalus.rb +29 -0
- data/lib/daodalus/connection.rb +19 -0
- data/lib/daodalus/dao.rb +41 -0
- data/lib/daodalus/dsl.rb +21 -0
- data/lib/daodalus/dsl/aggregation/group.rb +88 -0
- data/lib/daodalus/dsl/aggregation/limit.rb +23 -0
- data/lib/daodalus/dsl/aggregation/match.rb +35 -0
- data/lib/daodalus/dsl/aggregation/project.rb +79 -0
- data/lib/daodalus/dsl/aggregation/skip.rb +23 -0
- data/lib/daodalus/dsl/aggregation/sort.rb +29 -0
- data/lib/daodalus/dsl/aggregation/unwind.rb +23 -0
- data/lib/daodalus/dsl/aggregations.rb +44 -0
- data/lib/daodalus/dsl/clause.rb +19 -0
- data/lib/daodalus/dsl/matchers.rb +87 -0
- data/lib/daodalus/dsl/queries.rb +29 -0
- data/lib/daodalus/dsl/query.rb +46 -0
- data/lib/daodalus/dsl/select.rb +43 -0
- data/lib/daodalus/dsl/update.rb +86 -0
- data/lib/daodalus/dsl/updates.rb +71 -0
- data/lib/daodalus/dsl/where.rb +30 -0
- data/lib/daodalus/invalid_connection_error.rb +7 -0
- data/lib/daodalus/invalid_query_error.rb +4 -0
- data/spec/lib/daodalus/connection_spec.rb +22 -0
- data/spec/lib/daodalus/dao_spec.rb +34 -0
- data/spec/lib/daodalus/dsl/aggregation/group_spec.rb +92 -0
- data/spec/lib/daodalus/dsl/aggregation/limit_spec.rb +22 -0
- data/spec/lib/daodalus/dsl/aggregation/match_spec.rb +32 -0
- data/spec/lib/daodalus/dsl/aggregation/project_spec.rb +74 -0
- data/spec/lib/daodalus/dsl/aggregation/skip_spec.rb +22 -0
- data/spec/lib/daodalus/dsl/aggregation/sort_spec.rb +36 -0
- data/spec/lib/daodalus/dsl/aggregation/unwind_spec.rb +24 -0
- data/spec/lib/daodalus/dsl/clause_spec.rb +22 -0
- data/spec/lib/daodalus/dsl/query_spec.rb +35 -0
- data/spec/lib/daodalus/dsl/select_spec.rb +46 -0
- data/spec/lib/daodalus/dsl/update_spec.rb +113 -0
- data/spec/lib/daodalus/dsl/where_spec.rb +133 -0
- data/spec/lib/daodalus/dsl_spec.rb +12 -0
- data/spec/pointless_coverage_spec.rb +9 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/support/mongo_cleaner.rb +12 -0
- metadata +208 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Daodalus
|
4
|
+
describe DAO do
|
5
|
+
|
6
|
+
it 'takes a collection and connection name' do
|
7
|
+
expect { DAO.new(:animalhouse, :cats, :animalhouse) }.to_not raise_error
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'is not necessary to specify the connection name' do
|
11
|
+
expect { DAO.new(:animalhouse, :cats) }.to_not raise_error
|
12
|
+
end
|
13
|
+
|
14
|
+
let (:dao) { DAO.new(:animalhouse, :cats) }
|
15
|
+
|
16
|
+
it 'holds a connection to the mongo DB collection' do
|
17
|
+
dao.coll.should be_a Mongo::Collection
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'delegates basic mongo methods to its coll' do
|
21
|
+
dao.coll.should_receive(:find)
|
22
|
+
dao.find
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'returns an optional value for find_one' do
|
26
|
+
dao.find_one(name: 'felix').should be_none
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'returns an optional value for find_and_modify' do
|
30
|
+
dao.find_and_modify(query: { name: 'felix'}, update: { '$set' => { name: 'cat' }}).should be_none
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Daodalus
|
4
|
+
module DSL
|
5
|
+
module Aggregation
|
6
|
+
describe Group do
|
7
|
+
|
8
|
+
let (:dao) { DAO.new(:animalhouse, :cats) }
|
9
|
+
|
10
|
+
before do
|
11
|
+
dao.insert('name' => 'Terry',
|
12
|
+
'paws' => 3,
|
13
|
+
'likes' => ['tuna', 'catnip'],
|
14
|
+
'foods' => [{'type' => 'dry', 'name' => 'go cat'},
|
15
|
+
{'type' => 'wet', 'name' => 'whiskas'}])
|
16
|
+
|
17
|
+
dao.insert('name' => 'Jemima',
|
18
|
+
'paws' => 3,
|
19
|
+
'likes' => ['tuna', 'ginger beer'],
|
20
|
+
'foods' => [{'type' => 'cat', 'name' => 'go cat'},
|
21
|
+
{'type' => 'dog', 'name' => 'whiskas'}])
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'can group on a single field' do
|
25
|
+
dao.group_by('$foods.type').aggregate.should eq [
|
26
|
+
{"_id"=>["cat", "dog"]},
|
27
|
+
{"_id"=>["dry", "wet"]}
|
28
|
+
]
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'can group by a hash of fields' do
|
32
|
+
dao.group_by(cat: '$name', brand: '$foods.name').aggregate.should eq [
|
33
|
+
{"_id"=>{"cat"=>"Jemima", "brand"=>["go cat", "whiskas"]}},
|
34
|
+
{"_id"=>{"cat"=>"Terry", "brand"=>["go cat", "whiskas"]}}
|
35
|
+
]
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'can sum a set of fields' do
|
39
|
+
dao.group_by(1).sum("$paws").as(:paws).aggregate.should eq [{"_id"=>1, "paws"=>6}]
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'can build up a unique set of aggregate values' do
|
43
|
+
dao.insert('name' => 'Terry', 'paws' => 3)
|
44
|
+
dao.group_by("$paws").distinct("$name").as(:cats).aggregate.should eq [
|
45
|
+
{"_id"=>3, "cats"=>["Jemima", "Terry"]}
|
46
|
+
]
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'can build up a non-unique set of aggregate values' do
|
50
|
+
dao.insert('name' => 'Terry', 'paws' => 3)
|
51
|
+
dao.group_by(:"$paws").collect("$name").as(:cats).aggregate.should eq [
|
52
|
+
{"_id"=>3, "cats"=>["Terry", "Jemima", "Terry"]}
|
53
|
+
]
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'can get the first value for an aggregate field' do
|
57
|
+
dao.group_by(1).first('$name').as(:name).aggregate.should eq [
|
58
|
+
{"_id"=>1, "name"=>"Terry"}
|
59
|
+
]
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'can get the last value for an aggregate field' do
|
63
|
+
dao.group_by(1).last('$name').as(:name).aggregate.should eq [
|
64
|
+
{"_id"=>1, "name"=>"Jemima"}
|
65
|
+
]
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'can get the min value for an aggregate field' do
|
69
|
+
dao.insert('name' => 'Terry', 'paws' => 7)
|
70
|
+
dao.group_by(1).min('$paws').as(:paws).aggregate.should eq [
|
71
|
+
{"_id"=>1, "paws"=>3}
|
72
|
+
]
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'can get the max value for an aggregate field' do
|
76
|
+
dao.insert('name' => 'Terry', 'paws' => 7)
|
77
|
+
dao.group_by(1).max('$paws').as(:paws).aggregate.should eq [
|
78
|
+
{"_id"=>1, "paws"=>7}
|
79
|
+
]
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'can get the average value for an aggregate field' do
|
83
|
+
dao.insert('name' => 'Terry', 'paws' => 24)
|
84
|
+
dao.group_by(1).average('$paws').as(:paws).aggregate.should eq [
|
85
|
+
{"_id"=>1, "paws"=>10}
|
86
|
+
]
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Daodalus
|
4
|
+
module DSL
|
5
|
+
module Aggregation
|
6
|
+
describe Limit do
|
7
|
+
|
8
|
+
let (:dao) { DAO.new(:animalhouse, :cats) }
|
9
|
+
|
10
|
+
before do
|
11
|
+
dao.insert('name' => 'Terry')
|
12
|
+
dao.insert('name' => 'Jemima')
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'allows an aggregation query to be limited' do
|
16
|
+
dao.limit(1).project(:name).aggregate.should eq ['name' => 'Terry']
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Daodalus
|
4
|
+
module DSL
|
5
|
+
module Aggregation
|
6
|
+
describe Match do
|
7
|
+
|
8
|
+
let (:dao) { DAO.new(:animalhouse, :cats) }
|
9
|
+
|
10
|
+
before do
|
11
|
+
dao.insert('name' => 'Terry',
|
12
|
+
'paws' => 3,
|
13
|
+
'likes' => ['tuna', 'catnip'],
|
14
|
+
'foods' => [{'type' => 'dry', 'name' => 'go cat'},
|
15
|
+
{'type' => 'wet', 'name' => 'whiskas'}])
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'allows queries to be made on the pipeline' do
|
19
|
+
dao.match(:name).eq('Terry').and(paws: 3).aggregate.should have(1).item
|
20
|
+
dao.match(:name).eq('Terry').aggregate.first.fetch('paws').should eq 3
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'also works with the logical query operators' do
|
24
|
+
dao.match.any(
|
25
|
+
dao.where(:name).eq('Terrence'),
|
26
|
+
dao.where(:name).eq('Terry')
|
27
|
+
).aggregate.should have(1).item
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Daodalus
|
4
|
+
module DSL
|
5
|
+
module Aggregation
|
6
|
+
describe Project do
|
7
|
+
|
8
|
+
let (:dao) { DAO.new(:animalhouse, :cats) }
|
9
|
+
|
10
|
+
before do
|
11
|
+
dao.insert('name' => 'Terry',
|
12
|
+
'nickname' => 'Terry',
|
13
|
+
'breed' => 'Tabby',
|
14
|
+
'paws' => 3,
|
15
|
+
'likes' => ['tuna', 'catnip'],
|
16
|
+
'foods' => [{'type' => 'dry', 'name' => 'go cat'},
|
17
|
+
{'type' => 'wet', 'name' => 'whiskas'}])
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'can project a list of fields' do
|
22
|
+
dao.project(:name, :paws, 'foods.name').aggregate.should eq [
|
23
|
+
{'name' => 'Terry', 'paws' => 3, "foods"=>[{"name"=>"go cat"}, {"name"=>"whiskas"}] }
|
24
|
+
]
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'can rename fields' do
|
28
|
+
dao.project(feet: '$paws').aggregate.should eq ['feet' => 3]
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'can be chained with other projects' do
|
32
|
+
dao.project(:paws).and(cat: '$name').aggregate.should eq ['paws' => 3, 'cat' => 'Terry']
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'can alias fields using "as"' do
|
36
|
+
dao.project(:paws).and("$name").as(:cat).aggregate.should eq ['paws' => 3, 'cat' => 'Terry']
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'implements the #eq function' do
|
40
|
+
dao.project("$name").eq("$nickname").as(:thingy).aggregate.should eq [ 'thingy' => true ]
|
41
|
+
dao.project("$name").eq("$breed").as(:thingy).aggregate.should eq [ 'thingy' => false ]
|
42
|
+
dao.project("$name").eq('Terry').as(:thingy).aggregate.should eq [ 'thingy' => true ]
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'implements the #plus function' do
|
46
|
+
dao.project("$paws", 1).plus("$paws", 4).as(:thingy).aggregate.should eq [ 'thingy' => 11 ]
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'implements the #divide function' do
|
50
|
+
dao.project("$paws").divided_by("$paws").as(:thingy).aggregate.should eq [ 'thingy' => 1 ]
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'implements the #multiply function' do
|
54
|
+
dao.project("$paws").multiplied_by("$paws").as(:thingy).aggregate.should eq [ 'thingy' => 9 ]
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'implements the #subtract function' do
|
58
|
+
dao.project("$paws").minus("$paws").as(:thingy).aggregate.should eq [ 'thingy' => 0 ]
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'implements the #mod function' do
|
62
|
+
dao.project("$paws").mod(5).as(:thingy).aggregate.should eq [ 'thingy' => 3 ]
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'can create nested documents' do
|
66
|
+
dao.project(
|
67
|
+
dao.project("$paws").as(:feet).and(name: "$name"),
|
68
|
+
).as(:cat).aggregate.should eq [{"cat"=>{"feet"=>3, "name"=>"Terry"}}]
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Daodalus
|
4
|
+
module DSL
|
5
|
+
module Aggregation
|
6
|
+
describe Skip do
|
7
|
+
|
8
|
+
let (:dao) { DAO.new(:animalhouse, :cats) }
|
9
|
+
|
10
|
+
before do
|
11
|
+
dao.insert('name' => 'Terry')
|
12
|
+
dao.insert('name' => 'Jemima')
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'allows an aggregation query to skip some records' do
|
16
|
+
dao.skip(1).project(:name).aggregate.should eq ['name' => 'Jemima']
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Daodalus
|
4
|
+
module DSL
|
5
|
+
module Aggregation
|
6
|
+
describe Sort do
|
7
|
+
|
8
|
+
let (:dao) { DAO.new(:animalhouse, :cats) }
|
9
|
+
|
10
|
+
before do
|
11
|
+
dao.insert('name' => 'Terry', paws: 2)
|
12
|
+
dao.insert('name' => 'Jemima', paws: 4)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'allows an aggregation query to be sorted' do
|
16
|
+
dao.sort(name: 1).project(:name).aggregate.should eq [
|
17
|
+
{"name"=>"Jemima"},
|
18
|
+
{"name"=>"Terry"}
|
19
|
+
]
|
20
|
+
dao.sort(name: -1).project(:name).aggregate.should eq [
|
21
|
+
{"name"=>"Terry"},
|
22
|
+
{"name"=>"Jemima"}
|
23
|
+
]
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'accepts symbols for sort order' do
|
27
|
+
dao.sort(paws: :desc).project(:name).aggregate.should eq [
|
28
|
+
{"name"=>"Jemima"},
|
29
|
+
{"name"=>"Terry"}
|
30
|
+
]
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Daodalus
|
4
|
+
module DSL
|
5
|
+
module Aggregation
|
6
|
+
describe Unwind do
|
7
|
+
|
8
|
+
let (:dao) { DAO.new(:animalhouse, :cats) }
|
9
|
+
|
10
|
+
before do
|
11
|
+
dao.insert('name' => 'Terry', 'likes' => ['tuna', 'cakes'])
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'allows an aggregation query to be unwound' do
|
15
|
+
dao.unwind(:likes).project(:name, :likes).aggregate.should eq [
|
16
|
+
{"name"=>"Terry", "likes"=>"tuna"},
|
17
|
+
{"name"=>"Terry", "likes"=>"cakes"}
|
18
|
+
]
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Daodalus
|
4
|
+
module DSL
|
5
|
+
describe Clause do
|
6
|
+
|
7
|
+
let (:dao) { DAO.new(:animalhouse, :cats) }
|
8
|
+
|
9
|
+
it 'allows you to get the current query' do
|
10
|
+
dao.where(:name).eq('Trevor').to_query.should be_a Hash
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'allows you to get the current projection' do
|
14
|
+
dao.where(:name).eq('Trevor').to_projection.should be_a Hash
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'allows you to get the current update' do
|
18
|
+
dao.where(:name).eq('Trevor').to_update.should be_a Hash
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Daodalus
|
4
|
+
module DSL
|
5
|
+
describe Query do
|
6
|
+
|
7
|
+
let (:dao) { DAO.new(:animalhouse, :cats) }
|
8
|
+
let (:query) { dao.query }
|
9
|
+
|
10
|
+
it 'can have a where clause added to it' do
|
11
|
+
query.where(paws: 3).wheres.should eq ({paws: 3})
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'can have multiple where clauses added to it' do
|
15
|
+
q = query.where('paws' => 3).where('tail' => 'waggy')
|
16
|
+
q.wheres.should eq ({'paws' => 3, 'tail' => 'waggy'})
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'merges clauses where possible' do
|
20
|
+
q = query.where('paws' => { '$gte' => 3}).where('paws' => {'$lte' => 6})
|
21
|
+
q.wheres.should eq ({'paws' => {'$gte'=>3, '$lte'=>6}})
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'raises an error if two clauses cannot be merged' do
|
25
|
+
expect { query.where('paws' => 3).where('paws' => 4) }.to raise_error InvalidQueryError
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'can have select clauses added to it' do
|
29
|
+
q = query.select('paws' => 1, 'tail' => 1)
|
30
|
+
q.selects.should eq ({'_id' => 0, 'paws' => 1, 'tail' => 1})
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Daodalus
|
4
|
+
module DSL
|
5
|
+
describe Select do
|
6
|
+
|
7
|
+
let (:dao) { DAO.new(:animalhouse, :cats) }
|
8
|
+
|
9
|
+
before do
|
10
|
+
dao.insert('name' => 'Terry',
|
11
|
+
'paws' => 3,
|
12
|
+
'likes' => ['tuna', 'catnip'],
|
13
|
+
'lives' => [1,2,3,4,5,6,7,8,9],
|
14
|
+
'foods' => [{'type' => 'dry', 'name' => 'go cat'},
|
15
|
+
{'type' => 'wet', 'name' => 'whiskas'}])
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'allows the selection of fields to be returned' do
|
19
|
+
dao.select(:name, :paws).find_one.value.keys.should eq ['name', 'paws']
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'allows the chaining of selects' do
|
23
|
+
dao.select(:name).and(:paws).find_one.value.keys.should eq ['name', 'paws']
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'allows use of the positional operator' do
|
27
|
+
query = dao.select(:foods).by_position.where(:'foods.type').eq('wet')
|
28
|
+
result = query.find_one.value
|
29
|
+
result.fetch('foods').should eq (['type' => 'wet', 'name' => 'whiskas'])
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'implements #slice' do
|
33
|
+
dao.select(:lives).slice(-3).find_one.value.fetch('lives').should eq [7,8,9]
|
34
|
+
dao.select(:lives).slice(-3, 3).find_one.value.fetch('lives').should eq [7,8,9]
|
35
|
+
dao.select(:lives).slice(6, 5).find_one.value.fetch('lives').should eq [7,8,9]
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'implements #elem_match' do
|
39
|
+
dao.select(:foods).elem_match(
|
40
|
+
dao.where(:type).eq(:wet)
|
41
|
+
).find_one.value.fetch('foods').first.fetch('name').should eq 'whiskas'
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|