couch_potato 0.7.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +7 -3
  3. data/CHANGES.md +14 -0
  4. data/README.md +17 -12
  5. data/Rakefile +1 -17
  6. data/active_support_3_2 +7 -1
  7. data/active_support_4_0 +11 -0
  8. data/couch_potato.gemspec +2 -3
  9. data/lib/couch_potato/database.rb +0 -3
  10. data/lib/couch_potato/forbidden_attributes_protection.rb +15 -0
  11. data/lib/couch_potato/persistence/simple_property.rb +5 -1
  12. data/lib/couch_potato/persistence/type_caster.rb +2 -0
  13. data/lib/couch_potato/persistence.rb +9 -5
  14. data/lib/couch_potato/rspec/matchers/list_as_matcher.rb +1 -2
  15. data/lib/couch_potato/rspec/matchers/map_reduce_to_matcher.rb +114 -0
  16. data/lib/couch_potato/rspec/matchers/map_to_matcher.rb +1 -2
  17. data/lib/couch_potato/rspec/matchers/reduce_to_matcher.rb +7 -9
  18. data/lib/couch_potato/rspec/matchers.rb +23 -7
  19. data/lib/couch_potato/version.rb +1 -1
  20. data/lib/couch_potato/view/base_view_spec.rb +6 -2
  21. data/lib/couch_potato/view/custom_view_spec.rb +20 -19
  22. data/lib/couch_potato/view/model_view_spec.rb +10 -5
  23. data/lib/couch_potato/view/raw_view_spec.rb +2 -6
  24. data/lib/couch_potato.rb +1 -0
  25. data/spec/default_property_spec.rb +6 -0
  26. data/spec/property_spec.rb +13 -3
  27. data/spec/reload_spec.rb +14 -0
  28. data/spec/spec_helper.rb +6 -0
  29. data/spec/unit/base_view_spec_spec.rb +21 -14
  30. data/spec/unit/custom_view_spec_spec.rb +24 -0
  31. data/spec/unit/forbidden_attributes_protection_spec.rb +53 -0
  32. data/spec/unit/model_view_spec_spec.rb +24 -1
  33. data/spec/unit/persistence_spec.rb +40 -0
  34. data/spec/unit/rspec_matchers_spec.rb +126 -5
  35. data/spec/views_spec.rb +16 -10
  36. metadata +62 -35
  37. data/Gemfile.lock +0 -54
  38. data/active_support_3_0 +0 -4
  39. data/active_support_3_0.lock +0 -54
  40. data/active_support_3_1 +0 -4
  41. data/active_support_3_1.lock +0 -57
  42. data/active_support_3_2.lock +0 -55
  43. data/lib/couch_potato/rspec/matchers/print_r.js +0 -60
  44. data/rails/init.rb +0 -4
@@ -8,6 +8,8 @@ module CouchPotato
8
8
  #
9
9
  # in addition you can pass in conditions as a javascript string
10
10
  # view :my_view_only_completed, :key => :name, :conditions => 'doc.completed = true'
11
+ # and also a results filter (the results will be run through the given proc):
12
+ # view :my_view, :key => :name, :results_filter => lambda{|results| results.size}
11
13
  class ModelViewSpec < BaseViewSpec
12
14
  class ErlangGenerator
13
15
  def initialize(options, klass)
@@ -137,11 +139,14 @@ module CouchPotato
137
139
  end
138
140
 
139
141
  def process_results(results)
140
- if count?
141
- results['rows'].first.try(:[], 'value') || 0
142
- else
143
- results['rows'].map { |row| row['doc'] || row['id'] }
144
- end
142
+ processed = if count?
143
+ results['rows'].first.try(:[], 'value') || 0
144
+ else
145
+ results['rows'].map {|row|
146
+ row['doc'] || (row['id'] unless view_parameters[:include_docs])
147
+ }.compact
148
+ end
149
+ super processed
145
150
  end
