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 CHANGED
@@ -1,5 +1,9 @@
1
1
  ## Changes
2
2
 
3
+ ### 1.1.0
4
+
5
+ * adds conflict handling for Database#save/destroy (Alexander Lang)
6
+
3
7
  ### 1.0.1
4
8
 
5
9
  * fixes error when bulk loaded document does not respond to database= (Alexander Lang)
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
- def save_document(document, validate = true)
75
- return true unless document.dirty? || document.new?
76
- if document.new?
77
- create_document(document, validate)
78
- else
79
- update_document(document, validate)
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
- document.run_callbacks :destroy do
92
- document._deleted = true
93
- couchrest_database.delete_doc document.to_hash
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
  };
@@ -1,3 +1,3 @@
1
1
  module CouchPotato
2
- VERSION = "1.0.1"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -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
@@ -13,6 +13,10 @@ module CouchPotato
13
13
  options[:reduce]
14
14
  end
15
15
 
16
+ def lib
17
+ options[:lib]
18
+ end
19
+
16
20
  def view_parameters
17
21
  {:include_docs => options[:include_docs] || false}.merge(super)
18
22
  end
@@ -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
@@ -13,6 +13,7 @@ module CouchPotato
13
13
  Config.default_language = :javascript
14
14
 
15
15
  class NotFound < StandardError; end
16
+ class Conflict < StandardError; end
16
17
 
17
18
  # returns all the classes that implement the CouchPotato::Persistence module
18
19
  def self.models
@@ -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
@@ -3,4 +3,5 @@ class Person
3
3
 
4
4
  property :name, :type => Address
5
5
  property :ship_address
6
- end
6
+ property :information, :type => Hash
7
+ end
@@ -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
@@ -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' => {}, "_id" => "_design/design", "language" => "javascript")
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 map/reduce functions haven't changed" do
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 "does not pass in a reduce key if there is no reduce function" do
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
- db.should_receive(:save_doc).with('views' => {'view3' => {'map' => '<map code>'}})
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
- CouchPotato::View::ViewQuery.new(db, 'design', :view3 => {:map => '<map code>', :reduce => nil}).query_view!
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' => {'view4' => {'map' => '<map_code>', 'reduce' => '<reduce_code>'}}}, :view => nil
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', :view4 => {:map => '<map_code>', :reduce => '<new reduce_code>'}).query_view!
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' => {'view5' => {'map' => '<map_code>', 'reduce' => '<reduce_code>'}},
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', {:view5 => {:map => '<map_code>', :reduce => '<reduce_code>'}}, :list1 => '<new_list_code>').query_view!
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' => {'view6' => {'map' => '<map_code>', 'reduce' => '<reduce_code>'}}
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', {:view6 => {:map => '<map_code>', :reduce => '<reduce_code>'}}, :list1 => '<new_list_code>').query_view!
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' => {'view7' => {'map' => '<map_code>', 'reduce' => '<reduce_code>'}},
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', {:view7 => {:map => '<map_code>', :reduce => '<reduce_code>'}}, {}).query_view!
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' => {'view8' => {'map' => '<map_code>', 'reduce' => '<reduce_code>'}}
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', {:view8 => {:map => '<map_code>', :reduce => '<reduce_code>'}}, {}).query_view!
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/view9?key=1')
105
- CouchPotato::View::ViewQuery.new(db, 'my_design', {:view9 => {:map => '<map_code>', :reduce => '<reduce_code>'}}, :list1 => '<new_list_code>').query_view!(:key => 1)
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.1
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-06-07 00:00:00.000000000 Z
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