couchrest_model 1.1.0.beta2 → 1.1.0.beta3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/.gitignore +2 -1
  2. data/README.md +50 -3
  3. data/VERSION +1 -1
  4. data/benchmarks/dirty.rb +118 -0
  5. data/couchrest_model.gemspec +1 -0
  6. data/history.txt +12 -0
  7. data/lib/couchrest/model/base.rb +15 -23
  8. data/lib/couchrest/model/casted_array.rb +26 -1
  9. data/lib/couchrest/model/casted_by.rb +23 -0
  10. data/lib/couchrest/model/casted_hash.rb +76 -0
  11. data/lib/couchrest/model/casted_model.rb +9 -6
  12. data/lib/couchrest/model/collection.rb +10 -1
  13. data/lib/couchrest/model/configuration.rb +4 -4
  14. data/lib/couchrest/model/design_doc.rb +65 -71
  15. data/lib/couchrest/model/designs/view.rb +26 -19
  16. data/lib/couchrest/model/designs.rb +0 -2
  17. data/lib/couchrest/model/dirty.rb +49 -0
  18. data/lib/couchrest/model/extended_attachments.rb +7 -1
  19. data/lib/couchrest/model/persistence.rb +15 -4
  20. data/lib/couchrest/model/properties.rb +50 -9
  21. data/lib/couchrest/model/property.rb +6 -2
  22. data/lib/couchrest/model/proxyable.rb +13 -19
  23. data/lib/couchrest/model/support/couchrest_design.rb +33 -0
  24. data/lib/couchrest/model/views.rb +4 -18
  25. data/lib/couchrest_model.rb +8 -3
  26. data/spec/couchrest/base_spec.rb +1 -28
  27. data/spec/couchrest/casted_model_spec.rb +1 -1
  28. data/spec/couchrest/collection_spec.rb +0 -1
  29. data/spec/couchrest/design_doc_spec.rb +211 -0
  30. data/spec/couchrest/designs/view_spec.rb +41 -17
  31. data/spec/couchrest/designs_spec.rb +0 -5
  32. data/spec/couchrest/dirty_spec.rb +355 -0
  33. data/spec/couchrest/property_spec.rb +5 -2
  34. data/spec/couchrest/proxyable_spec.rb +0 -11
  35. data/spec/couchrest/subclass_spec.rb +6 -16
  36. data/spec/couchrest/view_spec.rb +6 -50
  37. data/spec/fixtures/base.rb +1 -1
  38. data/spec/fixtures/more/card.rb +2 -2
  39. data/spec/spec_helper.rb +2 -0
  40. metadata +9 -4
  41. data/Gemfile.lock +0 -76
  42. data/lib/couchrest/model/support/couchrest.rb +0 -19
@@ -2,7 +2,7 @@ module CouchRest
2
2
  module Model
3
3
  module Views
4
4
  extend ActiveSupport::Concern
5
-
5
+
6
6
  module ClassMethods
7
7
  # Define a CouchDB view. The name of the view will be the concatenation
8
8
  # of <tt>by</tt> and the keys joined by <tt>_and_</tt>
@@ -81,7 +81,6 @@ module CouchRest
81
81
  end
82
82
  keys.push opts
83
83
  design_doc.view_by(*keys)
84
- req_design_doc_refresh
85
84
  end
86
85
 
87
86
  # returns stored defaults if there is a view named this in the design doc
@@ -99,9 +98,9 @@ module CouchRest
99
98
  def view(name, query={}, &block)
100
99
  query = query.dup # Modifications made on copy!
101
100
  db = query.delete(:database) || database
102
- refresh_design_doc(db)
103
101
  query[:raw] = true if query[:reduce]
104
102
  raw = query.delete(:raw)
103
+ save_design_doc(db)
105
104
  fetch_view_with_docs(db, name, query, raw, &block)
106
105
  end
107
106
 
@@ -140,23 +139,10 @@ module CouchRest
140
139
 
