couchrest 0.26 → 0.28

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.
@@ -16,21 +16,19 @@ require 'rubygems'
16
16
  begin
17
17
  require 'json'
18
18
  rescue LoadError
19
- raise "You need install and require your own compatible json library since couchrest rest couldn't load the json/json_pure gem" unless Kernel.const_defined?("JSON")
19
+ raise "You need install and require your own json compatible library since couchrest rest couldn't load the json/json_pure gem" unless Kernel.const_defined?("JSON")
20
20
  end
21
21
  require 'rest_client'
22
22
 
23
23
  $:.unshift File.dirname(__FILE__) unless
24
24
  $:.include?(File.dirname(__FILE__)) ||
25
25
  $:.include?(File.expand_path(File.dirname(__FILE__)))
26
-
27
- $COUCHREST_DEBUG ||= false
28
-
26
+
29
27
  require 'couchrest/monkeypatches'
30
28
 
31
29
  # = CouchDB, close to the metal
32
30
  module CouchRest
33
- VERSION = '0.26' unless self.const_defined?("VERSION")
31
+ VERSION = '0.28' unless self.const_defined?("VERSION")
34
32
 
35
33
  autoload :Server, 'couchrest/core/server'
36
34
  autoload :Database, 'couchrest/core/database'
@@ -145,7 +143,7 @@ module CouchRest
145
143
  begin
146
144
  JSON.parse(RestClient.put(uri, payload))
147
145
  rescue Exception => e
148
- if $COUCHREST_DEBUG == true
146
+ if $DEBUG
149
147
  raise "Error while sending a PUT request #{uri}\npayload: #{payload.inspect}\n#{e}"
150
148
  else
151
149
  raise e
@@ -157,7 +155,7 @@ module CouchRest
157
155
  begin
158
156
  JSON.parse(RestClient.get(uri), :max_nesting => false)
159
157
  rescue => e
160
- if $COUCHREST_DEBUG == true
158
+ if $DEBUG
161
159
  raise "Error while sending a GET request #{uri}\n: #{e}"
162
160
  else
163
161
  raise e
@@ -170,7 +168,7 @@ module CouchRest
170
168
  begin
171
169
  JSON.parse(RestClient.post(uri, payload))
172
170
  rescue Exception => e
173
- if $COUCHREST_DEBUG == true
171
+ if $DEBUG
174
172
  raise "Error while sending a POST request #{uri}\npayload: #{payload.inspect}\n#{e}"
175
173
  else
176
174
  raise e
@@ -142,8 +142,14 @@ module CouchRest
142
142
  bulk_save
143
143
  end
144
144
  result = if doc['_id']
145
- slug = escape_docid(doc['_id'])
146
- CouchRest.put "#{@uri}/#{slug}", doc
145
+ slug = escape_docid(doc['_id'])
146
+ begin
147
+ CouchRest.put "#{@uri}/#{slug}", doc
148
+ rescue RestClient::ResourceNotFound
149
+ p "resource not found when saving even tho an id was passed"
150
+ slug = doc['_id'] = @server.next_uuid
151
+ CouchRest.put "#{@uri}/#{slug}", doc
152
+ end
147
153
  else
148
154
  begin
149
155
  slug = doc['_id'] = @server.next_uuid
@@ -265,11 +271,16 @@ module CouchRest
265
271
  # DELETE the database itself. This is not undoable and could be rather
266
272
  # catastrophic. Use with care!
267
273
  def delete!
274
+ clear_extended_doc_fresh_cache
268
275
  CouchRest.delete @uri
269
276
  end
270
277
 
271
278
  private
272
279
 
280
+ def clear_extended_doc_fresh_cache
281
+ ::CouchRest::ExtendedDocument.subclasses.each{|klass| klass.design_doc_fresh = false if klass.respond_to?(:design_doc_fresh=) }
282
+ end
283
+
273
284
  def uri_for_attachment(doc, name)
274
285
  if doc.is_a?(String)
275
286
  puts "CouchRest::Database#fetch_attachment will eventually require a doc as the first argument, not a doc.id"
