couchrest_model 1.1.2 → 1.2.0.beta

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 (43) hide show
  1. data/README.md +8 -2
  2. data/VERSION +1 -1
  3. data/couchrest_model.gemspec +2 -1
  4. data/history.md +8 -0
  5. data/lib/couchrest/model/base.rb +0 -20
  6. data/lib/couchrest/model/configuration.rb +2 -0
  7. data/lib/couchrest/model/core_extensions/time_parsing.rb +35 -9
  8. data/lib/couchrest/model/designs/design.rb +182 -0
  9. data/lib/couchrest/model/designs/view.rb +91 -48
  10. data/lib/couchrest/model/designs.rb +72 -19
  11. data/lib/couchrest/model/document_queries.rb +15 -45
  12. data/lib/couchrest/model/properties.rb +43 -2
  13. data/lib/couchrest/model/proxyable.rb +20 -54
  14. data/lib/couchrest/model/typecast.rb +1 -1
  15. data/lib/couchrest/model/validations/uniqueness.rb +7 -6
  16. data/lib/couchrest_model.rb +1 -5
  17. data/spec/fixtures/models/article.rb +22 -20
  18. data/spec/fixtures/models/base.rb +15 -7
  19. data/spec/fixtures/models/course.rb +7 -4
  20. data/spec/fixtures/models/project.rb +4 -1
  21. data/spec/fixtures/models/sale_entry.rb +5 -3
  22. data/spec/unit/base_spec.rb +51 -5
  23. data/spec/unit/core_extensions/time_parsing.rb +41 -0
  24. data/spec/unit/designs/design_spec.rb +291 -0
  25. data/spec/unit/designs/view_spec.rb +135 -40
  26. data/spec/unit/designs_spec.rb +341 -30
  27. data/spec/unit/dirty_spec.rb +67 -0
  28. data/spec/unit/inherited_spec.rb +2 -2
  29. data/spec/unit/property_protection_spec.rb +3 -1
  30. data/spec/unit/property_spec.rb +43 -3
  31. data/spec/unit/proxyable_spec.rb +57 -98
  32. data/spec/unit/subclass_spec.rb +14 -5
  33. data/spec/unit/validations_spec.rb +14 -12
  34. metadata +172 -129
  35. data/lib/couchrest/model/class_proxy.rb +0 -135
  36. data/lib/couchrest/model/collection.rb +0 -273
  37. data/lib/couchrest/model/design_doc.rb +0 -115
  38. data/lib/couchrest/model/support/couchrest_design.rb +0 -33
  39. data/lib/couchrest/model/views.rb +0 -148
  40. data/spec/unit/class_proxy_spec.rb +0 -167
  41. data/spec/unit/collection_spec.rb +0 -86
  42. data/spec/unit/design_doc_spec.rb +0 -212
  43. data/spec/unit/view_spec.rb +0 -352
@@ -1,6 +1,8 @@
1
1
  require "spec_helper"
2
2
 
3
3
  class DesignModel < CouchRest::Model::Base
4
+ use_database DB
5
+ property :name
4
6
  end
5
7
 
6
8
  describe CouchRest::Model::Designs do
@@ -12,26 +14,89 @@ describe CouchRest::Model::Designs do
12
14
  describe "class methods" do
13
15
 
14
16
  describe ".design" do
15
- before :each do
16
- @mapper = mock('DesignMapper')
17
- @mapper.stub!(:create_view_method)
17
+
18
+ before :each do
19
+ @klass = DesignModel.dup
18
20
  end
19
21
 
