couchrest 0.30 → 0.31

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -12,14 +12,17 @@ Note: CouchRest only support CouchDB 0.9.0 or newer.
12
12
 
13
13
  ## Easy Install
14
14
 
15
- Easy Install is moving to RubyForge, heads up for the gem.
15
+ $ sudo gem install couchrest
16
+
17
+ Alternatively, you can install from Github:
18
+
19
+ $ gem sources -a http://gems.github.com (you only have to do this once)
20
+ $ sudo gem install mattetti-couchrest
16
21
 
17
22
  ### Relax, it's RESTful
18
23
 
19
- The core of Couchrest is Heroku’s excellent REST Client Ruby HTTP wrapper.
20
- REST Client takes all the nastyness of Net::HTTP and gives is a pretty face,
21
- while still giving you more control than Open-URI. I recommend it anytime
22
- you’re interfacing with a well-defined web service.
24
+ CouchRest rests on top of a HTTP abstraction layer using by default Heroku’s excellent REST Client Ruby HTTP wrapper.
25
+ Other adapters can be added to support more http libraries.
23
26
 
24
27
  ### Running the Specs
25
28
 
@@ -27,7 +30,7 @@ The most complete documentation is the spec/ directory. To validate your
27
30
  CouchRest install, from the project root directory run `rake`, or `autotest`
28
31
  (requires RSpec and optionally ZenTest for autotest support).
29
32
 
30
- ## Examples
33
+ ## Examples (CouchRest Core)
31
34
 
32
35
  Quick Start:
33
36
 
@@ -59,12 +62,50 @@ Creating and Querying Views:
59
62
  })
60
63
  puts @db.view('first/test')['rows'].inspect
61
64
 
62
- ## CouchRest::Model
63
65
 
64
- CouchRest::Model has been deprecated and replaced by CouchRest::ExtendedDocument
66
+ ## CouchRest::ExtendedDocument
67
+
68
+ CouchRest::ExtendedDocument is a DSL/ORM for CouchDB. Basically, ExtendedDocument seats on top of CouchRest Core to add the concept of Model.
69
+ ExtendedDocument offers a lot of the usual ORM tools such as optional yet defined schema, validation, callbacks, pagination, casting and much more.
70
+
71
+ ### Model example
72
+
73
+ Check spec/couchrest/more and spec/fixtures/more for more examples
74
+
75
+ class Article < CouchRest::ExtendedDocument
76
+ use_database DB
77
+ unique_id :slug
78
+
79
+ view_by :date, :descending => true
80
+ view_by :user_id, :date
81
+
82
+ view_by :tags,
83
+ :map =>
84
+ "function(doc) {
85
+ if (doc['couchrest-type'] == 'Article' && doc.tags) {
86
+ doc.tags.forEach(function(tag){
87
+ emit(tag, 1);
88
+ });
89
+ }
90
+ }",
91
+ :reduce =>
92
+ "function(keys, values, rereduce) {
93
+ return sum(values);
94
+ }"
95
+
96
+ property :date
97
+ property :slug, :read_only => true
98
+ property :title
99
+ property :tags, :cast_as => ['String']
65
100
 
101
+ timestamps!
66
102
 
67
- ## CouchRest::ExtendedDocument
103
+ save_callback :before, :generate_slug_from_title
104
+
105
+ def generate_slug_from_title
106
+ self['slug'] = title.downcase.gsub(/[^a-z0-9]/,'-').squeeze('-').gsub(/^\-|\-$/,'') if new_document?
107
+ end
108
+ end
68
109
 
69
110
  ### Callbacks
70
111
 
@@ -97,9 +138,28 @@ If you want to cast an array of instances from a specific Class, use the trick s
97
138
 
98
139
  Pagination is available in any ExtendedDocument classes. Here are some usage examples:
99
140
 
100
- articles = Article.by_date :key => Date.today
101
- articles.paginate(:page => 1, :per_page => 3)
102
-
141
+ basic usage:
142
+
143
+ Article.all.paginate(:page => 1, :per_page => 5)
144
+
145
+ note: the above query will look like: `GET /db/_design/Article/_view/all?include_docs=true&skip=0&limit=5&reduce=false` and only fetch 5 documents.
146
+
147
+ Slightly more advance usage:
148
+
149
+ Article.by_name(:startkey => 'a', :endkey => {}).paginate(:page => 1, :per_page => 5)
150
+
151
+ note: the above query will look like: `GET /db/_design/Article/_view/by_name?startkey=%22a%22&limit=5&skip=0&endkey=%7B%7D&include_docs=true`
152
+ Basically, you can paginate through the articles starting by the letter a, 5 articles at a time.
153
+
154
+
155
+ Low level usage:
103
156
 