@@ -35,8 +35,11 @@ module CouchRest
35
35
  'all' => {
36
36
  'map' => "function(doc) {
37
37
  if (doc['couchrest-type'] == '#{self.to_s}') {
38
- emit(null,null);
38
+ emit(null,1);
39
39
  }
40
+ }",
41
+ 'reduce' => "function(keys, values) {
42
+ return sum(values);
40
43
  }"
41
44
  }
42
45
  }
@@ -44,16 +47,14 @@ module CouchRest
44
47
  end
45
48
 
46
49
  def refresh_design_doc
47
- design_doc['_id'] = design_doc_id
48
- design_doc.delete('_rev')
49
- #design_doc.database = nil
50
- self.design_doc_fresh = true
50
+ reset_design_doc
51
+ save_design_doc
51
52
  end
52
53
 
53
54
  # Save the design doc onto the default database, and update the
54
55
  # design_doc attribute
55
56
  def save_design_doc
56
- refresh_design_doc unless design_doc_fresh
57
+ reset_design_doc unless design_doc_fresh
57
58
  self.design_doc = update_design_doc(design_doc)
58
59
  end
59
60
 
@@ -64,6 +65,17 @@ module CouchRest
64
65
  end
65
66
 
66
67
  private
68
+
69
+ def reset_design_doc
70
+ current = self.database.get(design_doc_id) rescue nil
71
+ design_doc['_id'] = design_doc_id
72
+ if current.nil?
73
+ design_doc.delete('_rev')
74
+ else
75
+ design_doc['_rev'] = current['_rev']
76
+ end
77
+ self.design_doc_fresh = true
78
+ end
67
79
 
68
80
  # Writes out a design_doc to a given database, returning the
69
81
  # updated design doc
@@ -12,9 +12,18 @@ module CouchRest
12
12
  # name of the current class. Take the standard set of
13
13
  # CouchRest::Database#view options.
14
14
  def all(opts = {}, &block)
15
- view(:all, opts, &block)
15
+ view(:all, {:reduce => false}.merge(opts), &block)
16
16
  end
17
-
17
+
18
+ # Returns the number of documents that have the "couchrest-type" field
19
+ # equal to the name of the current class. Takes the standard set of
20
+ # CouchRest::Database#view options
21
+ def count(opts = {}, &block)
22
+ result = all({:reduce => true}.merge(opts), &block)['rows']
23
+ return 0 if result.empty?
24
+ result.first['value']
25
+ end
26
+
18
27
  # Load the first document that have the "couchrest-type" field equal to
19
28
  # the name of the current class.
20
29
  #
@@ -17,7 +17,7 @@ module CouchRest
17
17
  end
18
18
 
19
19
  def apply_defaults
20
- return unless self.respond_to?(:new_document?) && new_document?
20
+ return if self.respond_to?(:new_document?) && (new_document? == false)
21
21
  return unless self.class.respond_to?(:properties)
22
22
  return if self.class.properties.empty?
23
23
  # TODO: cache the default object
@@ -39,9 +39,10 @@ module CouchRest
39
39
  self.class.properties.each do |property|
40
40
  next unless property.casted
41
41
  key = self.has_key?(property.name) ? property.name : property.name.to_sym
42
+ # Don't cast the property unless it has a value
43
+ next unless self[key]
42
44
  target = property.type
43
45
  if target.is_a?(Array)
44
- next unless self[key]
45
46
  klass = ::CouchRest.constantize(target[0])
46
47
  self[property.name] = self[key].collect do |value|
47
48
  # Auto parse Time objects
@@ -56,12 +57,7 @@ module CouchRest
56
57
  else
57
58
  # Let people use :send as a Time parse arg
58
59
  klass = ::CouchRest.constantize(target)
59
- # I'm not convince we should or should not create a new instance if we are casting a doc/extended doc without default value and nothing was passed
60
- # unless (property.casted &&
61
- # (klass.superclass == CouchRest::ExtendedDocument || klass.superclass == CouchRest::Document) &&
62
- # (self[key].nil? || property.default.nil?))
63
- klass.send(property.init_method, self[key])
64
- #end
60
+ klass.send(property.init_method, self[key].dup)
65
61
  end