141
140
  def fetch_view(db, view_name, opts, &block)
142
141
  raise "A view needs a database to operate on (specify :database option, or use_database in the #{self.class} class)" unless db
143
- retryable = true
144
- begin
145
- design_doc.view_on(db, view_name, opts, &block)
146
- # the design doc may not have been saved yet on this database
147
- rescue RestClient::ResourceNotFound => e
148
- if retryable
149
- save_design_doc(db)
150
- retryable = false
151
- retry
152
- else
153
- raise e
154
- end
155
- end
142
+ design_doc.view_on(db, view_name, opts, &block)
156
143
  end
157
-
158
144
  end # module ClassMethods
159
-
145
+
160
146
  end
161
147
  end
162
148
  end
@@ -8,6 +8,7 @@ require "active_model/serialization"
8
8
  require "active_model/translation"
9
9
  require "active_model/validator"
10
10
  require "active_model/validations"
11
+ require "active_model/dirty"
11
12
 
12
13
  require 'active_support/core_ext'
13
14
  require 'active_support/json'
@@ -26,10 +27,14 @@ require 'couchrest/model'
26
27
  require 'couchrest/model/errors'
27
28
  require "couchrest/model/persistence"
28
29
  require "couchrest/model/typecast"
30
+ require "couchrest/model/casted_by"
31
+ require "couchrest/model/dirty"
29
32
  require "couchrest/model/property"
30
33
  require "couchrest/model/property_protection"
31
- require "couchrest/model/casted_array"
32
34
  require "couchrest/model/properties"
35
+ require "couchrest/model/casted_array"
36
+ require "couchrest/model/casted_hash"
37
+ require "couchrest/model/casted_model"
33
38
  require "couchrest/model/validations"
34
39
  require "couchrest/model/callbacks"
35
40
  require "couchrest/model/document_queries"
@@ -45,7 +50,8 @@ require "couchrest/model/designs"
45
50
  require "couchrest/model/designs/view"
46
51
 
47
52
  # Monkey patches applied to couchrest
48
- require "couchrest/model/support/couchrest"
53
+ require "couchrest/model/support/couchrest_design"
54
+
49
55
  # Core Extensions
50
56
  require "couchrest/model/core_extensions/hash"
51
57
  require "couchrest/model/core_extensions/time_parsing"
@@ -53,7 +59,6 @@ require "couchrest/model/core_extensions/time_parsing"
53
59
  # Base libraries
54
60
  require "couchrest/model/casted_model"
55
61
  require "couchrest/model/base"
56
-
57
62
  # Add rails support *after* everything has loaded
58
63
 
59
64
  require "couchrest/railtie"
@@ -246,7 +246,6 @@ describe "Model Base" do
246
246
 
247
247
  describe "finding all instances of a model" do
248
248
  before(:all) do
249
- WithTemplateAndUniqueID.req_design_doc_refresh
250
249
  WithTemplateAndUniqueID.all.map{|o| o.destroy}
251
250
  WithTemplateAndUniqueID.database.bulk_delete
252
251
  WithTemplateAndUniqueID.new('important-field' => '1').save
@@ -254,11 +253,6 @@ describe "Model Base" do
254
253
  WithTemplateAndUniqueID.new('important-field' => '3').save
255
254
  WithTemplateAndUniqueID.new('important-field' => '4').save
256
255
  end
257
- it "should make the design doc" do
258
- WithTemplateAndUniqueID.all
259
- d = WithTemplateAndUniqueID.design_doc
260
- d['views']['all']['map'].should include('WithTemplateAndUniqueID')
261
- end
262
256
  it "should find all" do
263
257
  rs = WithTemplateAndUniqueID.all
264
258
  rs.length.should == 4
@@ -268,7 +262,6 @@ describe "Model Base" do
268
262
  describe "counting all instances of a model" do
269
263
  before(:each) do
