couchrest_model 1.1.0.beta → 1.1.0.beta2
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +15 -15
- data/README.md +42 -0
- data/VERSION +1 -1
- data/couchrest_model.gemspec +1 -1
- data/history.txt +9 -0
- data/lib/couchrest/model/associations.rb +62 -50
- data/lib/couchrest/model/casted_model.rb +1 -1
- data/lib/couchrest/model/{support → core_extensions}/hash.rb +0 -0
- data/lib/couchrest/model/core_extensions/time_parsing.rb +66 -0
- data/lib/couchrest/model/designs/view.rb +34 -14
- data/lib/couchrest/model/properties.rb +4 -2
- data/lib/couchrest/model/proxyable.rb +18 -13
- data/lib/couchrest/model/typecast.rb +8 -28
- data/lib/couchrest/model/validations/uniqueness.rb +20 -9
- data/lib/couchrest/model/views.rb +1 -1
- data/lib/couchrest_model.rb +3 -1
- data/spec/couchrest/assocations_spec.rb +31 -10
- data/spec/couchrest/core_extensions/time_parsing.rb +77 -0
- data/spec/couchrest/designs/view_spec.rb +60 -19
- data/spec/couchrest/property_spec.rb +1 -505
- data/spec/couchrest/proxyable_spec.rb +46 -27
- data/spec/couchrest/typecast_spec.rb +524 -0
- data/spec/couchrest/validations_spec.rb +84 -36
- data/spec/fixtures/base.rb +9 -0
- metadata +10 -6
@@ -14,25 +14,36 @@ module CouchRest
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def validate_each(document, attribute, value)
|
17
|
-
|
18
|
-
|
17
|
+
keys = [attribute]
|
18
|
+
unless options[:scope].nil?
|
19
|
+
keys = (options[:scope].is_a?(Array) ? options[:scope] : [options[:scope]]) + keys
|
20
|
+
end
|
21
|
+
values = keys.map{|k| document.send(k)}
|
22
|
+
values = values.first if values.length == 1
|
23
|
+
|
24
|
+
view_name = options[:view].nil? ? "by_#{keys.join('_and_')}" : options[:view]
|
25
|
+
|
26
|
+
model = (document.respond_to?(:model_proxy) && document.model_proxy ? document.model_proxy : @model)
|
19
27
|
# Determine the base of the search
|
20
28
|
base = options[:proxy].nil? ? model : document.instance_eval(options[:proxy])
|
21
29
|
|
22
30
|
if base.respond_to?(:has_view?) && !base.has_view?(view_name)
|
23
31
|
raise "View #{document.class.name}.#{options[:view]} does not exist!" unless options[:view].nil?
|
24
|
-
|
32
|
+
keys << {:allow_nil => true}
|
33
|
+
model.view_by(*keys)
|
25
34
|
end
|
26
35
|
|
27
|
-
|
28
|
-
return if
|
36
|
+
rows = base.view(view_name, :key => values, :limit => 2, :include_docs => false)['rows']
|
37
|
+
return if rows.empty?
|
29
38
|
|
30
39
|
unless document.new?
|
31
|
-
return if
|
40
|
+
return if rows.find{|row| row['id'] == document.id}
|
32
41
|
end
|
33
|
-
|
34
|
-
if
|
35
|
-
|
42
|
+
|
43
|
+
if rows.length > 0
|
44
|
+
opts = options.merge(:value => value)
|
45
|
+
opts.delete(:scope) # Has meaning with I18n!
|
46
|
+
document.errors.add(attribute, :taken, opts)
|
36
47
|
end
|
37
48
|
end
|
38
49
|
|
@@ -100,7 +100,7 @@ module CouchRest
|
|
100
100
|
query = query.dup # Modifications made on copy!
|
101
101
|
db = query.delete(:database) || database
|
102
102
|
refresh_design_doc(db)
|
103
|
-
query[:raw] = true if query[:reduce]
|
103
|
+
query[:raw] = true if query[:reduce]
|
104
104
|
raw = query.delete(:raw)
|
105
105
|
fetch_view_with_docs(db, name, query, raw, &block)
|
106
106
|
end
|
data/lib/couchrest_model.rb
CHANGED
@@ -46,7 +46,9 @@ require "couchrest/model/designs/view"
|
|
46
46
|
|
47
47
|
# Monkey patches applied to couchrest
|
48
48
|
require "couchrest/model/support/couchrest"
|
49
|
-
|
49
|
+
# Core Extensions
|
50
|
+
require "couchrest/model/core_extensions/hash"
|
51
|
+
require "couchrest/model/core_extensions/time_parsing"
|
50
52
|
|
51
53
|
# Base libraries
|
52
54
|
require "couchrest/model/casted_model"
|
@@ -5,6 +5,35 @@ require File.join(FIXTURE_PATH, 'more', 'sale_invoice')
|
|
5
5
|
|
6
6
|
describe "Assocations" do
|
7
7
|
|
8
|
+
describe ".merge_belongs_to_association_options" do
|
9
|
+
before :all do
|
10
|
+
def SaleInvoice.merge_assoc_opts(*args)
|
11
|
+
merge_belongs_to_association_options(*args)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should return a default set of options" do
|
16
|
+
o = SaleInvoice.merge_assoc_opts(:cat)
|
17
|
+
o[:foreign_key].should eql('cat_id')
|
18
|
+
o[:class_name].should eql('Cat')
|
19
|
+
o[:proxy_name].should eql('cats')
|
20
|
+
o[:proxy].should eql('Cat') # same as class name
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should merge with provided options" do
|
24
|
+
o = SaleInvoice.merge_assoc_opts(:cat, :foreign_key => 'somecat_id', :proxy => 'some_cats')
|
25
|
+
o[:foreign_key].should eql('somecat_id')
|
26
|
+
o[:proxy].should eql('some_cats')
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should generate a proxy string if proxied" do
|
30
|
+
SaleInvoice.stub!(:proxy_owner_method).twice.and_return('company')
|
31
|
+
o = SaleInvoice.merge_assoc_opts(:cat)
|
32
|
+
o[:proxy].should eql('self.company.cats')
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
8
37
|
describe "of type belongs to" do
|
9
38
|
|
10
39
|
before :each do
|
@@ -43,14 +72,6 @@ describe "Assocations" do
|
|
43
72
|
@invoice.client
|
44
73
|
end
|
45
74
|
|
46
|
-
it "should raise error if class name does not exist" do
|
47
|
-
lambda do
|
48
|
-
class TestBadAssoc < CouchRest::Model::Base
|
49
|
-
belongs_to :test_bad_item
|
50
|
-
end
|
51
|
-
end.should raise_error(NameError, /TestBadAssoc#test_bad_item/)
|
52
|
-
end
|
53
|
-
|
54
75
|
it "should allow override of foreign key" do
|
55
76
|
@invoice.respond_to?(:alternate_client).should be_true
|
56
77
|
@invoice.respond_to?("alternate_client=").should be_true
|
@@ -78,8 +99,8 @@ describe "Assocations" do
|
|
78
99
|
end
|
79
100
|
|
80
101
|
it "should create an associated property and collection proxy" do
|
81
|
-
@invoice.respond_to?('entry_ids')
|
82
|
-
@invoice.respond_to?('entry_ids=')
|
102
|
+
@invoice.respond_to?('entry_ids').should be_true
|
103
|
+
@invoice.respond_to?('entry_ids=').should be_true
|
83
104
|
@invoice.entries.class.should eql(::CouchRest::CollectionOfProxy)
|
84
105
|
end
|
85
106
|
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require File.expand_path('../../../spec_helper', __FILE__)
|
3
|
+
|
4
|
+
describe "Time Parsing core extension" do
|
5
|
+
|
6
|
+
describe "Time" do
|
7
|
+
|
8
|
+
it "should respond to .parse_iso8601" do
|
9
|
+
Time.respond_to?("parse_iso8601").should be_true
|
10
|
+
end
|
11
|
+
|
12
|
+
describe ".parse_iso8601" do
|
13
|
+
|
14
|
+
describe "parsing" do
|
15
|
+
|
16
|
+
before :each do
|
17
|
+
# Time.parse should not be called for these tests!
|
18
|
+
Time.stub!(:parse).and_return(nil)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should parse JSON time" do
|
22
|
+
txt = "2011-04-01T19:05:30Z"
|
23
|
+
Time.parse_iso8601(txt).should eql(Time.utc(2011, 04, 01, 19, 05, 30))
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should parse JSON time as UTC without Z" do
|
27
|
+
txt = "2011-04-01T19:05:30"
|
28
|
+
Time.parse_iso8601(txt).should eql(Time.utc(2011, 04, 01, 19, 05, 30))
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should parse basic time as UTC" do
|
32
|
+
txt = "2011-04-01 19:05:30"
|
33
|
+
Time.parse_iso8601(txt).should eql(Time.utc(2011, 04, 01, 19, 05, 30))
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should parse JSON time with zone" do
|
37
|
+
txt = "2011-04-01T19:05:30 +02:00"
|
38
|
+
Time.parse_iso8601(txt).should eql(Time.new(2011, 04, 01, 19, 05, 30, "+02:00"))
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should parse JSON time with zone 2" do
|
42
|
+
txt = "2011-04-01T19:05:30-0200"
|
43
|
+
Time.parse_iso8601(txt).should eql(Time.new(2011, 04, 01, 19, 05, 30, "-02:00"))
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should parse dodgy time with zone" do
|
47
|
+
txt = "2011-04-01 19:05:30 +0200"
|
48
|
+
Time.parse_iso8601(txt).should eql(Time.new(2011, 04, 01, 19, 05, 30, "+02:00"))
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should parse dodgy time with zone 2" do
|
52
|
+
txt = "2011-04-01 19:05:30+0230"
|
53
|
+
Time.parse_iso8601(txt).should eql(Time.new(2011, 04, 01, 19, 05, 30, "+02:30"))
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should parse dodgy time with zone 3" do
|
57
|
+
txt = "2011-04-01 19:05:30 0230"
|
58
|
+
Time.parse_iso8601(txt).should eql(Time.new(2011, 04, 01, 19, 05, 30, "+02:30"))
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "resorting back to normal parse" do
|
64
|
+
before :each do
|
65
|
+
Time.should_receive(:parse)
|
66
|
+
end
|
67
|
+
it "should work with weird time" do
|
68
|
+
txt = "16/07/1981 05:04:00"
|
69
|
+
Time.parse_iso8601(txt)
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
@@ -7,6 +7,7 @@ class DesignViewModel < CouchRest::Model::Base
|
|
7
7
|
|
8
8
|
design do
|
9
9
|
view :by_name
|
10
|
+
view :by_just_name, :map => "function(doc) { emit(doc['name'], null); }"
|
10
11
|
end
|
11
12
|
end
|
12
13
|
|
@@ -32,7 +33,7 @@ describe "Design View" do
|
|
32
33
|
@obj = @klass.new(DesignViewModel, {}, 'test_view')
|
33
34
|
@obj.model.should eql(DesignViewModel)
|
34
35
|
@obj.name.should eql('test_view')
|
35
|
-
@obj.query.should
|
36
|
+
@obj.query.should be_empty
|
36
37
|
end
|
37
38
|
|
38
39
|
it "should complain if there is no name" do
|
@@ -51,7 +52,7 @@ describe "Design View" do
|
|
51
52
|
it "should copy attributes" do
|
52
53
|
@obj.model.should eql(DesignViewModel)
|
53
54
|
@obj.name.should eql('test_view')
|
54
|
-
@obj.query.should eql({:
|
55
|
+
@obj.query.should eql({:foo => :bar})
|
55
56
|
end
|
56
57
|
|
57
58
|
end
|
@@ -233,15 +234,6 @@ describe "Design View" do
|
|
233
234
|
end
|
234
235
|
end
|
235
236
|
|
236
|
-
describe "#keys" do
|
237
|
-
it "should request each row and provide key value" do
|
238
|
-
row = mock("Row")
|
239
|
-
row.should_receive(:key).twice.and_return('foo')
|
240
|
-
@obj.should_receive(:rows).and_return([row, row])
|
241
|
-
@obj.keys.should eql(['foo', 'foo'])
|
242
|
-
end
|
243
|
-
end
|
244
|
-
|
245
237
|
describe "#values" do
|
246
238
|
it "should request each row and provide value" do
|
247
239
|
row = mock("Row")
|
@@ -276,21 +268,25 @@ describe "Design View" do
|
|
276
268
|
@obj.should_receive(:update_query).with({:key => 'foo'})
|
277
269
|
@obj.key('foo')
|
278
270
|
end
|
279
|
-
it "should raise
|
271
|
+
it "should raise error if startkey set" do
|
280
272
|
@obj.query[:startkey] = 'bar'
|
281
273
|
lambda { @obj.key('foo') }.should raise_error
|
282
274
|
end
|
283
|
-
it "should raise
|
275
|
+
it "should raise error if endkey set" do
|
284
276
|
@obj.query[:endkey] = 'bar'
|
285
277
|
lambda { @obj.key('foo') }.should raise_error
|
286
278
|
end
|
287
|
-
it "should raise
|
279
|
+
it "should raise error if both startkey and endkey set" do
|
288
280
|
@obj.query[:startkey] = 'bar'
|
289
281
|
@obj.query[:endkey] = 'bar'
|
290
282
|
lambda { @obj.key('foo') }.should raise_error
|
291
283
|
end
|
284
|
+
it "should raise error if keys set" do
|
285
|
+
@obj.query[:keys] = 'bar'
|
286
|
+
lambda { @obj.key('foo') }.should raise_error
|
287
|
+
end
|
292
288
|
end
|
293
|
-
|
289
|
+
|
294
290
|
describe "#startkey" do
|
295
291
|
it "should update query with value" do
|
296
292
|
@obj.should_receive(:update_query).with({:startkey => 'foo'})
|
@@ -298,7 +294,11 @@ describe "Design View" do
|
|
298
294
|
end
|
299
295
|
it "should raise and error if key set" do
|
300
296
|
@obj.query[:key] = 'bar'
|
301
|
-
lambda { @obj.startkey('foo') }.should raise_error
|
297
|
+
lambda { @obj.startkey('foo') }.should raise_error(/View#startkey/)
|
298
|
+
end
|
299
|
+
it "should raise and error if keys set" do
|
300
|
+
@obj.query[:keys] = 'bar'
|
301
|
+
lambda { @obj.startkey('foo') }.should raise_error(/View#startkey/)
|
302
302
|
end
|
303
303
|
end
|
304
304
|
|
@@ -322,7 +322,11 @@ describe "Design View" do
|
|
322
322
|
end
|
323
323
|
it "should raise and error if key set" do
|
324
324
|
@obj.query[:key] = 'bar'
|
325
|
-
lambda { @obj.endkey('foo') }.should raise_error
|
325
|
+
lambda { @obj.endkey('foo') }.should raise_error(/View#endkey/)
|
326
|
+
end
|
327
|
+
it "should raise and error if keys set" do
|
328
|
+
@obj.query[:keys] = 'bar'
|
329
|
+
lambda { @obj.endkey('foo') }.should raise_error(/View#endkey/)
|
326
330
|
end
|
327
331
|
end
|
328
332
|
|
@@ -339,6 +343,33 @@ describe "Design View" do
|
|
339
343
|
end
|
340
344
|
end
|
341
345
|
|
346
|
+
describe "#keys" do
|
347
|
+
it "should update the query" do
|
348
|
+
@obj.should_receive(:update_query).with({:keys => ['foo', 'bar']})
|
349
|
+
@obj.keys(['foo', 'bar'])
|
350
|
+
end
|
351
|
+
it "should raise and error if key set" do
|
352
|
+
@obj.query[:key] = 'bar'
|
353
|
+
lambda { @obj.keys('foo') }.should raise_error(/View#keys/)
|
354
|
+
end
|
355
|
+
it "should raise and error if startkey or endkey set" do
|
356
|
+
@obj.query[:startkey] = 'bar'
|
357
|
+
lambda { @obj.keys('foo') }.should raise_error(/View#keys/)
|
358
|
+
@obj.query.delete(:startkey)
|
359
|
+
@obj.query[:endkey] = 'bar'
|
360
|
+
lambda { @obj.keys('foo') }.should raise_error(/View#keys/)
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
describe "#keys (without parameters)" do
|
365
|
+
it "should request each row and provide key value" do
|
366
|
+
row = mock("Row")
|
367
|
+
row.should_receive(:key).twice.and_return('foo')
|
368
|
+
@obj.should_receive(:rows).and_return([row, row])
|
369
|
+
@obj.keys.should eql(['foo', 'foo'])
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
342
373
|
describe "#descending" do
|
343
374
|
it "should update query" do
|
344
375
|
@obj.should_receive(:update_query).with({:descending => true})
|
@@ -700,7 +731,6 @@ describe "Design View" do
|
|
700
731
|
end
|
701
732
|
|
702
733
|
describe "loading documents" do
|
703
|
-
|
704
734
|
it "should return first" do
|
705
735
|
DesignViewModel.by_name.first.name.should eql("Judith")
|
706
736
|
end
|
@@ -715,7 +745,6 @@ describe "Design View" do
|
|
715
745
|
view.last.name.should eql("Peter")
|
716
746
|
view.all.length.should eql(3)
|
717
747
|
end
|
718
|
-
|
719
748
|
end
|
720
749
|
|
721
750
|
describe "index information" do
|
@@ -733,6 +762,18 @@ describe "Design View" do
|
|
733
762
|
end
|
734
763
|
end
|
735
764
|
|
765
|
+
describe "viewing" do
|
766
|
+
it "should load views with no reduce method" do
|
767
|
+
docs = DesignViewModel.by_just_name.all
|
768
|
+
docs.length.should eql(5)
|
769
|
+
end
|
770
|
+
it "should load documents by specific keys" do
|
771
|
+
docs = DesignViewModel.by_name.keys(["Judith", "Peter"]).all
|
772
|
+
docs[0].name.should eql("Judith")
|
773
|
+
docs[1].name.should eql("Peter")
|
774
|
+
end
|
775
|
+
end
|
776
|
+
|
736
777
|
describe "pagination" do
|
737
778
|
before :all do
|
738
779
|
DesignViewModel.paginates_per 3
|