104
157
  Article.paginate(:design_doc => 'Article', :view_name => 'by_date',
105
- :per_page => 3, :page => 2, :descending => true, :key => Date.today, :include_docs => true)
158
+ :per_page => 3, :page => 2, :descending => true, :key => Date.today, :include_docs => true)
159
+
160
+
161
+ ## Ruby on Rails
162
+
163
+ CouchRest is compatible with rails and can even be used a Rails plugin.
164
+ However, you might be interested in the CouchRest companion rails project:
165
+ [http://github.com/hpoydar/couchrest-rails](http://github.com/hpoydar/couchrest-rails)
@@ -1,3 +1,17 @@
1
+ == 0.31
2
+
3
+ * Major enhancements
4
+
5
+ * Created an abstraction HTTP layer to support different http adapters (Matt Aimonetti)
6
+ * Added ExtendedDocument.create({}) and #create!({}) so you don't have to do Model.new.create (Matt Aimonetti)
7
+
8
+ * Minor enhancements
9
+
10
+ * Added an init.rb file for easy usage as a Rails plugin (Aaron Quint)
11
+ * Bug fix: pagination shouldn't die on empty results (Arnaud Berthomier)
12
+ * Optimized ExtendedDocument.count to run about 3x faster (Matt Aimonetti)
13
+ * Added Float casting (Ryan Felton & Matt Aimonetti)
14
+
1
15
  == 0.30
2
16
 
3
17
  * Major enhancements
@@ -28,7 +28,7 @@ require 'couchrest/monkeypatches'
28
28
 
29
29
  # = CouchDB, close to the metal
30
30
  module CouchRest
31
- VERSION = '0.30' unless self.const_defined?("VERSION")
31
+ VERSION = '0.31' unless self.const_defined?("VERSION")
32
32
 
33
33
  autoload :Server, 'couchrest/core/server'
34
34
  autoload :Database, 'couchrest/core/database'
@@ -45,6 +45,7 @@ module CouchRest
45
45
  autoload :ExtendedDocument, 'couchrest/more/extended_document'
46
46
  autoload :CastedModel, 'couchrest/more/casted_model'
47
47
 
48
+ require File.join(File.dirname(__FILE__), 'couchrest', 'core', 'http_abstraction')
48
49
  require File.join(File.dirname(__FILE__), 'couchrest', 'mixins')
49
50
 
50
51
  # The CouchRest module methods handle the basic JSON serialization
@@ -118,9 +119,9 @@ module CouchRest
118
119
  }
119
120
  end
120
121
 
121
- # set proxy for RestClient to use
122
+ # set proxy to use
122
123
  def proxy url
123
- RestClient.proxy = url
124
+ HttpAbstraction.proxy = url
124
125
  end
125
126
 
126
127
  # ensure that a database exists
@@ -141,7 +142,7 @@ module CouchRest
141
142
  def put(uri, doc = nil)
142
143
  payload = doc.to_json if doc
143
144
  begin
144
- JSON.parse(RestClient.put(uri, payload))
145
+ JSON.parse(HttpAbstraction.put(uri, payload))
145
146
  rescue Exception => e
146
147
  if $DEBUG
147
148
  raise "Error while sending a PUT request #{uri}\npayload: #{payload.inspect}\n#{e}"
@@ -153,7 +154,7 @@ module CouchRest
153
154
 
154
155
  def get(uri)
155
156
  begin
156
- JSON.parse(RestClient.get(uri), :max_nesting => false)
157
+ JSON.parse(HttpAbstraction.get(uri), :max_nesting => false)
157
158
  rescue => e
158
159
  if $DEBUG
159
160
  raise "Error while sending a GET request #{uri}\n: #{e}"
@@ -166,7 +167,7 @@ module CouchRest
166
167
  def post uri, doc = nil
167
168
  payload = doc.to_json if doc
168
169
  begin