270
264
  @db = reset_test_db!
271
- WithTemplateAndUniqueID.req_design_doc_refresh
272
265
  end
273
266
 
274
267
  it ".count should return 0 if there are no docuemtns" do
@@ -287,17 +280,11 @@ describe "Model Base" do
287
280
  describe "finding the first instance of a model" do
288
281
  before(:each) do
289
282
  @db = reset_test_db!
290
- # WithTemplateAndUniqueID.req_design_doc_refresh # Removed by Sam Lown, design doc should be loaded automatically
291
283
  WithTemplateAndUniqueID.new('important-field' => '1').save
292
284
  WithTemplateAndUniqueID.new('important-field' => '2').save
293
285
  WithTemplateAndUniqueID.new('important-field' => '3').save
294
286
  WithTemplateAndUniqueID.new('important-field' => '4').save
295
287
  end
296
- it "should make the design doc" do
297
- WithTemplateAndUniqueID.all
298
- d = WithTemplateAndUniqueID.design_doc
299
- d['views']['all']['map'].should include('WithTemplateAndUniqueID')
300
- end
301
288
  it "should find first" do
302
289
  rs = WithTemplateAndUniqueID.first
303
290
  rs['important-field'].should == "1"
@@ -308,21 +295,6 @@ describe "Model Base" do
308
295
  end
309
296
  end
310
297
 
311
- describe "lazily refreshing the design document" do
312
- before(:all) do
313
- @db = reset_test_db!
314
- WithTemplateAndUniqueID.new('important-field' => '1').save
315
- end
316
- it "should not save the design doc twice" do
317
- WithTemplateAndUniqueID.all
318
- WithTemplateAndUniqueID.req_design_doc_refresh
319
- WithTemplateAndUniqueID.refresh_design_doc
320
- rev = WithTemplateAndUniqueID.design_doc['_rev']
321
- WithTemplateAndUniqueID.req_design_doc_refresh
322
- WithTemplateAndUniqueID.refresh_design_doc
323
- WithTemplateAndUniqueID.design_doc['_rev'].should eql(rev)
324
- end
325
- end
326
298
 
327
299
  describe "getting a model with a subobject field" do
328
300
  before(:all) do
@@ -378,6 +350,7 @@ describe "Model Base" do
378
350
  foundart.created_at.should == foundart.updated_at
379
351
  end
380
352
  it "should set the time on update" do
353
+ @art.title = "new title" # only saved if @art.changed? == true
381
354
  @art.save
382
355
  @art.created_at.should < @art.updated_at
383
356
  end
@@ -160,7 +160,7 @@ describe CouchRest::Model::CastedModel do
160
160
  end
161
161
 
162
162
  it "should cast the array properly" do
163
- @obj.keywords.should be_an_instance_of(Array)
163
+ @obj.keywords.should be_kind_of(Array)
164
164
  @obj.keywords.first.should == 'couch'
165
165
  end
166
166
  end
@@ -5,7 +5,6 @@ describe "Collections" do
5
5
 
6
6
  before(:all) do
7
7
  reset_test_db!
8
- Article.refresh_design_doc
9
8
  titles = ["very uniq one", "really interesting", "some fun",
10
9
  "really awesome", "crazy bob", "this rocks", "super rad"]
11
10
  titles.each_with_index do |title,i|
