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.
- data/.gitignore +1 -0
- data/.travis.yml +7 -3
- data/CHANGES.md +14 -0
- data/README.md +17 -12
- data/Rakefile +1 -17
- data/active_support_3_2 +7 -1
- data/active_support_4_0 +11 -0
- data/couch_potato.gemspec +2 -3
- data/lib/couch_potato/database.rb +0 -3
- data/lib/couch_potato/forbidden_attributes_protection.rb +15 -0
- data/lib/couch_potato/persistence/simple_property.rb +5 -1
- data/lib/couch_potato/persistence/type_caster.rb +2 -0
- data/lib/couch_potato/persistence.rb +9 -5
- data/lib/couch_potato/rspec/matchers/list_as_matcher.rb +1 -2
- data/lib/couch_potato/rspec/matchers/map_reduce_to_matcher.rb +114 -0
- data/lib/couch_potato/rspec/matchers/map_to_matcher.rb +1 -2
- data/lib/couch_potato/rspec/matchers/reduce_to_matcher.rb +7 -9
- data/lib/couch_potato/rspec/matchers.rb +23 -7
- data/lib/couch_potato/version.rb +1 -1
- data/lib/couch_potato/view/base_view_spec.rb +6 -2
- data/lib/couch_potato/view/custom_view_spec.rb +20 -19
- data/lib/couch_potato/view/model_view_spec.rb +10 -5
- data/lib/couch_potato/view/raw_view_spec.rb +2 -6
- data/lib/couch_potato.rb +1 -0
- data/spec/default_property_spec.rb +6 -0
- data/spec/property_spec.rb +13 -3
- data/spec/reload_spec.rb +14 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/unit/base_view_spec_spec.rb +21 -14
- data/spec/unit/custom_view_spec_spec.rb +24 -0
- data/spec/unit/forbidden_attributes_protection_spec.rb +53 -0
- data/spec/unit/model_view_spec_spec.rb +24 -1
- data/spec/unit/persistence_spec.rb +40 -0
- data/spec/unit/rspec_matchers_spec.rb +126 -5
- data/spec/views_spec.rb +16 -10
- metadata +62 -35
- data/Gemfile.lock +0 -54
- data/active_support_3_0 +0 -4
- data/active_support_3_0.lock +0 -54
- data/active_support_3_1 +0 -4
- data/active_support_3_1.lock +0 -57
- data/active_support_3_2.lock +0 -55
- data/lib/couch_potato/rspec/matchers/print_r.js +0 -60
- 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
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
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
@@ -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
|
data/spec/property_spec.rb
CHANGED
@@ -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
|
data/spec/reload_spec.rb
ADDED
@@ -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
@@ -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 "
|
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 "
|
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 "
|
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 "
|
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 "
|
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 "
|
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 "
|
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 "
|
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 "
|
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 "
|
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 "
|
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 "
|
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 "
|
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 "
|
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, '
|
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(
|
60
|
+
@view_spec = stub(:reduce_function => "function(keys, values, rereduce) {
|
56
61
|
if(rereduce) {
|
57
|
-
return(sum(
|
62
|
+
return(sum(values) * 2);
|
58
63
|
} else {
|
59
|
-
return(sum(
|
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 "
|
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
|
125
|
+
@db.view(Build.minimal_timeline).first.state.should eql('success')
|
131
126
|
end
|
132
127
|
|
133
|
-
it "
|
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 "
|
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
|
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
|