couchrest_model 1.0.0 → 1.1.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 (41) hide show
  1. data/.gitignore +1 -1
  2. data/Gemfile.lock +19 -20
  3. data/README.md +145 -20
  4. data/VERSION +1 -1
  5. data/couchrest_model.gemspec +2 -3
  6. data/history.txt +14 -0
  7. data/lib/couchrest/model/associations.rb +4 -4
  8. data/lib/couchrest/model/base.rb +5 -0
  9. data/lib/couchrest/model/callbacks.rb +1 -2
  10. data/lib/couchrest/model/collection.rb +1 -1
  11. data/lib/couchrest/model/designs/view.rb +486 -0
  12. data/lib/couchrest/model/designs.rb +81 -0
  13. data/lib/couchrest/model/document_queries.rb +1 -1
  14. data/lib/couchrest/model/persistence.rb +25 -16
  15. data/lib/couchrest/model/properties.rb +5 -1
  16. data/lib/couchrest/model/property.rb +2 -2
  17. data/lib/couchrest/model/proxyable.rb +152 -0
  18. data/lib/couchrest/model/typecast.rb +1 -1
  19. data/lib/couchrest/model/validations/casted_model.rb +3 -1
  20. data/lib/couchrest/model/validations/locale/en.yml +1 -1
  21. data/lib/couchrest/model/validations/uniqueness.rb +6 -7
  22. data/lib/couchrest/model/validations.rb +1 -0
  23. data/lib/couchrest/model/views.rb +11 -9
  24. data/lib/couchrest_model.rb +3 -0
  25. data/spec/couchrest/assocations_spec.rb +2 -2
  26. data/spec/couchrest/base_spec.rb +15 -1
  27. data/spec/couchrest/casted_model_spec.rb +30 -12
  28. data/spec/couchrest/class_proxy_spec.rb +2 -2
  29. data/spec/couchrest/collection_spec.rb +89 -0
  30. data/spec/couchrest/designs/view_spec.rb +766 -0
  31. data/spec/couchrest/designs_spec.rb +110 -0
  32. data/spec/couchrest/persistence_spec.rb +36 -7
  33. data/spec/couchrest/property_spec.rb +15 -0
  34. data/spec/couchrest/proxyable_spec.rb +329 -0
  35. data/spec/couchrest/{validations.rb → validations_spec.rb} +1 -3
  36. data/spec/couchrest/view_spec.rb +19 -91
  37. data/spec/fixtures/base.rb +8 -6
  38. data/spec/fixtures/more/article.rb +1 -1
  39. data/spec/fixtures/more/course.rb +4 -2
  40. metadata +21 -76
  41. data/lib/couchrest/model/view.rb +0 -190