66
62
  self[property.name].casted_by = self if self[property.name].respond_to?(:casted_by)
67
63
  end
@@ -125,7 +125,7 @@ module CouchRest
125
125
  array_casted_properties = self.class.properties.select { |property| property.casted && property.type.instance_of?(Array) }
126
126
  array_casted_properties.each do |property|
127
127
  casted_values = self.send(property.name)
128
- next unless casted_values.respond_to?(:each) && casted_values.first.respond_to?(:valid?)
128
+ next unless casted_values.is_a?(Array) && casted_values.first.respond_to?(:valid?)
129
129
  casted_values.each do |value|
130
130
  result = (result && value.valid?) if value.respond_to?(:valid?)
131
131
  end
@@ -92,7 +92,6 @@ module RestClient
92
92
  # establish_connection(uri)
93
93
  # http = Thread.current[:connection]
94
94
  # require 'ruby-debug'
95
- # debugger
96
95
  # req.body_stream = nil
97
96
  #
98
97
  # res = http.request(req, payload)
@@ -10,12 +10,12 @@ module CouchRest
10
10
 
11
11
  def initialize(keys={})
12
12
  raise StandardError unless self.is_a? Hash
13
+ apply_defaults # defined in CouchRest::Mixins::Properties
13
14
  super()
14
15
  keys.each do |k,v|
15
16
  self[k.to_s] = v
16
17
  end if keys
17
- apply_defaults # defined in CouchRest::Mixins::Properties
18
- # cast_keys # defined in CouchRest::Mixins::Properties
18
+ cast_keys # defined in CouchRest::Mixins::Properties
19
19
  end
20
20
 
21
21
  def []= key, value
@@ -1,6 +1,7 @@
1
1
  require 'mime/types'
2
2
  require File.join(File.dirname(__FILE__), "property")
3
3
  require File.join(File.dirname(__FILE__), '..', 'mixins', 'extended_document_mixins')
4
+ require "enumerator"
4
5
 
5
6
  module CouchRest
6
7
 
@@ -12,6 +13,10 @@ module CouchRest
12
13
  include CouchRest::Mixins::DesignDoc
13
14
  include CouchRest::Mixins::ExtendedAttachments
14
15
  include CouchRest::Mixins::ClassProxy
16
+
17
+ def self.subclasses
18
+ ObjectSpace.enum_for(:each_object, class << self; self; end).to_a.delete_if{|k| k == self}
19
+ end
15
20
 
16
21
  def self.inherited(subklass)
17
22
  subklass.send(:include, CouchRest::Mixins::Properties)
@@ -45,6 +50,8 @@ module CouchRest
45
50
  end
46
51
  end
47
52
 
53
+
54
+
48
55
 
49
56
  # Automatically set <tt>updated_at</tt> and <tt>created_at</tt> fields
50
57
  # on the document whenever saving occurs. CouchRest uses a pretty
@@ -16,8 +16,6 @@ module CouchRest
16
16
  # # This feature is still not fully ported over,
17
17
  # # test are lacking, so please use with caution
18
18
  # def auto_validate!
19
- # require 'ruby-debug'
20
- # debugger
21
19
  # auto_validation = true
22
20
  # end
23
21
 
@@ -18,23 +18,58 @@ end
18
18
 
19
19
  describe "casting an extended document" do
20
20
 
21
+ before(:each) do
22
+ @driver = Driver.new(:name => 'Matt')
23
+ @car = Car.new(:name => 'Renault 306', :driver => @driver)
24
+ end
25
+
26
+ it "should retain all properties of the casted attribute" do
27
+ @car.driver.should == @driver
28
+ end
29
+
30
+ it "should let the casted document know who casted it" do
31
+ @car.driver.casted_by.should == @car
32
+ end
33
+ end
34
+
35
+ describe "assigning a value to casted attribute after initializing an object" do
36
+
21
37
  before(:each) do
