couch_potato 0.7.1 → 1.0.0

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