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.
@@ -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