146
151
 
147
152
  private
@@ -12,14 +12,10 @@ module CouchPotato
12
12
  def map_function
13
13
  options[:map]
14
14
  end
15
-
16
- def process_results(results)
17
- options[:results_filter] ? options[:results_filter].call(results) : results
18
- end
19
-
15
+
20
16
  def reduce_function
21
17
  options[:reduce]
22
18
  end
23
19
  end
24
20
  end
25
- end
21
+ end
data/lib/couch_potato.rb CHANGED
@@ -5,6 +5,7 @@ require 'json/add/core'
5
5
  require 'ostruct'
6
6
 
7
7
  JSON.create_id = 'ruby_class'
8
+ CouchRest.decode_json_objects = true
8
9
 
9
10
  module CouchPotato
10
11
  Config = Struct.new(:database_name, :split_design_documents_per_view, :default_language).new
@@ -6,6 +6,7 @@ class Test
6
6
  property :test, :default => 'Test value'
7
7
  property :complex, :default => [1, 2, 3]
8
8
  property :false_value, :default => false
9
+ property :proc, :default => Proc.new { 1 + 2 }
9
10
  end
10
11
 
11
12
  describe 'default properties' do
@@ -41,4 +42,9 @@ describe 'default properties' do
41
42
  t = Test.new
42
43
  t.false_value.should == false
43
44
  end
45
+
46
+ it "uses the return value of a Proc given as the default" do
47
+ t = Test.new
48
+ t.proc.should == 3
49
+ end
44
50
  end
@@ -57,6 +57,14 @@ describe 'properties' do
57
57
  c.title.should == 3
58
58
  end
59
59
 
60
+ it "should persist a big decimal" do
61
+ require 'bigdecimal'
62
+ c = BigDecimalContainer.new :number => BigDecimal.new( '42.42' )
63
+ CouchPotato.database.save_document! c
64
+ c = CouchPotato.database.load_document c.id
65
+ c.number.should == BigDecimal.new( '42.42' )
66
+ end
67
+
60
68
  it "should persist a hash" do
61
69
  c = Comment.new :title => {'key' => 'value'}
62
70
  CouchPotato.database.save_document! c
@@ -68,7 +76,9 @@ describe 'properties' do
68
76
  c = Comment.new :title => value
69
77
  CouchPotato.database.save_document! c
70
78
  c = CouchPotato.database.load_document c.id
71
- c.title.should == value
79
+ c.title.to_json.should == value.to_json
80
+ # no id provided in embedded object, need to check yourself for content equality
81
+ c.title.should_not == value
72
82
  end
73
83
 
74
84
  it "should persist a child class" do
@@ -109,7 +119,7 @@ describe 'properties' do
109
119
  p.ship_address = a
110
120
  CouchPotato.database.save_document! p
111
121
  p = CouchPotato.database.load_document p.id
112
- p.ship_address.should === a
122
+ p.ship_address.to_json.should === a.to_json
113
123
  end
114
124
 
115
125
  it "should persist null for a null " do
@@ -223,7 +233,7 @@ describe 'properties' do
223
233
  w = Watch.new :custom_address => [address]
224
234
  CouchPotato.database.save_document! w
225
235
  w = CouchPotato.database.load_document w.id
226
- w.custom_address.should eql([address])
236
+ w.custom_address.to_json.should eql([address].to_json)
227
237
  end
228
238
 
229
239
  it "should handle nil values" do
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ describe CouchPotato::Persistence, '#reload' do
4
+ let(:db) { CouchPotato.database }
5
+
6
+ it 'returns a new instance from the database' do
7
+ comment = Comment.new title: 'hello'
8
+ db.save! comment
9
+
10
+ reloaded = comment.reload
11
+ expect(reloaded.object_id).to_not eql(comment.object_id)
12
+ expect(reloaded).to eql(comment)
13
+ end
14
+ end
data/spec/spec_helper.rb CHANGED
@@ -31,6 +31,12 @@ class Comment
31
31
  property :title
