couchrest_model 1.1.0.beta → 1.1.0.beta2
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.
- 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
|