couchrest_model 1.1.2 → 1.2.0.beta

Sign up to get free protection for your applications and to get access to all the features.
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