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