@@ -0,0 +1,110 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ class DesignModel < CouchRest::Model::Base
4
+
5
+ end
6
+
7
+ describe "Design" do
8
+
9
+ it "should accessable from model" do
10
+ DesignModel.respond_to?(:design).should be_true
11
+ end
12
+
13
+ describe "class methods" do
14
+
15
+ describe ".design" do
16
+ before :each do
17
+ @mapper = mock('DesignMapper')
18
+ @mapper.stub!(:create_view_method)
19
+ end
20
+
21
+ it "should instantiate a new DesignMapper" do
22
+ CouchRest::Model::Designs::DesignMapper.should_receive(:new).with(DesignModel).and_return(@mapper)
23
+ @mapper.should_receive(:create_view_method).with(:all)
24
+ @mapper.should_receive(:instance_eval)
25
+ DesignModel.design() { }
26
+ end
27
+
28
+ it "should allow methods to be called in mapper" do
29
+ @mapper.should_receive(:foo)
30
+ CouchRest::Model::Designs::DesignMapper.stub!(:new).and_return(@mapper)
31
+ DesignModel.design { foo }
32
+ end
33
+
34
+ it "should request a design refresh" do
35
+ DesignModel.should_receive(:req_design_doc_refresh)
36
+ DesignModel.design() { }
37
+ end
38
+
39
+ it "should work even if a block is not provided" do
40
+ lambda { DesignModel.design }.should_not raise_error
41
+ end
42
+
43
+ end
44
+
45
+ describe "default_per_page" do
46
+ it "should return 25 default" do
47
+ DesignModel.default_per_page.should eql(25)
48
+ end
49
+ end
50
+
51
+ describe ".paginates_per" do
52
+ it "should set the default per page value" do
53
+ DesignModel.paginates_per(21)
54
+ DesignModel.default_per_page.should eql(21)
55
+ end
56
+ end
57
+ end
58
+
59
+ describe "DesignMapper" do
60
+
61
+ before :all do
62
+ @klass = CouchRest::Model::Designs::DesignMapper
63
+ end
64
+
65
+ it "should initialize and set model" do
66
+ object = @klass.new(DesignModel)
67
+ object.send(:model).should eql(DesignModel)
68
+ end
69
+
70
+ describe "#view" do
71
+
72
+ before :each do
73
+ @object = @klass.new(DesignModel)
74
+ end
75
+
76
+ it "should call create method on view" do
77
+ CouchRest::Model::Designs::View.should_receive(:create).with(DesignModel, 'test', {})
78
+ @object.view('test')
79
+ end
80
+
81
+ it "should create a method on parent model" do
82
+ CouchRest::Model::Designs::View.stub!(:create)
83
+ @object.view('test_view')
84
+ DesignModel.should respond_to(:test_view)
85
+ end
86
+
87
+ it "should create a method for view instance" do
88
+ CouchRest::Model::Designs::View.stub!(:create)
89
+ @object.should_receive(:create_view_method).with('test')
90
+ @object.view('test')
91
+ end
92
+
93
+ end
94
+
95
+ describe "#create_view_method" do
96
+ before :each do
97
+ @object = @klass.new(DesignModel)
98
+ end
99
+
100
+ it "should create a method that returns view instance" do
101
+ CouchRest::Model::Designs::View.should_receive(:new).with(DesignModel, {}, 'test_view').and_return(nil)
102
+ @object.create_view_method('test_view')
103
+ DesignModel.test_view
104
+ end
105
+
106
+ end
107
+
108
+ end
109
+
110
+ end
@@ -15,17 +15,17 @@ describe "Model Persistence" do
15
15
  describe "creating a new document from database" do
16
16
 
17
17
  it "should instantialize" do
18
- doc = Article.create_from_database({'_id' => 'testitem1', '_rev' => 123, 'couchrest-type' => 'Article', 'name' => 'my test'})
18
+ doc = Article.build_from_database({'_id' => 'testitem1', '_rev' => 123, 'couchrest-type' => 'Article', 'name' => 'my test'})
19
19
  doc.class.should eql(Article)
20
20
  end
21
21
 
22
22
  it "should instantialize of same class if no couchrest-type included from DB" do
23
- doc = Article.create_from_database({'_id' => 'testitem1', '_rev' => 123, 'name' => 'my test'})
23
+ doc = Article.build_from_database({'_id' => 'testitem1', '_rev' => 123, 'name' => 'my test'})
24
24
  doc.class.should eql(Article)
25
25
  end
26
26
 
27
27
  it "should instantialize document of different type" do
28
- doc = Article.create_from_database({'_id' => 'testitem2', '_rev' => 123, Article.model_type_key => 'WithTemplateAndUniqueID', 'name' => 'my test'})
28
+ doc = Article.build_from_database({'_id' => 'testitem2', '_rev' => 123, Article.model_type_key => 'WithTemplateAndUniqueID', 'name' => 'my test'})
29
29
  doc.class.should eql(WithTemplateAndUniqueID)
30
30
  end
31
31
 
@@ -329,14 +329,14 @@ describe "Model Persistence" do
329
329
 
330
330
  describe "validation" do
331
331
  it "should run before_validation before validating" do
332
- @doc.run_before_validate.should be_nil
332
+ @doc.run_before_validation.should be_nil
333
333
  @doc.should be_valid
334
- @doc.run_before_validate.should be_true
334
+ @doc.run_before_validation.should be_true
335
335
  end
336
336
  it "should run after_validation after validating" do
337
- @doc.run_after_validate.should be_nil
337
+ @doc.run_after_validation.should be_nil
338
338
  @doc.should be_valid