@@ -0,0 +1,211 @@
1
+ # encoding: utf-8
2
+
3
+ require File.expand_path("../../spec_helper", __FILE__)
4
+ require File.join(FIXTURE_PATH, 'base')
5
+ require File.join(FIXTURE_PATH, 'more', 'article')
6
+
7
+ describe "Design Documents" do
8
+
9
+ before :all do
10
+ reset_test_db!
11
+ end
12
+
13
+ describe "CouchRest Extension" do
14
+
15
+ it "should have created a checksum! method" do
16
+ ::CouchRest::Design.new.should respond_to(:checksum!)
17
+ end
18
+
19
+ it "should calculate a consistent checksum for model" do
20
+ WithTemplateAndUniqueID.design_doc.checksum!.should eql('ff6fa2eaf774397391942d51428c1fe2')
21
+ end
22
+
23
+ it "should calculate checksum for complex model" do
24
+ Article.design_doc.checksum!.should eql('fb65c06a76b6141529e31e894ad00b1a')
25
+ end
26
+
27
+ it "should cache the generated checksum value" do
28
+ Article.design_doc.checksum!
29
+ Article.design_doc['couchrest-hash'].should_not be_blank
30
+ end
31
+ end
32
+
33
+ describe "class methods" do
34
+
35
+ describe ".design_doc" do
36
+ it "should provide Design document" do
37
+ Article.design_doc.should be_a(::CouchRest::Design)
38
+ end
39
+ end
40
+
41
+ describe ".design_doc_id" do
42
+ it "should provide a reasonable id" do
43
+ Article.design_doc_id.should eql("_design/Article")
44
+ end
45
+ end
46
+
47
+ describe ".design_doc_slug" do
48
+ it "should provide slug part of design doc" do
49
+ Article.design_doc_slug.should eql('Article')
50
+ end
51
+ end
52
+
53
+ describe ".design_doc_uri" do
54
+ it "should provide complete url" do
55
+ Article.design_doc_uri.should eql("#{COUCHHOST}/#{TESTDB}/_design/Article")
56
+ end
57
+ it "should provide complete url for new DB" do
58
+ db = mock("Database")
59
+ db.should_receive(:root).and_return('db')
60
+ Article.design_doc_uri(db).should eql("db/_design/Article")
61
+ end
62
+ end
63
+
64
+ describe ".stored_design_doc" do
65
+ it "should load a stored design from the database" do
66
+ Article.by_date
67
+ Article.stored_design_doc['_rev'].should_not be_blank
68
+ end
69
+ it "should return nil if not already stored" do
70
+ WithDefaultValues.stored_design_doc.should be_nil
71
+ end
72
+ end
73
+
74
+ describe ".save_design_doc" do
75
+ it "should call up the design updater" do
76
+ Article.should_receive(:update_design_doc).with('db', false)
77
+ Article.save_design_doc('db')
78
+ end
79
+ end
80
+
81
+ describe ".save_design_doc!" do
82
+ it "should call save_design_doc with force" do
83
+ Article.should_receive(:save_design_doc).with('db', true)
84
+ Article.save_design_doc!('db')
85
+ end
86
+ end
87
+
88
+ end
89
+
90
+ describe "basics" do
91
+
92
+ before :all do
93
+ reset_test_db!
94
+ end
95
+
96
+ it "should have been instantiated with views" do
97
+ d = Article.design_doc
98
+ d['views']['all']['map'].should include('Article')
99
+ end
100
+
101
+ it "should not have been saved yet" do
102
+ lambda { Article.database.get(Article.design_doc.id) }.should raise_error(RestClient::ResourceNotFound)
103
+ end
104
+
105
+ describe "after requesting a view" do
106
+ before :each do
107
+ Article.all
108
+ end
109
+ it "should have saved the design doc after view request" do
110
+ Article.database.get(Article.design_doc.id).should_not be_nil
111
+ end
112
+ end
113
+
114
+ describe "model with simple views" do
115
+ before(:all) do
116
+ Article.all.map{|a| a.destroy(true)}
117
+ Article.database.bulk_delete
118
+ written_at = Time.now - 24 * 3600 * 7
119
+ @titles = ["this and that", "also interesting", "more fun", "some junk"]
120
+ @titles.each do |title|
121
+ a = Article.new(:title => title)
122
+ a.date = written_at
123
+ a.save
124
+ written_at += 24 * 3600
125
+ end
126
+ end
127
+
128
+ it "will send request for the saved design doc on view request" do
129
+ reset_test_db!
130
+ Article.should_receive(:stored_design_doc).and_return(nil)
131
+ Article.by_date
132
+ end
133
+
134
+ it "should have generated a design doc" do
135
+ Article.design_doc["views"]["by_date"].should_not be_nil
136
+ end
137
+ it "should save the design doc when view requested" do
138
+ Article.by_date
139
+ doc = Article.database.get Article.design_doc.id
140
+ doc['views']['by_date'].should_not be_nil
141
+ end
142
+ it "should save design doc if a view changed" do
143
+ Article.by_date
144
+ orig = Article.stored_design_doc
145
+ design = Article.design_doc
146
+ view = design['views']['by_date']['map']
147
+ design['views']['by_date']['map'] = view + ' ' # little bit of white space
148
+ Article.by_date
149
+ Article.stored_design_doc['_rev'].should_not eql(orig['_rev'])
150
+ orig['views']['by_date']['map'].should_not eql(Article.design_doc['views']['by_date']['map'])
151
+ end
152
+ it "should not save design doc if not changed" do
153
+ Article.by_date
154
+ orig = Article.stored_design_doc['_rev']
155
+ Article.by_date
156
+ Article.stored_design_doc['_rev'].should eql(orig)
157
+ end
158
+ end
159
+
160
+ describe "when auto_update_design_doc false" do
161
+
162
+ before :all do
163
+ Article.auto_update_design_doc = false
164
+ Article.save_design_doc!
165
+ end
166
+
167
+ after :all do
168
+ Article.auto_update_design_doc = true
169
+ end
170
+
171
+ it "will not send a request for the saved design doc" do
172
+ Article.should_not_receive(:stored_design_doc)
173
+ Article.by_date
174
+ end
175
+
176
+ it "will not update stored design doc if view changed" do
177
+ Article.by_date
178
+ orig = Article.stored_design_doc
179
+ design = Article.design_doc
180
+ view = design['views']['by_date']['map']
181
+ design['views']['by_date']['map'] = view + ' '
182
+ Article.by_date
183
+ Article.stored_design_doc['_rev'].should eql(orig['_rev'])
184
+ end
185
+
186
+ it "will update stored design if forced" do
187
+ Article.by_date
188
+ orig = Article.stored_design_doc
189
+ design = Article.design_doc
190
+ view = design['views']['by_date']['map']
191
+ design['views']['by_date']['map'] = view + ' '
192
+ Article.save_design_doc!
193
+ Article.stored_design_doc['_rev'].should_not eql(orig['_rev'])
194
+ end
195
+ end
196
+ end
197
+
198
+ describe "lazily refreshing the design document" do
199
+ before(:all) do
200
+ @db = reset_test_db!
201
+ WithTemplateAndUniqueID.new('important-field' => '1').save
202
+ end
203
+ it "should not save the design doc twice" do
204
+ WithTemplateAndUniqueID.all
205
+ rev = WithTemplateAndUniqueID.design_doc['_rev']
206
+ WithTemplateAndUniqueID.design_doc['_rev'].should eql(rev)
207
+ end
208
+ end
209
+
210
+
211
+ end
@@ -74,7 +74,7 @@ describe "Design View" do
74
74
  it "should auto generate mapping from name" do