32
32
  end
33
33
 
34
+ class BigDecimalContainer
35
+ include CouchPotato::Persistence
36
+
37
+ property :number, :type => BigDecimal
38
+ end
39
+
34
40
  def recreate_db
35
41
  CouchPotato.couchrest_database.recreate!
36
42
  end
@@ -11,13 +11,13 @@ describe CouchPotato::View::BaseViewSpec, 'initialize' do
11
11
  CouchPotato::Config.default_language = @default_language
12
12
  end
13
13
 
14
- it "should raise an error when passing invalid view parameters" do
14
+ it "raises an error when passing invalid view parameters" do
15
15
  lambda {
16
16
  CouchPotato::View::BaseViewSpec.new Object, 'all', {}, {:start_key => '1'}
17
17
  }.should raise_error(ArgumentError, "invalid view parameter: start_key")
18
18
  end
19
19
 
20
- it "should not raise an error when passing valid view parameters" do
20
+ it "does not raise an error when passing valid view parameters" do
21
21
  lambda {
22
22
  CouchPotato::View::BaseViewSpec.new Object, 'all', {}, {
23
23
  :key => 'keyvalue',
@@ -38,66 +38,66 @@ describe CouchPotato::View::BaseViewSpec, 'initialize' do
38
38
  }.should_not raise_error
39
39
  end
40
40
 
41
- it "should remove stale when it's nil" do
41
+ it "removes stale when it's nil" do
42
42
  spec = CouchPotato::View::BaseViewSpec.new Object, 'all', {}, {:stale => nil}
43
43
  spec.view_parameters.should == {}
44
44
  end
45
45
 
46
- it "should convert a range passed as key into startkey and endkey" do
46
+ it "converts a range passed as key into startkey and endkey" do
47
47
  spec = CouchPotato::View::BaseViewSpec.new Object, 'all', {}, {:key => '1'..'2'}
48
48
  spec.view_parameters.should == {:startkey => '1', :endkey => '2'}
49
49
  end
50
50
 
51
- it "should convert a plain value to a hash with a key" do
51
+ it "converts a plain value to a hash with a key" do
52
52
  spec = CouchPotato::View::BaseViewSpec.new Object, 'all', {}, '2'
53
53
  spec.view_parameters.should == {:key => '2'}
54
54
  end
55
55
 
56
- it "should generate the design document path by snake_casing the class name but keeping double colons" do
56
+ it "generates the design document path by snake_casing the class name but keeping double colons" do
57
57
  spec = CouchPotato::View::BaseViewSpec.new 'Foo::BarBaz', '', {}, ''
58
58
  spec.design_document.should == 'foo::bar_baz'
59
59
  end
60
60
 
61
- it "should generate the design document independent of the view name by default" do
61
+ it "generates the design document independent of the view name by default" do
62
62
  CouchPotato::Config.split_design_documents_per_view = false
63
63
  spec = CouchPotato::View::BaseViewSpec.new 'User', 'by_login_and_email', {}, ''
64
64
  spec.design_document.should == 'user'
65
65
  end
66
66
 
67
- it "should generate the design document per view if configured to" do
67
+ it "generates the design document per view if configured to" do
68
68
  CouchPotato::Config.split_design_documents_per_view = true
69
69
  spec = CouchPotato::View::BaseViewSpec.new 'User', 'by_login_and_email', {}, ''
70
70
  spec.design_document.should == 'user_view_by_login_and_email'
71
71
  end
72
72
 
73
- it "should generate the design document independent of the list name by default" do
73
+ it "generates the design document independent of the list name by default" do
74
74
  CouchPotato::Config.split_design_documents_per_view = false
75
75
  spec = CouchPotato::View::BaseViewSpec.new stub(:lists => nil, :to_s => 'User'), '', {:list => 'test_list'}, {}
76
76
  spec.design_document.should == 'user'
77
77
  end
78
78
 
79
- it "should generate the design document per view if configured to" do
79
+ it "generates the design document per view if configured to" do
80
80
  CouchPotato::Config.split_design_documents_per_view = true
81
81
  spec = CouchPotato::View::BaseViewSpec.new stub(:lists => nil, :to_s => 'User'), '', {:list => :test_list}, {}
82
82
  spec.design_document.should == 'user_list_test_list'
83
83
  end
84
84
 
85
- it "should extract the list name from the options" do
85
+ it "extracts the list name from the options" do
86
86
  spec = CouchPotato::View::BaseViewSpec.new stub(:lists => nil), 'all', {:list => :test_list}, {}
87
87
  spec.list_name.should == :test_list
88
88
  end
89
89
 
90
- it "should extract the list from the view parameters" do
90
+ it "extracts the list from the view parameters" do
91
91
  spec = CouchPotato::View::BaseViewSpec.new stub(:lists => nil), 'all', {}, {:list => :test_list}
92
92
  spec.list_name.should == :test_list
93
93
  end
94
94
 
95
- it "should prefer the list name from the view parameters over the one from the options" do
95
+ it "prefers the list name from the view parameters over the one from the options" do
96
96
  spec = CouchPotato::View::BaseViewSpec.new stub(:lists => nil), 'all', {:list => 'my_list'}, {:list => :test_list}
97
97
  spec.list_name.should == :test_list
98
98
  end
99
99
 
100
- it "should return the list function" do
100
+ it "returns the list function" do
101
101
  klass = stub 'class'
102
102
  klass.stub(:lists).with('test_list').and_return('<list_code>')
103
103
  spec = CouchPotato::View::BaseViewSpec.new klass, 'all', {:list => 'test_list'}, {}
@@ -114,6 +114,13 @@ describe CouchPotato::View::BaseViewSpec, 'initialize' do
114
114
  spec = CouchPotato::View::BaseViewSpec.new Object, 'all', {:language => :erlang}, {}
115
115
  spec.language.should == :erlang
116
116
  end
117
+
118
+ it 'post-processes the results' do
119
+ filter = lambda{ |results| results.map{|r| r.to_i} }
120
+ spec = CouchPotato::View::BaseViewSpec.new Object, 'all', {:results_filter => filter}, {}
121
+
122
+ expect(spec.process_results(['1'])).to eql([1])
123
+ end
117
124
  end
118
125
  end
119
126
 
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe CouchPotato::View::CustomViewSpec, '#process_results' do
4
+ it 'returns the documents' do
5
+ spec = CouchPotato::View::CustomViewSpec.new(Child, 'all', {}, {})
6
+ processed_results = spec.process_results('rows' => [{'doc' => {JSON.create_id => 'Child'}}])
7
+
8
+ expect(processed_results.map{|row| row.class}).to eql([Child])
9
+ end
10
+
11
+ it 'returns values where there are no documents' do
12
+ spec = CouchPotato::View::CustomViewSpec.new(Child, 'all', {}, {:include_docs => false})
13
+ processed_results = spec.process_results('rows' => [{'value' => {JSON.create_id => 'Child'}}])
14
+
15
+ expect(processed_results.map{|row| row.class}).to eql([Child])
16
+ end
17
+
18
+ it 'filters out rows without documents when include_docs=true (i.e. doc has been deleted)' do
19
+ spec = CouchPotato::View::CustomViewSpec.new(Child, 'all', {}, {:include_docs => true})
20
+ processed_results = spec.process_results('rows' => [{'value' => {JSON.create_id => 'Child'}}])
21
+
22
+ expect(processed_results).to be_empty
23
+ end
24
+ end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ begin
4
+ require 'rack/test'
5
+ require 'active_model/forbidden_attributes_protection'
6
+ require 'action_controller/metal/strong_parameters'
7
+
8
+ describe 'forbidden attributes protection' do
9
+ context 'when initializing a new object' do
10
+ it 'raises an error when passing non-permitted attributes' do
11
+ expect {
12
+ Child.new ActionController::Parameters.new(:text => 'xx')
13
+ }.to raise_error(ActiveModel::ForbiddenAttributesError)
14
+ end
15
+
16
+ it 'raises no error when passing permitted attributes' do
17
+ expect {
18
+ Child.new ActionController::Parameters.new(:text => 'xx').permit!
19
+ }.to_not raise_error(ActiveModel::ForbiddenAttributesError)
20
+ end
21
+
22
+ it "raises no error when passing attributes that don't respond to permitted?" do
23
+ expect {
24
+ Child.new :text => 'xx'
25
+ }.to_not raise_error(ActiveModel::ForbiddenAttributesError)
26
+ end
27
+ end
28
+
29
+ context 'when mass-assigning attributes to an object' do
30
+ let(:subject) {Child.new}
31
+
32
+ it 'raises an error when passing non-permitted attributes' do
33
+ expect {
34
+ subject.attributes = ActionController::Parameters.new(:text => 'xx')
35
+ }.to raise_error(ActiveModel::ForbiddenAttributesError)
36
+ end
37
+
38
+ it 'raises no error when passing permitted attributes' do
39
+ expect {
40
+ subject.attributes = ActionController::Parameters.new(:text => 'xx').permit!
41
+ }.to_not raise_error(ActiveModel::ForbiddenAttributesError)
42
+ end
43
+
44
+ it "raises no error when passing attributes that don't respond to permitted?" do
45
+ expect {
46
+ subject.attributes = {:text => 'xx'}
47
+ }.to_not raise_error(ActiveModel::ForbiddenAttributesError)
48
+ end
49
+ end
50
+ end
51
+ rescue LoadError
52
+ puts "Skipping forbidden attributes protection specs."
53
+ end
@@ -1,6 +1,29 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe CouchPotato::View::ModelViewSpec, 'map_function' do
3
+ describe CouchPotato::View::ModelViewSpec, '#process_results' do
4
+ it 'returns the documents' do
5
+ spec = CouchPotato::View::ModelViewSpec.new(Object, 'all', {}, {})
6
+ processed_results = spec.process_results('rows' => [{'doc' => 'doc-1'}])
7
+
8
+ expect(processed_results).to eql(['doc-1'])
9
+ end
10
+
11
+ it 'returns ids where there are no documents' do
12
+ spec = CouchPotato::View::ModelViewSpec.new(Object, 'all', {}, {:include_docs => false})
13
+ processed_results = spec.process_results('rows' => [{'id' => 'doc-1'}])
14
+
15
+ expect(processed_results).to eql(['doc-1'])
16
+ end
17
+
18
+ it 'filters out rows without documents when include_docs=true (i.e. doc has been deleted)' do
19
+ spec = CouchPotato::View::ModelViewSpec.new(Object, 'all', {}, {:include_docs => true})
20
+ processed_results = spec.process_results('rows' => [{'id' => 'doc-1'}])
21
+
22
+ expect(processed_results).to be_empty
23
+ end
24
+ end
25
+
26
+ describe CouchPotato::View::ModelViewSpec, '#map_function' do
4
27
  it "should include conditions" do
5
28
  spec = CouchPotato::View::ModelViewSpec.new Object, 'all', {:conditions => 'doc.closed = true'}, {}
6
29
  spec.map_function.should include('if(doc.ruby_class && doc.ruby_class == \'Object\' && (doc.closed = true))')
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ class Dude
4
+ include CouchPotato::Persistence
5
+ property :name
6
+ end
7
+
8
+ class NoneDude
9
+ include CouchPotato::Persistence
10
+ property :name
11
+ end
12
+
13
+ describe "persistence" do
14
+ context '#eql?' do
15
+ it "should use the class and id for equality" do
16
+ dude22 = Dude.new(:id => "22", :name => "foo")
17
+ dude11 = Dude.new(:id => "11", :name => "bar")
18
+
19
+ none_dude22 = NoneDude.new(:id => "22", :name => "foo")
20
+
21
+ dude22.should eql(dude22)
22
+ dude22.should_not eql(none_dude22)
23
+ dude22.should_not eql(dude11)
24
+ end
25
+
26
+ it "should handle new objects without id to be never equal" do
27
+ dude = Dude.new(:name => "foo")
28
+ dude22 = Dude.new(:id => "22", :name => "foo")
29
+
30
+ dude.should_not eql(Dude.new(:name => "foo"))
31
+ dude22.should_not eql(Dude.new(:name => "foo"))
32
+ end
33
+
34
+ it "should handle same object references to be equal" do
35
+ dude = Dude.new(:name => "foo")
36
+
37
+ dude.should eql(dude)
38
+ end
39
+ end
40
+ end
@@ -30,6 +30,11 @@ describe CouchPotato::RSpec::MapToMatcher do
30
30
  @view_spec.should_not map({:name => 'horst', :tags => ['person', 'male']}).to(['horst', 2], ['male', 'person'])
31
31
  end
32
32
  end
33
+
34
+ it "should work with date emit values" do
35
+ spec = stub(:map_function => "function(doc) { emit(null, new Date(1368802800000)); }")
36
+ spec.should map({}).to([nil, "2013-05-17T15:00:00.000Z"])
37
+ end
33
38
 
34
39
  describe "failing specs" do
35
40
  before(:each) do
@@ -52,11 +57,11 @@ end
52
57
 
53
58
  describe CouchPotato::RSpec::ReduceToMatcher do
54
59
  before(:each) do
55
- @view_spec = stub(:reduce_function => "function(docs, keys, rereduce) {
60
+ @view_spec = stub(:reduce_function => "function(keys, values, rereduce) {
56
61
  if(rereduce) {
57
- return(sum(keys) * 2);
62
+ return(sum(values) * 2);
58
63
  } else {
59
- return(sum(keys));
64
+ return(sum(values));
60
65
  };
61
66
  }")
62
67
  end
@@ -68,6 +73,11 @@ describe CouchPotato::RSpec::ReduceToMatcher do
68
73
  it "should not pass if the given function returns different javascript" do
69
74
  @view_spec.should_not reduce([], [1, 2, 3]).to(7)
70
75
  end
76
+
77
+ it "should work with date return values" do
78
+ spec = stub(:reduce_function => "function() { return new Date(1368802800000); }")
79
+ spec.should reduce([], []).to("2013-05-17T15:00:00.000Z")
80
+ end
71
81
 
72
82
  describe "rereduce" do
73
83
  it "should pass if the given function return the expected javascript" do
@@ -95,6 +105,112 @@ describe CouchPotato::RSpec::ReduceToMatcher do
95
105
  end
96
106
  end
97
107
 
108
+ describe CouchPotato::RSpec::MapReduceToMatcher do
109
+ before(:each) do
110
+ @view_spec = stub(
111
+ :map_function => "function(doc) {
112
+ for (var i in doc.numbers)
113
+ emit([doc.age, doc.name], doc.numbers[i]);
114
+ }",
115
+ :reduce_function => "function (keys, values, rereduce) {
116
+ return Math.max.apply(this, values);
117
+ }")
118
+ @docs = [
119
+ {:name => "a", :age => 25, :numbers => [1, 2]},
120
+ {:name => "b", :age => 25, :numbers => [3, 4]},
121
+ {:name => "c", :age => 26, :numbers => [5, 6]},
122
+ {:name => "d", :age => 27, :numbers => [7, 8]}]
123
+ end
124
+
125
+ it "should handle date return values" do
126
+ spec = stub(:map_function => "function() { emit(null, null); }",
127
+ :reduce_function => "function() { return new Date(1368802800000); }")
128
+ spec.should map_reduce({}).to({"key" => nil, "value" => "2013-05-17T15:00:00.000Z"})
129
+ end
130
+
131
+ context "without grouping" do
132
+ it "should not group by key by default" do
133
+ @view_spec.should map_reduce(@docs).to({"key" => nil, "value" => 8})
134
+ end
135
+
136
+ it "should group by key with :group => false" do
137
+ @view_spec.should map_reduce(@docs).with_options(:group => false).to({"key" => nil, "value" => 8})
138
+ end
139
+ end
140
+
141
+ context "with grouping" do
142
+ [true, "exact"].each do |group_value|
143
+ it "should group by the full key with option :group => #{group_value}" do
144
+ @view_spec.should map_reduce(@docs).with_options(:group => group_value).to(
145
+ {"key" => [25, "a"], "value" => 2},
146
+ {"key" => [25, "b"], "value" => 4},
147
+ {"key" => [26, "c"], "value" => 6},
148
+ {"key" => [27, "d"], "value" => 8})
149
+ end
150
+ end
151
+
152
+ it "should group by parts of the keys based on the :group_level option" do
153
+ @view_spec.should map_reduce(@docs).with_options(:group_level => 1).to(
154
+ {"key" => [25], "value" => 4},
155
+ {"key" => [26], "value" => 6},
156
+ {"key" => [27], "value" => 8})
157
+ end
158
+ end
159
+
160
+ describe "rereducing" do
161
+ before :each do
162
+ @view_spec = stub(:map_function => "function(doc) {
163
+ emit(doc.name, doc.number);
164
+ }",
165
+ :reduce_function => "function (keys, values, rereduce) {
166
+ if (rereduce) {
167
+ var result = {rereduce_values: []};
168
+ for (var v in values) {
169
+ result.rereduce_values.push(values[v].reduce_values);
170
+ }
171
+ return result;
172
+ }
173
+ return {reduce_values: values};
174
+ }")
175
+ end
176
+
177
+ it "should reduce and rereduce for a single emit" do
178
+ @view_spec.should map_reduce({:name => "a", :number => 1}).to({"key" => nil, "value" => {"rereduce_values" => [[1]]}})
179
+ end
180
+
181
+ it "should split and reduce each half of emitted values separately and rereduce the results" do
182
+ docs = [
183
+ {:name => "a", :number => 1},
184
+ {:name => "a", :number => 2},
185
+ {:name => "a", :number => 3},
186
+ {:name => "a", :number => 4}]
187
+ @view_spec.should map_reduce(docs).to({"key" => nil, "value" => {"rereduce_values" => [[1, 2], [3, 4]]}})
188
+ end
189
+
190
+ it "should correctly split and rereduce with an odd number of emits" do
191
+ docs = [
192
+ {:name => "a", :number => 1},
193
+ {:name => "a", :number => 2},
194
+ {:name => "a", :number => 3}]
195
+ @view_spec.should map_reduce(docs).to({"key" => nil, "value" => {"rereduce_values" => [[1], [2, 3]]}})
196
+ end
197
+ end
198
+
199
+ describe "failing specs" do
200
+ it "should have a nice error message for failing should" do
201
+ lambda {
202
+ @view_spec.should map_reduce(@docs).with_options(:group => false).to({"key" => nil, "value" => 9})
203
+ }.should raise_error('Expected to map/reduce to [{"key"=>nil, "value"=>9}] but got [{"key"=>nil, "value"=>8}].')
204
+ end
205
+
206
+ it "should have a nice error message for failing should not" do
207
+ lambda {
208
+ @view_spec.should_not map_reduce(@docs).with_options(:group => false).to({"key" => nil, "value" => 8})
209
+ }.should raise_error('Expected not to map/reduce to [{"key"=>nil, "value"=>8}] but did.')
210
+ end
211
+ end
212
+ end
213
+
98
214
  describe CouchPotato::RSpec::ListAsMatcher do
