daodalus 0.1.1
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.
- 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
|