couchrest 0.26 → 0.28

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