openlogic-couchrest_model 1.0.0

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 (107) hide show
  1. data/.gitignore +11 -0
  2. data/.rspec +4 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +176 -0
  5. data/README.md +137 -0
  6. data/Rakefile +38 -0
  7. data/THANKS.md +21 -0
  8. data/VERSION +1 -0
  9. data/benchmarks/dirty.rb +118 -0
  10. data/couchrest_model.gemspec +36 -0
  11. data/history.md +309 -0
  12. data/init.rb +1 -0
  13. data/lib/couchrest/model.rb +10 -0
  14. data/lib/couchrest/model/associations.rb +231 -0
  15. data/lib/couchrest/model/base.rb +129 -0
  16. data/lib/couchrest/model/callbacks.rb +28 -0
  17. data/lib/couchrest/model/casted_array.rb +83 -0
  18. data/lib/couchrest/model/casted_by.rb +33 -0
  19. data/lib/couchrest/model/casted_hash.rb +84 -0
  20. data/lib/couchrest/model/class_proxy.rb +135 -0
  21. data/lib/couchrest/model/collection.rb +273 -0
  22. data/lib/couchrest/model/configuration.rb +67 -0
  23. data/lib/couchrest/model/connection.rb +70 -0
  24. data/lib/couchrest/model/core_extensions/hash.rb +9 -0
  25. data/lib/couchrest/model/core_extensions/time_parsing.rb +66 -0
  26. data/lib/couchrest/model/design_doc.rb +128 -0
  27. data/lib/couchrest/model/designs.rb +91 -0
  28. data/lib/couchrest/model/designs/view.rb +513 -0
  29. data/lib/couchrest/model/dirty.rb +39 -0
  30. data/lib/couchrest/model/document_queries.rb +99 -0
  31. data/lib/couchrest/model/embeddable.rb +78 -0
  32. data/lib/couchrest/model/errors.rb +25 -0
  33. data/lib/couchrest/model/extended_attachments.rb +83 -0
  34. data/lib/couchrest/model/persistence.rb +178 -0
  35. data/lib/couchrest/model/properties.rb +228 -0
  36. data/lib/couchrest/model/property.rb +114 -0
  37. data/lib/couchrest/model/property_protection.rb +71 -0
  38. data/lib/couchrest/model/proxyable.rb +183 -0
  39. data/lib/couchrest/model/support/couchrest_database.rb +13 -0
  40. data/lib/couchrest/model/support/couchrest_design.rb +33 -0
  41. data/lib/couchrest/model/typecast.rb +154 -0
  42. data/lib/couchrest/model/validations.rb +80 -0
  43. data/lib/couchrest/model/validations/casted_model.rb +16 -0
  44. data/lib/couchrest/model/validations/locale/en.yml +5 -0
  45. data/lib/couchrest/model/validations/uniqueness.rb +69 -0
  46. data/lib/couchrest/model/views.rb +151 -0
  47. data/lib/couchrest/railtie.rb +24 -0
  48. data/lib/couchrest_model.rb +66 -0
  49. data/lib/rails/generators/couchrest_model.rb +16 -0
  50. data/lib/rails/generators/couchrest_model/config/config_generator.rb +18 -0
  51. data/lib/rails/generators/couchrest_model/config/templates/couchdb.yml +21 -0
  52. data/lib/rails/generators/couchrest_model/model/model_generator.rb +27 -0
  53. data/lib/rails/generators/couchrest_model/model/templates/model.rb +2 -0
  54. data/spec/.gitignore +1 -0
  55. data/spec/fixtures/attachments/README +3 -0
  56. data/spec/fixtures/attachments/couchdb.png +0 -0
  57. data/spec/fixtures/attachments/test.html +11 -0
  58. data/spec/fixtures/config/couchdb.yml +10 -0
  59. data/spec/fixtures/models/article.rb +36 -0
  60. data/spec/fixtures/models/base.rb +164 -0
  61. data/spec/fixtures/models/card.rb +19 -0
  62. data/spec/fixtures/models/cat.rb +23 -0
  63. data/spec/fixtures/models/client.rb +6 -0
  64. data/spec/fixtures/models/course.rb +27 -0
  65. data/spec/fixtures/models/event.rb +8 -0
  66. data/spec/fixtures/models/invoice.rb +14 -0
  67. data/spec/fixtures/models/key_chain.rb +5 -0
  68. data/spec/fixtures/models/membership.rb +4 -0
  69. data/spec/fixtures/models/person.rb +11 -0
  70. data/spec/fixtures/models/project.rb +6 -0
  71. data/spec/fixtures/models/question.rb +7 -0
  72. data/spec/fixtures/models/sale_entry.rb +9 -0
  73. data/spec/fixtures/models/sale_invoice.rb +14 -0
  74. data/spec/fixtures/models/service.rb +10 -0
  75. data/spec/fixtures/models/user.rb +22 -0
  76. data/spec/fixtures/views/lib.js +3 -0
  77. data/spec/fixtures/views/test_view/lib.js +3 -0
  78. data/spec/fixtures/views/test_view/only-map.js +4 -0
  79. data/spec/fixtures/views/test_view/test-map.js +3 -0
  80. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  81. data/spec/functional/validations_spec.rb +8 -0
  82. data/spec/spec_helper.rb +60 -0
  83. data/spec/unit/active_model_lint_spec.rb +30 -0
  84. data/spec/unit/assocations_spec.rb +242 -0
  85. data/spec/unit/attachment_spec.rb +176 -0
  86. data/spec/unit/base_spec.rb +537 -0
  87. data/spec/unit/casted_spec.rb +72 -0
  88. data/spec/unit/class_proxy_spec.rb +167 -0
  89. data/spec/unit/collection_spec.rb +86 -0
  90. data/spec/unit/configuration_spec.rb +77 -0
  91. data/spec/unit/connection_spec.rb +148 -0
  92. data/spec/unit/core_extensions/time_parsing.rb +77 -0
  93. data/spec/unit/design_doc_spec.rb +241 -0
  94. data/spec/unit/designs/view_spec.rb +831 -0
  95. data/spec/unit/designs_spec.rb +134 -0
  96. data/spec/unit/dirty_spec.rb +436 -0
  97. data/spec/unit/embeddable_spec.rb +498 -0
  98. data/spec/unit/inherited_spec.rb +33 -0
  99. data/spec/unit/persistence_spec.rb +481 -0
  100. data/spec/unit/property_protection_spec.rb +192 -0
  101. data/spec/unit/property_spec.rb +481 -0
  102. data/spec/unit/proxyable_spec.rb +376 -0
  103. data/spec/unit/subclass_spec.rb +85 -0
  104. data/spec/unit/typecast_spec.rb +521 -0
  105. data/spec/unit/validations_spec.rb +140 -0
  106. data/spec/unit/view_spec.rb +367 -0
  107. metadata +301 -0