75
75
  lambda { @klass.create(DesignViewModel, 'by_title') }.should_not raise_error
76
76
  str = @design_doc['views']['by_title']['map']
77
- str.should include("((doc['couchrest-type'] == 'DesignViewModel') && (doc['title'] != null))")
77
+ str.should include("((doc['model'] == 'DesignViewModel') && (doc['title'] != null))")
78
78
  str.should include("emit(doc['title'], 1);")
79
79
  str = @design_doc['views']['by_title']['reduce']
80
80
  str.should include("return sum(values);")
@@ -163,10 +163,17 @@ describe "Design View" do
163
163
  end
164
164
  end
165
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
+
166
173
  describe "#count" do
167
174
  it "should raise an error if view prepared for group" do
168
175
  @obj.should_receive(:query).and_return({:group => true})
169
- lambda { @obj.count }.should raise_error
176
+ lambda { @obj.count }.should raise_error(/group/)
170
177
  end
171
178
 
172
179
  it "should return first row value if reduce possible" do
@@ -174,6 +181,8 @@ describe "Design View" do
174
181
  row = mock("Row")
175
182
  @obj.should_receive(:can_reduce?).and_return(true)
176
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)
177
186
  view.should_receive(:rows).and_return([row])
178
187
  row.should_receive(:value).and_return(2)
