daodalus-moped 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/.gitignore +7 -0
  2. data/.travis.yml +7 -0
  3. data/Gemfile +13 -0
  4. data/Gemfile.lock +72 -0
  5. data/LICENCE.md +9 -0
  6. data/README.md +316 -0
  7. data/Rakefile +8 -0
  8. data/daodalus-moped.gemspec +24 -0
  9. data/lib/daodalus.rb +29 -0
  10. data/lib/daodalus/connection.rb +19 -0
  11. data/lib/daodalus/dao.rb +35 -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 +31 -0
  24. data/lib/daodalus/dsl/query.rb +42 -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 +26 -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,7 @@
1
+ /.bundle
2
+ /log/*.log
3
+ /tmp
4
+ /tags
5
+ /coverage
6
+ *.gem
7
+ coverage
@@ -0,0 +1,7 @@
1
+ services:
2
+ - mongodb
3
+ rvm:
4
+ - 2.0.0
5
+ - 1.9.3
6
+ - 1.9.2
7
+ - rbx-19mode
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source 'http://rubygems.org'
2
+ gemspec
3
+
4
+ gem 'moped', '~> 1.4.5'
5
+ gem 'optional'
6
+
7
+ group :development do
8
+ gem "rake"
9
+ end
10
+
11
+ group :test do
12
+ gem "cucumber"
13
+ end
@@ -0,0 +1,72 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ daodalus-moped (0.1.1)
5
+ em-synchrony-moped
6
+ id
7
+ moped
8
+ optional
9
+
10
+ GEM
11
+ remote: http://rubygems.org/
12
+ specs:
13
+ activemodel (3.2.12)
14
+ activesupport (= 3.2.12)
15
+ builder (~> 3.0.0)
16
+ activesupport (3.2.12)
17
+ i18n (~> 0.6)
18
+ multi_json (~> 1.0)
19
+ builder (3.0.4)
20
+ cucumber (1.3.2)
21
+ builder (>= 2.1.2)
22
+ diff-lcs (>= 1.1.3)
23
+ gherkin (~> 2.12.0)
24
+ multi_json (~> 1.3)
25
+ diff-lcs (1.2.4)
26
+ em-resolv-replace (1.1.3)
27
+ em-synchrony (1.0.3)
28
+ eventmachine (>= 1.0.0.beta.1)
29
+ em-synchrony-moped (0.9.4)
30
+ em-resolv-replace (~> 1.1.3)
31
+ em-synchrony (~> 1.0)
32
+ eventmachine
33
+ moped (~> 1.4.5)
34
+ eventmachine (1.0.3)
35
+ gherkin (2.12.0)
36
+ multi_json (~> 1.3)
37
+ i18n (0.6.4)
38
+ id (0.0.9)
39
+ activemodel
40
+ activesupport
41
+ money
42
+ optional
43
+ money (5.1.1)
44
+ i18n (~> 0.6.0)
45
+ moped (1.4.5)
46
+ multi_json (1.7.6)
47
+ optional (0.0.7)
48
+ rake (10.0.4)
49
+ rspec (2.13.0)
50
+ rspec-core (~> 2.13.0)
51
+ rspec-expectations (~> 2.13.0)
52
+ rspec-mocks (~> 2.13.0)
53
+ rspec-core (2.13.1)
54
+ rspec-expectations (2.13.0)
55
+ diff-lcs (>= 1.1.3, < 2.0)
56
+ rspec-mocks (2.13.1)
57
+ simplecov (0.7.1)
58
+ multi_json (~> 1.0)
59
+ simplecov-html (~> 0.7.1)
60
+ simplecov-html (0.7.1)
61
+
62
+ PLATFORMS
63
+ ruby
64
+
65
+ DEPENDENCIES
66
+ cucumber
67
+ daodalus-moped!
68
+ moped (~> 1.4.5)
69
+ optional
70
+ rake
71
+ rspec
72
+ simplecov
@@ -0,0 +1,9 @@
1
+ Copyright (c) 2013 On The Beach Ltd
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,316 @@
1
+ # DAODALUS [![Build Status](https://travis-ci.org/onthebeach/daodalus.png)](http://travis-ci.org/onthebeach/daodalus) [![Code Climate](https://codeclimate.com/github/onthebeach/daodalus.png)](https://codeclimate.com/github/onthebeach/daodalus)
2
+
3
+ ### Take the sting out of constructing complex MongoDB queries, updates and aggregations.
4
+
5
+ In Greek mythology, Daedalus tried to prevent Icarus from flying too close to the sun, but Icarus ignored his father's warning, the wax holding together his wings melted, and he fell to his death. 'Daodalus' hopes to succeed where Daedalus failed by preventing your application code from flying too close to the code that interacts with your datastore.
6
+
7
+ Originally conceived of as an implementation of the Data Access Object pattern, it has evolved since then into more of a DSL to simplify doing complicated things with Mongo. However, by separating your models from the object used to interact with their stored form, we hope Daodalus still encourages your application and data layers to keep their distance from each other.
8
+
9
+ Find the docs [here](http://onthebeach.github.io/daodalus).
10
+
11
+ ## Registering connections
12
+
13
+ Before being able to use Daodalus you will need to create and register one or more connections to your instance (or instances) of MongoDB. Here's how you do it:
14
+
15
+ conn = Mongo::MongoClient.new('localhost', 27017, pool_size: 5)
16
+ Daodalus::Connection.register(conn, :name)
17
+
18
+ If you leave off the name, the connection will be registered as `:default`.
19
+
20
+ The connection can be any MongoDB connection class provided by the Ruby Mongo Driver - so you could also use a `MongoShardedClient` or `MongoReplicaSetClient` here.
21
+
22
+ ## Creating a DAO
23
+
24
+ Create a DAO by specifying a database and collection (and optional connection name, defaulting to 'default'):
25
+
26
+ dao = Daodalus::DAO.new(:my_db, :my_collection, :my_connection)
27
+
28
+ The `connection` name must match one of the connections you registered earlier.
29
+
30
+ ## Access to MongoDB Collection Methods
31
+
32
+ You now have access to several basic MongoDB methods as defined on the `Mongo::Collection` class of the MongoDB Ruby driver. The following methods work unchanged:
33
+
34
+ * `#find`
35
+ * `#update`
36
+ * `#insert`
37
+ * `#save`
38
+ * `#remove`
39
+ * `#count`
40
+ * `#aggregate`
41
+
42
+ However, `#find_one` and `#find_and_modify` work slightly differently. While the original methods return either the matched document or `nil`, Daodalus is allergic to `nil`s, and so avoids them by using optional types instead. So the values returned will be either `Some[value]` or `None`. You can read more about optional types and see the implementation used by Daodalus [here](http://github.com/rsslldnphy/optional).
43
+
44
+ These are the only methods exposed by the Daodalus DAO - but should you need to call any other methods on the collection, the `Mongo::Collection` object can be accessed directly by calling `dao.coll`.
45
+
46
+ ## Queries
47
+
48
+ Queries are built up by chaining together one or more 'where' clauses, like this:
49
+
50
+ dao.where(:name).eq('Terry').and(:paws).gte(3).find
51
+
52
+ Note that a longer-form version of the clause name usually exists as an alias if you prefer to be a bit more verbose. So the above could equally be expressed as:
53
+
54
+ dao.where(:name).equals('Terry').and(:paws).greater_than_or_equal(3).find
55
+
56
+ Notice the `find` at the end of the chain? This is what terminates the chain and sends your query to Mongo. You can also use `find_one` here (which, remember, will return an `Option`) as well as some other methods for updating and aggregation that we'll come to later.
57
+
58
+ If you need to make multiple assertions about the same field, you can simply chain them like this:
59
+
60
+ dao.where(:paws).gt(2).lt(5).find
61
+
62
+ But bear in mind that it's not possible to make an equality assertion *and* another assertion on the same field. (Which makes sense if you think about it.) This will raise an `InvalidQueryError`.
63
+
64
+ Querying against nested fields works just like it does in plain Mongo, using the dot operator. (You can pass field names as strings or symbols, it's up to you.)
65
+
66
+ dao.where(:'paws.2.toes').gt(3) # the third element of the paws array has a toes field that is > than 3
67
+
68
+ Here is the complete list of currently implemented 'where' clauses you can use with Daodalus.
69
+
70
+ | Clause | Alias | Usage |
71
+ | ---------- | -------------------------- | ----------------------------- |
72
+ | `#eq` | `#equals` | `dao.where(:paws).eq(4)` |
73
+ | `#ne` | `#not_equal` | `dao.where(:paws).ne(4)` |
74
+ | `#lt` | `#less_than` | `dao.where(:paws).lt(4)` |
75
+ | `#gt` | `#greater_than` | `dao.where(:paws).gt(4)` |
76
+ | `#lte` | `#less_than_or_equal` | `dao.where(:paws).lte(4)` |
77
+ | `#gte` | `#greater_than_or_equal` | `dao.where(:paws).gte(4)` |
78
+ | `#in` | - | `dao.where(:paws).in(4, 3)` |
79
+ | `#nin` | `#not_in` | `dao.where(:paws).gte(4)` |
80
+ | `#all` | - | `dao.where(:likes).all('tuna', 'catnip')` |
81
+ | `#size` | - | `dao.where(:likes).size(3)` |
82
+ | `#exists` | - | `dao.where(:tail).exists` |
83
+ | `#does_not_exist` | `#exists(false)` | `dao.where(:tail).does_not_exist` |
84
+
85
+ ### Logic
86
+
87
+ Using `#not` will negate the following where clause (one only), whatever it might be. So:
88
+
89
+ dao.where(:paws).not.gte(4)
90
+
91
+ translates as:
92
+
93
+ { 'paws' : { '$not' : { '$gte' : 4 } } }
94
+
95
+ The MongoDB `or` and `nor` operators have different names in Daodalus so that they make more sense in the context they're in. So, to match *any* of a set of clauses, use `#any` like this:
96
+
97
+ dao.where.any(
98
+ dao.where(:paws).eq(3),
99
+ dao.where(:name).eq('Terry')
100
+ )
101
+
102
+ # translates as { '$or' : [ { 'paws' : 3 }, { 'name' : 'Terry' }] }
103
+
104
+ ...and to match only if *none* of a set of clauses match, use `#none`:
105
+
106
+ dao.where.none(
107
+ dao.where(:paws).eq(3),
108
+ dao.where(:name).eq('Terry')
109
+ )
110
+
111
+ # translates as { '$nor' : [ { 'paws' : 3 }, { 'name' : 'Terry' }] }
112
+
113
+ ### Matching array elements
114
+
115
+ You can also ensure a specific array element matches a number of conditions using the `#elem_match` method.
116
+
117
+ dao.where(:foods).elem_match(
118
+ dao.where(:type).eq(:wet).and(:name).eq("Whiskas")
119
+ )
120
+
121
+ # translates as { 'foods' : { '$elemMatch' : { 'type' : 'wet', 'name' : 'Whiskas' } } }
122
+
123
+ This will match only documents with a `foods` array which contains an element with a `type` value of "wet" *and* a `name` value of "Whiskas".
124
+
125
+ ### The hard way
126
+
127
+ If you want to just pass a hash as a where clause just as you would if you were using the driver directly, you can do that too, like this:
128
+
129
+ dao.where(name: 'Terry', paws: 3)
130
+
131
+ This can be more readable for simple equality matching over a number of fields.
132
+
133
+ ## Selecting Fields to be Returned (Projection)
134
+
135
+ You don't always want the whole document to be returned. Mongo allows you to specify which fields you're interested in. To do this in Daodalus, use `#select`. Here's how to specify a bunch of fields to be selected:
136
+
137
+ dao.select(:name, :paws, :tail)
138
+
139
+ NB. Although MongoDB *always* selects the `_id` field by default, Daodalus does not do this (in order to reduce the number of 'special cases'). If you need the `_id` field, simply specify it as part of the select.
140
+
141
+ ### Chaining selects
142
+
143
+ If you're selecting lots of fields, you may wish to split the statement over multiple lines - or you could wish to separate groups of selected fields for semantic reasons. To help keep this neat, selects can be chained with the `and` method like this:
144
+
145
+ dao.select(:cats, :dogs, :fish).and(:pigs, :sheep, :horseys)
146
+
147
+ ### The positional operator
148
+
149
+ In Mongo, if you want to select only the first matched subdocument in an array you use the positional operator (`$`). In Daodalus, we've given that the slightly more descriptive name of `#by_position`. Use it like this:
150
+
151
+ dao.select(:cats).by_position.where(:'cats.paws').gt(3) # select and return only the first cat with > 3 paws
152
+
153
+ You could also have specified the positional operator manually as part of the field name like this:
154
+
155
+ dao.select(:'cats.$').where(:'cats.paws').gt(3)
156
+
157
+ ### Slice
158
+
159
+ To select a subsection of an array, use `#slice`.
160
+
161
+ dao.select(:favourite_foods).slice(4) # get the first four
162
+ dao.select(:favourite_foods).slice(-4) # get the last four
163
+ dao.select(:favourite_foods).slice(10, 4) # get four, starting with the 10th
164
+ dao.select(:favourite_foods).slice(-10, 4) # get four, starting with the 10th from last
165
+
166
+ ### Elem_match
167
+
168
+ It's also possible to use `#elem_match` as part of a select clause, in a similar way to how it's used in a where clause. This time, it makes sure that only the first element in an array that matches the provided query is returned.
169
+
170
+ dao.select(:favourite_foods).elem_match(
171
+ dao.where(:price).lte(5_00)
172
+ )
173
+
174
+ ## Updating
175
+
176
+ Updating can be done using the various update methods listed below, followed by the `update`, `upsert`, or `find_and_modify` methods to end the chain (in the same way that we've used `find` and `find_and_modify` up to now). So you end up with something like this:
177
+
178
+ dao.set(name: 'Poor Terry').dec(:paws).where(:name).eq('Terry').update
179
+ dao.set(name: 'Poor Terry').dec(:paws).where(:name).eq('Terry').upsert
180
+ dao.set(name: 'Poor Terry').where(:name).eq('Terry').find_and_modify
181
+
182
+ Each method also accepts an optional hash of options to pass to the respective Mongo driver method. See Mongo docs for details.
183
+
184
+ Here's the list of all currently implemented update methods you can use and how to use them:
185
+
186
+ | Method | Usage |
187
+ | -------- | ----------------------------- |
188
+ | `#set` | `dao.set(paws: 3)` |
189
+ | `#unset` | `dao.unset(:name, :paws)` |
190
+ | `#inc` | `dao.inc(:paws, 2) #default is 1` |
191
+ | `#dec` | `dao.dec(:paws) #default is 1` |
192
+ | `#rename` | `dao.rename(:paws, :feet)` |
193
+ | `#pop_first` | `dao.pop_first(:likes)` |
194
+ | `#pop_last` | `dao.pop_last(:likes)` |
195
+ | `#push` | `dao.push(:likes, 'Marxist political economy')` |
196
+ | `#push_all` | `dao.push_all(:likes, ['bananas', 'crisps'])` |
197
+ | `#add_to_set` | `dao.add_to_set(:likes, 'bananas')` |
198
+ | `#add_each_to_set` | `dao.add_each_to_set(:likes, ['bananas', 'crisps'])` |
199
+ | `#pull` | `dao.pull(:likes, 'Marxist political economy')` |
200
+ | `#pull_all` | `dao.pull_all(:likes, ['bananas', 'crisps'])` |
201
+
202
+ NB. `#push`, `#add_to_set`, and `#pull` can all accept multiple arguments - they will be converted to the appropriate array based `all` or `each` command under the hood. So you can write `dao.push(:likes, 'Marxist political economy', 'cake')` for example.
203
+
204
+ ## Aggregation Framework
205
+
206
+ Using the aggregation framework involves simply chaining together a series of pipeline operators to build the query you want, finishing the (arbitrarily long) chain with a call to `aggregate`. Let's look at the different pipeline operators in turn.
207
+
208
+ ### Match
209
+
210
+ Match allows you to query for a subset of documents in exactly the same way as you do with `where` - except as part of the aggregation pipeline. All the same methods are supported. This is what it looks like:
211
+
212
+ dao.match(:paws).gt(3).and(:name).in("Jemima", "Terry").aggregate
213
+
214
+ dao.match.any(
215
+ dao.where(:name).eq('Terry'),
216
+ dao.where(likes: 'tuna')
217
+ ).aggregate
218
+
219
+ Ok, not that exciting yet. But it becomes exciting when you start combining it with the other operators!
220
+
221
+ ### Group
222
+
223
+ This works very similar to a `GROUP BY` in SQL. As such Daodalus adds an alias for the method `group_by` - which you may find makes your code read a bit nicer. You can group by a single key like this:
224
+
225
+ dao.group_by(:'$name').aggregate
226
+
227
+ which will give you result documents of the form `{ '_id' : 'whatever the name is' }`. You can also group by multiple keys like this (you need to provide a name, or alias, for each one):
228
+
229
+ dao.group_by(name: '$name', paws: '$paws').aggregate
230
+
231
+ which will produce result documents of the form `{ '_id' : { 'name' : 'whatever the name is', 'paws' : 'no of paws' } }`.
232
+
233
+ The `$`s are required to indicate that you want the value of the specified field, rather than just the string literal you're passing. For more info see the Mongo docs.
234
+
235
+ Group also allows you to build aggregates of fields in various ways. You must always provide a name/alias for an aggregate field, by following the call to the function with a call to `as`, like this:
236
+
237
+ dao.group_by('$breed').sum(1).as(:total).aggregate
238
+
239
+ Here are the aggregate group functions supported by Daodalus:
240
+
241
+ | Function | Alias | Usage |
242
+ | ------------- | -------------- | ---------------------------------------------------- |
243
+ | `#sum` | `#total` | `dao.group_by('$breed').sum('$paws').as(:total_paws)` |
244
+ | `#add_to_set` | `#distinct` | `dao.group_by(1).distinct('$breeds').as(:breeds)` |
245
+ | `#push` | `#collect` | `dao.group_by(1).collect('$names').as(:names)` |
246
+ | `#first` | - | `dao.group_by(1).first('$names').as(:name)` |
247
+ | `#last` | - | `dao.group_by(1).last('$names').as(:name)` |
248
+ | `#max` | - | `dao.group_by(1).max('$paws').as(:max_paws)` |
249
+ | `#min` | - | `dao.group_by(1).min('$paws').as(:min_paws)` |
250
+ | `#avg` | `#average` | `dao.group_by(1).average('$paws').as(:average_paws)` |
251
+
252
+ ### Project
253
+
254
+ Return a subset of fields from an aggregation and perform simple calculations on the returned fields with `#project`.
255
+
256
+ To simply return one or more fields as they are, pass them to `#project` like this:
257
+
258
+ dao.project(:name, :paws, :'food.type').aggregate
259
+ # NB. as with #select, the '_id' field is excluded by default
260
+
261
+ To give aliases to your fields, pass a hash in the structure you want. You'll need to use the `$` operator to signify the fields you want your aliases to refer to.
262
+
263
+ dao.project(cat: "$name").aggregate
264
+
265
+ Another way to provide aliases is to use the `as` method like this:
266
+
267
+ dao.project("$paws").as(:feet).aggregate
268
+
269
+ You can chain `project` calls together using `and`, like this:
270
+
271
+ dao.project(:name, :likes).and(feet: "$paws").and("$foods.name").as(:foods).aggregate
272
+
273
+ You can build nested documents by using nested projects:
274
+
275
+ dao.project(
276
+ dao.project(:name, :paws)
277
+ ).as(:cat).aggregate
278
+
279
+ And finally, you can perform simple operations on the data, like this:
280
+
281
+ dao.project(4).minus("$paws").as(:accidents).aggregate
282
+
283
+ Here's the list of the currently supported `project` functions:
284
+
285
+ | Function | Alias | Usage |
286
+ | ------------- | -------------- | ---------------------------------------------------- |
287
+ | `#eq` | - | `dao.project('$breed').eq('Tabby').as(:is_tabby)` |
288
+ | `#add` | `#plus` | `dao.project('$paws').plus('$lives', 3).as(:a_number)` |
289
+ | `#subtract` | `#minus` | `dao.project('$paws').minus(1).as(:less_paws)` |
290
+ | `#divide` | `#divided_by` | `dao.project('$paws').divided_by(2).as(:half_my_paws)` |
291
+ | `#multiply` | `#multiplied_by` | `dao.project('$paws').multiplied_by(2).as(:double_paws)` |
292
+ | `#mod` | - | `dao.project('$paws').mod(2).as(:odd_paws)` |
293
+
294
+ ### Skip and Limit
295
+
296
+ Some very simple aggregation functions, analogous to the identically named methods you can use with a normal Mongo cursor. Skip an certain number of documents with `#skip` and limit the number returned with `#limit`.
297
+
298
+ dao.skip(100).limit(20).aggregate
299
+
300
+ ### Sort
301
+
302
+ Again, analogous to the method on `MongoCursor`. Pass a hash with the fields you want to sort by as keys and either `1` for ascending order or `-1` for descending order. If you like, instead of `1` and `-1` you can also use the symbols `:asc` and `:desc` like this:
303
+
304
+ dao.sort(breed: :asc, paws: :desc).aggregate
305
+
306
+ ### Unwind
307
+
308
+ If you have an array field and you want to unwind the document into one copy for each array element ('unwinding' them out of the array) use `#unwind`. Using it is simple, just pass it the field name you want to unwind. Remember it has to be an array field!
309
+
310
+ dao.unwind(:likes).aggregate
311
+
312
+ ## License
313
+
314
+ Daodalus is Copyright © 2012-2013 On The Beach Ltd.
315
+
316
+ It is free software, and may be redistributed under the terms specified in the LICENSE file.
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ task :default => :spec
5
+
6
+ require 'rspec/core/rake_task'
7
+
8
+ RSpec::Core::RakeTask.new
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ Gem::Specification.new do |gem|
3
+ gem.authors = ["Russell Dunphy"]
4
+ gem.email = ["russell.dunphy@onthebeach.co.uk"]
5
+ gem.description = %q{Take the sting out of building complex MongoDB queries, updates and aggregations.}
6
+ gem.summary = gem.description
7
+ gem.homepage = "http://onthebeach.github.io/daodalus-moped"
8
+
9
+ gem.add_runtime_dependency 'id'
10
+ gem.add_runtime_dependency 'optional'
11
+
12
+ gem.add_runtime_dependency 'moped'
13
+ gem.add_runtime_dependency 'em-synchrony-moped'
14
+
15
+ gem.add_development_dependency 'rspec'
16
+ gem.add_development_dependency 'simplecov'
17
+
18
+ gem.files = `git ls-files`.split($\)
19
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
20
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
21
+ gem.name = "daodalus-moped"
22
+ gem.require_paths = ["lib"]
23
+ gem.version = "0.0.1"
24
+ end