169
- JSON.parse(RestClient.post(uri, payload))
170
+ JSON.parse(HttpAbstraction.post(uri, payload))
170
171
  rescue Exception => e
171
172
  if $DEBUG
172
173
  raise "Error while sending a POST request #{uri}\npayload: #{payload.inspect}\n#{e}"
@@ -177,11 +178,11 @@ module CouchRest
177
178
  end
178
179
 
179
180
  def delete uri
180
- JSON.parse(RestClient.delete(uri))
181
+ JSON.parse(HttpAbstraction.delete(uri))
181
182
  end
182
183
 
183
184
  def copy uri, destination
184
- JSON.parse(RestClient.copy(uri, {'Destination' => destination}))
185
+ JSON.parse(HttpAbstraction.copy(uri, {'Destination' => destination}))
185
186
  end
186
187
 
187
188
  def paramify_url url, params = {}
@@ -0,0 +1,35 @@
1
+ module RestClientAdapter
2
+
3
+ module API
4
+ def proxy=(url)
5
+ RestClient.proxy = url
6
+ end
7
+
8
+ def proxy
9
+ RestClient.proxy
10
+ end
11
+
12
+ def get(uri, headers={})
13
+ RestClient.get(uri, headers)
14
+ end
15
+
16
+ def post(uri, payload, headers={})
17
+ RestClient.post(uri, payload, headers)
18
+ end
19
+
20
+ def put(uri, payload, headers={})
21
+ RestClient.put(uri, payload, headers)
22
+ end
23
+
24
+ def delete(uri, headers={})
25
+ RestClient.delete(uri, headers)
26
+ end
27
+
28
+ def copy(uri, headers)
29
+ RestClient::Request.execute( :method => :copy,
30
+ :url => uri,
31
+ :headers => headers)
32
+ end
33
+ end
34
+
35
+ end
@@ -58,7 +58,7 @@ module CouchRest
58
58
  keys = params.delete(:keys)
59
59
  funcs = funcs.merge({:keys => keys}) if keys
60
60
  url = CouchRest.paramify_url "#{@root}/_temp_view", params
61
- JSON.parse(RestClient.post(url, funcs.to_json, {"Content-Type" => 'application/json'}))
61
+ JSON.parse(HttpAbstraction.post(url, funcs.to_json, {"Content-Type" => 'application/json'}))
62
62
  end
63
63
 
64
64
  # backwards compatibility is a plus
@@ -100,11 +100,8 @@ module CouchRest
100
100
 
101
101
  # GET an attachment directly from CouchDB
102
102
  def fetch_attachment(doc, name)
103
- # slug = escape_docid(docid)
104
- # name = CGI.escape(name)
105
103
  uri = url_for_attachment(doc, name)
106
- RestClient.get uri
107
- # "#{@uri}/#{slug}/#{name}"
104
+ HttpAbstraction.get uri
108
105
  end
109
106
 
110
107
  # PUT an attachment directly to CouchDB
@@ -112,14 +109,14 @@ module CouchRest
112
109
  docid = escape_docid(doc['_id'])
113
110
  name = CGI.escape(name)
114
111
  uri = url_for_attachment(doc, name)
115
- JSON.parse(RestClient.put(uri, file, options))
112
+ JSON.parse(HttpAbstraction.put(uri, file, options))
116
113
  end
117
114
 
118
115
  # DELETE an attachment directly from CouchDB
119
116
  def delete_attachment doc, name
120
117
  uri = url_for_attachment(doc, name)
121
118
  # this needs a rev
122
- JSON.parse(RestClient.delete(uri))
119
+ JSON.parse(HttpAbstraction.delete(uri))
123
120
  end
124
121
 
125
122
  # Save a document to CouchDB. This will use the <tt>_id</tt> field from
@@ -146,7 +143,7 @@ module CouchRest
146
143
  slug = escape_docid(doc['_id'])
147
144
  begin
148
145
  CouchRest.put "#{@root}/#{slug}", doc
149
- rescue RestClient::ResourceNotFound
146
+ rescue HttpAbstraction::ResourceNotFound
150
147
  p "resource not found when saving even tho an id was passed"
151
148
  slug = doc['_id'] = @server.next_uuid
152
149
  CouchRest.put "#{@root}/#{slug}", doc
@@ -252,7 +249,7 @@ module CouchRest
252
249
  def recreate!
