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.
@@ -14,25 +14,36 @@ module CouchRest
14
14
  end
15
15
 
16
16
  def validate_each(document, attribute, value)
17
- view_name = options[:view].nil? ? "by_#{attribute}" : options[:view]
18
- model = document.model_proxy || @model
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
- model.view_by attribute
32
+ keys << {:allow_nil => true}
33
+ model.view_by(*keys)
25
34
  end
26
35
 
27
- docs = base.view(view_name, :key => value, :limit => 2, :include_docs => false)['rows']
28
- return if docs.empty?
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 docs.find{|doc| doc['id'] == document.id}
40
+ return if rows.find{|row| row['id'] == document.id}
32
41
  end
33
-
34
- if docs.length > 0
35
- document.errors.add(attribute, :taken, options.merge(:value => value))
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
@@ -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
- require "couchrest/model/support/hash"
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 eql({:reduce => false})
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({:reduce => false, :foo => :bar})
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 and error if startkey set" do
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 and error if endkey set" do
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 and error if both startkey and endkey set" do
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