99
215
  before(:each) do
100
216
  @view_spec = stub(:list_function => "function() {var row = getRow(); send(JSON.stringify([{text: row.text + ' world'}]));}")
@@ -107,7 +223,12 @@ describe CouchPotato::RSpec::ListAsMatcher do
107
223
  it "should not pass if the function does not return the expected json" do
108
224
  @view_spec.should_not list({'rows' => [{:text => 'hello'}]}).as([{'text' => 'hello there'}])
109
225
  end
110
-
226
+
227
+ it "should work with date values" do
228
+ spec = stub(:list_function => "function() { send(JSON.stringify([{date: new Date(1368802800000)}])); }")
229
+ spec.should list({"rows" => [{}]}).as([{"date" => "2013-05-17T15:00:00.000Z"}])
230
+ end
231
+
111
232
  describe "failing specs" do
112
233
  it "should have a nice error message for failing should" do
113
234
  lambda {
@@ -121,4 +242,4 @@ describe CouchPotato::RSpec::ListAsMatcher do
121
242
  }.should raise_error('Expected to not list as [{"text"=>"hello world"}] but did.')
122
243
  end
123
244
  end
124
- end
245
+ end
data/spec/views_spec.rb CHANGED
@@ -105,11 +105,6 @@ describe 'views' do
105
105
  @db.view(Build.count(:reduce => true)).should == 0