253
250
  delete!
254
251
  create!
255
- rescue RestClient::ResourceNotFound
252
+ rescue HttpAbstraction::ResourceNotFound
256
253
  ensure
257
254
  create!
258
255
  end
@@ -3,10 +3,6 @@ require 'delegate'
3
3
  module CouchRest
4
4
  class Document < Response
5
5
  include CouchRest::Mixins::Attachments
6
-
7
- # def self.inherited(subklass)
8
- # subklass.send(:extlib_inheritable_accessor, :database)
9
- # end
10
6
 
11
7
  extlib_inheritable_accessor :database
12
8
  attr_accessor :database
@@ -0,0 +1,48 @@
1
+ require 'couchrest/core/adapters/restclient'
2
+
3
+ # Abstraction layet for HTTP communications.
4
+ #
5
+ # By defining a basic API that CouchRest is relying on,
6
+ # it allows for easy experimentations and implementations of various libraries.
7
+ #
8
+ # Most of the API is based on the RestClient API that was used in the early version of CouchRest.
9
+ #
10
+ module HttpAbstraction
11
+
12
+ # here is the list of exception expected by CouchRest
13
+ # please convert the underlying errors in this set of known
14
+ # exceptions.
15
+ class ResourceNotFound < StandardError; end
16
+ class RequestFailed < StandardError; end
17
+ class RequestTimeout < StandardError; end
18
+ class ServerBrokeConnection < StandardError; end
19
+ class Conflict < StandardError; end
20
+
21
+
22
+ # # Here is the API you need to implement if you want to write a new adapter
23
+ # # See adapters/restclient.rb for more information.
24
+ #
25
+ # def self.proxy=(url)
26
+ # end
27
+ #
28
+ # def self.proxy
29
+ # end
30
+ #
31
+ # def self.get(uri, headers=nil)
32
+ # end
33
+ #
34
+ # def self.post(uri, payload, headers=nil)
35
+ # end
36
+ #
37
+ # def self.put(uri, payload, headers=nil)
38
+ # end
39
+ #
40
+ # def self.delete(uri, headers=nil)
41
+ # end
42
+ #
43
+ # def self.copy(uri, headers)
44
+ # end
45
+
46
+ end
47
+
48
+ HttpAbstraction.extend(RestClientAdapter::API)
@@ -209,12 +209,14 @@ module CouchRest
209
209
 
210
210
  def remember_where_we_left_off(results, page)
211
211
  last_row = results['rows'].last
212
- @last_key = last_row['key']
213
- @last_docid = last_row['id']
212
+ if last_row
213
+ @last_key = last_row['key']
214
+ @last_docid = last_row['id']
215
+ end
214
216
  @last_page = page
215
217
  end
216
218
  end
217
219
 
218
220
  end
219
221
  end
220
- end
222
+ end
@@ -37,9 +37,6 @@ module CouchRest
37
37
  if (doc['couchrest-type'] == '#{self.to_s}') {
38
38
  emit(null,1);
39
39
  }
40
- }",
41
- 'reduce' => "function(keys, values) {
42
- return sum(values);
43
40
  }"
44
41
  }
45
42
  }
@@ -19,9 +19,7 @@ module CouchRest
19
19
  # equal to the name of the current class. Takes the standard set of
20
20
  # CouchRest::Database#view options
21
21
  def count(opts = {}, &block)
22
- result = all({:reduce => true}.merge(opts), &block)['rows']
23
- return 0 if result.empty?
24
- result.first['value']
22
+ all({:raw => true, :limit => 0}.merge(opts), &block)['total_rows']
25
23
  end
26
24
 
27
25
  # Load the first document that have the "couchrest-type" field equal to
@@ -56,7 +56,6 @@ module CouchRest
56
56
  def cast_keys
57
57
  return unless self.class.properties
58
58
  self.class.properties.each do |property|
59
-
60
59
  next unless property.casted
61
60
  key = self.has_key?(property.name) ? property.name : property.name.to_sym
62
61
  # Don't cast the property unless it has a value
@@ -75,6 +74,9 @@ module CouchRest
75
74
  self[property.name] = if ((property.init_method == 'new') && target == 'Time')
76
75
  # Using custom time parsing method because Ruby's default method is toooo slow
