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