339
- @doc.run_after_validate.should be_true
339
+ @doc.run_after_validation.should be_true
340
340
  end
341
341
  end
342
342
 
@@ -412,4 +412,33 @@ describe "Model Persistence" do
412
412
  end
413
413
 
414
414
 
415
+ describe "#reload" do
416
+ it "reloads defined attributes" do
417
+ i = Article.create!(:title => "Reload when changed")
418
+ i.title.should == "Reload when changed"
419
+
420
+ i.title = "..."
421
+ i.title.should == "..."
422
+
423
+ i.reload
424
+ i.title.should == "Reload when changed"
425
+ end
426
+
427
+ it "reloads defined attributes set to nil" do
428
+ i = Article.create!(:title => "Reload when nil")
429
+ i.title.should == "Reload when nil"
430
+
431
+ i.title = nil
432
+ i.title.should be_nil
433
+
434
+ i.reload
435
+ i.title.should == "Reload when nil"
436
+ end
437
+
438
+ it "returns self" do
439
+ i = Article.create!(:title => "Reload return self")
440
+ i.reload.should be(i)
441
+ end
442
+ end
443
+
415
444
  end
@@ -340,6 +340,11 @@ describe "Model properties" do
340
340
  @course.estimate.should eql(1232434123.323)
341
341
  end
342
342
 
343
+ it "should handle numbers with whitespace" do
344
+ @course.estimate = " 24.35 "
345
+ @course.estimate.should eql(24.35)
346
+ end
347
+
343
348
  [ Object.new, true, '00.0', '0.', '-.0', 'string' ].each do |value|
344
349
  it "does not typecast non-numeric value #{value.inspect}" do
345
350
  @course.estimate = value
@@ -426,6 +431,11 @@ describe "Model properties" do
426
431
  @course['hours'].should eql(-24)
427
432
  end
428
433
 
434
+ it "should handle numbers with whitespace" do
435
+ @course.hours = " 24 "
436
+ @course['hours'].should eql(24)
437
+ end
438
+
429
439
  [ Object.new, true, '00.0', '0.', '-.0', 'string' ].each do |value|
430
440
  it "does not typecast non-numeric value #{value.inspect}" do
431
441
  @course.hours = value
@@ -511,6 +521,11 @@ describe "Model properties" do
511
521
  @course['profit'].should eql(BigDecimal('-24.35'))
512
522
  end
513
523
 
524
+ it "should handle numbers with whitespace" do
525
+ @course.profit = " 24.35 "
526
+ @course['profit'].should eql(BigDecimal('24.35'))
527
+ end
528
+
514
529
  [ Object.new, true, '00.0', '0.', '-.0', 'string' ].each do |value|
515
530
  it "does not typecast non-numeric value #{value.inspect}" do
516
531
  @course.profit = value