77
76
  self[key].is_a?(String) ? Time.mktime_with_offset(self[key].dup) : self[key]
77
+ # Float instances don't get initialized with #new
78
+ elsif ((property.init_method == 'new') && target == 'Float')
79
+ cast_float(self[key])
78
80
  else
79
81
  # Let people use :send as a Time parse arg
80
82
  klass = ::CouchRest.constantize(target)
@@ -84,6 +86,15 @@ module CouchRest
84
86
  end
85
87
 
86
88
  end
89
+
90
+ def cast_float(value)
91
+ begin
92
+ Float(value)
93
+ rescue
94
+ value
95
+ end
96
+ end
97
+
87
98
  end
88
99
 
89
100
  module ClassMethods
@@ -72,7 +72,7 @@ module CouchRest
72
72
  #
73
73
  # To understand the capabilities of this view system more completely,
74
74
  # it is recommended that you read the RSpec file at
75
- # <tt>spec/core/model_spec.rb</tt>.
75
+ # <tt>spec/couchrest/more/extended_doc_spec.rb</tt>.
76
76
 
77
77
  def view_by(*keys)
78
78
  opts = keys.pop if keys.last.is_a?(Hash)
@@ -124,14 +124,6 @@ module CouchRest
124
124
  # potentially large indexes.
125
125
  def cleanup_design_docs!(db = database)
126
126
  save_design_doc_on(db)
127
- # db.refresh_design_doc
128
- # db.save_design_doc
129
- # design_doc = model_design_doc(db)
130
- # if design_doc
131
- # db.delete_doc(design_doc)
132
- # else
133
- # false
134
- # end
135
127
  end
136
128
 
137
129
  private
@@ -162,7 +154,7 @@ module CouchRest
162
154
  begin
163
155
  design_doc.view_on(db, view_name, opts, &block)
164
156
  # the design doc may not have been saved yet on this database
165
- rescue RestClient::ResourceNotFound => e
157
+ rescue HttpAbstraction::ResourceNotFound => e
166
158
  if retryable
167
159
  save_design_doc_on(db)
168
160
  retryable = false
@@ -51,63 +51,63 @@ if RUBY_VERSION.to_f < 1.9
51
51
  end
52
52
  end
53
53
 
54
- module RestClient
55
- def self.copy(url, headers={})
56
- Request.execute(:method => :copy,
57
- :url => url,
58
- :headers => headers)
59
- end
60
-
61
- # class Request
62
- #
63
- # def establish_connection(uri)
64
- # Thread.current[:connection].finish if (Thread.current[:connection] && Thread.current[:connection].started?)
65
- # p net_http_class
66
- # net = net_http_class.new(uri.host, uri.port)
67
- # net.use_ssl = uri.is_a?(URI::HTTPS)
68
- # net.verify_mode = OpenSSL::SSL::VERIFY_NONE
69
- # Thread.current[:connection] = net
70
- # Thread.current[:connection].start
71
- # Thread.current[:connection]
72
- # end
73
- #
74
- # def transmit(uri, req, payload)
75
- # setup_credentials(req)
76
- #
77
- # Thread.current[:host] ||= uri.host
78
- # Thread.current[:port] ||= uri.port
79
- #
80
- # if (Thread.current[:connection].nil? || (Thread.current[:host] != uri.host))
81
- # p "establishing a connection"
82
- # establish_connection(uri)
83
- # end
54
+ # module RestClient
55
+ # # def self.copy(url, headers={})
56
+ # # Request.execute(:method => :copy,
57
+ # # :url => url,
58
+ # # :headers => headers)
59
+ # # end
84
60
  #
