couch_potato 1.0.1 → 1.1.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/CHANGES.md +4 -0
- data/README.md +16 -0
- data/lib/couch_potato/database.rb +47 -13
- data/lib/couch_potato/persistence/type_caster.rb +2 -0
- data/lib/couch_potato/rspec/matchers/map_reduce_to_matcher.rb +13 -1
- data/lib/couch_potato/rspec/matchers/map_to_matcher.rb +12 -0
- data/lib/couch_potato/version.rb +1 -1
- data/lib/couch_potato/view/base_view_spec.rb +1 -1
- data/lib/couch_potato/view/custom_view_spec.rb +4 -0
- data/lib/couch_potato/view/raw_view_spec.rb +7 -0
- data/lib/couch_potato/view/view_query.rb +5 -1
- data/lib/couch_potato.rb +1 -0
- data/spec/conflict_handling_spec.rb +57 -0
- data/spec/fixtures/person.rb +2 -1
- data/spec/property_spec.rb +7 -0
- data/spec/unit/database_spec.rb +38 -3
- data/spec/unit/rspec_matchers_spec.rb +35 -18
- data/spec/unit/view_query_spec.rb +63 -22
- metadata +4 -2
data/CHANGES.md
CHANGED
data/README.md
CHANGED
@@ -160,6 +160,16 @@ You can of course also retrieve your instance:
|
|
160
160
|
|
161
161
|
CouchPotato.database.load_document "id_of_the_user_document" # => <#User 0x3075>
|
162
162
|
|
163
|
+
#### Handling conflicts
|
164
|
+
|
165
|
+
CouchDB uses MVCC to detect write conflicts. If a conflict occurs when trying to update a document CouchDB returns an error. To handle conflicts easily you can save documents like this:
|
166
|
+
|
167
|
+
CouchPotato.database.save_document user do |user|
|
168
|
+
user.name = 'joe'
|
169
|
+
end
|
170
|
+
|
171
|
+
When a conflict occurs Couch Potato automatically reloads the document, runs the block and tries to save it again. Note that the block is also run before initally saving the document.
|
172
|
+
|
163
173
|
#### Operations on multiple documents
|
164
174
|
|
165
175
|
You can also load a bunch of documents with one request.
|
@@ -299,6 +309,12 @@ You can also pass in custom map/reduce functions with the custom view spec:
|
|
299
309
|
view :all, :map => "function(doc) { emit(doc.created_at, null)}", :include_docs => true, :type => :custom
|
300
310
|
end
|
301
311
|
|
312
|
+
commonJS modules can also be used in custom views:
|
313
|
+
|
314
|
+
class User
|
315
|
+
view :all, :map => "function(doc) { emit(null, require("lib/test").test)}", :lib => {:test => "exports.test = 'test'"}, :include_docs => true, :type => :custom
|
316
|
+
end
|
317
|
+
|
302
318
|
If you don't want the results to be converted into models the raw view is your friend:
|
303
319
|
|
304
320
|
class User
|
@@ -47,9 +47,11 @@ module CouchPotato
|
|
47
47
|
spec.design_document,
|
48
48
|
{spec.view_name => {
|
49
49
|
:map => spec.map_function,
|
50
|
-
:reduce => spec.reduce_function
|
50
|
+
:reduce => spec.reduce_function
|
51
|
+
}
|
51
52
|
},
|
52
53
|
({spec.list_name => spec.list_function} unless spec.list_name.nil?),
|
54
|
+
spec.lib,
|
53
55
|
spec.language
|
54
56
|
).query_view!(spec.view_parameters)
|
55
57
|
processed_results = spec.process_results results
|
@@ -70,13 +72,27 @@ module CouchPotato
|
|
70
72
|
first(spec) || raise(CouchPotato::NotFound)
|
71
73
|
end
|
72
74
|
|
73
|
-
# saves a document. returns true on success, false on failure
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
75
|
+
# saves a document. returns true on success, false on failure.
|
76
|
+
# if passed a block will:
|
77
|
+
# * yield the object to be saved to the block and run if once before saving
|
78
|
+
# * on conflict: reload the document, run the block again and retry saving
|
79
|
+
def save_document(document, validate = true, &block)
|
80
|
+
retries = 0
|
81
|
+
begin
|
82
|
+
block.call document if block
|
83
|
+
save_document_without_conflict_handling(document, validate)
|
84
|
+
rescue RestClient::Conflict => e
|
85
|
+
if block
|
86
|
+
document = document.reload
|
87
|
+
if retries == 5
|
88
|
+
raise CouchPotato::Conflict.new
|
89
|
+
else
|
90
|
+
retries += 1
|
91
|
+
retry
|
92
|
+
end
|
93
|
+
else
|
94
|
+
raise e
|
95
|
+
end
|
80
96
|
end
|
81
97
|
end
|
82
98
|
alias_method :save, :save_document
|
@@ -88,12 +104,12 @@ module CouchPotato
|
|
88
104
|
alias_method :save!, :save_document!
|
89
105
|
|
90
106
|
def destroy_document(document)
|
91
|
-
|
92
|
-
document
|
93
|
-
|
107
|
+
begin
|
108
|
+
destroy_document_without_conflict_handling document
|
109
|
+
rescue RestClient::Conflict
|
110
|
+
document = document.reload
|
111
|
+
retry
|
94
112
|
end
|
95
|
-
document._id = nil
|
96
|
-
document._rev = nil
|
97
113
|
end
|
98
114
|
alias_method :destroy, :destroy_document
|
99
115
|
|
@@ -141,6 +157,24 @@ module CouchPotato
|
|
141
157
|
|
142
158
|
private
|
143
159
|
|
160
|
+
def destroy_document_without_conflict_handling(document)
|
161
|
+
document.run_callbacks :destroy do
|
162
|
+
document._deleted = true
|
163
|
+
couchrest_database.delete_doc document.to_hash
|
164
|
+
end
|
165
|
+
document._id = nil
|
166
|
+
document._rev = nil
|
167
|
+
end
|
168
|
+
|
169
|
+
def save_document_without_conflict_handling(document, validate = true)
|
170
|
+
return true unless document.dirty? || document.new?
|
171
|
+
if document.new?
|
172
|
+
create_document(document, validate)
|
173
|
+
else
|
174
|
+
update_document(document, validate)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
144
178
|
def bulk_load(ids)
|
145
179
|
response = couchrest_database.bulk_load ids
|
146
180
|
docs = response['rows'].map{|row| row["doc"]}.compact
|
@@ -34,6 +34,8 @@ module CouchPotato
|
|
34
34
|
value.to_s.scan(NUMBER_REGEX).join.to_f unless value.blank?
|
35
35
|
elsif type == BigDecimal
|
36
36
|
BigDecimal.new(value.to_s) unless value.blank?
|
37
|
+
elsif type == Hash
|
38
|
+
value.to_hash unless value.blank?
|
37
39
|
else
|
38
40
|
type.json_create value unless value.blank?
|
39
41
|
end
|
@@ -32,8 +32,20 @@ module CouchPotato
|
|
32
32
|
var options = #{@options.to_json};
|
33
33
|
var map = #{view_spec.map_function};
|
34
34
|
var reduce = #{view_spec.reduce_function};
|
35
|
+
var lib = #{view_spec.respond_to?(:lib) && view_spec.lib.to_json};
|
35
36
|
|
36
37
|
// Map the input docs
|
38
|
+
var require = function(modulePath) {
|
39
|
+
var exports = {};
|
40
|
+
var pathArray = modulePath.split("/").slice(2);
|
41
|
+
var result = lib;
|
42
|
+
for (var i in pathArray) {
|
43
|
+
result = result[pathArray[i]]
|
44
|
+
}
|
45
|
+
eval(result);
|
46
|
+
return exports;
|
47
|
+
}
|
48
|
+
|
37
49
|
var mapResults = [];
|
38
50
|
var emit = function(key, value) {
|
39
51
|
mapResults.push({key: key, value: value});
|
@@ -108,7 +120,7 @@ module CouchPotato
|
|
108
120
|
|
109
121
|
def failure_message_for_should_not
|
110
122
|
"Expected not to map/reduce to #{@actual_ruby.inspect} but did."
|
111
|
-
end
|
123
|
+
end
|
112
124
|
end
|
113
125
|
end
|
114
126
|
end
|
@@ -25,7 +25,19 @@ module CouchPotato
|
|
25
25
|
js = <<-JS
|
26
26
|
var doc = #{@input_ruby.to_json};
|
27
27
|
var map = #{view_spec.map_function};
|
28
|
+
var lib = #{view_spec.respond_to?(:lib) && view_spec.lib.to_json};
|
28
29
|
var result = [];
|
30
|
+
var require = function(modulePath) {
|
31
|
+
var exports = {};
|
32
|
+
var pathArray = modulePath.split("/").slice(2);
|
33
|
+
var result = lib;
|
34
|
+
for (var i in pathArray) {
|
35
|
+
result = result[pathArray[i]]
|
36
|
+
}
|
37
|
+
eval(result);
|
38
|
+
return exports;
|
39
|
+
}
|
40
|
+
|
29
41
|
var emit = function(key, value) {
|
30
42
|
result.push([key, value]);
|
31
43
|
};
|
data/lib/couch_potato/version.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module CouchPotato
|
2
2
|
module View
|
3
3
|
class BaseViewSpec
|
4
|
-
attr_reader :reduce_function, :list_name, :list_function, :design_document, :view_name, :klass, :options, :language
|
4
|
+
attr_reader :reduce_function, :lib, :list_name, :list_function, :design_document, :view_name, :klass, :options, :language
|
5
5
|
attr_accessor :view_parameters
|
6
6
|
|
7
7
|
private :klass, :options
|
@@ -8,6 +8,9 @@ module CouchPotato
|
|
8
8
|
#
|
9
9
|
# example:
|
10
10
|
# view :my_custom_view, :map => "function(doc) { emit(doc._id, null); }", :type => :raw, :results_filter => lambda{|results| results['rows].map{|row| row['value']}}
|
11
|
+
#
|
12
|
+
# example:
|
13
|
+
# view :my_custom_view, :map => "function(doc) { emit(doc._id, null); }", :type => :raw, :lib => {:module => "exports.name = 'module';"
|
11
14
|
class RawViewSpec < BaseViewSpec
|
12
15
|
def map_function
|
13
16
|
options[:map]
|
@@ -16,6 +19,10 @@ module CouchPotato
|
|
16
19
|
def reduce_function
|
17
20
|
options[:reduce]
|
18
21
|
end
|
22
|
+
|
23
|
+
def lib
|
24
|
+
options[:lib]
|
25
|
+
end
|
19
26
|
end
|
20
27
|
end
|
21
28
|
end
|
@@ -2,12 +2,13 @@ module CouchPotato
|
|
2
2
|
module View
|
3
3
|
# Used to query views (and create them if they don't exist). Usually you won't have to use this class directly. Instead it is used internally by the CouchPotato::Database.view method.
|
4
4
|
class ViewQuery
|
5
|
-
def initialize(couchrest_database, design_document_name, view, list = nil, language = :javascript)
|
5
|
+
def initialize(couchrest_database, design_document_name, view, list = nil, lib = nil, language = :javascript)
|
6
6
|
@database = couchrest_database
|
7
7
|
@design_document_name = design_document_name
|
8
8
|
@view_name = view.keys[0]
|
9
9
|
@map_function = view.values[0][:map]
|
10
10
|
@reduce_function = view.values[0][:reduce]
|
11
|
+
@lib = lib
|
11
12
|
@language = language
|
12
13
|
if list
|
13
14
|
@list_function = list.values[0]
|
@@ -34,6 +35,9 @@ module CouchPotato
|
|
34
35
|
view_updated unless design_doc.nil?
|
35
36
|
design_doc ||= empty_design_document
|
36
37
|
design_doc['views'][@view_name.to_s] = view_functions
|
38
|
+
if @lib
|
39
|
+
design_doc['views']['lib'] = (design_doc['views']['lib'] || {}).merge(@lib)
|
40
|
+
end
|
37
41
|
if @list_function
|
38
42
|
design_doc['lists'] ||= {}
|
39
43
|
design_doc['lists'][@list_name.to_s] = @list_function
|
data/lib/couch_potato.rb
CHANGED
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'conflict handling' do
|
4
|
+
let(:db) { CouchPotato::Database.new(CouchPotato.couchrest_database) }
|
5
|
+
class Measurement
|
6
|
+
include CouchPotato::Persistence
|
7
|
+
|
8
|
+
property :value
|
9
|
+
end
|
10
|
+
|
11
|
+
it 're-runs the block with a reloaded instance and saves again when there is a conflict' do
|
12
|
+
measurement = Measurement.new value: 1
|
13
|
+
db.save! measurement
|
14
|
+
|
15
|
+
db.couchrest_database.save_doc measurement.reload._document.merge('value' => 2)
|
16
|
+
|
17
|
+
measurement.is_dirty
|
18
|
+
db.save measurement do |m|
|
19
|
+
m.value += 1
|
20
|
+
end
|
21
|
+
|
22
|
+
expect(measurement.reload.value).to eql(3)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'raises an error after 5 tries' do
|
26
|
+
couchrest_database = stub(:couchrest_database, info: stub.as_null_object)
|
27
|
+
couchrest_database.stub(:save_doc).and_raise(RestClient::Conflict)
|
28
|
+
db = CouchPotato::Database.new(couchrest_database)
|
29
|
+
measurement = stub(:measurement).as_null_object
|
30
|
+
measurement.stub(:run_callbacks).and_yield
|
31
|
+
|
32
|
+
expect {
|
33
|
+
db.save(measurement, false) {|m| }
|
34
|
+
}.to raise_error(CouchPotato::Conflict)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'runs the block before saving' do
|
38
|
+
measurement = Measurement.new value: 1
|
39
|
+
db.save! measurement
|
40
|
+
|
41
|
+
db.save measurement do |m|
|
42
|
+
m.value += 1
|
43
|
+
end
|
44
|
+
|
45
|
+
expect(measurement.reload.value).to eql(2)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'retries destroying a document' do
|
49
|
+
measurement = Measurement.new value: 1
|
50
|
+
db.save! measurement
|
51
|
+
|
52
|
+
db.couchrest_database.save_doc measurement.reload._document.merge('value' => 2)
|
53
|
+
db.destroy measurement
|
54
|
+
|
55
|
+
expect(measurement.reload).to be_nil
|
56
|
+
end
|
57
|
+
end
|
data/spec/fixtures/person.rb
CHANGED
data/spec/property_spec.rb
CHANGED
@@ -72,6 +72,13 @@ describe 'properties' do
|
|
72
72
|
c.title.should == {'key' => 'value'}
|
73
73
|
end
|
74
74
|
|
75
|
+
it "should persist a HashWithIndifferentAccess" do
|
76
|
+
c = Person.new :information => HashWithIndifferentAccess.new({"key" => "value"})
|
77
|
+
CouchPotato.database.save_document! c
|
78
|
+
c = CouchPotato.database.load_document c.id
|
79
|
+
c.information.should == {'key' => 'value'}
|
80
|
+
end
|
81
|
+
|
75
82
|
def it_should_persist value
|
76
83
|
c = Comment.new :title => value
|
77
84
|
CouchPotato.database.save_document! c
|
data/spec/unit/database_spec.rb
CHANGED
@@ -342,9 +342,10 @@ describe CouchPotato::Database, 'view' do
|
|
342
342
|
CouchPotato::View::ViewQuery.stub(:new => stub('view query', :query_view! => {'rows' => [@result]}))
|
343
343
|
end
|
344
344
|
|
345
|
-
it "initialzes a view query with map/reduce/list funtions" do
|
345
|
+
it "initialzes a view query with map/reduce/list/lib funtions" do
|
346
346
|
@spec.stub(:design_document => 'design_doc', :view_name => 'my_view',
|
347
347
|
:map_function => '<map_code>', :reduce_function => '<reduce_code>',
|
348
|
+
:lib => {:test => '<test_code>'},
|
348
349
|
:list_name => 'my_list', :list_function => '<list_code>', :language => 'javascript')
|
349
350
|
CouchPotato::View::ViewQuery.should_receive(:new).with(
|
350
351
|
@couchrest_db,
|
@@ -354,21 +355,55 @@ describe CouchPotato::Database, 'view' do
|
|
354
355
|
:reduce => '<reduce_code>'
|
355
356
|
}},
|
356
357
|
{'my_list' => '<list_code>'},
|
358
|
+
{:test => '<test_code>'},
|
357
359
|
'javascript')
|
358
360
|
@db.view(@spec)
|
359
361
|
end
|
360
362
|
|
363
|
+
it "initialzes a view query with map/reduce/list funtions" do
|
364
|
+
@spec.stub(:design_document => 'design_doc', :view_name => 'my_view',
|
365
|
+
:map_function => '<map_code>', :reduce_function => '<reduce_code>',
|
366
|
+
:lib => nil, :list_name => 'my_list', :list_function => '<list_code>',
|
367
|
+
:language => 'javascript')
|
368
|
+
CouchPotato::View::ViewQuery.should_receive(:new).with(
|
369
|
+
@couchrest_db,
|
370
|
+
'design_doc',
|
371
|
+
{'my_view' => {
|
372
|
+
:map => '<map_code>',
|
373
|
+
:reduce => '<reduce_code>'
|
374
|
+
}},
|
375
|
+
{'my_list' => '<list_code>'},
|
376
|
+
nil,
|
377
|
+
'javascript')
|
378
|
+
@db.view(@spec)
|
379
|
+
end
|
380
|
+
|
381
|
+
it "initialzes a view query with only map/reduce/lib functions" do
|
382
|
+
@spec.stub(:design_document => 'design_doc', :view_name => 'my_view',
|
383
|
+
:map_function => '<map_code>', :reduce_function => '<reduce_code>',
|
384
|
+
:list_name => nil, :list_function => nil,
|
385
|
+
:lib => {:test => '<test_code>'}).as_null_object
|
386
|
+
CouchPotato::View::ViewQuery.should_receive(:new).with(
|
387
|
+
@couchrest_db,
|
388
|
+
'design_doc',
|
389
|
+
{'my_view' => {
|
390
|
+
:map => '<map_code>',
|
391
|
+
:reduce => '<reduce_code>'
|
392
|
+
}}, nil, {:test => '<test_code>'}, anything)
|
393
|
+
@db.view(@spec)
|
394
|
+
end
|
395
|
+
|
361
396
|
it "initialzes a view query with only map/reduce functions" do
|
362
397
|
@spec.stub(:design_document => 'design_doc', :view_name => 'my_view',
|
363
398
|
:map_function => '<map_code>', :reduce_function => '<reduce_code>',
|
364
|
-
:list_name => nil, :list_function => nil).as_null_object
|
399
|
+
:lib => nil, :list_name => nil, :list_function => nil).as_null_object
|
365
400
|
CouchPotato::View::ViewQuery.should_receive(:new).with(
|
366
401
|
@couchrest_db,
|
367
402
|
'design_doc',
|
368
403
|
{'my_view' => {
|
369
404
|
:map => '<map_code>',
|
370
405
|
:reduce => '<reduce_code>'
|
371
|
-
}}, nil, anything)
|
406
|
+
}}, nil, nil, anything)
|
372
407
|
@db.view(@spec)
|
373
408
|
end
|
374
409
|
|
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
2
2
|
require 'couch_potato/rspec'
|
3
3
|
|
4
4
|
describe CouchPotato::RSpec::MapToMatcher do
|
5
|
-
|
5
|
+
|
6
6
|
describe "basic map function" do
|
7
7
|
before(:each) do
|
8
8
|
@view_spec = stub(:map_function => "function(doc) {emit(doc.name, doc.tags.length);}")
|
@@ -16,16 +16,16 @@ describe CouchPotato::RSpec::MapToMatcher do
|
|
16
16
|
@view_spec.should_not map({:name => 'horst', :tags => ['person', 'male']}).to(['horst', 3])
|
17
17
|
end
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
describe "functions emitting multiple times" do
|
21
21
|
before(:each) do
|
22
22
|
@view_spec = stub(:map_function => "function(doc) {emit(doc.name, doc.tags.length); emit(doc.tags[0], doc.tags[1])};")
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
it "should pass if the given function emits the expected javascript" do
|
26
26
|
@view_spec.should map({:name => 'horst', :tags => ['person', 'male']}).to(['horst', 2], ['person', 'male'])
|
27
27
|
end
|
28
|
-
|
28
|
+
|
29
29
|
it "should return false if the given function emits different javascript" do
|
30
30
|
@view_spec.should_not map({:name => 'horst', :tags => ['person', 'male']}).to(['horst', 2], ['male', 'person'])
|
31
31
|
end
|
@@ -35,18 +35,26 @@ describe CouchPotato::RSpec::MapToMatcher do
|
|
35
35
|
spec = stub(:map_function => "function(doc) { emit(null, new Date(1368802800000)); }")
|
36
36
|
spec.should map({}).to([nil, "2013-05-17T15:00:00.000Z"])
|
37
37
|
end
|
38
|
-
|
38
|
+
|
39
|
+
it "should work with commonJS modules" do
|
40
|
+
spec = stub(
|
41
|
+
:map_function => "function(doc) { var test = require('views/lib/test'); emit(null, test.test); }",
|
42
|
+
:lib => {:test => "exports.test = 'test';"}
|
43
|
+
)
|
44
|
+
spec.should map({}).to([nil, "test"])
|
45
|
+
end
|
46
|
+
|
39
47
|
describe "failing specs" do
|
40
48
|
before(:each) do
|
41
49
|
@view_spec = stub(:map_function => "function(doc) {emit(doc.name, null)}")
|
42
50
|
end
|
43
|
-
|
51
|
+
|
44
52
|
it "should have a nice error message for failing should" do
|
45
53
|
lambda {
|
46
54
|
@view_spec.should map({:name => 'bill'}).to(['linus', nil])
|
47
55
|
}.should raise_error('Expected to map to [["linus", nil]] but got [["bill", nil]].')
|
48
56
|
end
|
49
|
-
|
57
|
+
|
50
58
|
it "should have a nice error message for failing should not" do
|
51
59
|
lambda {
|
52
60
|
@view_spec.should_not map({:name => 'bill'}).to(['bill', nil])
|
@@ -65,11 +73,11 @@ describe CouchPotato::RSpec::ReduceToMatcher do
|
|
65
73
|
};
|
66
74
|
}")
|
67
75
|
end
|
68
|
-
|
76
|
+
|
69
77
|
it "should pass if the given function return the expected javascript" do
|
70
78
|
@view_spec.should reduce([], [1, 2, 3]).to(6)
|
71
79
|
end
|
72
|
-
|
80
|
+
|
73
81
|
it "should not pass if the given function returns different javascript" do
|
74
82
|
@view_spec.should_not reduce([], [1, 2, 3]).to(7)
|
75
83
|
end
|
@@ -78,25 +86,25 @@ describe CouchPotato::RSpec::ReduceToMatcher do
|
|
78
86
|
spec = stub(:reduce_function => "function() { return new Date(1368802800000); }")
|
79
87
|
spec.should reduce([], []).to("2013-05-17T15:00:00.000Z")
|
80
88
|
end
|
81
|
-
|
89
|
+
|
82
90
|
describe "rereduce" do
|
83
91
|
it "should pass if the given function return the expected javascript" do
|
84
92
|
@view_spec.should rereduce([], [1, 2, 3]).to(12)
|
85
93
|
end
|
86
|
-
|
94
|
+
|
87
95
|
it "should not pass if the given function returns different javascript" do
|
88
96
|
@view_spec.should_not rereduce([], [1, 2, 3]).to(13)
|
89
97
|
end
|
90
98
|
end
|
91
|
-
|
99
|
+
|
92
100
|
describe 'failing specs' do
|
93
|
-
|
101
|
+
|
94
102
|
it "should have a nice error message for failing should" do
|
95
103
|
lambda {
|
96
104
|
@view_spec.should reduce([], [1, 2, 3]).to(7)
|
97
105
|
}.should raise_error('Expected to reduce to 7 but got 6.')
|
98
106
|
end
|
99
|
-
|
107
|
+
|
100
108
|
it "should have a nice error message for failing should not" do
|
101
109
|
lambda {
|
102
110
|
@view_spec.should_not reduce([], [1, 2, 3]).to(6)
|
@@ -114,7 +122,8 @@ describe CouchPotato::RSpec::MapReduceToMatcher do
|
|
114
122
|
}",
|
115
123
|
:reduce_function => "function (keys, values, rereduce) {
|
116
124
|
return Math.max.apply(this, values);
|
117
|
-
}"
|
125
|
+
}"
|
126
|
+
)
|
118
127
|
@docs = [
|
119
128
|
{:name => "a", :age => 25, :numbers => [1, 2]},
|
120
129
|
{:name => "b", :age => 25, :numbers => [3, 4]},
|
@@ -128,6 +137,14 @@ describe CouchPotato::RSpec::MapReduceToMatcher do
|
|
128
137
|
spec.should map_reduce({}).to({"key" => nil, "value" => "2013-05-17T15:00:00.000Z"})
|
129
138
|
end
|
130
139
|
|
140
|
+
it "should handle date return values" do
|
141
|
+
spec = stub(
|
142
|
+
:map_function => "function() { var test = require('views/lib/test'); emit(null, test.test); }",
|
143
|
+
:reduce_function => "function(keys, values) { return 'test' }",
|
144
|
+
:lib => {:test => "exports.test = 'test'"})
|
145
|
+
spec.should map_reduce({}).to({"key" => nil, "value" => "test"})
|
146
|
+
end
|
147
|
+
|
131
148
|
context "without grouping" do
|
132
149
|
it "should not group by key by default" do
|
133
150
|
@view_spec.should map_reduce(@docs).to({"key" => nil, "value" => 8})
|
@@ -215,11 +232,11 @@ describe CouchPotato::RSpec::ListAsMatcher do
|
|
215
232
|
before(:each) do
|
216
233
|
@view_spec = stub(:list_function => "function() {var row = getRow(); send(JSON.stringify([{text: row.text + ' world'}]));}")
|
217
234
|
end
|
218
|
-
|
235
|
+
|
219
236
|
it "should pass if the function return the expected json" do
|
220
237
|
@view_spec.should list({'rows' => [{:text => 'hello'}]}).as([{'text' => 'hello world'}])
|
221
238
|
end
|
222
|
-
|
239
|
+
|
223
240
|
it "should not pass if the function does not return the expected json" do
|
224
241
|
@view_spec.should_not list({'rows' => [{:text => 'hello'}]}).as([{'text' => 'hello there'}])
|
225
242
|
end
|
@@ -235,7 +252,7 @@ describe CouchPotato::RSpec::ListAsMatcher do
|
|
235
252
|
@view_spec.should list({'rows' => [{:text => 'hello'}]}).as([{'text' => 'hello there'}])
|
236
253
|
}.should raise_error('Expected to list as [{"text"=>"hello there"}] but got [{"text"=>"hello world"}].')
|
237
254
|
end
|
238
|
-
|
255
|
+
|
239
256
|
it "should have a nice error message for failing should not" do
|
240
257
|
lambda {
|
241
258
|
@view_spec.should_not list({'rows' => [{:text => 'hello'}]}).as([{'text' => 'hello world'}])
|
@@ -15,10 +15,13 @@ describe CouchPotato::View::ViewQuery, 'query_view!' do
|
|
15
15
|
db = mock 'db', :get => nil, :view => nil
|
16
16
|
|
17
17
|
db.should_receive(:save_doc).with(
|
18
|
-
'views' => {'view' => {'map' => '<map_code>', 'reduce' => '<reduce_code>'}},
|
19
|
-
'lists' => {},
|
18
|
+
'views' => {'view' => {'map' => '<map_code>', 'reduce' => '<reduce_code>'}, 'lib' => {'test' => '<lib_code>'}},
|
19
|
+
'lists' => {},
|
20
|
+
"_id" => "_design/design",
|
21
|
+
"language" => "javascript"
|
22
|
+
)
|
20
23
|
|
21
|
-
CouchPotato::View::ViewQuery.new(db, 'design', :view => {:map => '<map_code>', :reduce => '<reduce_code>'}).query_view!
|
24
|
+
CouchPotato::View::ViewQuery.new(db, 'design', {:view => {:map => '<map_code>', :reduce => '<reduce_code>'}}, nil, {'test' => "<lib_code>"}).query_view!
|
22
25
|
end
|
23
26
|
|
24
27
|
it 'updates a view in erlang if it does not exist' do
|
@@ -30,13 +33,13 @@ describe CouchPotato::View::ViewQuery, 'query_view!' do
|
|
30
33
|
|
31
34
|
CouchPotato::View::ViewQuery.new(db, 'design',
|
32
35
|
{:view => {:map => '<map_code>', :reduce => '<reduce_code>'}},
|
33
|
-
nil, :erlang).query_view!
|
36
|
+
nil, nil, :erlang).query_view!
|
34
37
|
end
|
35
38
|
|
36
|
-
it "does not update a view when the
|
39
|
+
it "does not update a view when the views object haven't changed" do
|
37
40
|
db = mock 'db', :get => {'views' => {'view' => {'map' => '<map_code>', 'reduce' => '<reduce_code>'}}}, :view => nil
|
38
41
|
db.should_not_receive(:save_doc)
|
39
|
-
CouchPotato::View::ViewQuery.new(db, 'design', :view => {:map => '<map_code>', :reduce => '<reduce_code>'}).query_view!
|
42
|
+
CouchPotato::View::ViewQuery.new(db, 'design', {:view => {:map => '<map_code>', :reduce => '<reduce_code>'}}, nil, nil).query_view!
|
40
43
|
end
|
41
44
|
|
42
45
|
it "does not update a view when the list function hasn't changed" do
|
@@ -45,64 +48,102 @@ describe CouchPotato::View::ViewQuery, 'query_view!' do
|
|
45
48
|
CouchPotato::View::ViewQuery.new(db, 'design', {:view => {:map => '<map_code>', :reduce => '<reduce_code>'}}, :list0 => '<list_code>').query_view!
|
46
49
|
end
|
47
50
|
|
51
|
+
it "does not update a view when the lib function hasn't changed" do
|
52
|
+
db = mock 'db', :get => {'views' => {'view' => {'map' => '<map_code>', 'reduce' => '<reduce_code>'}}, 'lib' => {'test' => '<lib_code>'}}, :view => nil
|
53
|
+
db.should_not_receive(:save_doc)
|
54
|
+
CouchPotato::View::ViewQuery.new(db, 'design', {:view => {:map => '<map_code>', :reduce => '<reduce_code>'}}, nil, {'test' => "<lib_code>"}).query_view!
|
55
|
+
end
|
56
|
+
|
48
57
|
it "updates a view when the map function has changed" do
|
49
58
|
db = mock 'db', :get => {'views' => {'view2' => {'map' => '<map_code>', 'reduce' => '<reduce_code>'}}}, :view => nil
|
50
59
|
db.should_receive(:save_doc)
|
51
60
|
CouchPotato::View::ViewQuery.new(db, 'design', :view2 => {:map => '<new map_code>', :reduce => '<reduce_code>'}).query_view!
|
52
61
|
end
|
53
62
|
|
54
|
-
it "
|
55
|
-
db = mock 'db', :get => {'views' => {}}, :view => nil
|
63
|
+
it "updates a view when the map function has changed" do
|
64
|
+
db = mock 'db', :get => {'views' => {'view3' => {'map' => '<map_code>'}}}, :view => nil
|
65
|
+
db.should_receive(:save_doc)
|
66
|
+
CouchPotato::View::ViewQuery.new(db, 'design', :view3 => {:map => '<new map_code>'}).query_view!
|
67
|
+
end
|
56
68
|
|
57
|
-
|
69
|
+
it "updates a view when the lib hash has changed" do
|
70
|
+
db = mock 'db', :get => {'views' => {'view4' => {'map' => '<map_code>'}}, 'lib' => {'test' => "<test_lib>"}}, :view => nil
|
71
|
+
db.should_receive(:save_doc)
|
72
|
+
CouchPotato::View::ViewQuery.new(db, 'design', {:view4 => {:map => '<map_code>'}}, nil, {:test => "<test_lib>"}).query_view!
|
73
|
+
end
|
74
|
+
|
75
|
+
it "doesn't override libs with different names" do
|
76
|
+
db = mock 'db', :get => {'views' => {'view5' => {'map' => '<map_code>'}, 'lib' => {'test' => "<test_lib>"}}}, :view => nil
|
77
|
+
db.should_receive(:save_doc).with({
|
78
|
+
'views' => {
|
79
|
+
'view5' => {'map' => '<map_code>'},
|
80
|
+
'lib' => {'test' => '<test_lib>', 'test1' => '<test1_lib>'}
|
81
|
+
}
|
82
|
+
})
|
83
|
+
CouchPotato::View::ViewQuery.new(db, 'design', {:view5 => {:map => '<map_code>'}}, nil, {'test1' => '<test1_lib>'}).query_view!
|
84
|
+
end
|
58
85
|
|
59
|
-
|
86
|
+
it "overrides libs with the same name" do
|
87
|
+
db = mock 'db', :get => {'views' => {'view6' => {'map' => '<map_code>'}, 'lib' => {'test' => "<test_lib>"}}}, :view => nil
|
88
|
+
db.should_receive(:save_doc).with({
|
89
|
+
'views' => {
|
90
|
+
'view6' => {'map' => '<map_code>'},
|
91
|
+
'lib' => {'test' => '<test1_lib>'}
|
92
|
+
}
|
93
|
+
})
|
94
|
+
CouchPotato::View::ViewQuery.new(db, 'design', {:view6 => {:map => '<map_code>'}}, nil, {'test' => '<test1_lib>'}).query_view!
|
95
|
+
end
|
96
|
+
|
97
|
+
it "does not pass in reduce or lib keys if there is no lib or reduce object" do
|
98
|
+
db = mock 'db', :get => {'views' => {}}, :view => nil
|
99
|
+
db.should_receive(:save_doc).with('views' => {'view7' => {'map' => '<map code>'}})
|
100
|
+
CouchPotato::View::ViewQuery.new(db, 'design', :view7 => {:map => '<map code>', :reduce => nil}).query_view!
|
60
101
|
end
|
61
102
|
|
62
103
|
it "updates a view when the reduce function has changed" do
|
63
|
-
db = mock 'db', :get => {'views' => {'
|
104
|
+
db = mock 'db', :get => {'views' => {'view8' => {'map' => '<map_code>', 'reduce' => '<reduce_code>'}}}, :view => nil
|
64
105
|
db.should_receive(:save_doc)
|
65
|
-
CouchPotato::View::ViewQuery.new(db, 'design', :
|
106
|
+
CouchPotato::View::ViewQuery.new(db, 'design', :view8 => {:map => '<map_code>', :reduce => '<new reduce_code>'}).query_view!
|
66
107
|
end
|
67
108
|
|
68
109
|
it "updates a view when the list function has changed" do
|
69
110
|
db = mock 'db', :get => {
|
70
|
-
'views' => {'
|
111
|
+
'views' => {'view9' => {'map' => '<map_code>', 'reduce' => '<reduce_code>'}},
|
71
112
|
'lists' => {'list1' => '<list_code>'}
|
72
113
|
}, :view => nil
|
73
114
|
db.should_receive(:save_doc)
|
74
|
-
CouchPotato::View::ViewQuery.new(db, 'design', {:
|
115
|
+
CouchPotato::View::ViewQuery.new(db, 'design', {:view9 => {:map => '<map_code>', :reduce => '<reduce_code>'}}, :list1 => '<new_list_code>').query_view!
|
75
116
|
end
|
76
117
|
|
77
118
|
it "updates a view when there wasn't a list function but now there is one" do
|
78
119
|
db = mock 'db', :get => {
|
79
|
-
'views' => {'
|
120
|
+
'views' => {'view10' => {'map' => '<map_code>', 'reduce' => '<reduce_code>'}}
|
80
121
|
}, :view => nil
|
81
122
|
db.should_receive(:save_doc)
|
82
|
-
CouchPotato::View::ViewQuery.new(db, 'design', {:
|
123
|
+
CouchPotato::View::ViewQuery.new(db, 'design', {:view10 => {:map => '<map_code>', :reduce => '<reduce_code>'}}, :list1 => '<new_list_code>').query_view!
|
83
124
|
end
|
84
125
|
|
85
126
|
it "does not update a view when there is a list function but no list function is passed" do
|
86
127
|
db = mock 'db', :get => {
|
87
|
-
'views' => {'
|
128
|
+
'views' => {'view11' => {'map' => '<map_code>', 'reduce' => '<reduce_code>'}},
|
88
129
|
'lists' => {'list1' => '<list_code>'}
|
89
130
|
}, :view => nil
|
90
131
|
db.should_not_receive(:save_doc)
|
91
|
-
CouchPotato::View::ViewQuery.new(db, 'design', {:
|
132
|
+
CouchPotato::View::ViewQuery.new(db, 'design', {:view11 => {:map => '<map_code>', :reduce => '<reduce_code>'}}, {}).query_view!
|
92
133
|
end
|
93
134
|
|
94
135
|
it "does not update a view when there were no lists before and no list function is passed" do
|
95
136
|
db = mock 'db', :get => {
|
96
|
-
'views' => {'
|
137
|
+
'views' => {'view12' => {'map' => '<map_code>', 'reduce' => '<reduce_code>'}}
|
97
138
|
}, :view => nil
|
98
139
|
db.should_not_receive(:save_doc)
|
99
|
-
CouchPotato::View::ViewQuery.new(db, 'design', {:
|
140
|
+
CouchPotato::View::ViewQuery.new(db, 'design', {:view12 => {:map => '<map_code>', :reduce => '<reduce_code>'}}, {}).query_view!
|
100
141
|
end
|
101
142
|
|
102
143
|
it "queries CouchRest directly when querying a list" do
|
103
144
|
db = stub('db').as_null_object
|
104
|
-
CouchRest.should_receive(:get).with('http://127.0.0.1:5984/couch_potato_test/_design/my_design/_list/list1/
|
105
|
-
CouchPotato::View::ViewQuery.new(db, 'my_design', {:
|
145
|
+
CouchRest.should_receive(:get).with('http://127.0.0.1:5984/couch_potato_test/_design/my_design/_list/list1/view13?key=1')
|
146
|
+
CouchPotato::View::ViewQuery.new(db, 'my_design', {:view13 => {:map => '<map_code>', :reduce => '<reduce_code>'}}, :list1 => '<new_list_code>').query_view!(:key => 1)
|
106
147
|
end
|
107
148
|
|
108
149
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: couch_potato
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-11-01 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: json
|
@@ -179,6 +179,7 @@ files:
|
|
179
179
|
- rails/reload_classes.rb
|
180
180
|
- spec/attachments_spec.rb
|
181
181
|
- spec/callbacks_spec.rb
|
182
|
+
- spec/conflict_handling_spec.rb
|
182
183
|
- spec/create_spec.rb
|
183
184
|
- spec/default_property_spec.rb
|
184
185
|
- spec/destroy_spec.rb
|
@@ -244,6 +245,7 @@ summary: Ruby persistence layer for CouchDB
|
|
244
245
|
test_files:
|
245
246
|
- spec/attachments_spec.rb
|
246
247
|
- spec/callbacks_spec.rb
|
248
|
+
- spec/conflict_handling_spec.rb
|
247
249
|
- spec/create_spec.rb
|
248
250
|
- spec/default_property_spec.rb
|
249
251
|
- spec/destroy_spec.rb
|