22
38
  @car = Car.new(:name => 'Renault 306')
23
39
  @driver = Driver.new(:name => 'Matt')
24
40
  end
25
41
 
26
- # it "should not create an empty casted object" do
27
- # @car.driver.should be_nil
28
- # end
42
+ it "should not create an empty casted object" do
43
+ @car.driver.should be_nil
44
+ end
29
45
 
30
- it "should let you assign the casted attribute after instantializing an object" do
46
+ # Note that this isn't casting the attribute, it's just assigning it a value
47
+ # (see "should not cast attribute")
48
+ it "should let you assign the value" do
31
49
  @car.driver = @driver
32
50
  @car.driver.name.should == 'Matt'
33
51
  end
34
52
 
35
- it "should let the casted document who casted it" do
36
- Car.new(:name => 'Renault 306', :driver => @driver)
37
- @car.driver.casted_by.should == @car
53
+ it "should not cast attribute" do
54
+ @car.driver = JSON.parse(JSON.generate(@driver))
55
+ @car.driver.should_not be_instance_of(Driver)
56
+ end
57
+
58
+ end
59
+
60
+ describe "casting an extended document from parsed JSON" do
61
+
62
+ before(:each) do
63
+ @driver = Driver.new(:name => 'Matt')
64
+ @car = Car.new(:name => 'Renault 306', :driver => @driver)
65
+ @new_car = Car.new(JSON.parse(JSON.generate(@car)))
66
+ end
67
+
68
+ it "should cast casted attribute" do
69
+ @new_car.driver.should be_instance_of(Driver)
38
70
  end
39
71
 
72
+ it "should retain all properties of the casted attribute" do
73
+ @new_car.driver.should == @driver
74
+ end
40
75
  end
@@ -10,6 +10,8 @@ class WithCastedModelMixin < Hash
10
10
  include CouchRest::CastedModel
11
11
  property :name
12
12
  property :no_value
13
+ property :details, :default => {}
14
+ property :casted_attribute, :cast_as => 'WithCastedModelMixin'
13
15
  end
14
16
 
15
17
  class DummyModel < CouchRest::ExtendedDocument
@@ -41,11 +43,28 @@ describe CouchRest::CastedModel do
41
43
  @obj.name = 'Matt'
42
44
  @obj.name.should == 'Matt'
43
45
  end
46
+
47
+ it "should allow override of default" do
48
+ @obj = WithCastedModelMixin.new(:name => 'Eric', :details => {'color' => 'orange'})
49
+ @obj.name.should == 'Eric'
50
+ @obj.details['color'].should == 'orange'
51
+ end
52
+ end
53
+
54
+ describe "casted as an attribute, but without a value" do
55
+ before(:each) do
56
+ @obj = DummyModel.new
57
+ @casted_obj = @obj.casted_attribute
58
+ end
59
+ it "should be nil" do
60
+ @casted_obj.should == nil
61
+ end
44
62
  end
45
63
 
46
64
  describe "casted as attribute" do
47
65
  before(:each) do
48
- @obj = DummyModel.new(:casted_attribute => {:name => 'whatever'})
66
+ casted = {:name => 'not whatever'}
67
+ @obj = DummyModel.new(:casted_attribute => {:name => 'whatever', :casted_attribute => casted})
49
68
  @casted_obj = @obj.casted_attribute
50
69
  end
51
70
 
@@ -68,6 +87,14 @@ describe CouchRest::CastedModel do
68
87
  it "should return nil for the unknown attribute" do
69
88
  @casted_obj["unknown"].should be_nil
70
89
  end
90
+
91
+ it "should return {} for the hash attribute" do
92
+ @casted_obj.details.should == {}
93
+ end
94
+
95
+ it "should cast its own attributes" do
96
+ @casted_obj.casted_attribute.should be_instance_of(WithCastedModelMixin)
97
+ end
71
98
  end
72
99
 
73
100
  describe "casted as an array of a different type" do
