couchrest_model 1.1.0.beta2 → 1.1.0.beta3

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 (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