@@ -0,0 +1,376 @@
1
+ require "spec_helper"
2
+
3
+ class DummyProxyable < CouchRest::Model::Base
4
+ proxy_database_method :db
5
+ def db
6
+ 'db'
7
+ end
8
+ end
9
+
10
+ class ProxyKitten < CouchRest::Model::Base
11
+ end
12
+
13
+ describe CouchRest::Model::Proxyable do
14
+
15
+ describe "#proxy_database" do
16
+
17
+ before do
18
+ @class = Class.new(CouchRest::Model::Base)
19
+ @class.class_eval do
20
+ def slug; 'proxy'; end
21
+ end
22
+ @obj = @class.new
23
+ end
24
+
25
+ it "should respond to method" do
26
+ @obj.should respond_to(:proxy_database)
27
+ end
28
+
29
+ it "should provide proxy database from method" do
30
+ @class.stub!(:proxy_database_method).twice.and_return(:slug)
31
+ @obj.proxy_database.should be_a(CouchRest::Database)
32
+ @obj.proxy_database.name.should eql('couchrest_proxy')
33
+ end
34
+
35
+ it "should raise an error if called and no proxy_database_method set" do
36
+ lambda { @obj.proxy_database }.should raise_error(StandardError, /Please set/)
37
+ end
38
+
39
+ end
40
+
41
+ describe "class methods" do
42
+
43
+
44
+ describe ".proxy_owner_method" do
45
+ before(:each) do
46
+ @class = DummyProxyable.clone
47
+ end
48
+ it "should provide proxy_owner_method accessors" do
49
+ @class.should respond_to(:proxy_owner_method)
50
+ @class.should respond_to(:proxy_owner_method=)
51
+ end
52
+ it "should work as expected" do
53
+ @class.proxy_owner_method = "foo"
54
+ @class.proxy_owner_method.should eql("foo")
55
+ end
56
+ end
57
+
58
+ describe ".proxy_database_method" do
59
+ before do
60
+ @class = Class.new(CouchRest::Model::Base)
61
+ end
62
+ it "should be possible to set the proxy database method" do
63
+ @class.proxy_database_method :db
64
+ @class.proxy_database_method.should eql(:db)
65
+ end
66
+ end
67
+
68
+ describe ".proxy_for" do
69
+ before(:each) do
70
+ @class = DummyProxyable.clone
71
+ end
72
+
73
+ it "should be provided" do
74
+ @class.should respond_to(:proxy_for)
75
+ end
76
+
77
+ it "should create a new method" do
78
+ DummyProxyable.stub!(:method_defined?).and_return(true)
79
+ DummyProxyable.proxy_for(:cats)
80
+ DummyProxyable.new.should respond_to(:cats)
81
+ end
82
+
83
+ describe "generated method" do
84
+ it "should call ModelProxy" do
85
+ DummyProxyable.proxy_for(:cats)
86
+ @obj = DummyProxyable.new
87
+ CouchRest::Model::Proxyable::ModelProxy.should_receive(:new).with(Cat, @obj, 'dummy_proxyable', 'db').and_return(true)
88
+ @obj.should_receive(:proxy_database).and_return('db')
89
+ @obj.cats
90
+ end
91
+
92
+ it "should call class on root namespace" do
93
+ class ::Document < CouchRest::Model::Base
94
+ def self.foo; puts 'bar'; end
95
+ end
96
+ DummyProxyable.proxy_for(:documents)
97
+ @obj = DummyProxyable.new
98
+ CouchRest::Model::Proxyable::ModelProxy.should_receive(:new).with(::Document, @obj, 'dummy_proxyable', 'db').and_return(true)
99
+ @obj.should_receive('proxy_database').and_return('db')
100
+ @obj.documents
101
+ end
102
+ end
103
+ end
104
+
105
+ describe ".proxied_by" do
106
+ before do
107
+ @class = Class.new(CouchRest::Model::Base)
108
+ end
109
+
110
+ it "should be provided" do
111
+ @class.should respond_to(:proxied_by)
112
+ end
113
+
114
+ it "should add an attribute accessor" do
115
+ @class.proxied_by(:foobar)
116
+ @class.new.should respond_to(:foobar)
117
+ end
118
+
119
+ it "should provide #model_proxy method" do
120
+ @class.proxied_by(:foobar)
121
+ @class.new.should respond_to(:model_proxy)
122
+ end
123
+
124
+ it "should set the proxy_owner_method" do
125
+ @class.proxied_by(:foobar)
126
+ @class.proxy_owner_method.should eql(:foobar)
127
+ end
128
+
129
+ it "should raise an error if model name pre-defined" do
130
+ lambda { @class.proxied_by(:object_id) }.should raise_error
131
+ end
132
+
133
+ it "should raise an error if object already has a proxy" do
134
+ @class.proxied_by(:department)
135
+ lambda { @class.proxied_by(:company) }.should raise_error
136
+ end
137
+
138
+ it "should overwrite the database method to provide an error" do
139
+ @class.proxied_by(:company)
140
+ lambda { @class.database }.should raise_error(StandardError, /database must be accessed via/)
141
+ end
142
+ end
143
+ end
144
+
145
+ describe "ModelProxy" do
146
+
147
+ before :all do
148
+ @klass = CouchRest::Model::Proxyable::ModelProxy
149
+ end
150
+
151
+ it "should initialize and set variables" do
152
+ @obj = @klass.new(Cat, 'owner', 'owner_name', 'database')
153
+ @obj.model.should eql(Cat)
154
+ @obj.owner.should eql('owner')
155
+ @obj.owner_name.should eql('owner_name')
156
+ @obj.database.should eql('database')
157
+ end
158
+
159
+ describe "instance" do
160
+
161
+ before :each do
162
+ @obj = @klass.new(Cat, 'owner', 'owner_name', 'database')
163
+ end
164
+
165
+ it "should proxy new call" do
166
+ @obj.should_receive(:proxy_block_update).with(:new, 'attrs', 'opts')
167
+ @obj.new('attrs', 'opts')
168
+ end
169
+
170
+ it "should proxy build_from_database" do
171
+ @obj.should_receive(:proxy_block_update).with(:build_from_database, 'attrs', 'opts')
172
+ @obj.build_from_database('attrs', 'opts')
173
+ end
174
+
175
+ describe "#method_missing" do
176
+ it "should return design view object" do
177
+ m = "by_some_property"
178
+ inst = mock('DesignView')
179
+ inst.stub!(:proxy).and_return(inst)
180
+ @obj.should_receive(:has_view?).with(m).and_return(true)
181
+ Cat.should_receive(:respond_to?).with(m).and_return(true)
182
+ Cat.should_receive(:send).with(m).and_return(inst)
183
+ @obj.method_missing(m).should eql(inst)
184
+ end
185
+
186
+ it "should call view if necessary" do
187
+ m = "by_some_property"
188
+ @obj.should_receive(:has_view?).with(m).and_return(true)
189
+ Cat.should_receive(:respond_to?).with(m).and_return(false)
190
+ @obj.should_receive(:view).with(m, {}).and_return('view')
191
+ @obj.method_missing(m).should eql('view')
192
+ end
193
+
194
+ it "should provide wrapper for #first_from_view" do
195
+ m = "find_by_some_property"
196
+ view = "by_some_property"
197
+ @obj.should_receive(:has_view?).with(m).and_return(false)
198
+ @obj.should_receive(:has_view?).with(view).and_return(true)
199
+ @obj.should_receive(:first_from_view).with(view).and_return('view')
200
+ @obj.method_missing(m).should eql('view')
201
+ end
202
+
203
+ end
204
+
205
+ it "should proxy #all" do
206
+ Cat.should_receive(:all).with({:database => 'database'})
207
+ @obj.should_receive(:proxy_update_all)
208
+ @obj.all
209
+ end
210
+
211
+ it "should proxy #count" do
212
+ Cat.should_receive(:all).with({:database => 'database', :raw => true, :limit => 0}).and_return({'total_rows' => 3})
213
+ @obj.count.should eql(3)
214
+ end
215
+
216
+ it "should proxy #first" do
217
+ Cat.should_receive(:first).with({:database => 'database'})
218
+ @obj.should_receive(:proxy_update)
219
+ @obj.first
220
+ end
221
+
222
+ it "should proxy #last" do
223
+ Cat.should_receive(:last).with({:database => 'database'})
224
+ @obj.should_receive(:proxy_update)
225
+ @obj.last
226
+ end
227
+
228
+ it "should proxy #get" do
229
+ Cat.should_receive(:get).with(32, 'database')
230
+ @obj.should_receive(:proxy_update)
231
+ @obj.get(32)
232
+ end
233
+ it "should proxy #find" do
234
+ Cat.should_receive(:get).with(32, 'database')
235
+ @obj.should_receive(:proxy_update)
236
+ @obj.find(32)
237
+ end
238
+
239
+ it "should proxy #has_view?" do
240
+ Cat.should_receive(:has_view?).with('view').and_return(false)
241
+ @obj.has_view?('view')
242
+ end
243
+
244
+ it "should proxy #view_by" do
245
+ Cat.should_receive(:view_by).with('name').and_return(false)
246
+ @obj.view_by('name')
247
+ end
248
+
249
+ it "should proxy #view" do
250
+ Cat.should_receive(:view).with('view', {:database => 'database'})
251
+ @obj.should_receive(:proxy_update_all)
252
+ @obj.view('view')
253
+ end
254
+
255
+ it "should proxy #first_from_view" do
256
+ Cat.should_receive(:first_from_view).with('view', {:database => 'database'})
257
+ @obj.should_receive(:proxy_update)
258
+ @obj.first_from_view('view')
259
+ end
260
+
261
+ it "should proxy design_doc" do
262
+ Cat.should_receive(:design_doc)
263
+ @obj.design_doc
264
+ end
265
+
266
+ describe "#save_design_doc" do
267
+ it "should be proxied without args" do
268
+ Cat.should_receive(:save_design_doc).with('database')
269
+ @obj.save_design_doc
270
+ end
271
+
272
+ it "should be proxied with database arg" do
273
+ Cat.should_receive(:save_design_doc).with('db')
274
+ @obj.save_design_doc('db')
275
+ end
276
+ end
277
+
278
+
279
+
280
+ ### Updating methods
281
+
282
+ describe "#proxy_update" do
283
+ it "should set returned doc fields" do
284
+ doc = mock(:Document)
285
+ doc.should_receive(:is_a?).with(Cat).and_return(true)
286
+ doc.should_receive(:database=).with('database')
287
+ doc.should_receive(:model_proxy=).with(@obj)
288
+ doc.should_receive(:send).with('owner_name=', 'owner')
289
+ @obj.send(:proxy_update, doc).should eql(doc)
290
+ end
291
+
292
+ it "should not set anything if matching document not provided" do
293
+ doc = mock(:DocumentFoo)
294
+ doc.should_receive(:is_a?).with(Cat).and_return(false)
295
+ doc.should_not_receive(:database=)
296
+ doc.should_not_receive(:model_proxy=)
297
+ doc.should_not_receive(:owner_name=)
298
+ @obj.send(:proxy_update, doc).should eql(doc)
299
+ end
300
+
301
+ it "should pass nil straight through without errors" do
302
+ lambda { @obj.send(:proxy_update, nil).should eql(nil) }.should_not raise_error
303
+ end
304
+ end
305
+
306
+ it "#proxy_update_all should update array of docs" do
307
+ docs = [{}, {}]
308
+ @obj.should_receive(:proxy_update).twice.with({})
309
+ @obj.send(:proxy_update_all, docs)
310
+ end
311
+
312
+ describe "#proxy_block_update" do
313
+ it "should proxy block updates" do
314
+ doc = { }
315
+ @obj.model.should_receive(:new).and_yield(doc)
316
+ @obj.should_receive(:proxy_update).with(doc)
317
+ @obj.send(:proxy_block_update, :new)
318
+ end
319
+ end
320
+
321
+ end
322
+
323
+ end
324
+
325
+ describe "scenarios" do
326
+
327
+ before :all do
328
+ class ProxyableCompany < CouchRest::Model::Base
329
+ use_database DB
330
+ property :slug
331
+ proxy_for :proxyable_invoices
332
+ def proxy_database
333
+ @db ||= TEST_SERVER.database!(TESTDB + "-#{slug}")
334
+ end
335
+ end
336
+
337
+ class ProxyableInvoice < CouchRest::Model::Base
338
+ property :client
339
+ property :total
340
+ proxied_by :proxyable_company
341
+ validates_uniqueness_of :client
342
+ design do
343
+ view :by_total
344
+ end
345
+ end
346
+
347
+
348
+ @company = ProxyableCompany.create(:slug => 'samco')
349
+ end
350
+
351
+ it "should create the new database" do
352
+ @company.proxyable_invoices.all.should be_empty
353
+ TEST_SERVER.databases.find{|db| db =~ /#{TESTDB}-samco/}.should_not be_nil
354
+ end
355
+
356
+ it "should allow creation of new entries" do
357
+ inv = @company.proxyable_invoices.new(:client => "Lorena", :total => 35)
358
+ # inv.database.should_not be_nil
359
+ inv.save.should be_true
360
+ @company.proxyable_invoices.count.should eql(1)
361
+ @company.proxyable_invoices.first.client.should eql("Lorena")
362
+ end
363
+
364
+ it "should validate uniqueness" do
365
+ inv = @company.proxyable_invoices.new(:client => "Lorena", :total => 40)
366
+ inv.save.should be_false
367
+ end
368
+
369
+ it "should allow design views" do
370
+ item = @company.proxyable_invoices.by_total.key(35).first
371
+ item.client.should eql('Lorena')
372
+ end
373
+
374
+ end
375
+
376
+ end
@@ -0,0 +1,85 @@
1
+ require "spec_helper"
2
+
3
+ # add a default value
4
+ Card.property :bg_color, :default => '#ccc'
5
+
6
+ class BusinessCard < Card
7
+ property :extension_code
8
+ property :job_title
9
+
10
+ validates_presence_of :extension_code
11
+ validates_presence_of :job_title
12
+ end
13
+
14
+ class DesignBusinessCard < BusinessCard
15
+ property :bg_color, :default => '#eee'
16
+ end
17
+
18
+ class OnlineCourse < Course
19
+ property :url
20
+ view_by :url
21
+ end
22
+
23
+ class Animal < CouchRest::Model::Base
24
+ use_database TEST_SERVER.default_database
25
+ property :name
26
+ view_by :name
27
+ end
28
+
29
+ class Dog < Animal; end
30
+
31
+ describe "Subclassing a Model" do
32
+
33
+ before(:each) do
34
+ @card = BusinessCard.new
35
+ end
36
+
37
+ it "shouldn't messup the parent's properties" do
38
+ Card.properties.should_not == BusinessCard.properties
39
+ end
40
+
41
+ it "should share the same db default" do
42
+ @card.database.uri.should == Card.database.uri
43
+ end
44
+
45
+ it "should have kept the validation details" do
46
+ @card.should_not be_valid
47
+ end
48
+
49
+ it "should have added the new validation details" do
50
+ validated_fields = @card.class.validators.map{|v| v.attributes}.flatten
51
+ validated_fields.should include(:extension_code)
52
+ validated_fields.should include(:job_title)
53
+ end
54
+
55
+ it "should not add to the parent's validations" do
56
+ validated_fields = Card.validators.map{|v| v.attributes}.flatten
57
+ validated_fields.should_not include(:extension_code)
58
+ validated_fields.should_not include(:job_title)
59
+ end
60
+
61
+ it "should inherit default property values" do
62
+ @card.bg_color.should == '#ccc'
63
+ end
64
+
65
+ it "should be able to overwrite a default property" do
66
+ DesignBusinessCard.new.bg_color.should == '#eee'
67
+ end
68
+
69
+ it "should have a design doc slug based on the subclass name" do
70
+ OnlineCourse.design_doc_slug.should =~ /^OnlineCourse/
71
+ end
72
+
73
+ it "should not add views to the parent's design_doc" do
74
+ Course.design_doc['views'].keys.should_not include('by_url')
75
+ end
76
+
77
+ it "should not add the parent's views to its design doc" do
78
+ OnlineCourse.design_doc['views'].keys.should_not include('by_title')
79
+ end
80
+
81
+ it "should have an all view with a guard clause for model == subclass name in the map function" do
82
+ OnlineCourse.design_doc['views']['all']['map'].should =~ /if \(doc\['#{OnlineCourse.model_type_key}'\] == 'OnlineCourse'\)/
83
+ end
84
+ end
85
+