couch_potato 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|