179
188
  @obj.count.should eql(2)
@@ -182,6 +191,8 @@ describe "Design View" do
182
191
  view = mock("SubView")
183
192
  @obj.should_receive(:can_reduce?).and_return(true)
184
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)
185
196
  view.should_receive(:rows).and_return([])
186
197
  @obj.count.should eql(0)
187
198
  end
@@ -375,6 +386,24 @@ describe "Design View" do
375
386
  @obj.should_receive(:update_query).with({:descending => true})
376
387
  @obj.descending
377
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
378
407
  end
379
408
 
380
409
  describe "#limit" do
@@ -528,6 +557,7 @@ describe "Design View" do
528
557
  # disable real execution!
529
558
  @design_doc = mock("DesignDoc")
530
559
  @design_doc.stub!(:view_on)
560
+ @obj.model.stub!(:save_design_doc)
531
561
  @obj.model.stub!(:design_doc).and_return(@design_doc)
532
562
  end
533
563
 
@@ -550,29 +580,19 @@ describe "Design View" do
550
580
  @obj.send(:execute)
551
581
  end
552
582
 
553
- it "should populate the results" do
554
- @obj.should_receive(:can_reduce?).and_return(true)
555
- @design_doc.should_receive(:view_on).and_return('foos')
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)
556
586
  @obj.send(:execute)
557
- @obj.result.should eql('foos')
558
587
  end
559
588
 
560
- it "should retry once on a resource not found error" do
589
+ it "should populate the results" do
561
590
  @obj.should_receive(:can_reduce?).and_return(true)
562
- @obj.model.should_receive(:save_design_doc)
563
- @design_doc.should_receive(:view_on).ordered.and_raise(RestClient::ResourceNotFound)
564
- @design_doc.should_receive(:view_on).ordered.and_return('foos')
591
+ @design_doc.should_receive(:view_on).and_return('foos')
565
592
  @obj.send(:execute)
566
593
  @obj.result.should eql('foos')
567
594
  end
568
595
 
569
- it "should retry twice and fail on a resource not found error" do
570
- @obj.should_receive(:can_reduce?).and_return(true)
571
- @obj.model.should_receive(:save_design_doc)
572
- @design_doc.should_receive(:view_on).twice.and_raise(RestClient::ResourceNotFound)
573
- lambda { @obj.send(:execute) }.should raise_error(RestClient::ResourceNotFound)
574
- end
575
-
576
596
  it "should remove nil values from query" do
577
597
  @obj.should_receive(:can_reduce?).and_return(true)
578
598
  @obj.stub!(:use_database).and_return('database')
@@ -772,6 +792,10 @@ describe "Design View" do
772
792
  docs[0].name.should eql("Judith")
773
793
  docs[1].name.should eql("Peter")
774
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
775
799
  end
776
800
 
777
801
  describe "pagination" do
@@ -31,11 +31,6 @@ describe "Design" do
31
31
  DesignModel.design { foo }
32
32
  end
33
33
 
34
- it "should request a design refresh" do
35
- DesignModel.should_receive(:req_design_doc_refresh)
36
- DesignModel.design() { }
37
- end
38
-
39
34
  it "should work even if a block is not provided" do
40
35
  lambda { DesignModel.design }.should_not raise_error
41
36
  end