couch_view 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,359 @@
1
+ Feature: CouchView
2
+ As a programmer
3
+ I want a `CouchView` mixin for my `CouchRest::Model::Base` models
4
+ So that I can define maps and reduces on my model
5
+
6
+
7
+ @db
8
+ Scenario: Define a map over a property
9
+ Given the following model definition:
10
+ """
11
+ class Article < CouchRest::Model::Base
12
+ include CouchView
13
+ property :label
14
+ end
15
+ """
16
+
17
+ When I pass :label to the `map` class method:
18
+ """
19
+ Article.map :label
20
+ """
21
+
22
+ Then my model should respond to "map_by_label" and "map_by_label!":
23
+ """
24
+ Article.should respond_to(:map_by_label)
25
+ Article.should respond_to(:map_by_label!)
26
+ """
27
+
28
+ When I create some articles with labels:
29
+ """
30
+ Article.create :label => "moonmaster9000"
31
+ Article.create :label => "grantmichaels"
32
+ """
33
+
34
+ Then they should be indexed in my label map:
35
+ """
36
+ Article.map_by_label!.collect(&:label).should == ["grantmichaels", "moonmaster9000"]
37
+ Article.map_by_label.get!.collect(&:label).should == ["grantmichaels", "moonmaster9000"]
38
+ """
39
+
40
+
41
+ @db @focus
42
+ Scenario: Define a map on your model with conditions
43
+ Given the following conditions:
44
+ """
45
+ module Published
46
+ def conditions
47
+ "#{super} && doc.published == true"
48
+ end
49
+ end
50
+
51
+ module Visible
52
+ def conditions
53
+ "#{super} && doc.visible == true"
54
+ end
55
+ end
56
+ """
57
+
58
+ When I add them as conditions to a map over my model's label property:
59
+ """
60
+ class Article < CouchRest::Model::Base
61
+ include CouchView
62
+
63
+ property :label
64
+ property :published, TrueClass, :default => false
65
+ property :visible, TrueClass, :default => false
66
+
67
+ map :label do
68
+ conditions Published, Visible
69
+ end
70
+ end
71
+ """
72
+
73
+ And I create visible and published documents:
74
+ """
75
+ Article.create :label => "unpublished"
76
+ Article.create :label => "published", :published => true
77
+ Article.create :label => "visible", :visible => true
78
+ Article.create :label => "published_and_visible", :published => true, :visible => true
79
+ """
80
+
81
+ Then I should be able to query them through my query proxy:
82
+ """
83
+ Article.map_by_label!.collect(&:label).sort.should ==
84
+ ["published", "published_and_visible", "unpublished", "visible"]
85
+
86
+ Article.map_by_label.published.get!.collect(&:label).sort.should ==
87
+ ["published", "published_and_visible"]
88
+
89
+ Article.map_by_label.visible.get!.collect(&:label).sort.should ==
90
+ ["published_and_visible", "visible"]
91
+
92
+ Article.map_by_label.published.visible.get!.collect(&:label).sort.should ==
93
+ ["published_and_visible"]
94
+ """
95
+
96
+
97
+ @db
98
+ Scenario: Define a map on your model with the `map` class method
99
+ Given the following map definition:
100
+ """
101
+ class ById
102
+ include CouchView::Map
103
+ end
104
+ """
105
+
106
+ When I mix `CouchView` into my model and define a `map ById`:
107
+ """
108
+ class Article < CouchRest::Model::Base
109
+ include CouchView
110
+
111
+ map ById
112
+ end
113
+ """
114
+
115
+ Then my model should respond to `map_by_id`:
116
+ """
117
+ Article.should respond_to(:map_by_id)
118
+ """
119
+
120
+ And my model should respond to `map_by_id!`:
121
+ """
122
+ Article.should respond_to(:map_by_id!)
123
+ """
124
+
125
+
126
+ @db
127
+ Scenario: Retrieve a map proxy
128
+
129
+ Given the following map definition:
130
+ """
131
+ class ById
132
+ include CouchView::Map
133
+ end
134
+ """
135
+
136
+ When I mix `CouchView` into my model and define a `map ById`:
137
+ """
138
+ class Article < CouchRest::Model::Base
139
+ include CouchView
140
+ map ById
141
+ end
142
+ """
143
+
144
+ And I call the "map_by_id" method on my model:
145
+ """
146
+ @proxy = Article.map_by_id
147
+ """
148
+
149
+ Then I should receive a view proxy:
150
+ """
151
+ @proxy.class.should == CouchView::Proxy
152
+ """
153
+
154
+ And my proxy should map over ":by_by_id":
155
+ """
156
+ @proxy._map.should == :by_by_id
157
+ """
158
+
159
+ And my proxy should map on the "Article" model:
160
+ """
161
+ @proxy._model.should == Article
162
+ """
163
+
164
+
165
+ @db
166
+ Scenario: Generate a reduce proxy for counting the number of results in your query
167
+
168
+ Given the following map definition:
169
+ """
170
+ class ById
171
+ include CouchView::Map
172
+ end
173
+ """
174
+
175
+ When I mix `CouchView` into my model and define a `map ById`:
176
+ """
177
+ class Article < CouchRest::Model::Base
178
+ include CouchView
179
+ map ById
180
+ end
181
+ """
182
+
183
+ Then my model should respond to `count_by_id`:
184
+ """
185
+ Article.should respond_to(:count_by_id)
186
+ """
187
+
188
+ And my model should respond to `count_by_id!`:
189
+ """
190
+ Article.should respond_to(:count_by_id!)
191
+ """
192
+
193
+ When I call the `count_by_id` method
194
+ """
195
+ @proxy = Article.count_by_id
196
+ """
197
+
198
+ Then I should receive a count proxy:
199
+ """
200
+ @proxy.class.should be(CouchView::Count::Proxy)
201
+ """
202
+
203
+ @db
204
+ Scenario: Counting the number of rows in a reduce query
205
+
206
+ Given the following map definition:
207
+ """
208
+ class ById
209
+ include CouchView::Map
210
+ end
211
+ """
212
+
213
+ When I mix `CouchView` into my model and define a `map ById`:
214
+ """
215
+ class Article < CouchRest::Model::Base
216
+ include CouchView
217
+ map ById
218
+ end
219
+ """
220
+
221
+ And I create 4 articles:
222
+ """
223
+ 4.times { Article.create }
224
+ """
225
+
226
+ Then `count_by_id!` should return 4:
227
+ """
228
+ Article.count_by_id!.should == 4
229
+ """
230
+
231
+ @db
232
+ Scenario: Query a map on your model
233
+
234
+ Given the following map definition:
235
+ """
236
+ class ById
237
+ include CouchView::Map
238
+ end
239
+ """
240
+
241
+ When I mix `CouchView` into my model and define a `map ById`:
242
+ """
243
+ class Article < CouchRest::Model::Base
244
+ include CouchView
245
+
246
+ map ById
247
+ end
248
+ """
249
+
250
+ And I create an Article:
251
+ """
252
+ @article = Article.create
253
+ """
254
+
255
+ Then `map_by_id!` should return the article
256
+ """
257
+ Article.map_by_id!.first.should == @article
258
+ """
259
+
260
+ And `map_by_id.get!` should return the article
261
+ """
262
+ Article.map_by_id.get!.first.should == @article
263
+ """
264
+
265
+
266
+ @db
267
+ Scenario: Defining a custom "reduce" on your view
268
+
269
+ Given a Article model:
270
+ """
271
+ class Article < CouchRest::Model::Base
272
+ include CouchView
273
+ property :label
274
+ end
275
+ """
276
+
277
+ When I define a map over labels with a custom reduce that always returns -1:
278
+ """
279
+ Article.couch_view do
280
+ map :label
281
+ reduce <<-JS
282
+ function(key, values){
283
+ return -1;
284
+ }
285
+ JS
286
+ end
287
+ """
288
+
289
+ Then my model should respond to `reduce_by_label`:
290
+ """
291
+ Article.should respond_to(:reduce_by_label)
292
+ """
293
+
294
+ And my model should not respond to `count_by_label`:
295
+ """
296
+ Article.should_not respond_to(:count_by_label)
297
+ """
298
+
299
+ When I create two articles with the same label:
300
+ """
301
+ 2.times { Article.create :label => "moonmaster9000-rocks" }
302
+ """
303
+
304
+ Then `reduce_by_label` should return -1:
305
+ """
306
+ Article.reduce_by_label!['rows'].first['value'].should == -1
307
+ """
308
+
309
+ @db @focus
310
+ Scenario: Giving your view a custom name
311
+
312
+ Given the following model:
313
+ """
314
+ class Article < CouchRest::Model::Base
315
+ include CouchView
316
+ property :label
317
+ end
318
+ """
319
+
320
+ When I call "couch_view" with a custom name "over_label" and specify a map over labels in a block:
321
+ """
322
+ Article.couch_view :over_label do
323
+ map :label
324
+ end
325
+ """
326
+
327
+ Then my model should respond to "map_over_label" and "count_over_label":
328
+ """
329
+ Article.should respond_to(:map_over_label)
330
+ Article.should respond_to(:map_over_label!)
331
+
332
+ Article.should respond_to(:count_over_label)
333
+ Article.should respond_to(:count_over_label!)
334
+ """
335
+
336
+ But my model should not respond to "map_by_label" or "count_by_label":
337
+ """
338
+ Article.should_not respond_to(:map_by_label)
339
+ Article.should_not respond_to(:map_by_label!)
340
+
341
+ Article.should_not respond_to(:count_by_label)
342
+ Article.should_not respond_to(:count_by_label!)
343
+ """
344
+
345
+ When I create two articles with labels:
346
+ """
347
+ Article.create :label => "hi"
348
+ Article.create :label => "there"
349
+ """
350
+
351
+ Then "map_over_label" should return them:
352
+ """
353
+ Article.map_over_label!.collect(&:label).should == ["hi", "there"]
354
+ """
355
+
356
+ And "count_over_label" should return 2:
357
+ """
358
+ Article.count_over_label!.should == 2
359
+ """
@@ -0,0 +1,117 @@
1
+ Feature: CouchView::Map
2
+ As a programmer
3
+ I want a `CouchView::Map` mixin
4
+ So that I can create a class with a CouchDB map function
5
+
6
+
7
+ Scenario: Mixing CouchView::Map into a class should generate a map over ids
8
+
9
+ Given the following class definition:
10
+ """
11
+ class Map
12
+ include CouchView::Map
13
+ end
14
+ """
15
+
16
+ When I instantiate a new Map:
17
+ """
18
+ Map.new.map
19
+ """
20
+
21
+ Then I should receive the following CouchDB javascript map:
22
+ """
23
+ function(doc){
24
+ emit(doc._id, null)
25
+ }
26
+ """
27
+
28
+ Scenario: Instantiating a Map with a Model should generate a map over that model
29
+
30
+ Given the following model definition:
31
+ """
32
+ class Article < CouchRest::Model::Base
33
+ end
34
+ """
35
+
36
+ And the following map definition:
37
+ """
38
+ class Map
39
+ include CouchView::Map
40
+ end
41
+ """
42
+
43
+ When I instantiate a new Map with Article:
44
+ """
45
+ Map.new(Article).map
46
+ """
47
+
48
+ Then I should receive the following map over the Article documents:
49
+ """
50
+ function(doc){
51
+ if (doc['couchrest-type'] == 'Article')
52
+ emit(doc._id, null)
53
+ }
54
+ """
55
+
56
+
57
+ Scenario: Defining a custom map
58
+
59
+ Given the the following custom map:
60
+ """
61
+ class ByLabel
62
+ include CouchView::Map
63
+
64
+ def map
65
+ "
66
+ function(doc){
67
+ if (#{conditions})
68
+ emit(doc.label, null)
69
+ }
70
+ "
71
+ end
72
+ end
73
+ """
74
+
75
+ When I instantiate a new ByLabel map:
76
+ """
77
+ ByLabel.new.map
78
+ """
79
+
80
+ Then I should receive a CouchDB javascript map over the labels:
81
+ """
82
+ function(doc){
83
+ if (true)
84
+ emit(doc.label, null)
85
+ }
86
+ """
87
+
88
+
89
+ Scenario: Decorating a map with conditions
90
+ Given the following map definition:
91
+ """
92
+ class ById
93
+ include CouchView::Map
94
+ end
95
+ """
96
+
97
+ And the following module:
98
+ """
99
+ module Published
100
+ def conditions
101
+ "#{super} && doc.published"
102
+ end
103
+ end
104
+ """
105
+
106
+ When I instantiate my map, extend it with my module, and call map:
107
+ """
108
+ ById.new.extend(Published).map
109
+ """
110
+
111
+ Then I should receive a CouchDB javascript map function over the published documents:
112
+ """
113
+ function(doc){
114
+ if (true && doc.published)
115
+ emit(doc._id, null)
116
+ }
117
+ """
@@ -0,0 +1,236 @@
1
+ Feature: CouchView::Proxy
2
+
3
+ A proxy object that lets you lazily build CouchDB map queries.
4
+
5
+
6
+ h2. Creating a map proxy
7
+
8
+ To create a new map proxy, simply instantiate `CouchView::Proxy` with the model to call on, and the map to call:
9
+
10
+ proxy = CouchView::Proxy.new Article, :by_id
11
+
12
+
13
+ h2. Adding CouchDB query parameters to the proxy
14
+
15
+ You can add CouchDB query parameters to your view proxy by calling their corresponding methods on the proxy.
16
+
17
+ For example, suppose we'd like to limit the results of our query to 10 documents:
18
+
19
+ proxy.limit(10)
20
+
21
+ This will return a new proxy that's just like the original proxy, except that it will also include a `limit=10` query string parameter when making a call to CouchDB.
22
+
23
+ If you'd rather update the existing proxy, instead of getting a new one, you can append a "!" onto the end:
24
+
25
+ proxy.limit!(10)
26
+
27
+
28
+ h2. Modifying the map to call
29
+
30
+ You can modify the map to call by calling a method that doesn't correspond to a CouchDB view query parameter.
31
+
32
+ For example, suppose our `Article` model responded to `by_label` and `by_label_published`:
33
+
34
+ proxy = CouchView::Proxy.new Article, :by_label
35
+
36
+ proxy._map #==> :by_label
37
+
38
+ proxy.published!
39
+
40
+ proxy._map #==> :by_label_published
41
+
42
+ If you call several methods on your proxy that don't correspond to CouchDB query parameters, then the proxy will alpha-sort them to generate the view name to call:
43
+
44
+ proxy = CouchView::Proxy.new Article, :by_label
45
+
46
+ proxy.published!.visible!.active!
47
+
48
+ proxy._map #==> :by_label_active_published_visible
49
+
50
+
51
+ h2. Triggering the call
52
+
53
+ You can trigger a call on a map proxy by calling either the `.each` method or the `get!` method:
54
+
55
+ proxy = CouchView::Proxy.new Article, :by_label
56
+
57
+ proxy.each {...} #==> executes "Article.by_label"
58
+ proxy.get! #==> executes "Article.by_label"
59
+
60
+
61
+
62
+ Scenario: Creating a map proxy
63
+
64
+ Given an Article model with a view "by_id":
65
+ """
66
+ class Article < CouchRest::Model::Base
67
+ view_by :id
68
+ end
69
+ """
70
+
71
+ When I instantiate a new CouchView::Proxy with "Article" and ":by_id":
72
+ """
73
+ @proxy = CouchView::Proxy.new Article, :by_id
74
+ """
75
+
76
+ Then the "_map" method should return ":by_id":
77
+ """
78
+ @proxy._map.should == :by_id
79
+ """
80
+
81
+ And the "_model" method should return "Article":
82
+ """
83
+ @proxy._model.should == Article
84
+ """
85
+
86
+ And the "_options" method should return the default map option of `:reduce => false`:
87
+ """
88
+ @proxy._options.should == {:reduce => false}
89
+ """
90
+
91
+
92
+ Scenario: Generating a new proxy by adding a query option
93
+
94
+ Given an Article model with a view "by_id":
95
+ """
96
+ class Article < CouchRest::Model::Base
97
+ view_by :id
98
+ end
99
+ """
100
+
101
+ When I instantiate a new CouchView::Proxy with "Article" and ":by_id":
102
+ """
103
+ @proxy = CouchView::Proxy.new Article, :by_id
104
+ """
105
+
106
+ And I limit the results to 10:
107
+ """
108
+ @new_proxy = @proxy.limit 10
109
+ """
110
+
111
+ Then @new_proxy should be a new object:
112
+ """
113
+ @new_proxy.object_id.should_not == @proxy.object_id
114
+ """
115
+
116
+ And the "_options" method on @new_proxy should return a limit of 10:
117
+ """
118
+ @new_proxy._options.should == {:reduce => false, :limit => 10}
119
+ """
120
+
121
+ And the "_options" method on the @proxy should return the default map option of `:reduce => false`:
122
+ """
123
+ @proxy._options.should == {:reduce => false}
124
+ """
125
+
126
+
127
+ Scenario: Destructively modifying the existring query by adding a query option with an exclamation point at the end
128
+
129
+ Given an Article model with a view "by_id":
130
+ """
131
+ class Article < CouchRest::Model::Base
132
+ view_by :id
133
+ end
134
+ """
135
+
136
+ When I instantiate a new CouchView::Proxy with "Article" and ":by_id":
137
+ """
138
+ @proxy = CouchView::Proxy.new Article, :by_id
139
+ """
140
+
141
+ And I destructively limit the results to 10:
142
+ """
143
+ @new_proxy = @proxy.limit! 10
144
+ """
145
+
146
+ Then @new_proxy should not be a new object:
147
+ """
148
+ @new_proxy.object_id.should == @proxy.object_id
149
+ """
150
+
151
+ And the "_options" method on the new proxy should return a limit of 10:
152
+ """
153
+ @proxy._options.should == {:reduce => false, :limit => 10}
154
+ """
155
+
156
+
157
+ Scenario: The map name should include alpha-sorted conditions
158
+
159
+ Given an Article model with a view "by_id":
160
+ """
161
+ class Article < CouchRest::Model::Base
162
+ view_by :id
163
+ end
164
+ """
165
+
166
+ When I instantiate a new CouchView::Proxy with "Article" and ":by_id":
167
+ """
168
+ @proxy = CouchView::Proxy.new Article, :by_id
169
+ """
170
+
171
+ And I call the published! method on the proxy:
172
+ """
173
+ @proxy.published!
174
+ """
175
+
176
+ Then the "_map" method should return ":by_id_published":
177
+ """
178
+ @proxy._map.should == :by_id_published
179
+ """
180
+
181
+ When I call the active! method on the proxy:
182
+ """
183
+ @proxy.active!
184
+ """
185
+
186
+ Then the "_map" method should return ":by_id_active_published":
187
+ """
188
+ @proxy._map.should == :by_id_active_published
189
+ """
190
+
191
+ When I call the visible! method on the proxy:
192
+ """
193
+ @proxy.visible!
194
+ """
195
+
196
+ Then the "_map" method should return ":by_id_active_published_visible":
197
+ """
198
+ @proxy._map.should == :by_id_active_published_visible
199
+ """
200
+
201
+ @db
202
+ Scenario: Executing the proxy with "get!" or "each!"
203
+
204
+ Given an Article model with a view "by_id":
205
+ """
206
+ class Article < CouchRest::Model::Base
207
+ view_by :id
208
+ end
209
+ """
210
+
211
+ When I instantiate a new CouchView::Proxy with "Article" and ":by_id":
212
+ """
213
+ @proxy = CouchView::Proxy.new Article, :by_id
214
+ """
215
+
216
+ And I call the "get!" method on the proxy
217
+ """
218
+ @call = proc { @proxy.get! }
219
+ """
220
+
221
+ Then the proxy should call the ":by_id" method on the Article model:
222
+ """
223
+ Article.should_receive(:by_id)
224
+ @call.call
225
+ """
226
+
227
+ When I call the "each" method on the proxy
228
+ """
229
+ Article.should_receive(:by_id).and_return ["result"]
230
+ @call = proc { @proxy.each {|a| a.should == "result"} }
231
+ """
232
+
233
+ Then the proxy should call the ":by_id" method on the Article model:
234
+ """
235
+ @call.call
236
+ """