@@ -0,0 +1,329 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ require File.join(FIXTURE_PATH, 'more', 'cat')
4
+
5
+ class DummyProxyable < CouchRest::Model::Base
6
+ def proxy_database
7
+ 'db' # Do not use this!
8
+ end
9
+ end
10
+
11
+ class ProxyKitten < CouchRest::Model::Base
12
+ end
13
+
14
+ describe "Proxyable" do
15
+
16
+ it "should provide #model_proxy method" do
17
+ DummyProxyable.new.should respond_to(:model_proxy)
18
+ end
19
+
20
+ describe "class methods" do
21
+
22
+ describe ".proxy_for" do
23
+
24
+ it "should be provided" do
25
+ DummyProxyable.should respond_to(:proxy_for)
26
+ end
27
+
28
+ it "should create a new method" do
29
+ DummyProxyable.stub!(:method_defined?).and_return(true)
30
+ DummyProxyable.proxy_for(:cats)
31
+ DummyProxyable.new.should respond_to(:cats)
32
+ end
33
+
34
+ describe "generated method" do
35
+ it "should call ModelProxy" do
36
+ DummyProxyable.proxy_for(:cats)
37
+ @obj = DummyProxyable.new
38
+ CouchRest::Model::Proxyable::ModelProxy.should_receive(:new).with(Cat, @obj, 'dummy_proxyable', 'db').and_return(true)
39
+ @obj.should_receive('proxy_database').and_return('db')
40
+ @obj.should_receive(:respond_to?).with('proxy_database').and_return(true)
41
+ @obj.cats
42
+ end
43
+
44
+ it "should call class on root namespace" do
45
+ class ::Document < CouchRest::Model::Base
46
+ def self.foo; puts 'bar'; end
47
+ end
48
+ DummyProxyable.proxy_for(:documents)
49
+ @obj = DummyProxyable.new
50
+ CouchRest::Model::Proxyable::ModelProxy.should_receive(:new).with(::Document, @obj, 'dummy_proxyable', 'db').and_return(true)
51
+ @obj.should_receive('proxy_database').and_return('db')
52
+ @obj.should_receive(:respond_to?).with('proxy_database').and_return(true)
53
+ @obj.documents
54
+ end
55
+
56
+ it "should raise an error if the database method is missing" do
57
+ DummyProxyable.proxy_for(:cats)
58
+ @obj = DummyProxyable.new
59
+ @obj.should_receive(:respond_to?).with('proxy_database').and_return(false)
60
+ lambda { @obj.cats }.should raise_error(StandardError, "Missing #proxy_database method for proxy")
61
+ end
62
+
63
+ it "should raise an error if custom database method missing" do
64
+ DummyProxyable.proxy_for(:proxy_kittens, :database_method => "foobardom")
65
+ @obj = DummyProxyable.new
66
+ lambda { @obj.proxy_kittens }.should raise_error(StandardError, "Missing #foobardom method for proxy")
67
+ end
68
+
69
+
70
+ end
71
+
72
+ end
73
+
74
+ describe ".proxied_by" do
75
+ it "should be provided" do
76
+ DummyProxyable.should respond_to(:proxied_by)
77
+ end
78
+
79
+ it "should add an attribute accessor" do
80
+ DummyProxyable.proxied_by(:foobar)
81
+ DummyProxyable.new.should respond_to(:foobar)
82
+ end
83
+
84
+ it "should raise an error if model name pre-defined" do
85
+ lambda { DummyProxyable.proxied_by(:object_id) }.should raise_error
86
+ end
87
+ end
88
+
89
+ end
90
+
91
+ describe "ModelProxy" do
92
+
93
+ before :all do
94
+ @klass = CouchRest::Model::Proxyable::ModelProxy
95
+ end
96
+
97
+ it "should initialize and set variables" do
98
+ @obj = @klass.new(Cat, 'owner', 'owner_name', 'database')
99
+ @obj.model.should eql(Cat)
100
+ @obj.owner.should eql('owner')
101
+ @obj.owner_name.should eql('owner_name')
102
+ @obj.database.should eql('database')
103
+ end
104
+
105
+ describe "instance" do
106
+
107
+ before :each do
108
+ @obj = @klass.new(Cat, 'owner', 'owner_name', 'database')
109
+ end
110
+
111
+ it "should proxy new call" do
112
+ Cat.should_receive(:new).and_return({})
113
+ @obj.should_receive(:proxy_update).and_return(true)
114
+ @obj.new
115
+ end
116
+
117
+ it "should proxy build_from_database" do
118
+ Cat.should_receive(:build_from_database).and_return({})
119
+ @obj.should_receive(:proxy_update).with({}).and_return(true)
120
+ @obj.build_from_database
121
+ end
122
+
123
+ describe "#method_missing" do
124
+ it "should return design view object" do
125
+ m = "by_some_property"
126
+ inst = mock('DesignView')
127
+ inst.stub!(:proxy).and_return(inst)
128
+ @obj.should_receive(:has_view?).with(m).and_return(true)
129
+ Cat.should_receive(:respond_to?).with(m).and_return(true)
130
+ Cat.should_receive(:send).with(m).and_return(inst)
131
+ @obj.method_missing(m).should eql(inst)
132
+ end
133
+
134
+ it "should call view if necessary" do
135
+ m = "by_some_property"
136
+ @obj.should_receive(:has_view?).with(m).and_return(true)
137
+ Cat.should_receive(:respond_to?).with(m).and_return(false)
138
+ @obj.should_receive(:view).with(m, {}).and_return('view')
139
+ @obj.method_missing(m).should eql('view')
140
+ end
141
+
142
+ it "should provide wrapper for #first_from_view" do
143
+ m = "find_by_some_property"
144
+ view = "by_some_property"
145
+ @obj.should_receive(:has_view?).with(m).and_return(false)
146
+ @obj.should_receive(:has_view?).with(view).and_return(true)
147
+ @obj.should_receive(:first_from_view).with(view).and_return('view')
148
+ @obj.method_missing(m).should eql('view')
149
+ end
150
+
151
+ end
152
+
153
+ it "should proxy #all" do
154
+ Cat.should_receive(:all).with({:database => 'database'})
155
+ @obj.should_receive(:proxy_update_all)
156
+ @obj.all
157
+ end
158
+
159
+ it "should proxy #count" do
160
+ Cat.should_receive(:all).with({:database => 'database', :raw => true, :limit => 0}).and_return({'total_rows' => 3})
161
+ @obj.count.should eql(3)
162
+ end
163
+
164
+ it "should proxy #first" do
165
+ Cat.should_receive(:first).with({:database => 'database'})
166
+ @obj.should_receive(:proxy_update)
167
+ @obj.first
168
+ end
169
+
170
+ it "should proxy #last" do
171
+ Cat.should_receive(:last).with({:database => 'database'})
172
+ @obj.should_receive(:proxy_update)
173
+ @obj.last
174
+ end
175
+
176
+ it "should proxy #get" do
177
+ Cat.should_receive(:get).with(32, 'database')
178
+ @obj.should_receive(:proxy_update)
179
+ @obj.get(32)
180
+ end
181
+ it "should proxy #find" do
182
+ Cat.should_receive(:get).with(32, 'database')
183
+ @obj.should_receive(:proxy_update)
184
+ @obj.find(32)
185
+ end
186
+
187
+ it "should proxy #has_view?" do
188
+ Cat.should_receive(:has_view?).with('view').and_return(false)
189
+ @obj.has_view?('view')
190
+ end
191
+
192
+ it "should proxy #view_by" do
193
+ Cat.should_receive(:view_by).with('name').and_return(false)
194
+ @obj.view_by('name')
195
+ end
196
+
197
+ it "should proxy #view" do
198
+ Cat.should_receive(:view).with('view', {:database => 'database'})
199
+ @obj.should_receive(:proxy_update_all)
200
+ @obj.view('view')
201
+ end
202
+
203
+ it "should proxy #first_from_view" do
204
+ Cat.should_receive(:first_from_view).with('view', {:database => 'database'})
205
+ @obj.should_receive(:proxy_update)
206
+ @obj.first_from_view('view')
207
+ end
208
+
209
+ it "should proxy design_doc" do
210
+ Cat.should_receive(:design_doc)
211
+ @obj.design_doc
212
+ end
213
+
214
+ describe "#refresh_design_doc" do
215
+ it "should be proxied without database arg" do
216
+ Cat.should_receive(:refresh_design_doc).with('database')
217
+ @obj.refresh_design_doc
218
+ end
219
+ it "should be proxied with database arg" do
220
+ Cat.should_receive(:refresh_design_doc).with('db')
221
+ @obj.refresh_design_doc('db')
222
+ end
223
+ end
224
+
225
+ describe "#save_design_doc" do
226
+ it "should be proxied without args" do
227
+ Cat.should_receive(:save_design_doc).with('database')
228
+ @obj.save_design_doc
229
+ end
230
+
231
+ it "should be proxied with database arg" do
232
+ Cat.should_receive(:save_design_doc).with('db')
233
+ @obj.save_design_doc('db')
234
+ end
235
+ end
236
+
237
+
238
+
239
+ ### Updating methods
240
+
241
+ describe "#proxy_update" do
242
+ it "should set returned doc fields" do
243
+ doc = mock(:Document)
244
+ doc.should_receive(:respond_to?).with(:database=).and_return(true)
245
+ doc.should_receive(:database=).with('database')
246
+ doc.should_receive(:respond_to?).with(:model_proxy=).and_return(true)
247
+ doc.should_receive(:model_proxy=).with(@obj)
248
+ doc.should_receive(:respond_to?).with('owner_name=').and_return(true)
249
+ doc.should_receive(:send).with('owner_name=', 'owner')
250
+ @obj.send(:proxy_update, doc).should eql(doc)
251
+ end
252
+
253
+ it "should not fail if some fields missing" do
254
+ doc = mock(:Document)
255
+ doc.should_receive(:respond_to?).with(:database=).and_return(true)
256
+ doc.should_receive(:database=).with('database')
257
+ doc.should_receive(:respond_to?).with(:model_proxy=).and_return(false)
258
+ doc.should_not_receive(:model_proxy=)
259
+ doc.should_receive(:respond_to?).with('owner_name=').and_return(false)
260
+ doc.should_not_receive(:owner_name=)
261
+ @obj.send(:proxy_update, doc).should eql(doc)
262
+ end
263
+
264
+ it "should pass nil straight through without errors" do
265
+ lambda { @obj.send(:proxy_update, nil).should eql(nil) }.should_not raise_error
266
+ end
267
+ end
268
+
269
+ it "#proxy_update_all should update array of docs" do
270
+ docs = [{}, {}]
271
+ @obj.should_receive(:proxy_update).twice.with({})
272
+ @obj.send(:proxy_update_all, docs)
273
+ end
274
+
275
+ end
276
+
277
+ end
278
+
279
+ describe "scenarios" do
280
+
281
+ before :all do
282
+ class ProxyableCompany < CouchRest::Model::Base
283
+ use_database DB
284
+ property :slug
285
+ proxy_for :proxyable_invoices
286
+ def proxy_database
287
+ @db ||= TEST_SERVER.database!(TESTDB + "-#{slug}")
288
+ end
289
+ end
290
+
291
+ class ProxyableInvoice < CouchRest::Model::Base
292
+ property :client
293
+ property :total
294
+ proxied_by :proxyable_company
295
+ validates_uniqueness_of :client
296
+ design do
297
+ view :by_total
298
+ end
299
+ end
300
+
301
+
302
+ @company = ProxyableCompany.create(:slug => 'samco')
303
+ end
304
+
305
+ it "should create the new database" do
306
+ @company.proxyable_invoices.all.should be_empty
307
+ TEST_SERVER.databases.find{|db| db =~ /#{TESTDB}-samco/}.should_not be_nil
308
+ end
309
+
310
+ it "should allow creation of new entries" do
311
+ inv = @company.proxyable_invoices.new(:client => "Lorena", :total => 35)
312
+ inv.save.should be_true
313
+ @company.proxyable_invoices.count.should eql(1)
314
+ @company.proxyable_invoices.first.client.should eql("Lorena")
315
+ end
316
+
317
+ it "should validate uniqueness" do
318
+ inv = @company.proxyable_invoices.new(:client => "Lorena", :total => 40)
319
+ inv.save.should be_false
320
+ end
321
+
322
+ it "should allow design views" do
323
+ item = @company.proxyable_invoices.by_total.key(35).first
324
+ item.client.should eql('Lorena')
325
+ end
326
+
327
+ end
328
+
329
+ end
@@ -25,7 +25,7 @@ describe "Validations" do
25
25
  it "should not validate a non-unique document" do
26
26
  @obj = WithUniqueValidation.create(:title => 'title 1')
27
27
  @obj.should_not be_valid
28
- @obj.errors[:title].should eql(['is already taken'])
28
+ @obj.errors[:title].should == ["has already been taken"]
29
29
  end
30
30
 
31
31
  it "should save already created document" do
@@ -57,7 +57,6 @@ describe "Validations" do
57
57
  @obj.class.should_receive('view').and_return({'rows' => [ ]})
58
58
  @obj.valid?
59
59
  end
60
-
61
60
  end
62
61
 
63
62
  context "with a proxy parameter" do
@@ -76,7 +75,6 @@ describe "Validations" do
76
75
  proxy.should_receive('view').and_return({'rows' => [ ]})
77
76
  @obj.valid?
78
77
  end
79
-
80
78
  end
81
79
 
82
80