@@ -107,6 +134,14 @@ describe CouchRest::CastedModel do
107
134
  casted_obj.name.should == "test"
108
135
  end
109
136
 
137
+ it "should retain an override of a casted model attribute's default" do
138
+ casted_obj = @obj.casted_attribute
139
+ casted_obj.details['color'] = 'orange'
140
+ @obj.save
141
+ casted_obj = DummyModel.get(@obj.id).casted_attribute
142
+ casted_obj.details['color'].should == 'orange'
143
+ end
144
+
110
145
  end
111
146
 
112
147
  describe "saving document with array of casted models and validation" do
@@ -224,6 +224,7 @@ describe "ExtendedDocument" do
224
224
 
225
225
  describe "finding all instances of a model" do
226
226
  before(:all) do
227
+ WithTemplateAndUniqueID.design_doc_fresh = false
227
228
  WithTemplateAndUniqueID.all.map{|o| o.destroy(true)}
228
229
  WithTemplateAndUniqueID.database.bulk_delete
229
230
  WithTemplateAndUniqueID.new('important-field' => '1').save
@@ -241,10 +242,30 @@ describe "ExtendedDocument" do
241
242
  rs.length.should == 4
242
243
  end
243
244
  end
244
-
245
+
246
+ describe "counting all instances of a model" do
247
+ before(:each) do
248
+ @db = reset_test_db!
249
+ WithTemplateAndUniqueID.design_doc_fresh = false
250
+ end
251
+
252
+ it ".count should return 0 if there are no docuemtns" do
253
+ WithTemplateAndUniqueID.count.should == 0
254
+ end
255
+
256
+ it ".count should return the number of documents" do
257
+ WithTemplateAndUniqueID.new('important-field' => '1').save
258
+ WithTemplateAndUniqueID.new('important-field' => '2').save
259
+ WithTemplateAndUniqueID.new('important-field' => '3').save
260
+
261
+ WithTemplateAndUniqueID.count.should == 3
262
+ end
263
+ end
264
+
245
265
  describe "finding the first instance of a model" do
246
266
  before(:each) do
247
267
  @db = reset_test_db!
268
+ WithTemplateAndUniqueID.design_doc_fresh = false
248
269
  WithTemplateAndUniqueID.new('important-field' => '1').save
249
270
  WithTemplateAndUniqueID.new('important-field' => '2').save
250
271
  WithTemplateAndUniqueID.new('important-field' => '3').save
@@ -100,7 +100,8 @@ describe "ExtendedDocument views" do
100
100
 
101
101
  describe "a ducktype view" do
102
102
  before(:all) do
103
- @id = TEST_SERVER.default_database.save_doc({:dept => true})['id']
103
+ reset_test_db!
104
+ @id = DB.save_doc({:dept => true})['id']
104
105
  end
105
106
  it "should setup" do
106
107
  duck = Course.get(@id) # from a different db
@@ -111,7 +112,7 @@ describe "ExtendedDocument views" do
111
112
  @doc = Course.design_doc
112
113
  @doc["views"]["by_dept"]["map"].should_not include("couchrest")
113
114
  end
114
- it "should not look for class" do |variable|
115
+ it "should not look for class" do
115
116
  @as = Course.by_dept
116
117
  @as[0]['_id'].should == @id
117
118
  end
@@ -120,7 +121,7 @@ describe "ExtendedDocument views" do
120
121
  describe "a model class not tied to a database" do
121
122
  before(:all) do
122
123
  reset_test_db!
123
- @db = TEST_SERVER.default_database
124
+ @db = DB
124
125
  %w{aaa bbb ddd eee}.each do |title|
125
126
  u = Unattached.new(:title => title)
126
127
  u.database = @db
@@ -185,6 +186,7 @@ describe "ExtendedDocument views" do
185
186
  end
186
187
  it "should be able to cleanup the db/bump the revision number" do
187
188
  # if the previous specs were not run, the model_design_doc will be blank
189
+ Unattached.use_database DB
188
190
  Unattached.view_by :questions
189
191
  Unattached.by_questions(:database => @db)
