couch_view 0.0.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.
@@ -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
+ """