85
- # display_log request_log
86
- # http = Thread.current[:connection]
87
- # http.read_timeout = @timeout if @timeout
88
- #
89
- # begin
90
- # res = http.request(req, payload)
91
- # rescue
92
- # p "Net::HTTP connection failed, reconnecting"
93
- # establish_connection(uri)
94
- # http = Thread.current[:connection]
95
- # require 'ruby-debug'
96
- # req.body_stream = nil
97
- #
98
- # res = http.request(req, payload)
99
- # display_log response_log(res)
100
- # result res
101
- # else
102
- # display_log response_log(res)
103
- # process_result res
104
- # end
105
- #
106
- # rescue EOFError
107
- # raise RestClient::ServerBrokeConnection
108
- # rescue Timeout::Error
109
- # raise RestClient::RequestTimeout
110
- # end
111
- # end
112
-
113
- end
61
+ # # class Request
62
+ # #
63
+ # # def establish_connection(uri)
64
+ # # Thread.current[:connection].finish if (Thread.current[:connection] && Thread.current[:connection].started?)
65
+ # # p net_http_class
66
+ # # net = net_http_class.new(uri.host, uri.port)
67
+ # # net.use_ssl = uri.is_a?(URI::HTTPS)
68
+ # # net.verify_mode = OpenSSL::SSL::VERIFY_NONE
69
+ # # Thread.current[:connection] = net
70
+ # # Thread.current[:connection].start
71
+ # # Thread.current[:connection]
72
+ # # end
73
+ # #
74
+ # # def transmit(uri, req, payload)
75
+ # # setup_credentials(req)
76
+ # #
77
+ # # Thread.current[:host] ||= uri.host
78
+ # # Thread.current[:port] ||= uri.port
79
+ # #
80
+ # # if (Thread.current[:connection].nil? || (Thread.current[:host] != uri.host))
81
+ # # p "establishing a connection"
82
+ # # establish_connection(uri)
83
+ # # end
84
+ # #
85
+ # # display_log request_log
86
+ # # http = Thread.current[:connection]
87
+ # # http.read_timeout = @timeout if @timeout
88
+ # #
89
+ # # begin
90
+ # # res = http.request(req, payload)
91
+ # # rescue
92
+ # # p "Net::HTTP connection failed, reconnecting"
93
+ # # establish_connection(uri)
94
+ # # http = Thread.current[:connection]
95
+ # # require 'ruby-debug'
96
+ # # req.body_stream = nil
97
+ # #
98
+ # # res = http.request(req, payload)
99
+ # # display_log response_log(res)
100
+ # # result res
101
+ # # else
102
+ # # display_log response_log(res)
103
+ # # process_result res
104
+ # # end
105
+ # #
106
+ # # rescue EOFError
107
+ # # raise RestClient::ServerBrokeConnection
108
+ # # rescue Timeout::Error
109
+ # # raise RestClient::RequestTimeout
110
+ # # end
111
+ # # end
112
+ #
113
+ # end
@@ -52,8 +52,25 @@ module CouchRest
52
52
  end
53
53
  end
54
54
 
55
-
55
+ # Defines an instance and save it directly to the database
56
+ #
57
+ # ==== Returns
58
+ # returns the reloaded document
59
+ def self.create(options)
60
+ instance = new(options)
61
+ instance.create
62
+ instance
63
+ end
56
64
 
65
+ # Defines an instance and save it directly to the database
66
+ #
67
+ # ==== Returns
68
+ # returns the reloaded document or raises an exception
69
+ def self.create!(options)
70
+ instance = new(options)
71
+ instance.create!
72
+ instance
73
+ end
57
74
 
58
75
  # Automatically set <tt>updated_at</tt> and <tt>created_at</tt> fields
59
76
  # on the document whenever saving occurs. CouchRest uses a pretty
@@ -191,7 +191,7 @@ describe CouchRest do
191
191
  describe "using a proxy for RestClient connections" do
192
192
  it "should set proxy url for RestClient" do
193
193
  CouchRest.proxy 'http://localhost:8888/'
194
- proxy_uri = URI.parse(RestClient.proxy)
194
+ proxy_uri = URI.parse(HttpAbstraction.proxy)
195
195
  proxy_uri.host.should eql( 'localhost' )
196
196
  proxy_uri.port.should eql( 8888 )
197
197
  CouchRest.proxy nil
@@ -690,7 +690,7 @@ describe CouchRest::Database do
690
690
 
691
691
  it "should recreate a db even tho it doesn't exist" do
692
692
  @cr.databases.should_not include(@db2.name)
693
- @db2.recreate!
693
+ begin @db2.recreate! rescue nil end
694
694
  @cr.databases.should include(@db2.name)
695
695
  end
696
696
 
@@ -97,6 +97,22 @@ describe "ExtendedDocument" do
97
97
  end
98
98
  end
99
99
 
