couchrest_model 1.0.0 → 1.1.0.beta

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