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.
Files changed (49) hide show
  1. data/.gitignore +7 -0
  2. data/.travis.yml +7 -0
  3. data/Gemfile +10 -0
  4. data/Gemfile.lock +65 -0
  5. data/LICENCE.md +9 -0
  6. data/README.md +316 -0
  7. data/Rakefile +8 -0
  8. data/daodalus.gemspec +23 -0
  9. data/lib/daodalus.rb +29 -0
  10. data/lib/daodalus/connection.rb +19 -0
  11. data/lib/daodalus/dao.rb +41 -0
  12. data/lib/daodalus/dsl.rb +21 -0
  13. data/lib/daodalus/dsl/aggregation/group.rb +88 -0
  14. data/lib/daodalus/dsl/aggregation/limit.rb +23 -0
  15. data/lib/daodalus/dsl/aggregation/match.rb +35 -0
  16. data/lib/daodalus/dsl/aggregation/project.rb +79 -0
  17. data/lib/daodalus/dsl/aggregation/skip.rb +23 -0
  18. data/lib/daodalus/dsl/aggregation/sort.rb +29 -0
  19. data/lib/daodalus/dsl/aggregation/unwind.rb +23 -0
  20. data/lib/daodalus/dsl/aggregations.rb +44 -0
  21. data/lib/daodalus/dsl/clause.rb +19 -0
  22. data/lib/daodalus/dsl/matchers.rb +87 -0
  23. data/lib/daodalus/dsl/queries.rb +29 -0
  24. data/lib/daodalus/dsl/query.rb +46 -0
  25. data/lib/daodalus/dsl/select.rb +43 -0
  26. data/lib/daodalus/dsl/update.rb +86 -0
  27. data/lib/daodalus/dsl/updates.rb +71 -0
  28. data/lib/daodalus/dsl/where.rb +30 -0
  29. data/lib/daodalus/invalid_connection_error.rb +7 -0
  30. data/lib/daodalus/invalid_query_error.rb +4 -0
  31. data/spec/lib/daodalus/connection_spec.rb +22 -0
  32. data/spec/lib/daodalus/dao_spec.rb +34 -0
  33. data/spec/lib/daodalus/dsl/aggregation/group_spec.rb +92 -0
  34. data/spec/lib/daodalus/dsl/aggregation/limit_spec.rb +22 -0
  35. data/spec/lib/daodalus/dsl/aggregation/match_spec.rb +32 -0
  36. data/spec/lib/daodalus/dsl/aggregation/project_spec.rb +74 -0
  37. data/spec/lib/daodalus/dsl/aggregation/skip_spec.rb +22 -0
  38. data/spec/lib/daodalus/dsl/aggregation/sort_spec.rb +36 -0
  39. data/spec/lib/daodalus/dsl/aggregation/unwind_spec.rb +24 -0
  40. data/spec/lib/daodalus/dsl/clause_spec.rb +22 -0
  41. data/spec/lib/daodalus/dsl/query_spec.rb +35 -0
  42. data/spec/lib/daodalus/dsl/select_spec.rb +46 -0
  43. data/spec/lib/daodalus/dsl/update_spec.rb +113 -0
  44. data/spec/lib/daodalus/dsl/where_spec.rb +133 -0
  45. data/spec/lib/daodalus/dsl_spec.rb +12 -0
  46. data/spec/pointless_coverage_spec.rb +9 -0
  47. data/spec/spec_helper.rb +18 -0
  48. data/spec/support/mongo_cleaner.rb +12 -0
  49. 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