190
192
  original_revision = Unattached.model_design_doc(@db)['_rev']
@@ -196,7 +198,9 @@ describe "ExtendedDocument views" do
196
198
  describe "class proxy" do
197
199
  before(:all) do
198
200
  reset_test_db!
199
- @us = Unattached.on(TEST_SERVER.default_database)
201
+ # setup the class default doc to save the design doc
202
+ Unattached.use_database DB
203
+ @us = Unattached.on(DB)
200
204
  %w{aaa bbb ddd eee}.each do |title|
201
205
  u = @us.new(:title => title)
202
206
  u.save
@@ -254,7 +258,6 @@ describe "ExtendedDocument views" do
254
258
 
255
259
  describe "a model with a compound key view" do
256
260
  before(:all) do
257
- Article.design_doc_fresh = false
258
261
  Article.by_user_id_and_date.each{|a| a.destroy(true)}
259
262
  Article.database.bulk_delete
260
263
  written_at = Time.now - 24 * 3600 * 7
@@ -1,5 +1,5 @@
1
1
  class Article < CouchRest::ExtendedDocument
2
- use_database TEST_SERVER.default_database
2
+ use_database DB
3
3
  unique_id :slug
4
4
 
5
5
  view_by :date, :descending => true
@@ -5,7 +5,7 @@ class Card < CouchRest::ExtendedDocument
5
5
  auto_validate!
6
6
 
7
7
  # Set the default database to use
8
- use_database TEST_SERVER.default_database
8
+ use_database DB
9
9
 
10
10
  # Official Schema
11
11
  property :first_name
@@ -2,7 +2,7 @@ class Cat < CouchRest::ExtendedDocument
2
2
  include ::CouchRest::Validation
3
3
 
4
4
  # Set the default database to use
5
- use_database TEST_SERVER.default_database
5
+ use_database DB
6
6
 
7
7
  property :name
8
8
  property :toys, :cast_as => ['CatToy'], :default => []
@@ -1,5 +1,5 @@
1
1
  class Event < CouchRest::ExtendedDocument
2
- use_database TEST_SERVER.default_database
2
+ use_database DB
3
3
 
4
4
  property :subject
5
5
  property :occurs_at, :cast_as => 'Time', :send => 'parse'
@@ -3,7 +3,7 @@ class Invoice < CouchRest::ExtendedDocument
3
3
  include CouchRest::Validation
4
4
 
5
5
  # Set the default database to use
6
- use_database TEST_SERVER.default_database
6
+ use_database DB
7
7
 
8
8
  # Official Schema
9
9
  property :client_name
@@ -3,7 +3,7 @@ class Service < CouchRest::ExtendedDocument
3
3
  include CouchRest::Validation
4
4
  auto_validate!
5
5
  # Set the default database to use
6
- use_database TEST_SERVER.default_database
6
+ use_database DB
7
7
 
8
8
  # Official Schema
9
9
  property :name, :length => 4...20
@@ -12,6 +12,7 @@ unless defined?(FIXTURE_PATH)
12
12
  TESTDB = 'couchrest-test'
13
13
  TEST_SERVER = CouchRest.new
14
14
  TEST_SERVER.default_database = TESTDB
15
+ DB = TEST_SERVER.database(TESTDB)
15
16
  end
16
17
 
17
18
  class Basic < CouchRest::ExtendedDocument
@@ -19,10 +20,8 @@ class Basic < CouchRest::ExtendedDocument
19
20
  end
20
21
 
21
22
  def reset_test_db!
22
- cr = TEST_SERVER
23
- db = cr.database(TESTDB)
24
- db.recreate! rescue nil
25
- db
23
+ DB.recreate! rescue nil
24
+ DB
26
25
  end
27
26
 
28
27
  Spec::Runner.configure do |config|
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: couchrest
3
3
  version: !ruby/object:Gem::Version
4
- version: "0.26"
4
+ version: "0.28"
5
5
  platform: ruby
6
6
  authors:
7
7
  - J. Chris Anderson