openlogic-couchrest_model 1.0.0
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 +11 -0
- data/.rspec +4 -0
- data/Gemfile +4 -0
- data/LICENSE +176 -0
- data/README.md +137 -0
- data/Rakefile +38 -0
- data/THANKS.md +21 -0
- data/VERSION +1 -0
- data/benchmarks/dirty.rb +118 -0
- data/couchrest_model.gemspec +36 -0
- data/history.md +309 -0
- data/init.rb +1 -0
- data/lib/couchrest/model.rb +10 -0
- data/lib/couchrest/model/associations.rb +231 -0
- data/lib/couchrest/model/base.rb +129 -0
- data/lib/couchrest/model/callbacks.rb +28 -0
- data/lib/couchrest/model/casted_array.rb +83 -0
- data/lib/couchrest/model/casted_by.rb +33 -0
- data/lib/couchrest/model/casted_hash.rb +84 -0
- data/lib/couchrest/model/class_proxy.rb +135 -0
- data/lib/couchrest/model/collection.rb +273 -0
- data/lib/couchrest/model/configuration.rb +67 -0
- data/lib/couchrest/model/connection.rb +70 -0
- data/lib/couchrest/model/core_extensions/hash.rb +9 -0
- data/lib/couchrest/model/core_extensions/time_parsing.rb +66 -0
- data/lib/couchrest/model/design_doc.rb +128 -0
- data/lib/couchrest/model/designs.rb +91 -0
- data/lib/couchrest/model/designs/view.rb +513 -0
- data/lib/couchrest/model/dirty.rb +39 -0
- data/lib/couchrest/model/document_queries.rb +99 -0
- data/lib/couchrest/model/embeddable.rb +78 -0
- data/lib/couchrest/model/errors.rb +25 -0
- data/lib/couchrest/model/extended_attachments.rb +83 -0
- data/lib/couchrest/model/persistence.rb +178 -0
- data/lib/couchrest/model/properties.rb +228 -0
- data/lib/couchrest/model/property.rb +114 -0
- data/lib/couchrest/model/property_protection.rb +71 -0
- data/lib/couchrest/model/proxyable.rb +183 -0
- data/lib/couchrest/model/support/couchrest_database.rb +13 -0
- data/lib/couchrest/model/support/couchrest_design.rb +33 -0
- data/lib/couchrest/model/typecast.rb +154 -0
- data/lib/couchrest/model/validations.rb +80 -0
- data/lib/couchrest/model/validations/casted_model.rb +16 -0
- data/lib/couchrest/model/validations/locale/en.yml +5 -0
- data/lib/couchrest/model/validations/uniqueness.rb +69 -0
- data/lib/couchrest/model/views.rb +151 -0
- data/lib/couchrest/railtie.rb +24 -0
- data/lib/couchrest_model.rb +66 -0
- data/lib/rails/generators/couchrest_model.rb +16 -0
- data/lib/rails/generators/couchrest_model/config/config_generator.rb +18 -0
- data/lib/rails/generators/couchrest_model/config/templates/couchdb.yml +21 -0
- data/lib/rails/generators/couchrest_model/model/model_generator.rb +27 -0
- data/lib/rails/generators/couchrest_model/model/templates/model.rb +2 -0
- data/spec/.gitignore +1 -0
- data/spec/fixtures/attachments/README +3 -0
- data/spec/fixtures/attachments/couchdb.png +0 -0
- data/spec/fixtures/attachments/test.html +11 -0
- data/spec/fixtures/config/couchdb.yml +10 -0
- data/spec/fixtures/models/article.rb +36 -0
- data/spec/fixtures/models/base.rb +164 -0
- data/spec/fixtures/models/card.rb +19 -0
- data/spec/fixtures/models/cat.rb +23 -0
- data/spec/fixtures/models/client.rb +6 -0
- data/spec/fixtures/models/course.rb +27 -0
- data/spec/fixtures/models/event.rb +8 -0
- data/spec/fixtures/models/invoice.rb +14 -0
- data/spec/fixtures/models/key_chain.rb +5 -0
- data/spec/fixtures/models/membership.rb +4 -0
- data/spec/fixtures/models/person.rb +11 -0
- data/spec/fixtures/models/project.rb +6 -0
- data/spec/fixtures/models/question.rb +7 -0
- data/spec/fixtures/models/sale_entry.rb +9 -0
- data/spec/fixtures/models/sale_invoice.rb +14 -0
- data/spec/fixtures/models/service.rb +10 -0
- data/spec/fixtures/models/user.rb +22 -0
- data/spec/fixtures/views/lib.js +3 -0
- data/spec/fixtures/views/test_view/lib.js +3 -0
- data/spec/fixtures/views/test_view/only-map.js +4 -0
- data/spec/fixtures/views/test_view/test-map.js +3 -0
- data/spec/fixtures/views/test_view/test-reduce.js +3 -0
- data/spec/functional/validations_spec.rb +8 -0
- data/spec/spec_helper.rb +60 -0
- data/spec/unit/active_model_lint_spec.rb +30 -0
- data/spec/unit/assocations_spec.rb +242 -0
- data/spec/unit/attachment_spec.rb +176 -0
- data/spec/unit/base_spec.rb +537 -0
- data/spec/unit/casted_spec.rb +72 -0
- data/spec/unit/class_proxy_spec.rb +167 -0
- data/spec/unit/collection_spec.rb +86 -0
- data/spec/unit/configuration_spec.rb +77 -0
- data/spec/unit/connection_spec.rb +148 -0
- data/spec/unit/core_extensions/time_parsing.rb +77 -0
- data/spec/unit/design_doc_spec.rb +241 -0
- data/spec/unit/designs/view_spec.rb +831 -0
- data/spec/unit/designs_spec.rb +134 -0
- data/spec/unit/dirty_spec.rb +436 -0
- data/spec/unit/embeddable_spec.rb +498 -0
- data/spec/unit/inherited_spec.rb +33 -0
- data/spec/unit/persistence_spec.rb +481 -0
- data/spec/unit/property_protection_spec.rb +192 -0
- data/spec/unit/property_spec.rb +481 -0
- data/spec/unit/proxyable_spec.rb +376 -0
- data/spec/unit/subclass_spec.rb +85 -0
- data/spec/unit/typecast_spec.rb +521 -0
- data/spec/unit/validations_spec.rb +140 -0
- data/spec/unit/view_spec.rb +367 -0
- metadata +301 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require File.expand_path('../../../spec_helper', __FILE__)
|
|
3
|
+
|
|
4
|
+
describe "Time Parsing core extension" do
|
|
5
|
+
|
|
6
|
+
describe "Time" do
|
|
7
|
+
|
|
8
|
+
it "should respond to .parse_iso8601" do
|
|
9
|
+
Time.respond_to?("parse_iso8601").should be_true
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe ".parse_iso8601" do
|
|
13
|
+
|
|
14
|
+
describe "parsing" do
|
|
15
|
+
|
|
16
|
+
before :each do
|
|
17
|
+
# Time.parse should not be called for these tests!
|
|
18
|
+
Time.stub!(:parse).and_return(nil)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "should parse JSON time" do
|
|
22
|
+
txt = "2011-04-01T19:05:30Z"
|
|
23
|
+
Time.parse_iso8601(txt).should eql(Time.utc(2011, 04, 01, 19, 05, 30))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "should parse JSON time as UTC without Z" do
|
|
27
|
+
txt = "2011-04-01T19:05:30"
|
|
28
|
+
Time.parse_iso8601(txt).should eql(Time.utc(2011, 04, 01, 19, 05, 30))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "should parse basic time as UTC" do
|
|
32
|
+
txt = "2011-04-01 19:05:30"
|
|
33
|
+
Time.parse_iso8601(txt).should eql(Time.utc(2011, 04, 01, 19, 05, 30))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "should parse JSON time with zone" do
|
|
37
|
+
txt = "2011-04-01T19:05:30 +02:00"
|
|
38
|
+
Time.parse_iso8601(txt).should eql(Time.new(2011, 04, 01, 19, 05, 30, "+02:00"))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "should parse JSON time with zone 2" do
|
|
42
|
+
txt = "2011-04-01T19:05:30-0200"
|
|
43
|
+
Time.parse_iso8601(txt).should eql(Time.new(2011, 04, 01, 19, 05, 30, "-02:00"))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "should parse dodgy time with zone" do
|
|
47
|
+
txt = "2011-04-01 19:05:30 +0200"
|
|
48
|
+
Time.parse_iso8601(txt).should eql(Time.new(2011, 04, 01, 19, 05, 30, "+02:00"))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "should parse dodgy time with zone 2" do
|
|
52
|
+
txt = "2011-04-01 19:05:30+0230"
|
|
53
|
+
Time.parse_iso8601(txt).should eql(Time.new(2011, 04, 01, 19, 05, 30, "+02:30"))
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "should parse dodgy time with zone 3" do
|
|
57
|
+
txt = "2011-04-01 19:05:30 0230"
|
|
58
|
+
Time.parse_iso8601(txt).should eql(Time.new(2011, 04, 01, 19, 05, 30, "+02:30"))
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
describe "resorting back to normal parse" do
|
|
64
|
+
before :each do
|
|
65
|
+
Time.should_receive(:parse)
|
|
66
|
+
end
|
|
67
|
+
it "should work with weird time" do
|
|
68
|
+
txt = "16/07/1981 05:04:00"
|
|
69
|
+
Time.parse_iso8601(txt)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
end
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require 'spec_helper'
|
|
3
|
+
|
|
4
|
+
describe CouchRest::Model::DesignDoc do
|
|
5
|
+
|
|
6
|
+
before :all do
|
|
7
|
+
reset_test_db!
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
describe "CouchRest Extension" do
|
|
11
|
+
|
|
12
|
+
it "should have created a checksum! method" do
|
|
13
|
+
::CouchRest::Design.new.should respond_to(:checksum!)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "should calculate a consistent checksum for model" do
|
|
17
|
+
WithTemplateAndUniqueID.design_doc.checksum!.should eql('caa2b4c27abb82b4e37421de76d96ffc')
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "should calculate checksum for complex model" do
|
|
21
|
+
Article.design_doc.checksum!.should eql('70dff8caea143bf40fad09adf0701104')
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "should cache the generated checksum value" do
|
|
25
|
+
Article.design_doc.checksum!
|
|
26
|
+
Article.design_doc['couchrest-hash'].should_not be_blank
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
describe "class methods" do
|
|
31
|
+
|
|
32
|
+
describe ".design_doc" do
|
|
33
|
+
it "should provide Design document" do
|
|
34
|
+
Article.design_doc.should be_a(::CouchRest::Design)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
describe ".design_doc_id" do
|
|
39
|
+
it "should provide a reasonable id" do
|
|
40
|
+
Article.design_doc_id.should eql("_design/Article")
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
describe ".design_doc_slug" do
|
|
45
|
+
it "should provide slug part of design doc" do
|
|
46
|
+
Article.design_doc_slug.should eql('Article')
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
describe ".design_doc_uri" do
|
|
51
|
+
it "should provide complete url" do
|
|
52
|
+
Article.design_doc_uri.should eql("#{COUCHHOST}/#{TESTDB}/_design/Article")
|
|
53
|
+
end
|
|
54
|
+
it "should provide complete url for new DB" do
|
|
55
|
+
db = mock("Database")
|
|
56
|
+
db.should_receive(:root).and_return('db')
|
|
57
|
+
Article.design_doc_uri(db).should eql("db/_design/Article")
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
describe ".stored_design_doc" do
|
|
62
|
+
it "should load a stored design from the database" do
|
|
63
|
+
Article.by_date
|
|
64
|
+
Article.stored_design_doc['_rev'].should_not be_blank
|
|
65
|
+
end
|
|
66
|
+
it "should return nil if not already stored" do
|
|
67
|
+
WithDefaultValues.stored_design_doc.should be_nil
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
describe ".save_design_doc" do
|
|
72
|
+
it "should call up the design updater" do
|
|
73
|
+
Article.should_receive(:update_design_doc).with('db', false)
|
|
74
|
+
Article.save_design_doc('db')
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
describe ".save_design_doc!" do
|
|
79
|
+
it "should call save_design_doc with force" do
|
|
80
|
+
Article.should_receive(:save_design_doc).with('db', true)
|
|
81
|
+
Article.save_design_doc!('db')
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
describe "basics" do
|
|
88
|
+
|
|
89
|
+
before :all do
|
|
90
|
+
reset_test_db!
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it "should have been instantiated with views" do
|
|
94
|
+
d = Article.design_doc
|
|
95
|
+
d['views']['all']['map'].should include('Article')
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it "should not have been saved yet" do
|
|
99
|
+
lambda { Article.database.get(Article.design_doc.id) }.should raise_error(RestClient::ResourceNotFound)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
describe "after requesting a view" do
|
|
103
|
+
before :each do
|
|
104
|
+
Article.all
|
|
105
|
+
end
|
|
106
|
+
it "should have saved the design doc after view request" do
|
|
107
|
+
Article.database.get(Article.design_doc.id).should_not be_nil
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
describe "model with simple views" do
|
|
112
|
+
before(:all) do
|
|
113
|
+
Article.all.map{|a| a.destroy(true)}
|
|
114
|
+
Article.database.bulk_delete
|
|
115
|
+
written_at = Time.now - 24 * 3600 * 7
|
|
116
|
+
@titles = ["this and that", "also interesting", "more fun", "some junk"]
|
|
117
|
+
@titles.each do |title|
|
|
118
|
+
a = Article.new(:title => title)
|
|
119
|
+
a.date = written_at
|
|
120
|
+
a.save
|
|
121
|
+
written_at += 24 * 3600
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it "will send request for the saved design doc on view request" do
|
|
126
|
+
reset_test_db!
|
|
127
|
+
Article.should_receive(:stored_design_doc).and_return(nil)
|
|
128
|
+
Article.by_date
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it "should have generated a design doc" do
|
|
132
|
+
Article.design_doc["views"]["by_date"].should_not be_nil
|
|
133
|
+
end
|
|
134
|
+
it "should save the design doc when view requested" do
|
|
135
|
+
Article.by_date
|
|
136
|
+
doc = Article.database.get Article.design_doc.id
|
|
137
|
+
doc['views']['by_date'].should_not be_nil
|
|
138
|
+
end
|
|
139
|
+
it "should save design doc if a view changed" do
|
|
140
|
+
Article.by_date
|
|
141
|
+
orig = Article.stored_design_doc
|
|
142
|
+
design = Article.design_doc
|
|
143
|
+
view = design['views']['by_date']['map']
|
|
144
|
+
design['views']['by_date']['map'] = view + ' ' # little bit of white space
|
|
145
|
+
Article.by_date
|
|
146
|
+
Article.stored_design_doc['_rev'].should_not eql(orig['_rev'])
|
|
147
|
+
orig['views']['by_date']['map'].should_not eql(Article.design_doc['views']['by_date']['map'])
|
|
148
|
+
end
|
|
149
|
+
it "should not save design doc if not changed" do
|
|
150
|
+
Article.by_date
|
|
151
|
+
orig = Article.stored_design_doc['_rev']
|
|
152
|
+
Article.by_date
|
|
153
|
+
Article.stored_design_doc['_rev'].should eql(orig)
|
|
154
|
+
end
|
|
155
|
+
it "should recreate the design doc if database deleted" do
|
|
156
|
+
Article.database.recreate!
|
|
157
|
+
lambda { Article.by_date }.should_not raise_error(RestClient::ResourceNotFound)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
describe "when auto_update_design_doc false" do
|
|
162
|
+
# We really do need a new class for each of example. If we try
|
|
163
|
+
# to use the same class the examples interact with each in ways
|
|
164
|
+
# that can hide failures because the design document gets cached
|
|
165
|
+
# at the class level.
|
|
166
|
+
let(:model_class) {
|
|
167
|
+
class_name = "#{example.metadata[:full_description].gsub(/\s+/,'_').camelize}Model"
|
|
168
|
+
doc = CouchRest::Document.new("_id" => "_design/#{class_name}")
|
|
169
|
+
doc["language"] = "javascript"
|
|
170
|
+
doc["views"] = {"all" => {"map" =>
|
|
171
|
+
"function(doc) {
|
|
172
|
+
if (doc['type'] == 'Article') {
|
|
173
|
+
emit(doc['_id'],1);
|
|
174
|
+
}
|
|
175
|
+
}"},
|
|
176
|
+
"by_name" => {"map" =>
|
|
177
|
+
"function(doc) {
|
|
178
|
+
if ((doc['type'] == '#{class_name}') && (doc['name'] != null)) {
|
|
179
|
+
emit(doc['name'], null);
|
|
180
|
+
}",
|
|
181
|
+
"reduce" =>
|
|
182
|
+
"function(keys, values, rereduce) {
|
|
183
|
+
return sum(values);
|
|
184
|
+
}"}}
|
|
185
|
+
|
|
186
|
+
DB.save_doc doc
|
|
187
|
+
|
|
188
|
+
eval <<-KLASS
|
|
189
|
+
class ::#{class_name} < CouchRest::Model::Base
|
|
190
|
+
use_database DB
|
|
191
|
+
self.auto_update_design_doc = false
|
|
192
|
+
design do
|
|
193
|
+
view :by_name
|
|
194
|
+
end
|
|
195
|
+
property :name, String
|
|
196
|
+
end
|
|
197
|
+
KLASS
|
|
198
|
+
|
|
199
|
+
class_name.constantize
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
it "will not update stored design doc if view changed" do
|
|
203
|
+
model_class.by_name
|
|
204
|
+
orig = model_class.stored_design_doc
|
|
205
|
+
design = model_class.design_doc
|
|
206
|
+
view = design['views']['by_name']['map']
|
|
207
|
+
design['views']['by_name']['map'] = view + ' '
|
|
208
|
+
model_class.by_name
|
|
209
|
+
model_class.stored_design_doc['_rev'].should eql(orig['_rev'])
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
it "will update stored design if forced" do
|
|
213
|
+
model_class.by_name
|
|
214
|
+
orig = model_class.stored_design_doc
|
|
215
|
+
design = model_class.design_doc
|
|
216
|
+
view = design['views']['by_name']['map']
|
|
217
|
+
design['views']['by_name']['map'] = view + ' '
|
|
218
|
+
model_class.save_design_doc!
|
|
219
|
+
model_class.stored_design_doc['_rev'].should_not eql(orig['_rev'])
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
it "is able to use predefined views" do
|
|
223
|
+
model_class.by_name(key: "special").all
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
describe "lazily refreshing the design document" do
|
|
229
|
+
before(:all) do
|
|
230
|
+
@db = reset_test_db!
|
|
231
|
+
WithTemplateAndUniqueID.new('slug' => '1').save
|
|
232
|
+
end
|
|
233
|
+
it "should not save the design doc twice" do
|
|
234
|
+
WithTemplateAndUniqueID.all
|
|
235
|
+
rev = WithTemplateAndUniqueID.design_doc['_rev']
|
|
236
|
+
WithTemplateAndUniqueID.design_doc['_rev'].should eql(rev)
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
end
|
|
@@ -0,0 +1,831 @@
|
|
|
1
|
+
require File.expand_path("../../../spec_helper", __FILE__)
|
|
2
|
+
|
|
3
|
+
class DesignViewModel < CouchRest::Model::Base
|
|
4
|
+
use_database DB
|
|
5
|
+
property :name
|
|
6
|
+
property :title
|
|
7
|
+
|
|
8
|
+
design do
|
|
9
|
+
view :by_name
|
|
10
|
+
view :by_just_name, :map => "function(doc) { emit(doc['name'], null); }"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
describe "Design View" do
|
|
15
|
+
|
|
16
|
+
describe "(unit tests)" do
|
|
17
|
+
|
|
18
|
+
before :each do
|
|
19
|
+
@klass = CouchRest::Model::Designs::View
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe ".new" do
|
|
23
|
+
|
|
24
|
+
describe "with invalid parent model" do
|
|
25
|
+
it "should burn" do
|
|
26
|
+
lambda { @klass.new(String) }.should raise_exception
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
describe "with CouchRest Model" do
|
|
31
|
+
|
|
32
|
+
it "should setup attributes" do
|
|
33
|
+
@obj = @klass.new(DesignViewModel, {}, 'test_view')
|
|
34
|
+
@obj.model.should eql(DesignViewModel)
|
|
35
|
+
@obj.name.should eql('test_view')
|
|
36
|
+
@obj.query.should be_empty
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "should complain if there is no name" do
|
|
40
|
+
lambda { @klass.new(DesignViewModel, {}, nil) }.should raise_error
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
describe "with previous view instance" do
|
|
46
|
+
|
|
47
|
+
before :each do
|
|
48
|
+
first = @klass.new(DesignViewModel, {}, 'test_view')
|
|
49
|
+
@obj = @klass.new(first, {:foo => :bar})
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "should copy attributes" do
|
|
53
|
+
@obj.model.should eql(DesignViewModel)
|
|
54
|
+
@obj.name.should eql('test_view')
|
|
55
|
+
@obj.query.should eql({:foo => :bar})
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
describe ".create" do
|
|
63
|
+
|
|
64
|
+
before :each do
|
|
65
|
+
@design_doc = {}
|
|
66
|
+
DesignViewModel.stub!(:design_doc).and_return(@design_doc)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it "should add a basic view" do
|
|
70
|
+
@klass.create(DesignViewModel, 'test_view', :map => 'foo')
|
|
71
|
+
@design_doc['views']['test_view'].should_not be_nil
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it "should auto generate mapping from name" do
|
|
75
|
+
lambda { @klass.create(DesignViewModel, 'by_title') }.should_not raise_error
|
|
76
|
+
str = @design_doc['views']['by_title']['map']
|
|
77
|
+
str.should include("((doc['#{DesignViewModel.model_type_key}'] == 'DesignViewModel') && (doc['title'] != null))")
|
|
78
|
+
str.should include("emit(doc['title'], 1);")
|
|
79
|
+
str = @design_doc['views']['by_title']['reduce']
|
|
80
|
+
str.should include("return sum(values);")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "should auto generate mapping from name with and" do
|
|
84
|
+
@klass.create(DesignViewModel, 'by_title_and_name')
|
|
85
|
+
str = @design_doc['views']['by_title_and_name']['map']
|
|
86
|
+
str.should include("(doc['title'] != null) && (doc['name'] != null)")
|
|
87
|
+
str.should include("emit([doc['title'], doc['name']], 1);")
|
|
88
|
+
str = @design_doc['views']['by_title_and_name']['reduce']
|
|
89
|
+
str.should include("return sum(values);")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
describe "instance methods" do
|
|
95
|
+
|
|
96
|
+
before :each do
|
|
97
|
+
@obj = @klass.new(DesignViewModel, {}, 'test_view')
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
describe "#rows" do
|
|
101
|
+
it "should execute query" do
|
|
102
|
+
@obj.should_receive(:execute).and_return(true)
|
|
103
|
+
@obj.should_receive(:result).twice.and_return({'rows' => []})
|
|
104
|
+
@obj.rows.should be_empty
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it "should wrap rows in ViewRow class" do
|
|
108
|
+
@obj.should_receive(:execute).and_return(true)
|
|
109
|
+
@obj.should_receive(:result).twice.and_return({'rows' => [{:foo => :bar}]})
|
|
110
|
+
CouchRest::Model::Designs::ViewRow.should_receive(:new).with({:foo => :bar}, @obj.model)
|
|
111
|
+
@obj.rows
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
describe "#all" do
|
|
116
|
+
it "should ensure docs included and call docs" do
|
|
117
|
+
@obj.should_receive(:include_docs!)
|
|
118
|
+
@obj.should_receive(:docs)
|
|
119
|
+
@obj.all
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
describe "#docs" do
|
|
124
|
+
it "should provide docs from rows" do
|
|
125
|
+
@obj.should_receive(:rows).and_return([])
|
|
126
|
+
@obj.docs
|
|
127
|
+
end
|
|
128
|
+
it "should cache the results" do
|
|
129
|
+
@obj.should_receive(:rows).once.and_return([])
|
|
130
|
+
@obj.docs
|
|
131
|
+
@obj.docs
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
describe "#first" do
|
|
136
|
+
it "should provide the first result of loaded query" do
|
|
137
|
+
@obj.should_receive(:result).and_return(true)
|
|
138
|
+
@obj.should_receive(:all).and_return([:foo])
|
|
139
|
+
@obj.first.should eql(:foo)
|
|
140
|
+
end
|
|
141
|
+
it "should perform a query if no results cached" do
|
|
142
|
+
view = mock('SubView')
|
|
143
|
+
@obj.should_receive(:result).and_return(nil)
|
|
144
|
+
@obj.should_receive(:limit).with(1).and_return(view)
|
|
145
|
+
view.should_receive(:all).and_return([:foo])
|
|
146
|
+
@obj.first.should eql(:foo)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
describe "#last" do
|
|
151
|
+
it "should provide the last result of loaded query" do
|
|
152
|
+
@obj.should_receive(:result).and_return(true)
|
|
153
|
+
@obj.should_receive(:all).and_return([:foo, :bar])
|
|
154
|
+
@obj.first.should eql(:foo)
|
|
155
|
+
end
|
|
156
|
+
it "should perform a query if no results cached" do
|
|
157
|
+
view = mock('SubView')
|
|
158
|
+
@obj.should_receive(:result).and_return(nil)
|
|
159
|
+
@obj.should_receive(:limit).with(1).and_return(view)
|
|
160
|
+
view.should_receive(:descending).and_return(view)
|
|
161
|
+
view.should_receive(:all).and_return([:foo, :bar])
|
|
162
|
+
@obj.last.should eql(:bar)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
describe "#length" do
|
|
167
|
+
it "should provide a length from the docs array" do
|
|
168
|
+
@obj.should_receive(:docs).and_return([1, 2, 3])
|
|
169
|
+
@obj.length.should eql(3)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
describe "#count" do
|
|
174
|
+
it "should raise an error if view prepared for group" do
|
|
175
|
+
@obj.should_receive(:query).and_return({:group => true})
|
|
176
|
+
lambda { @obj.count }.should raise_error(/group/)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
it "should return first row value if reduce possible" do
|
|
180
|
+
view = mock("SubView")
|
|
181
|
+
row = mock("Row")
|
|
182
|
+
@obj.should_receive(:can_reduce?).and_return(true)
|
|
183
|
+
@obj.should_receive(:reduce).and_return(view)
|
|
184
|
+
view.should_receive(:skip).with(0).and_return(view)
|
|
185
|
+
view.should_receive(:limit).with(1).and_return(view)
|
|
186
|
+
view.should_receive(:rows).and_return([row])
|
|
187
|
+
row.should_receive(:value).and_return(2)
|
|
188
|
+
@obj.count.should eql(2)
|
|
189
|
+
end
|
|
190
|
+
it "should return 0 if no rows and reduce possible" do
|
|
191
|
+
view = mock("SubView")
|
|
192
|
+
@obj.should_receive(:can_reduce?).and_return(true)
|
|
193
|
+
@obj.should_receive(:reduce).and_return(view)
|
|
194
|
+
view.should_receive(:skip).with(0).and_return(view)
|
|
195
|
+
view.should_receive(:limit).with(1).and_return(view)
|
|
196
|
+
view.should_receive(:rows).and_return([])
|
|
197
|
+
@obj.count.should eql(0)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
it "should perform limit request for total_rows" do
|
|
201
|
+
view = mock("SubView")
|
|
202
|
+
@obj.should_receive(:limit).with(0).and_return(view)
|
|
203
|
+
view.should_receive(:total_rows).and_return(4)
|
|
204
|
+
@obj.should_receive(:can_reduce?).and_return(false)
|
|
205
|
+
@obj.count.should eql(4)
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
describe "#empty?" do
|
|
210
|
+
it "should check the #all method for any results" do
|
|
211
|
+
all = mock("All")
|
|
212
|
+
all.should_receive(:empty?).and_return('win')
|
|
213
|
+
@obj.should_receive(:all).and_return(all)
|
|
214
|
+
@obj.empty?.should eql('win')
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
describe "#each" do
|
|
219
|
+
it "should call each method on all" do
|
|
220
|
+
@obj.should_receive(:all).and_return([])
|
|
221
|
+
@obj.each
|
|
222
|
+
end
|
|
223
|
+
it "should call each and pass block" do
|
|
224
|
+
set = [:foo, :bar]
|
|
225
|
+
@obj.should_receive(:all).and_return(set)
|
|
226
|
+
result = []
|
|
227
|
+
@obj.each do |s|
|
|
228
|
+
result << s
|
|
229
|
+
end
|
|
230
|
+
result.should eql(set)
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
describe "#offset" do
|
|
235
|
+
it "should excute" do
|
|
236
|
+
@obj.should_receive(:execute).and_return({'offset' => 3})
|
|
237
|
+
@obj.offset.should eql(3)
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
describe "#total_rows" do
|
|
242
|
+
it "should excute" do
|
|
243
|
+
@obj.should_receive(:execute).and_return({'total_rows' => 3})
|
|
244
|
+
@obj.total_rows.should eql(3)
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
describe "#values" do
|
|
249
|
+
it "should request each row and provide value" do
|
|
250
|
+
row = mock("Row")
|
|
251
|
+
row.should_receive(:value).twice.and_return('foo')
|
|
252
|
+
@obj.should_receive(:rows).and_return([row, row])
|
|
253
|
+
@obj.values.should eql(['foo', 'foo'])
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
describe "#[]" do
|
|
258
|
+
it "should execute and provide requested field" do
|
|
259
|
+
@obj.should_receive(:execute).and_return({'total_rows' => 2})
|
|
260
|
+
@obj['total_rows'].should eql(2)
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
describe "#info" do
|
|
265
|
+
it "should raise error" do
|
|
266
|
+
lambda { @obj.info }.should raise_error
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
describe "#database" do
|
|
271
|
+
it "should update query with value" do
|
|
272
|
+
@obj.should_receive(:update_query).with({:database => 'foo'})
|
|
273
|
+
@obj.database('foo')
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
describe "#key" do
|
|
278
|
+
it "should update query with value" do
|
|
279
|
+
@obj.should_receive(:update_query).with({:key => 'foo'})
|
|
280
|
+
@obj.key('foo')
|
|
281
|
+
end
|
|
282
|
+
it "should raise error if startkey set" do
|
|
283
|
+
@obj.query[:startkey] = 'bar'
|
|
284
|
+
lambda { @obj.key('foo') }.should raise_error
|
|
285
|
+
end
|
|
286
|
+
it "should raise error if endkey set" do
|
|
287
|
+
@obj.query[:endkey] = 'bar'
|
|
288
|
+
lambda { @obj.key('foo') }.should raise_error
|
|
289
|
+
end
|
|
290
|
+
it "should raise error if both startkey and endkey set" do
|
|
291
|
+
@obj.query[:startkey] = 'bar'
|
|
292
|
+
@obj.query[:endkey] = 'bar'
|
|
293
|
+
lambda { @obj.key('foo') }.should raise_error
|
|
294
|
+
end
|
|
295
|
+
it "should raise error if keys set" do
|
|
296
|
+
@obj.query[:keys] = 'bar'
|
|
297
|
+
lambda { @obj.key('foo') }.should raise_error
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
describe "#startkey" do
|
|
302
|
+
it "should update query with value" do
|
|
303
|
+
@obj.should_receive(:update_query).with({:startkey => 'foo'})
|
|
304
|
+
@obj.startkey('foo')
|
|
305
|
+
end
|
|
306
|
+
it "should raise and error if key set" do
|
|
307
|
+
@obj.query[:key] = 'bar'
|
|
308
|
+
lambda { @obj.startkey('foo') }.should raise_error(/View#startkey/)
|
|
309
|
+
end
|
|
310
|
+
it "should raise and error if keys set" do
|
|
311
|
+
@obj.query[:keys] = 'bar'
|
|
312
|
+
lambda { @obj.startkey('foo') }.should raise_error(/View#startkey/)
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
describe "#startkey_doc" do
|
|
317
|
+
it "should update query with value" do
|
|
318
|
+
@obj.should_receive(:update_query).with({:startkey_docid => 'foo'})
|
|
319
|
+
@obj.startkey_doc('foo')
|
|
320
|
+
end
|
|
321
|
+
it "should update query with object id if available" do
|
|
322
|
+
doc = mock("Document")
|
|
323
|
+
doc.should_receive(:id).and_return(44)
|
|
324
|
+
@obj.should_receive(:update_query).with({:startkey_docid => 44})
|
|
325
|
+
@obj.startkey_doc(doc)
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
describe "#endkey" do
|
|
330
|
+
it "should update query with value" do
|
|
331
|
+
@obj.should_receive(:update_query).with({:endkey => 'foo'})
|
|
332
|
+
@obj.endkey('foo')
|
|
333
|
+
end
|
|
334
|
+
it "should raise and error if key set" do
|
|
335
|
+
@obj.query[:key] = 'bar'
|
|
336
|
+
lambda { @obj.endkey('foo') }.should raise_error(/View#endkey/)
|
|
337
|
+
end
|
|
338
|
+
it "should raise and error if keys set" do
|
|
339
|
+
@obj.query[:keys] = 'bar'
|
|
340
|
+
lambda { @obj.endkey('foo') }.should raise_error(/View#endkey/)
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
describe "#endkey_doc" do
|
|
345
|
+
it "should update query with value" do
|
|
346
|
+
@obj.should_receive(:update_query).with({:endkey_docid => 'foo'})
|
|
347
|
+
@obj.endkey_doc('foo')
|
|
348
|
+
end
|
|
349
|
+
it "should update query with object id if available" do
|
|
350
|
+
doc = mock("Document")
|
|
351
|
+
doc.should_receive(:id).and_return(44)
|
|
352
|
+
@obj.should_receive(:update_query).with({:endkey_docid => 44})
|
|
353
|
+
@obj.endkey_doc(doc)
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
describe "#keys" do
|
|
358
|
+
it "should update the query" do
|
|
359
|
+
@obj.should_receive(:update_query).with({:keys => ['foo', 'bar']})
|
|
360
|
+
@obj.keys(['foo', 'bar'])
|
|
361
|
+
end
|
|
362
|
+
it "should raise and error if key set" do
|
|
363
|
+
@obj.query[:key] = 'bar'
|
|
364
|
+
lambda { @obj.keys('foo') }.should raise_error(/View#keys/)
|
|
365
|
+
end
|
|
366
|
+
it "should raise and error if startkey or endkey set" do
|
|
367
|
+
@obj.query[:startkey] = 'bar'
|
|
368
|
+
lambda { @obj.keys('foo') }.should raise_error(/View#keys/)
|
|
369
|
+
@obj.query.delete(:startkey)
|
|
370
|
+
@obj.query[:endkey] = 'bar'
|
|
371
|
+
lambda { @obj.keys('foo') }.should raise_error(/View#keys/)
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
describe "#keys (without parameters)" do
|
|
376
|
+
it "should request each row and provide key value" do
|
|
377
|
+
row = mock("Row")
|
|
378
|
+
row.should_receive(:key).twice.and_return('foo')
|
|
379
|
+
@obj.should_receive(:rows).and_return([row, row])
|
|
380
|
+
@obj.keys.should eql(['foo', 'foo'])
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
describe "#descending" do
|
|
385
|
+
it "should update query" do
|
|
386
|
+
@obj.should_receive(:update_query).with({:descending => true})
|
|
387
|
+
@obj.descending
|
|
388
|
+
end
|
|
389
|
+
it "should reverse start and end keys if given" do
|
|
390
|
+
@obj = @obj.startkey('a').endkey('z')
|
|
391
|
+
@obj = @obj.descending
|
|
392
|
+
@obj.query[:endkey].should eql('a')
|
|
393
|
+
@obj.query[:startkey].should eql('z')
|
|
394
|
+
end
|
|
395
|
+
it "should reverse even if start or end nil" do
|
|
396
|
+
@obj = @obj.startkey('a')
|
|
397
|
+
@obj = @obj.descending
|
|
398
|
+
@obj.query[:endkey].should eql('a')
|
|
399
|
+
@obj.query[:startkey].should be_nil
|
|
400
|
+
end
|
|
401
|
+
it "should reverse start_doc and end_doc keys if given" do
|
|
402
|
+
@obj = @obj.startkey_doc('a').endkey_doc('z')
|
|
403
|
+
@obj = @obj.descending
|
|
404
|
+
@obj.query[:endkey_docid].should eql('a')
|
|
405
|
+
@obj.query[:startkey_docid].should eql('z')
|
|
406
|
+
end
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
describe "#limit" do
|
|
410
|
+
it "should update query with value" do
|
|
411
|
+
@obj.should_receive(:update_query).with({:limit => 3})
|
|
412
|
+
@obj.limit(3)
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
describe "#skip" do
|
|
417
|
+
it "should update query with value" do
|
|
418
|
+
@obj.should_receive(:update_query).with({:skip => 3})
|
|
419
|
+
@obj.skip(3)
|
|
420
|
+
end
|
|
421
|
+
it "should update query with default value" do
|
|
422
|
+
@obj.should_receive(:update_query).with({:skip => 0})
|
|
423
|
+
@obj.skip
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
describe "#reduce" do
|
|
428
|
+
it "should update query" do
|
|
429
|
+
@obj.should_receive(:can_reduce?).and_return(true)
|
|
430
|
+
@obj.should_receive(:update_query).with({:reduce => true, :include_docs => nil})
|
|
431
|
+
@obj.reduce
|
|
432
|
+
end
|
|
433
|
+
it "should raise error if query cannot be reduced" do
|
|
434
|
+
@obj.should_receive(:can_reduce?).and_return(false)
|
|
435
|
+
lambda { @obj.reduce }.should raise_error
|
|
436
|
+
end
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
describe "#group" do
|
|
440
|
+
it "should update query" do
|
|
441
|
+
@obj.should_receive(:query).and_return({:reduce => true})
|
|
442
|
+
@obj.should_receive(:update_query).with({:group => true})
|
|
443
|
+
@obj.group
|
|
444
|
+
end
|
|
445
|
+
it "should raise error if query not prepared for reduce" do
|
|
446
|
+
@obj.should_receive(:query).and_return({:reduce => false})
|
|
447
|
+
lambda { @obj.group }.should raise_error
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
describe "#group" do
|
|
452
|
+
it "should update query" do
|
|
453
|
+
@obj.should_receive(:query).and_return({:reduce => true})
|
|
454
|
+
@obj.should_receive(:update_query).with({:group => true})
|
|
455
|
+
@obj.group
|
|
456
|
+
end
|
|
457
|
+
it "should raise error if query not prepared for reduce" do
|
|
458
|
+
@obj.should_receive(:query).and_return({:reduce => false})
|
|
459
|
+
lambda { @obj.group }.should raise_error
|
|
460
|
+
end
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
describe "#group_level" do
|
|
464
|
+
it "should update query" do
|
|
465
|
+
@obj.should_receive(:group).and_return(@obj)
|
|
466
|
+
@obj.should_receive(:update_query).with({:group_level => 3})
|
|
467
|
+
@obj.group_level(3)
|
|
468
|
+
end
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
describe "#include_docs" do
|
|
472
|
+
it "should call include_docs! on new view" do
|
|
473
|
+
@obj.should_receive(:update_query).and_return(@obj)
|
|
474
|
+
@obj.should_receive(:include_docs!)
|
|
475
|
+
@obj.include_docs
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
describe "#reset!" do
|
|
480
|
+
it "should empty all cached data" do
|
|
481
|
+
@obj.should_receive(:result=).with(nil)
|
|
482
|
+
@obj.instance_exec { @rows = 'foo'; @docs = 'foo' }
|
|
483
|
+
@obj.reset!
|
|
484
|
+
@obj.instance_exec { @rows }.should be_nil
|
|
485
|
+
@obj.instance_exec { @docs }.should be_nil
|
|
486
|
+
end
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
#### PROTECTED METHODS
|
|
490
|
+
|
|
491
|
+
describe "#include_docs!" do
|
|
492
|
+
it "should set query value" do
|
|
493
|
+
@obj.should_receive(:result).and_return(false)
|
|
494
|
+
@obj.should_not_receive(:reset!)
|
|
495
|
+
@obj.send(:include_docs!)
|
|
496
|
+
@obj.query[:include_docs].should be_true
|
|
497
|
+
end
|
|
498
|
+
it "should reset if result and no docs" do
|
|
499
|
+
@obj.should_receive(:result).and_return(true)
|
|
500
|
+
@obj.should_receive(:include_docs?).and_return(false)
|
|
501
|
+
@obj.should_receive(:reset!)
|
|
502
|
+
@obj.send(:include_docs!)
|
|
503
|
+
@obj.query[:include_docs].should be_true
|
|
504
|
+
end
|
|
505
|
+
it "should raise an error if view is reduced" do
|
|
506
|
+
@obj.query[:reduce] = true
|
|
507
|
+
lambda { @obj.send(:include_docs!) }.should raise_error
|
|
508
|
+
end
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
describe "#include_docs?" do
|
|
512
|
+
it "should return true if set" do
|
|
513
|
+
@obj.should_receive(:query).and_return({:include_docs => true})
|
|
514
|
+
@obj.send(:include_docs?).should be_true
|
|
515
|
+
end
|
|
516
|
+
it "should return false if not set" do
|
|
517
|
+
@obj.should_receive(:query).and_return({})
|
|
518
|
+
@obj.send(:include_docs?).should be_false
|
|
519
|
+
@obj.should_receive(:query).and_return({:include_docs => false})
|
|
520
|
+
@obj.send(:include_docs?).should be_false
|
|
521
|
+
end
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
describe "#update_query" do
|
|
525
|
+
it "returns a new instance of view" do
|
|
526
|
+
@obj.send(:update_query).object_id.should_not eql(@obj.object_id)
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
it "returns a new instance of view with extra parameters" do
|
|
530
|
+
new_obj = @obj.send(:update_query, {:foo => :bar})
|
|
531
|
+
new_obj.query[:foo].should eql(:bar)
|
|
532
|
+
end
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
describe "#design_doc" do
|
|
536
|
+
it "should call design_doc on model" do
|
|
537
|
+
@obj.model.should_receive(:design_doc)
|
|
538
|
+
@obj.send(:design_doc)
|
|
539
|
+
end
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
describe "#can_reduce?" do
|
|
543
|
+
it "should check and prove true" do
|
|
544
|
+
@obj.should_receive(:name).and_return('test_view')
|
|
545
|
+
@obj.should_receive(:design_doc).and_return({'views' => {'test_view' => {'reduce' => 'foo'}}})
|
|
546
|
+
@obj.send(:can_reduce?).should be_true
|
|
547
|
+
end
|
|
548
|
+
it "should check and prove false" do
|
|
549
|
+
@obj.should_receive(:name).and_return('test_view')
|
|
550
|
+
@obj.should_receive(:design_doc).and_return({'views' => {'test_view' => {'reduce' => nil}}})
|
|
551
|
+
@obj.send(:can_reduce?).should be_false
|
|
552
|
+
end
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
describe "#execute" do
|
|
556
|
+
before :each do
|
|
557
|
+
# disable real execution!
|
|
558
|
+
@design_doc = mock("DesignDoc")
|
|
559
|
+
@design_doc.stub!(:view_on)
|
|
560
|
+
@obj.model.stub!(:save_design_doc)
|
|
561
|
+
@obj.model.stub!(:design_doc).and_return(@design_doc)
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
it "should return previous result if set" do
|
|
565
|
+
@obj.result = "foo"
|
|
566
|
+
@obj.send(:execute).should eql('foo')
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
it "should raise issue if no database" do
|
|
570
|
+
@obj.should_receive(:query).and_return({:database => nil})
|
|
571
|
+
model = mock("SomeModel")
|
|
572
|
+
model.should_receive(:database).and_return(nil)
|
|
573
|
+
@obj.should_receive(:model).and_return(model)
|
|
574
|
+
lambda { @obj.send(:execute) }.should raise_error
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
it "should delete the reduce option if not going to be used" do
|
|
578
|
+
@obj.should_receive(:can_reduce?).and_return(false)
|
|
579
|
+
@obj.query.should_receive(:delete).with(:reduce)
|
|
580
|
+
@obj.send(:execute)
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
it "should call to save the design document" do
|
|
584
|
+
@obj.should_receive(:can_reduce?).and_return(false)
|
|
585
|
+
@obj.model.should_receive(:save_design_doc).with(DB)
|
|
586
|
+
@obj.send(:execute)
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
it "should populate the results" do
|
|
590
|
+
@obj.should_receive(:can_reduce?).and_return(true)
|
|
591
|
+
@design_doc.should_receive(:view_on).and_return('foos')
|
|
592
|
+
@obj.send(:execute)
|
|
593
|
+
@obj.result.should eql('foos')
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
it "should remove nil values from query" do
|
|
597
|
+
@obj.should_receive(:can_reduce?).and_return(true)
|
|
598
|
+
@obj.stub!(:use_database).and_return('database')
|
|
599
|
+
@obj.query = {:reduce => true, :limit => nil, :skip => nil}
|
|
600
|
+
@design_doc.should_receive(:view_on).with('database', 'test_view', {:reduce => true})
|
|
601
|
+
@obj.send(:execute)
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
describe "pagination methods" do
|
|
608
|
+
|
|
609
|
+
describe "#page" do
|
|
610
|
+
it "should call limit and skip" do
|
|
611
|
+
@obj.should_receive(:limit).with(25).and_return(@obj)
|
|
612
|
+
@obj.should_receive(:skip).with(25).and_return(@obj)
|
|
613
|
+
@obj.page(2)
|
|
614
|
+
end
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
describe "#per" do
|
|
618
|
+
it "should raise an error if page not called before hand" do
|
|
619
|
+
lambda { @obj.per(12) }.should raise_error
|
|
620
|
+
end
|
|
621
|
+
it "should not do anything if number less than or eql 0" do
|
|
622
|
+
view = @obj.page(1)
|
|
623
|
+
view.per(0).should eql(view)
|
|
624
|
+
end
|
|
625
|
+
it "should set limit and update skip" do
|
|
626
|
+
view = @obj.page(2).per(10)
|
|
627
|
+
view.query[:skip].should eql(10)
|
|
628
|
+
view.query[:limit].should eql(10)
|
|
629
|
+
end
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
describe "#total_count" do
|
|
633
|
+
it "set limit and skip to nill and perform count" do
|
|
634
|
+
@obj.should_receive(:limit).with(nil).and_return(@obj)
|
|
635
|
+
@obj.should_receive(:skip).with(nil).and_return(@obj)
|
|
636
|
+
@obj.should_receive(:count).and_return(5)
|
|
637
|
+
@obj.total_count.should eql(5)
|
|
638
|
+
@obj.total_count.should eql(5) # Second to test caching
|
|
639
|
+
end
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
describe "#num_pages" do
|
|
643
|
+
it "should use total_count and limit_value" do
|
|
644
|
+
@obj.should_receive(:total_count).and_return(200)
|
|
645
|
+
@obj.should_receive(:limit_value).and_return(25)
|
|
646
|
+
@obj.num_pages.should eql(8)
|
|
647
|
+
end
|
|
648
|
+
end
|
|
649
|
+
|
|
650
|
+
describe "#current_page" do
|
|
651
|
+
it "should use offset and limit" do
|
|
652
|
+
@obj.should_receive(:offset_value).and_return(25)
|
|
653
|
+
@obj.should_receive(:limit_value).and_return(25)
|
|
654
|
+
@obj.current_page.should eql(2)
|
|
655
|
+
end
|
|
656
|
+
end
|
|
657
|
+
end
|
|
658
|
+
end
|
|
659
|
+
end
|
|
660
|
+
|
|
661
|
+
describe "ViewRow" do
|
|
662
|
+
|
|
663
|
+
before :all do
|
|
664
|
+
@klass = CouchRest::Model::Designs::ViewRow
|
|
665
|
+
end
|
|
666
|
+
|
|
667
|
+
describe "intialize" do
|
|
668
|
+
it "should store reference to model" do
|
|
669
|
+
obj = @klass.new({}, "model")
|
|
670
|
+
obj.model.should eql('model')
|
|
671
|
+
end
|
|
672
|
+
it "should copy details from hash" do
|
|
673
|
+
obj = @klass.new({:foo => :bar, :test => :example}, "")
|
|
674
|
+
obj[:foo].should eql(:bar)
|
|
675
|
+
obj[:test].should eql(:example)
|
|
676
|
+
end
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
describe "running" do
|
|
680
|
+
before :each do
|
|
681
|
+
end
|
|
682
|
+
|
|
683
|
+
it "should provide id" do
|
|
684
|
+
obj = @klass.new({'id' => '123456'}, 'model')
|
|
685
|
+
obj.id.should eql('123456')
|
|
686
|
+
end
|
|
687
|
+
|
|
688
|
+
it "should provide key" do
|
|
689
|
+
obj = @klass.new({'key' => 'thekey'}, 'model')
|
|
690
|
+
obj.key.should eql('thekey')
|
|
691
|
+
end
|
|
692
|
+
|
|
693
|
+
it "should provide the value" do
|
|
694
|
+
obj = @klass.new({'value' => 'thevalue'}, 'model')
|
|
695
|
+
obj.value.should eql('thevalue')
|
|
696
|
+
end
|
|
697
|
+
|
|
698
|
+
it "should provide the raw document" do
|
|
699
|
+
obj = @klass.new({'doc' => 'thedoc'}, 'model')
|
|
700
|
+
obj.raw_doc.should eql('thedoc')
|
|
701
|
+
end
|
|
702
|
+
|
|
703
|
+
it "should instantiate a new document" do
|
|
704
|
+
hash = {'doc' => {'_id' => '12345', 'name' => 'sam'}}
|
|
705
|
+
obj = @klass.new(hash, DesignViewModel)
|
|
706
|
+
doc = mock('DesignViewModel')
|
|
707
|
+
obj.model.should_receive(:build_from_database).with(hash['doc']).and_return(doc)
|
|
708
|
+
obj.doc.should eql(doc)
|
|
709
|
+
end
|
|
710
|
+
|
|
711
|
+
it "should try to load from id if no document" do
|
|
712
|
+
hash = {'id' => '12345', 'value' => 5}
|
|
713
|
+
obj = @klass.new(hash, DesignViewModel)
|
|
714
|
+
doc = mock('DesignViewModel')
|
|
715
|
+
obj.model.should_receive(:get).with('12345').and_return(doc)
|
|
716
|
+
obj.doc.should eql(doc)
|
|
717
|
+
end
|
|
718
|
+
|
|
719
|
+
it "should try to load linked document if available" do
|
|
720
|
+
hash = {'id' => '12345', 'value' => {'_id' => '54321'}}
|
|
721
|
+
obj = @klass.new(hash, DesignViewModel)
|
|
722
|
+
doc = mock('DesignViewModel')
|
|
723
|
+
obj.model.should_receive(:get).with('54321').and_return(doc)
|
|
724
|
+
obj.doc.should eql(doc)
|
|
725
|
+
end
|
|
726
|
+
|
|
727
|
+
it "should try to return nil for document if none available" do
|
|
728
|
+
hash = {'value' => 23} # simulate reduce
|
|
729
|
+
obj = @klass.new(hash, DesignViewModel)
|
|
730
|
+
doc = mock('DesignViewModel')
|
|
731
|
+
obj.model.should_not_receive(:get)
|
|
732
|
+
obj.doc.should be_nil
|
|
733
|
+
end
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
end
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
describe "scenarios" do
|
|
742
|
+
|
|
743
|
+
before :all do
|
|
744
|
+
@objs = [
|
|
745
|
+
{:name => "Judith"},
|
|
746
|
+
{:name => "Lorena"},
|
|
747
|
+
{:name => "Peter"},
|
|
748
|
+
{:name => "Sam"},
|
|
749
|
+
{:name => "Vilma"}
|
|
750
|
+
].map{|h| DesignViewModel.create(h)}
|
|
751
|
+
end
|
|
752
|
+
|
|
753
|
+
describe "loading documents" do
|
|
754
|
+
it "should return first" do
|
|
755
|
+
DesignViewModel.by_name.first.name.should eql("Judith")
|
|
756
|
+
end
|
|
757
|
+
|
|
758
|
+
it "should return last" do
|
|
759
|
+
DesignViewModel.by_name.last.name.should eql("Vilma")
|
|
760
|
+
end
|
|
761
|
+
|
|
762
|
+
it "should allow multiple results" do
|
|
763
|
+
view = DesignViewModel.by_name.limit(3)
|
|
764
|
+
view.total_rows.should eql(5)
|
|
765
|
+
view.last.name.should eql("Peter")
|
|
766
|
+
view.all.length.should eql(3)
|
|
767
|
+
end
|
|
768
|
+
end
|
|
769
|
+
|
|
770
|
+
describe "index information" do
|
|
771
|
+
it "should provide total_rows" do
|
|
772
|
+
DesignViewModel.by_name.total_rows.should eql(5)
|
|
773
|
+
end
|
|
774
|
+
it "should provide total_rows" do
|
|
775
|
+
DesignViewModel.by_name.total_rows.should eql(5)
|
|
776
|
+
end
|
|
777
|
+
it "should provide an offset" do
|
|
778
|
+
DesignViewModel.by_name.offset.should eql(0)
|
|
779
|
+
end
|
|
780
|
+
it "should provide a set of keys" do
|
|
781
|
+
DesignViewModel.by_name.limit(2).keys.should eql(["Judith", "Lorena"])
|
|
782
|
+
end
|
|
783
|
+
end
|
|
784
|
+
|
|
785
|
+
describe "viewing" do
|
|
786
|
+
it "should load views with no reduce method" do
|
|
787
|
+
docs = DesignViewModel.by_just_name.all
|
|
788
|
+
docs.length.should eql(5)
|
|
789
|
+
end
|
|
790
|
+
it "should load documents by specific keys" do
|
|
791
|
+
docs = DesignViewModel.by_name.keys(["Judith", "Peter"]).all
|
|
792
|
+
docs[0].name.should eql("Judith")
|
|
793
|
+
docs[1].name.should eql("Peter")
|
|
794
|
+
end
|
|
795
|
+
it "should provide count even if limit or skip set" do
|
|
796
|
+
docs = DesignViewModel.by_name.limit(20).skip(2)
|
|
797
|
+
docs.count.should eql(5)
|
|
798
|
+
end
|
|
799
|
+
end
|
|
800
|
+
|
|
801
|
+
describe "pagination" do
|
|
802
|
+
before :all do
|
|
803
|
+
DesignViewModel.paginates_per 3
|
|
804
|
+
end
|
|
805
|
+
before :each do
|
|
806
|
+
@view = DesignViewModel.by_name.page(1)
|
|
807
|
+
end
|
|
808
|
+
|
|
809
|
+
it "should calculate number of pages" do
|
|
810
|
+
@view.num_pages.should eql(2)
|
|
811
|
+
end
|
|
812
|
+
it "should return results from first page" do
|
|
813
|
+
@view.all.first.name.should eql('Judith')
|
|
814
|
+
@view.all.last.name.should eql('Peter')
|
|
815
|
+
end
|
|
816
|
+
it "should return results from second page" do
|
|
817
|
+
@view.page(2).all.first.name.should eql('Sam')
|
|
818
|
+
@view.page(2).all.last.name.should eql('Vilma')
|
|
819
|
+
end
|
|
820
|
+
|
|
821
|
+
it "should allow overriding per page count" do
|
|
822
|
+
@view = @view.per(10)
|
|
823
|
+
@view.num_pages.should eql(1)
|
|
824
|
+
@view.all.last.name.should eql('Vilma')
|
|
825
|
+
end
|
|
826
|
+
end
|
|
827
|
+
|
|
828
|
+
end
|
|
829
|
+
|
|
830
|
+
|
|
831
|
+
end
|