100
+ describe "creating a new document" do
101
+ it "should instantialize and save a document" do
102
+ article = Article.create(:title => 'my test')
103
+ article.title.should == 'my test'
104
+ article.should_not be_new_document
105
+ end
106
+
107
+ it "should trigger the create callbacks" do
108
+ doc = WithCallBacks.create(:name => 'my other test')
109
+ doc.run_before_create.should be_true
110
+ doc.run_after_create.should be_true
111
+ doc.run_before_save.should be_true
112
+ doc.run_after_save.should be_true
113
+ end
114
+ end
115
+
100
116
  describe "update attributes without saving" do
101
117
  before(:each) do
102
118
  a = Article.get "big-bad-danger" rescue nil
@@ -121,7 +121,7 @@ describe "ExtendedDocument views" do
121
121
  describe "a model class not tied to a database" do
122
122
  before(:all) do
123
123
  reset_test_db!
124
- @db = DB
124
+ @db = DB
125
125
  %w{aaa bbb ddd eee}.each do |title|
126
126
  u = Unattached.new(:title => title)
127
127
  u.database = @db
@@ -133,14 +133,15 @@ describe "ExtendedDocument views" do
133
133
  lambda{Unattached.all}.should raise_error
134
134
  end
135
135
  it "should query all" do
136
- rs = Unattached.all :database=>@db
136
+ Unattached.cleanup_design_docs!(@db)
137
+ rs = Unattached.all :database => @db
137
138
  rs.length.should == 4
138
139
  end
139
140
  it "should barf on query if no database given" do
140
141
  lambda{Unattached.view :by_title}.should raise_error
141
142
  end
142
143
  it "should make the design doc upon first query" do
143
- Unattached.by_title :database=>@db
144
+ Unattached.by_title :database => @db
144
145
  doc = Unattached.design_doc
145
146
  doc['views']['all']['map'].should include('Unattached')
146
147
  end
@@ -157,7 +158,7 @@ describe "ExtendedDocument views" do
157
158
  things = []
158
159
  Unattached.view(:by_title, :database=>@db) do |thing|
159
160
  things << thing
160
- end
161
+ end
161
162
  things[0]["doc"]["title"].should =='aaa'
162
163
  end
163
164
  it "should yield with by_key method" do
@@ -141,6 +141,29 @@ describe "ExtendedDocument properties" do
141
141
  @event['occurs_at'].should be_an_instance_of(Time)
142
142
  end
143
143
  end
144
+
145
+ describe "casting to Float object" do
146
+ class RootBeerFloat < CouchRest::ExtendedDocument
147
+ use_database DB
148
+ property :price, :cast_as => 'Float'
149
+ end
150
+
151
+ it "should convert a string into a float if casted as so" do
152
+ RootBeerFloat.new(:price => '12.50').price.should == 12.50
153
+ RootBeerFloat.new(:price => '9').price.should == 9.0
154
+ RootBeerFloat.new(:price => '-9').price.should == -9.0
155
+ end
156
+
157
+ it "should not convert a string if it's not a string that can be cast as a float" do
158
+ RootBeerFloat.new(:price => 'test').price.should == 'test'
159
+ end
160
+
161
+ it "should work fine when a float is being passed" do
162
+ RootBeerFloat.new(:price => 9.99).price.should == 9.99
163
+ end
164
+
165
+ end
166
+
144
167
  end
145
168
 
146
169
  end
@@ -20,7 +20,7 @@ class Basic < CouchRest::ExtendedDocument
20
20
  end
21
21
 
22
22
  def reset_test_db!
23
- DB.recreate! rescue nil
23
+ DB.recreate! rescue nil
24
24
  DB
25
25
  end
26
26
 
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.30"
4
+ version: "0.31"
5
5
  platform: ruby
6
6
  authors:
7
7
  - J. Chris Anderson
@@ -62,9 +62,11 @@ files:
62
62
  - examples/word_count/word_count_views.rb
63
63
  - lib/couchrest/commands/generate.rb
64
64
  - lib/couchrest/commands/push.rb
65
+ - lib/couchrest/core/adapters/restclient.rb
65
66
  - lib/couchrest/core/database.rb
66
67
  - lib/couchrest/core/design.rb
67
68
  - lib/couchrest/core/document.rb
69
+ - lib/couchrest/core/http_abstraction.rb
68
70
  - lib/couchrest/core/response.rb
69
71
  - lib/couchrest/core/server.rb
70
72
  - lib/couchrest/core/view.rb