106
106
  end
107
107
 
108
- it "should return the total_rows" do
109
- @db.save_document! Build.new(:state => 'success', :time => '2008-01-01')
110
- @db.view(Build.count(:reduce => false)).total_rows.should == 1
111
- end
112
-
113
108
  describe "with multiple keys" do
114
109
  it "should return the documents with matching keys" do
115
110
  build = Build.new(:state => 'success', :time => '2008-01-01')
@@ -125,19 +120,19 @@ describe 'views' do
125
120
  end
126
121
 
127
122
  describe "properties defined" do
128
- it "should assign the configured properties" do
123
+ it "assigns the configured properties" do
129
124
  CouchPotato.couchrest_database.save_doc(:state => 'success', :time => '2008-01-01', JSON.create_id.to_sym => 'Build')
130
- @db.view(Build.minimal_timeline).first.state.should == 'success'
125
+ @db.view(Build.minimal_timeline).first.state.should eql('success')
131
126
  end
132
127
 
133
- it "should not assign the properties not configured" do
128
+ it "does not assign the properties not configured" do
134
129
  CouchPotato.couchrest_database.save_doc(:state => 'success', :time => '2008-01-01', JSON.create_id.to_sym => 'Build')
135
130
  @db.view(Build.minimal_timeline).first.time.should be_nil
136
131
  end
137
132
 
138
- it "should assign the id even if it is not configured" do
133
+ it "assigns the id even if it is not configured" do
139
134
  id = CouchPotato.couchrest_database.save_doc(:state => 'success', :time => '2008-01-01', JSON.create_id.to_sym => 'Build')['id']
140
- @db.view(Build.minimal_timeline).first._id.should == id
135
+ @db.view(Build.minimal_timeline).first._id.should eql(id)
141
136
  end
142
137
  end
143
138
 
@@ -278,4 +273,15 @@ describe 'views' do
278
273
  @db.view(Coworker.all(:list => :append_doe)).first.name.should == 'joe doe'
279
274
  end
280
275
  end
276
+
277
+ describe 'with stale views' do
278
+ it 'does not return deleted documents' do
279
+ build = Build.new
280
+ @db.save_document! build
281
+ @db.view(Build.timeline)
282
+ @db.destroy build
283
+
284
+ expect(@db.view(Build.timeline(:stale => 'ok'))).to be_empty
285
+ end
286
+ end
281
287
  end