20
- it "should instantiate a new DesignMapper" do
21
- CouchRest::Model::Designs::DesignMapper.should_receive(:new).with(DesignModel).and_return(@mapper)
22
- @mapper.should_receive(:create_view_method).with(:all)
23
- @mapper.should_receive(:instance_eval)
24
- DesignModel.design() { }
22
+ describe "without block" do
23
+ it "should create design_doc and all methods" do
24
+ @klass.design
25
+ @klass.should respond_to(:design_doc)
26
+ @klass.should respond_to(:all)
27
+ end
28
+
29
+ it "should created named design_doc method and not all" do
30
+ @klass.design :stats
31
+ @klass.should respond_to(:stats_design_doc)
32
+ @klass.should_not respond_to(:all)
33
+ end
34
+
35
+ it "should have added itself to a design_blocks array" do
36
+ @klass.design
37
+ blocks = @klass.instance_variable_get(:@_design_blocks)
38
+ blocks.length.should eql(1)
39
+ blocks.first.should eql({:args => [nil], :block => nil})
40
+ end
41
+
42
+ it "should have added itself to a design_blocks array" do
43
+ @klass.design
44
+ blocks = @klass.instance_variable_get(:@_design_blocks)
45
+ blocks.length.should eql(1)
46
+ blocks.first.should eql({:args => [nil], :block => nil})
47
+ end
48
+
49
+ it "should have added itself to a design_blocks array with prefix" do
50
+ @klass.design :stats
51
+ blocks = @klass.instance_variable_get(:@_design_blocks)
52
+ blocks.length.should eql(1)
53
+ blocks.first.should eql({:args => [:stats], :block => nil})
54
+ end
55
+ end
56
+
57
+ describe "with block" do
58
+ before :each do
59
+ @block = Proc.new do
60
+ disable_auto_update
61
+ end
62
+ @klass.design &@block
63
+ end
64
+
65
+ it "should pass calls to mapper" do
66
+ @klass.design_doc.auto_update.should be_false
67
+ end
68
+
69
+ it "should have added itself to a design_blocks array" do
70
+ blocks = @klass.instance_variable_get(:@_design_blocks)
71
+ blocks.length.should eql(1)
72
+ blocks.first.should eql({:args => [nil], :block => @block})
73
+ end
74
+
75
+ it "should handle multiple designs" do
76
+ @block2 = Proc.new do
77
+ view :by_name
78
+ end
79
+ @klass.design :stats, &@block2
80
+ blocks = @klass.instance_variable_get(:@_design_blocks)
81
+ blocks.length.should eql(2)
82
+ blocks.first.should eql({:args => [nil], :block => @block})
83
+ blocks.last.should eql({:args => [:stats], :block => @block2})
84
+ end
25
85
  end
26
86
 
27
- it "should allow methods to be called in mapper" do
28
- @mapper.should_receive(:foo)
29
- CouchRest::Model::Designs::DesignMapper.stub!(:new).and_return(@mapper)
30
- DesignModel.design { foo }
87
+ end
88
+
89
+ describe "inheritance" do
90
+ before :each do
91
+ klass = DesignModel.dup
92
+ klass.design do
93
+ view :by_name
94
+ end
95
+ @klass = Class.new(klass)
31
96
  end
32
97
 
33
- it "should work even if a block is not provided" do
34
- lambda { DesignModel.design }.should_not raise_error
98
+ it "should add designs to sub module" do
99
+ @klass.should respond_to(:design_doc)
35
100
  end
36
101
 
37
102
  end
@@ -51,14 +116,85 @@ describe CouchRest::Model::Designs do
51
116
  end
52
117
 
53
118
  describe "DesignMapper" do
54
-
119
+
55
120
  before :all do
56
121
  @klass = CouchRest::Model::Designs::DesignMapper
57
122
  end
58
123
 
