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.
Files changed (107) hide show
  1. data/.gitignore +11 -0
  2. data/.rspec +4 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +176 -0
  5. data/README.md +137 -0
  6. data/Rakefile +38 -0
  7. data/THANKS.md +21 -0
  8. data/VERSION +1 -0
  9. data/benchmarks/dirty.rb +118 -0
  10. data/couchrest_model.gemspec +36 -0
  11. data/history.md +309 -0
  12. data/init.rb +1 -0
  13. data/lib/couchrest/model.rb +10 -0
  14. data/lib/couchrest/model/associations.rb +231 -0
  15. data/lib/couchrest/model/base.rb +129 -0
  16. data/lib/couchrest/model/callbacks.rb +28 -0
  17. data/lib/couchrest/model/casted_array.rb +83 -0
  18. data/lib/couchrest/model/casted_by.rb +33 -0
  19. data/lib/couchrest/model/casted_hash.rb +84 -0
  20. data/lib/couchrest/model/class_proxy.rb +135 -0
  21. data/lib/couchrest/model/collection.rb +273 -0
  22. data/lib/couchrest/model/configuration.rb +67 -0
  23. data/lib/couchrest/model/connection.rb +70 -0
  24. data/lib/couchrest/model/core_extensions/hash.rb +9 -0
  25. data/lib/couchrest/model/core_extensions/time_parsing.rb +66 -0
  26. data/lib/couchrest/model/design_doc.rb +128 -0
  27. data/lib/couchrest/model/designs.rb +91 -0
  28. data/lib/couchrest/model/designs/view.rb +513 -0
  29. data/lib/couchrest/model/dirty.rb +39 -0
  30. data/lib/couchrest/model/document_queries.rb +99 -0
  31. data/lib/couchrest/model/embeddable.rb +78 -0
  32. data/lib/couchrest/model/errors.rb +25 -0
  33. data/lib/couchrest/model/extended_attachments.rb +83 -0
  34. data/lib/couchrest/model/persistence.rb +178 -0
  35. data/lib/couchrest/model/properties.rb +228 -0
  36. data/lib/couchrest/model/property.rb +114 -0
  37. data/lib/couchrest/model/property_protection.rb +71 -0
  38. data/lib/couchrest/model/proxyable.rb +183 -0
  39. data/lib/couchrest/model/support/couchrest_database.rb +13 -0
  40. data/lib/couchrest/model/support/couchrest_design.rb +33 -0
  41. data/lib/couchrest/model/typecast.rb +154 -0
  42. data/lib/couchrest/model/validations.rb +80 -0
  43. data/lib/couchrest/model/validations/casted_model.rb +16 -0
  44. data/lib/couchrest/model/validations/locale/en.yml +5 -0
  45. data/lib/couchrest/model/validations/uniqueness.rb +69 -0
  46. data/lib/couchrest/model/views.rb +151 -0
  47. data/lib/couchrest/railtie.rb +24 -0
  48. data/lib/couchrest_model.rb +66 -0
  49. data/lib/rails/generators/couchrest_model.rb +16 -0
  50. data/lib/rails/generators/couchrest_model/config/config_generator.rb +18 -0
  51. data/lib/rails/generators/couchrest_model/config/templates/couchdb.yml +21 -0
  52. data/lib/rails/generators/couchrest_model/model/model_generator.rb +27 -0
  53. data/lib/rails/generators/couchrest_model/model/templates/model.rb +2 -0
  54. data/spec/.gitignore +1 -0
  55. data/spec/fixtures/attachments/README +3 -0
  56. data/spec/fixtures/attachments/couchdb.png +0 -0
  57. data/spec/fixtures/attachments/test.html +11 -0
  58. data/spec/fixtures/config/couchdb.yml +10 -0
  59. data/spec/fixtures/models/article.rb +36 -0
  60. data/spec/fixtures/models/base.rb +164 -0
  61. data/spec/fixtures/models/card.rb +19 -0
  62. data/spec/fixtures/models/cat.rb +23 -0
  63. data/spec/fixtures/models/client.rb +6 -0
  64. data/spec/fixtures/models/course.rb +27 -0
  65. data/spec/fixtures/models/event.rb +8 -0
  66. data/spec/fixtures/models/invoice.rb +14 -0
  67. data/spec/fixtures/models/key_chain.rb +5 -0
  68. data/spec/fixtures/models/membership.rb +4 -0
  69. data/spec/fixtures/models/person.rb +11 -0
  70. data/spec/fixtures/models/project.rb +6 -0
  71. data/spec/fixtures/models/question.rb +7 -0
  72. data/spec/fixtures/models/sale_entry.rb +9 -0
  73. data/spec/fixtures/models/sale_invoice.rb +14 -0
  74. data/spec/fixtures/models/service.rb +10 -0
  75. data/spec/fixtures/models/user.rb +22 -0
  76. data/spec/fixtures/views/lib.js +3 -0
  77. data/spec/fixtures/views/test_view/lib.js +3 -0
  78. data/spec/fixtures/views/test_view/only-map.js +4 -0
  79. data/spec/fixtures/views/test_view/test-map.js +3 -0
  80. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  81. data/spec/functional/validations_spec.rb +8 -0
  82. data/spec/spec_helper.rb +60 -0
  83. data/spec/unit/active_model_lint_spec.rb +30 -0
  84. data/spec/unit/assocations_spec.rb +242 -0
  85. data/spec/unit/attachment_spec.rb +176 -0
  86. data/spec/unit/base_spec.rb +537 -0
  87. data/spec/unit/casted_spec.rb +72 -0
  88. data/spec/unit/class_proxy_spec.rb +167 -0
  89. data/spec/unit/collection_spec.rb +86 -0
  90. data/spec/unit/configuration_spec.rb +77 -0
  91. data/spec/unit/connection_spec.rb +148 -0
  92. data/spec/unit/core_extensions/time_parsing.rb +77 -0
  93. data/spec/unit/design_doc_spec.rb +241 -0
  94. data/spec/unit/designs/view_spec.rb +831 -0
  95. data/spec/unit/designs_spec.rb +134 -0
  96. data/spec/unit/dirty_spec.rb +436 -0
  97. data/spec/unit/embeddable_spec.rb +498 -0
  98. data/spec/unit/inherited_spec.rb +33 -0
  99. data/spec/unit/persistence_spec.rb +481 -0
  100. data/spec/unit/property_protection_spec.rb +192 -0
  101. data/spec/unit/property_spec.rb +481 -0
  102. data/spec/unit/proxyable_spec.rb +376 -0
  103. data/spec/unit/subclass_spec.rb +85 -0
  104. data/spec/unit/typecast_spec.rb +521 -0
  105. data/spec/unit/validations_spec.rb +140 -0
  106. data/spec/unit/view_spec.rb +367 -0
  107. 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