59
- it "should initialize and set model" do
60
- object = @klass.new(DesignModel)
61
- object.send(:model).should eql(DesignModel)
124
+ describe 'initialize without prefix' do
125
+
126
+ before :all do
127
+ @object = @klass.new(DesignModel)
128
+ end
129
+
130
+ it "should set basic variables" do
131
+ @object.send(:model).should eql(DesignModel)
132
+ @object.send(:prefix).should be_nil
133
+ @object.send(:method).should eql('design_doc')
134
+ end
135
+
136
+ it "should add design doc to list" do
137
+ @object.model.design_docs.should include(@object.model.design_doc)
138
+ end
139
+
140
+ it "should create a design doc method" do
141
+ @object.model.should respond_to('design_doc')
142
+ @object.design_doc.should eql(@object.model.design_doc)
143
+ end
144
+
145
+ it "should use default for autoupdate" do
146
+ @object.design_doc.auto_update.should be_true
147
+ end
148
+
149
+ end
150
+
151
+ describe 'initialize with prefix' do
152
+ before :all do
153
+ @object = @klass.new(DesignModel, 'stats')
154
+ end
155
+
156
+ it "should set basics" do
157
+ @object.send(:model).should eql(DesignModel)
158
+ @object.send(:prefix).should eql('stats')
159
+ @object.send(:method).should eql('stats_design_doc')
160
+ end
161
+
162
+ it "should add design doc to list" do
163
+ @object.model.design_docs.should include(@object.model.stats_design_doc)
164
+ end
165
+
166
+ it "should not create an all method" do
167
+ @object.model.should_not respond_to('all')
168
+ end
169
+
170
+ it "should create a design doc method" do
171
+ @object.model.should respond_to('stats_design_doc')
172
+ @object.design_doc.should eql(@object.model.stats_design_doc)
173
+ end
174
+
175
+ end
176
+
177
+ describe "#disable_auto_update" do
178
+ it "should disable auto updates" do
179
+ @object = @klass.new(DesignModel)
180
+ @object.disable_auto_update
181
+ @object.design_doc.auto_update.should be_false
182
+ end
183
+ end
184
+
185
+ describe "#enable_auto_update" do
186
+ it "should enable auto updates" do
187
+ @object = @klass.new(DesignModel)
188
+ @object.enable_auto_update
189
+ @object.design_doc.auto_update.should be_true
190
+ end
191
+ end
192
+
193
+ describe "#model_type_key" do
194
+ it "should return models type key" do
195
+ @object = @klass.new(DesignModel)
196
+ @object.model_type_key.should eql(@object.model.model_type_key)
197
+ end
62
198
  end
63
199
 
64
200
  describe "#view" do
@@ -68,22 +204,20 @@ describe CouchRest::Model::Designs do
68
204
  end
69
205
 
70
206
  it "should call create method on view" do
71
- CouchRest::Model::Designs::View.should_receive(:create).with(DesignModel, 'test', {})
207
+ CouchRest::Model::Designs::View.should_receive(:define).with(@object.design_doc, 'test', {})
72
208
  @object.view('test')
73
209
  end
74
210
 
75
211
  it "should create a method on parent model" do
76
- CouchRest::Model::Designs::View.stub!(:create)
212
+ CouchRest::Model::Designs::View.stub!(:define)
77
213
  @object.view('test_view')
78
214
  DesignModel.should respond_to(:test_view)
79
215
  end
80
216
 
81
217
  it "should create a method for view instance" do
82
- CouchRest::Model::Designs::View.stub!(:create)
83
- @object.should_receive(:create_view_method).with('test')
218
+ @object.design_doc.should_receive(:create_view).with('test', {})
84
219
  @object.view('test')
85
220
  end
86
-
87
221
  end
88
222
 
89
223
  describe "#filter" do
@@ -97,22 +231,199 @@ describe CouchRest::Model::Designs do
97
231
  DesignModel.design_doc['filters'].should_not be_empty
98
232
  DesignModel.design_doc['filters']['important'].should_not be_blank
99
233
  end
234
+ end
100
235
 
236
+ end
237
+
238
+
239
+ class DesignsNoAutoUpdate < CouchRest::Model::Base
240
+ use_database DB
241
+ property :title, String
242
+ design do
243
+ disable_auto_update
244
+ view :by_title_fail, :by => ['title']
245
+ view :by_title, :reduce => true
101
246
  end
247
+ end
102
248
 
103
- describe "#create_view_method" do
104
- before :each do
105
- @object = @klass.new(DesignModel)
249
+ describe "Scenario testing" do
250
+
251
+ describe "with auto update disabled" do
252
+
253
+ before :all do
254
+ reset_test_db!
255
+ @mod = DesignsNoAutoUpdate
256
+ end
257
+
258
+ before(:all) do
259
+ id = @mod.to_s
260
+ doc = CouchRest::Document.new("_id" => "_design/#{id}")
261
+ doc["language"] = "javascript"
262
+ doc["views"] = {"all" => {"map" => "function(doc) { if (doc['type'] == '#{id}') { emit(doc['_id'],1); } }"},
263
+ "by_title" => {"map" =>
264
+ "function(doc) {
265
+ if ((doc['type'] == '#{id}') && (doc['title'] != null)) {
266
+ emit(doc['title'], 1);
267
+ }
268
+ }", "reduce" => "function(k,v,r) { return sum(v); }"}}
269
+ DB.save_doc doc
270
+ end
271
+
272
+ it "will fail if reduce is not specific in view" do
273
+ @mod.create(:title => 'This is a test')
274
+ lambda { @mod.by_title_fail.first }.should raise_error(RestClient::ResourceNotFound)
106
275
  end
107
276
 
108
- it "should create a method that returns view instance" do
109
- CouchRest::Model::Designs::View.should_receive(:new).with(DesignModel, {}, 'test_view').and_return(nil)
110
- @object.create_view_method('test_view')
111
- DesignModel.test_view
277
+ it "will perform view request" do
278
+ @mod.create(:title => 'This is a test')
279
+ @mod.by_title.first.title.should eql("This is a test")
112
280
  end
113
281
 
114
282
  end
115
283
 
284
+ describe "using views" do
285
+
286
+ describe "to find a single item" do
287
+
288
+ before(:all) do
289
+ reset_test_db!
290
+ %w{aaa bbb ddd eee}.each do |title|
291
+ Course.new(:title => title, :active => (title == 'bbb')).save
292
+ end
293
+ end
294
+
295
+ it "should return single matched record with find helper" do
296
+ course = Course.find_by_title('bbb')
297
+ course.should_not be_nil
298
+ course.title.should eql('bbb') # Ensure really is a Course!
299
+ end
300
+
301
+ it "should return nil if not found" do
302
+ course = Course.find_by_title('fff')
303
+ course.should be_nil
304
+ end
305
+
306
+ it "should peform search on view with two properties" do
307
+ course = Course.find_by_title_and_active(['bbb', true])
308
+ course.should_not be_nil
309
+ course.title.should eql('bbb') # Ensure really is a Course!
310
+ end
311
+
312
+ it "should return nil if not found" do
313
+ course = Course.find_by_title_and_active(['bbb', false])
314
+ course.should be_nil
315
+ end
316
+
317
+ it "should raise exception if view not present" do
318
+ lambda { Course.find_by_foobar('123') }.should raise_error(NoMethodError)
319
+ end
320
+
321
+ end
322
+
323
+ describe "a model class with database provided manually" do
324
+
325
+ class Unattached < CouchRest::Model::Base
326
+ property :title
327
+ property :questions
328
+ property :professor
329
+ design do
330
+ view :by_title
331
+ end
332
+
333
+ # Force the database to always be nil
334
+ def self.database
335
+ nil
336
+ end
337
+ end
338
+
339
+ before(:all) do
340
+ reset_test_db!
341
+ @db = DB
342
+ %w{aaa bbb ddd eee}.each do |title|
343
+ u = Unattached.new(:title => title)
344
+ u.database = @db
345
+ u.save
346
+ @first_id ||= u.id
347
+ end
348
+ end
349
+ it "should barf on all if no database given" do
350
+ lambda{Unattached.all.first}.should raise_error
351
+ end
352
+ it "should query all" do
353
+ rs = Unattached.all.database(@db).all
354
+ rs.length.should == 4
355
+ end
356
+ it "should barf on query if no database given" do
357
+ lambda{Unattached.by_title.all}.should raise_error /Database must be defined/
358
+ end
359
+ it "should make the design doc upon first query" do
360
+ Unattached.by_title.database(@db)
361
+ doc = Unattached.design_doc
362
+ doc['views']['all']['map'].should include('Unattached')
363
+ end
364
+ it "should merge query params" do
365
+ rs = Unattached.by_title.database(@db).startkey("bbb").endkey("eee")
366
+ rs.length.should == 3
367
+ end
368
+ it "should return nil on get if no database given" do
369
+ Unattached.get("aaa").should be_nil
370
+ end
371
+ it "should barf on get! if no database given" do
372
+ lambda{Unattached.get!("aaa")}.should raise_error
373
+ end
374
+ it "should get from specific database" do
375
+ u = Unattached.get(@first_id, @db)
376
+ u.title.should == "aaa"
377
+ end
378
+ it "should barf on first if no database given" do
379
+ lambda{Unattached.first}.should raise_error
380
+ end
381
+ it "should get first" do
382
+ u = Unattached.all.database(@db).first
383
+ u.title.should =~ /\A...\z/
384
+ end
385
+ it "should get last" do
386
+ u = Unattached.all.database(@db).last
387
+ u.title.should == "aaa"
388
+ end
389
+
390
+ end
391
+
392
+ describe "a model with a compound key view" do
393
+ before(:all) do
394
+ reset_test_db!
395
+ written_at = Time.now - 24 * 3600 * 7
396
+ @titles = ["uniq one", "even more interesting", "less fun", "not junk"]
397
+ @user_ids = ["quentin", "aaron"]
398
+ @titles.each_with_index do |title,i|
399
+ u = i % 2
400
+ a = Article.new(:title => title, :user_id => @user_ids[u])
401
+ a.date = written_at
402
+ a.save
403
+ written_at += 24 * 3600
404
+ end
405
+ end
406
+ it "should create the design doc" do
407
+ Article.by_user_id_and_date rescue nil
408
+ doc = Article.design_doc
409
+ doc['views']['by_date'].should_not be_nil
410
+ end
411
+ it "should sort correctly" do
412
+ articles = Article.by_user_id_and_date.all
413
+ articles.collect{|a|a['user_id']}.should == ['aaron', 'aaron', 'quentin',
414
+ 'quentin']
415
+ articles[1].title.should == 'not junk'
416
+ end
417
+ it "should be queryable with couchrest options" do
418
+ articles = Article.by_user_id_and_date(:limit => 1, :startkey => 'quentin').all
419
+ articles.length.should == 1
420
+ articles[0].title.should == "even more interesting"
421
+ end
422
+ end
423
+
424
+
425
+ end
426
+
116
427
  end
117
428
 
118
429
  end
@@ -99,6 +99,32 @@ describe "Dirty" do
99
99
  @card.first_name_changed?.should be_true
100
100
  end
101
101
 
102
+ it 'should report changes if the record is modified by attributes' do
103
+ @card = Card.new
104
+ @card.attributes = {:first_name => 'danny'}
105
+ @card.changed?.should be_true
106
+ @card.first_name_changed?.should be_true
107
+ end
108
+
109
+ it 'should report no changes if the record is modified with an invalid property by attributes' do
110
+ @card = Card.new
111
+ @card.attributes = {:middle_name => 'danny'}
112
+ @card.changed?.should be_false
113
+ @card.first_name_changed?.should be_false
114
+ end
115
+
116
+ it "should report no changes if the record is modified with update_attributes" do
117
+ @card = Card.new
118
+ @card.update_attributes(:first_name => 'henry')
119
+ @card.changed?.should be_false
120
+ end
121
+
122
+ it "should report no changes if the record is modified with an invalid property by update_attributes" do
123
+ @card = Card.new
124
+ @card.update_attributes(:middle_name => 'peter')
125
+ @card.changed?.should be_false
126
+ end
127
+
102
128
  it "should report no changes for unmodified records" do
103
129
  card_id = Card.create!(:first_name => "matt").id
104
130
  @card = Card.find(card_id)
@@ -433,4 +459,45 @@ describe "Dirty" do
433
459
 
434
460
  end
435
461
 
462
+
463
+ describe "when mass_assign_any_attribute true" do
464
+ before(:each) do
465
+ # dupe Card class so that no other tests are effected
466
+ card_class = Card.dup
467
+ card_class.class_eval do
468
+ mass_assign_any_attribute true
469
+ end
470
+ @card = card_class.new(:first_name => 'Sam')
471
+ end
472
+
473
+ it "should report no changes if the record is modified with update_attributes" do
474
+ @card.update_attributes(:other_name => 'henry')
475
+ @card.changed?.should be_false
476
+ end
477
+
478
+ it "should report not new if the record is modified with update_attributes" do
479
+ @card.update_attributes(:other_name => 'henry')
480
+ @card.new?.should be_false
481
+ end
482
+
483
+ it 'should report changes when updated with attributes' do
484
+ @card.save
485
+ @card.attributes = {:testing => 'fooobar'}
486
+ @card.changed?.should be_true
487
+ end
488
+
489
+ it 'should report changes when updated with a known property' do
490
+ @card.save
491
+ @card.first_name = 'Danny'
492
+ @card.changed?.should be_true
493
+ end
494
+
495
+ it "should not report changes if property is updated with same value" do
496
+ @card.update_attributes :testing => 'fooobar'
497
+ @card.attributes = {'testing' => 'fooobar'}
498
+ @card.changed?.should be_false
499
+ end
500
+
501
+ end
502
+
436
503
  end
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  class PlainParent
4
- class_inheritable_accessor :foo
4
+ class_attribute :foo
5
5
  self.foo = :bar
6
6
  end
7
7
 
@@ -9,7 +9,7 @@ class PlainChild < PlainParent
9
9
  end
10
10
 
11
11
  class ExtendedParent < CouchRest::Model::Base
12
- class_inheritable_accessor :foo
12
+ class_attribute :foo
13
13
  self.foo = :bar
14
14
  end
15
15
 
@@ -150,7 +150,9 @@ describe "Model Attributes" do
150
150
  use_database TEST_SERVER.default_database
151
151
  property :name
152
152
  property :admin, :default => false, :protected => true
153
- view_by :name
153
+ design do
154
+ view :by_name
155
+ end
154
156
  end
155
157
 
156
158
  before(:each) do
@@ -148,6 +148,26 @@ describe CouchRest::Model::Property do
148
148
  @card['test'].should be_nil
149
149
  end
150
150
 
151
+ it 'should not allow them to be updated with update_attributes' do
152
+ @card.update_attributes(:test => 'fooobar')
153
+ @card['test'].should be_nil
154
+ end
155
+
156
+ it 'should not have a different revision after update_attributes' do
157
+ @card.save
158
+ rev = @card.rev
159
+ @card.update_attributes(:test => 'fooobar')
160
+ @card.rev.should eql(rev)
161
+ end
162
+
163
+ it 'should not have a different revision after save' do
164
+ @card.save
165
+ rev = @card.rev
166
+ @card.attributes = {:test => 'fooobar'}
167
+ @card.save
168
+ @card.rev.should eql(rev)
169
+ end
170
+
151
171
  end
152
172
 
153
173
  describe "when mass_assign_any_attribute true" do
@@ -161,13 +181,33 @@ describe CouchRest::Model::Property do
161
181
  end
162
182
 
163
183
  it 'should allow them to be updated' do
164
- @card.attributes = {:test => 'fooobar'}
165
- @card['test'].should eql('fooobar')
184
+ @card.attributes = {:testing => 'fooobar'}
185
+ @card['testing'].should eql('fooobar')
186
+ end
187
+
188
+ it 'should allow them to be updated with update_attributes' do
189
+ @card.update_attributes(:testing => 'fooobar')
190
+ @card['testing'].should eql('fooobar')
166
191
  end
192
+
193
+ it 'should have a different revision after update_attributes' do
194
+ @card.save
195
+ rev = @card.rev
196
+ @card.update_attributes(:testing => 'fooobar')
197
+ @card.rev.should_not eql(rev)
198
+ end
199
+
200
+ it 'should have a different revision after save' do
201
+ @card.save
202
+ rev = @card.rev
203
+ @card.attributes = {:testing => 'fooobar'}
204
+ @card.save
205
+ @card.rev.should_not eql(rev)
206
+ end
207
+
167
208
  end
168
209
  end
169
210
 
170
-
171
211
  describe "mass assignment protection" do
172
212
 
173
213
  it "should not store protected attribute using mass assignment" do