Merge commit 'mattetti/master'
This commit is contained in:
commit
1e44302d1a
92
README.md
92
README.md
|
@ -12,14 +12,17 @@ Note: CouchRest only support CouchDB 0.9.0 or newer.
|
||||||
|
|
||||||
## Easy Install
|
## Easy Install
|
||||||
|
|
||||||
Easy Install is moving to RubyForge, heads up for the gem.
|
$ sudo gem install couchrest
|
||||||
|
|
||||||
|
Alternatively, you can install from Github:
|
||||||
|
|
||||||
|
$ gem sources -a http://gems.github.com (you only have to do this once)
|
||||||
|
$ sudo gem install mattetti-couchrest
|
||||||
|
|
||||||
### Relax, it's RESTful
|
### Relax, it's RESTful
|
||||||
|
|
||||||
The core of Couchrest is Heroku’s excellent REST Client Ruby HTTP wrapper.
|
CouchRest rests on top of a HTTP abstraction layer using by default Heroku’s excellent REST Client Ruby HTTP wrapper.
|
||||||
REST Client takes all the nastyness of Net::HTTP and gives is a pretty face,
|
Other adapters can be added to support more http libraries.
|
||||||
while still giving you more control than Open-URI. I recommend it anytime
|
|
||||||
you’re interfacing with a well-defined web service.
|
|
||||||
|
|
||||||
### Running the Specs
|
### Running the Specs
|
||||||
|
|
||||||
|
@ -27,7 +30,7 @@ The most complete documentation is the spec/ directory. To validate your
|
||||||
CouchRest install, from the project root directory run `rake`, or `autotest`
|
CouchRest install, from the project root directory run `rake`, or `autotest`
|
||||||
(requires RSpec and optionally ZenTest for autotest support).
|
(requires RSpec and optionally ZenTest for autotest support).
|
||||||
|
|
||||||
## Examples
|
## Examples (CouchRest Core)
|
||||||
|
|
||||||
Quick Start:
|
Quick Start:
|
||||||
|
|
||||||
|
@ -59,12 +62,50 @@ Creating and Querying Views:
|
||||||
})
|
})
|
||||||
puts @db.view('first/test')['rows'].inspect
|
puts @db.view('first/test')['rows'].inspect
|
||||||
|
|
||||||
## CouchRest::Model
|
|
||||||
|
|
||||||
CouchRest::Model has been deprecated and replaced by CouchRest::ExtendedDocument
|
## CouchRest::ExtendedDocument
|
||||||
|
|
||||||
|
CouchRest::ExtendedDocument is a DSL/ORM for CouchDB. Basically, ExtendedDocument seats on top of CouchRest Core to add the concept of Model.
|
||||||
|
ExtendedDocument offers a lot of the usual ORM tools such as optional yet defined schema, validation, callbacks, pagination, casting and much more.
|
||||||
|
|
||||||
## CouchRest::ExtendedDocument
|
### Model example
|
||||||
|
|
||||||
|
Check spec/couchrest/more and spec/fixtures/more for more examples
|
||||||
|
|
||||||
|
class Article < CouchRest::ExtendedDocument
|
||||||
|
use_database DB
|
||||||
|
unique_id :slug
|
||||||
|
|
||||||
|
view_by :date, :descending => true
|
||||||
|
view_by :user_id, :date
|
||||||
|
|
||||||
|
view_by :tags,
|
||||||
|
:map =>
|
||||||
|
"function(doc) {
|
||||||
|
if (doc['couchrest-type'] == 'Article' && doc.tags) {
|
||||||
|
doc.tags.forEach(function(tag){
|
||||||
|
emit(tag, 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}",
|
||||||
|
:reduce =>
|
||||||
|
"function(keys, values, rereduce) {
|
||||||
|
return sum(values);
|
||||||
|
}"
|
||||||
|
|
||||||
|
property :date
|
||||||
|
property :slug, :read_only => true
|
||||||
|
property :title
|
||||||
|
property :tags, :cast_as => ['String']
|
||||||
|
|
||||||
|
timestamps!
|
||||||
|
|
||||||
|
save_callback :before, :generate_slug_from_title
|
||||||
|
|
||||||
|
def generate_slug_from_title
|
||||||
|
self['slug'] = title.downcase.gsub(/[^a-z0-9]/,'-').squeeze('-').gsub(/^\-|\-$/,'') if new?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
### Callbacks
|
### Callbacks
|
||||||
|
|
||||||
|
@ -84,12 +125,14 @@ CouchRest uses a mixin you can find in lib/mixins/callbacks which is extracted f
|
||||||
set_callback :save, :after, :after_method, :if => :condition
|
set_callback :save, :after, :after_method, :if => :condition
|
||||||
set_callback :save, :around {|r| stuff; yield; stuff }
|
set_callback :save, :around {|r| stuff; yield; stuff }
|
||||||
|
|
||||||
Or the new shorter version:
|
Or the aliased short version:
|
||||||
|
|
||||||
before_save :before_method, :another_method
|
before_save :before_method, :another_method
|
||||||
after_save :after_method, :another_method, :if => :condition
|
after_save :after_method, :another_method, :if => :condition
|
||||||
around_save {|r| stuff; yield; stuff }
|
around_save {|r| stuff; yield; stuff }
|
||||||
|
|
||||||
|
To halt the callback, simply return a :halt symbol in your callback method.
|
||||||
|
|
||||||
Check the mixin or the ExtendedDocument class to see how to implement your own callbacks.
|
Check the mixin or the ExtendedDocument class to see how to implement your own callbacks.
|
||||||
|
|
||||||
### Casting
|
### Casting
|
||||||
|
@ -102,3 +145,32 @@ you can define some casting rules.
|
||||||
|
|
||||||
If you want to cast an array of instances from a specific Class, use the trick shown above ["ClassName"]
|
If you want to cast an array of instances from a specific Class, use the trick shown above ["ClassName"]
|
||||||
|
|
||||||
|
### Pagination
|
||||||
|
|
||||||
|
Pagination is available in any ExtendedDocument classes. Here are some usage examples:
|
||||||
|
|
||||||
|
basic usage:
|
||||||
|
|
||||||
|
Article.all.paginate(:page => 1, :per_page => 5)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Slightly more advance usage:
|
||||||
|
|
||||||
|
Article.by_name(:startkey => 'a', :endkey => {}).paginate(:page => 1, :per_page => 5)
|
||||||
|
|
||||||
|
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`
|
||||||
|
Basically, you can paginate through the articles starting by the letter a, 5 articles at a time.
|
||||||
|
|
||||||
|
|
||||||
|
Low level usage:
|
||||||
|
|
||||||
|
Article.paginate(:design_doc => 'Article', :view_name => 'by_date',
|
||||||
|
:per_page => 3, :page => 2, :descending => true, :key => Date.today, :include_docs => true)
|
||||||
|
|
||||||
|
|
||||||
|
## Ruby on Rails
|
||||||
|
|
||||||
|
CouchRest is compatible with rails and can even be used a Rails plugin.
|
||||||
|
However, you might be interested in the CouchRest companion rails project:
|
||||||
|
[http://github.com/hpoydar/couchrest-rails](http://github.com/hpoydar/couchrest-rails)
|
||||||
|
|
2
Rakefile
2
Rakefile
|
@ -24,7 +24,7 @@ spec = Gem::Specification.new do |s|
|
||||||
s.description = "CouchRest provides a simple interface on top of CouchDB's RESTful HTTP API, as well as including some utility scripts for managing views and attachments."
|
s.description = "CouchRest provides a simple interface on top of CouchDB's RESTful HTTP API, as well as including some utility scripts for managing views and attachments."
|
||||||
s.has_rdoc = true
|
s.has_rdoc = true
|
||||||
s.authors = ["J. Chris Anderson", "Matt Aimonetti"]
|
s.authors = ["J. Chris Anderson", "Matt Aimonetti"]
|
||||||
s.files = %w( LICENSE README.md Rakefile THANKS.md ) +
|
s.files = %w( LICENSE README.md Rakefile THANKS.md history.txt) +
|
||||||
Dir["{examples,lib,spec,utils}/**/*"] -
|
Dir["{examples,lib,spec,utils}/**/*"] -
|
||||||
Dir["spec/tmp"]
|
Dir["spec/tmp"]
|
||||||
s.extra_rdoc_files = %w( README.md LICENSE THANKS.md )
|
s.extra_rdoc_files = %w( README.md LICENSE THANKS.md )
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Gem::Specification.new do |s|
|
Gem::Specification.new do |s|
|
||||||
s.name = %q{couchrest}
|
s.name = %q{couchrest}
|
||||||
s.version = "0.29"
|
s.version = "0.30"
|
||||||
|
|
||||||
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
||||||
s.authors = ["J. Chris Anderson", "Matt Aimonetti"]
|
s.authors = ["J. Chris Anderson", "Matt Aimonetti"]
|
||||||
|
@ -10,11 +10,10 @@ Gem::Specification.new do |s|
|
||||||
s.description = %q{CouchRest provides a simple interface on top of CouchDB's RESTful HTTP API, as well as including some utility scripts for managing views and attachments.}
|
s.description = %q{CouchRest provides a simple interface on top of CouchDB's RESTful HTTP API, as well as including some utility scripts for managing views and attachments.}
|
||||||
s.email = %q{jchris@apache.org}
|
s.email = %q{jchris@apache.org}
|
||||||
s.extra_rdoc_files = ["README.md", "LICENSE", "THANKS.md"]
|
s.extra_rdoc_files = ["README.md", "LICENSE", "THANKS.md"]
|
||||||
s.files = ["LICENSE", "README.md", "Rakefile", "THANKS.md", "examples/model", "examples/model/example.rb", "examples/word_count", "examples/word_count/markov", "examples/word_count/views", "examples/word_count/views/books", "examples/word_count/views/books/chunked-map.js", "examples/word_count/views/books/united-map.js", "examples/word_count/views/markov", "examples/word_count/views/markov/chain-map.js", "examples/word_count/views/markov/chain-reduce.js", "examples/word_count/views/word_count", "examples/word_count/views/word_count/count-map.js", "examples/word_count/views/word_count/count-reduce.js", "examples/word_count/word_count.rb", "examples/word_count/word_count_query.rb", "examples/word_count/word_count_views.rb", "lib/couchrest", "lib/couchrest/commands", "lib/couchrest/commands/generate.rb", "lib/couchrest/commands/push.rb", "lib/couchrest/core", "lib/couchrest/core/database.rb", "lib/couchrest/core/design.rb", "lib/couchrest/core/document.rb", "lib/couchrest/core/response.rb", "lib/couchrest/core/server.rb", "lib/couchrest/core/view.rb", "lib/couchrest/helper", "lib/couchrest/helper/pager.rb", "lib/couchrest/helper/streamer.rb", "lib/couchrest/helper/upgrade.rb", "lib/couchrest/mixins", "lib/couchrest/mixins/attachments.rb", "lib/couchrest/mixins/callbacks.rb", "lib/couchrest/mixins/class_proxy.rb", "lib/couchrest/mixins/design_doc.rb", "lib/couchrest/mixins/document_queries.rb", "lib/couchrest/mixins/extended_attachments.rb", "lib/couchrest/mixins/extended_document_mixins.rb", "lib/couchrest/mixins/properties.rb", "lib/couchrest/mixins/validation.rb", "lib/couchrest/mixins/views.rb", "lib/couchrest/mixins.rb", "lib/couchrest/monkeypatches.rb", "lib/couchrest/more", "lib/couchrest/more/casted_model.rb", "lib/couchrest/more/extended_document.rb", "lib/couchrest/more/property.rb", "lib/couchrest/support", "lib/couchrest/support/blank.rb", "lib/couchrest/support/class.rb", "lib/couchrest/support/rails.rb", "lib/couchrest/validation", "lib/couchrest/validation/auto_validate.rb", "lib/couchrest/validation/contextual_validators.rb", "lib/couchrest/validation/validation_errors.rb", "lib/couchrest/validation/validators", "lib/couchrest/validation/validators/absent_field_validator.rb", "lib/couchrest/validation/validators/confirmation_validator.rb", "lib/couchrest/validation/validators/format_validator.rb", "lib/couchrest/validation/validators/formats", "lib/couchrest/validation/validators/formats/email.rb", "lib/couchrest/validation/validators/formats/url.rb", "lib/couchrest/validation/validators/generic_validator.rb", "lib/couchrest/validation/validators/length_validator.rb", "lib/couchrest/validation/validators/method_validator.rb", "lib/couchrest/validation/validators/numeric_validator.rb", "lib/couchrest/validation/validators/required_field_validator.rb", "lib/couchrest.rb", "spec/couchrest", "spec/couchrest/core", "spec/couchrest/core/couchrest_spec.rb", "spec/couchrest/core/database_spec.rb", "spec/couchrest/core/design_spec.rb", "spec/couchrest/core/document_spec.rb", "spec/couchrest/core/server_spec.rb", "spec/couchrest/helpers", "spec/couchrest/helpers/pager_spec.rb", "spec/couchrest/helpers/streamer_spec.rb", "spec/couchrest/more", "spec/couchrest/more/casted_extended_doc_spec.rb", "spec/couchrest/more/casted_model_spec.rb", "spec/couchrest/more/extended_doc_attachment_spec.rb", "spec/couchrest/more/extended_doc_spec.rb", "spec/couchrest/more/extended_doc_subclass_spec.rb", "spec/couchrest/more/extended_doc_view_spec.rb", "spec/couchrest/more/property_spec.rb", "spec/fixtures", "spec/fixtures/attachments", "spec/fixtures/attachments/couchdb.png", "spec/fixtures/attachments/README", "spec/fixtures/attachments/test.html", "spec/fixtures/more", "spec/fixtures/more/article.rb", "spec/fixtures/more/card.rb", "spec/fixtures/more/cat.rb", "spec/fixtures/more/course.rb", "spec/fixtures/more/event.rb", "spec/fixtures/more/invoice.rb", "spec/fixtures/more/person.rb", "spec/fixtures/more/question.rb", "spec/fixtures/more/service.rb", "spec/fixtures/views", "spec/fixtures/views/lib.js", "spec/fixtures/views/test_view", "spec/fixtures/views/test_view/lib.js", "spec/fixtures/views/test_view/only-map.js", "spec/fixtures/views/test_view/test-map.js", "spec/fixtures/views/test_view/test-reduce.js", "spec/spec.opts", "spec/spec_helper.rb", "utils/remap.rb", "utils/subset.rb"]
|
s.files = ["LICENSE", "README.md", "Rakefile", "THANKS.md", "history.txt", "examples/model", "examples/model/example.rb", "examples/word_count", "examples/word_count/markov", "examples/word_count/views", "examples/word_count/views/books", "examples/word_count/views/books/chunked-map.js", "examples/word_count/views/books/united-map.js", "examples/word_count/views/markov", "examples/word_count/views/markov/chain-map.js", "examples/word_count/views/markov/chain-reduce.js", "examples/word_count/views/word_count", "examples/word_count/views/word_count/count-map.js", "examples/word_count/views/word_count/count-reduce.js", "examples/word_count/word_count.rb", "examples/word_count/word_count_query.rb", "examples/word_count/word_count_views.rb", "lib/couchrest", "lib/couchrest/commands", "lib/couchrest/commands/generate.rb", "lib/couchrest/commands/push.rb", "lib/couchrest/core", "lib/couchrest/core/database.rb", "lib/couchrest/core/design.rb", "lib/couchrest/core/document.rb", "lib/couchrest/core/response.rb", "lib/couchrest/core/server.rb", "lib/couchrest/core/view.rb", "lib/couchrest/helper", "lib/couchrest/helper/pager.rb", "lib/couchrest/helper/streamer.rb", "lib/couchrest/helper/upgrade.rb", "lib/couchrest/mixins", "lib/couchrest/mixins/attachments.rb", "lib/couchrest/mixins/callbacks.rb", "lib/couchrest/mixins/class_proxy.rb", "lib/couchrest/mixins/collection.rb", "lib/couchrest/mixins/design_doc.rb", "lib/couchrest/mixins/document_queries.rb", "lib/couchrest/mixins/extended_attachments.rb", "lib/couchrest/mixins/extended_document_mixins.rb", "lib/couchrest/mixins/properties.rb", "lib/couchrest/mixins/validation.rb", "lib/couchrest/mixins/views.rb", "lib/couchrest/mixins.rb", "lib/couchrest/monkeypatches.rb", "lib/couchrest/more", "lib/couchrest/more/casted_model.rb", "lib/couchrest/more/extended_document.rb", "lib/couchrest/more/property.rb", "lib/couchrest/support", "lib/couchrest/support/blank.rb", "lib/couchrest/support/class.rb", "lib/couchrest/support/rails.rb", "lib/couchrest/validation", "lib/couchrest/validation/auto_validate.rb", "lib/couchrest/validation/contextual_validators.rb", "lib/couchrest/validation/validation_errors.rb", "lib/couchrest/validation/validators", "lib/couchrest/validation/validators/absent_field_validator.rb", "lib/couchrest/validation/validators/confirmation_validator.rb", "lib/couchrest/validation/validators/format_validator.rb", "lib/couchrest/validation/validators/formats", "lib/couchrest/validation/validators/formats/email.rb", "lib/couchrest/validation/validators/formats/url.rb", "lib/couchrest/validation/validators/generic_validator.rb", "lib/couchrest/validation/validators/length_validator.rb", "lib/couchrest/validation/validators/method_validator.rb", "lib/couchrest/validation/validators/numeric_validator.rb", "lib/couchrest/validation/validators/required_field_validator.rb", "lib/couchrest.rb", "spec/couchrest", "spec/couchrest/core", "spec/couchrest/core/couchrest_spec.rb", "spec/couchrest/core/database_spec.rb", "spec/couchrest/core/design_spec.rb", "spec/couchrest/core/document_spec.rb", "spec/couchrest/core/server_spec.rb", "spec/couchrest/helpers", "spec/couchrest/helpers/pager_spec.rb", "spec/couchrest/helpers/streamer_spec.rb", "spec/couchrest/more", "spec/couchrest/more/casted_extended_doc_spec.rb", "spec/couchrest/more/casted_model_spec.rb", "spec/couchrest/more/extended_doc_attachment_spec.rb", "spec/couchrest/more/extended_doc_spec.rb", "spec/couchrest/more/extended_doc_subclass_spec.rb", "spec/couchrest/more/extended_doc_view_spec.rb", "spec/couchrest/more/property_spec.rb", "spec/fixtures", "spec/fixtures/attachments", "spec/fixtures/attachments/couchdb.png", "spec/fixtures/attachments/README", "spec/fixtures/attachments/test.html", "spec/fixtures/more", "spec/fixtures/more/article.rb", "spec/fixtures/more/card.rb", "spec/fixtures/more/cat.rb", "spec/fixtures/more/course.rb", "spec/fixtures/more/event.rb", "spec/fixtures/more/invoice.rb", "spec/fixtures/more/person.rb", "spec/fixtures/more/question.rb", "spec/fixtures/more/service.rb", "spec/fixtures/views", "spec/fixtures/views/lib.js", "spec/fixtures/views/test_view", "spec/fixtures/views/test_view/lib.js", "spec/fixtures/views/test_view/only-map.js", "spec/fixtures/views/test_view/test-map.js", "spec/fixtures/views/test_view/test-reduce.js", "spec/spec.opts", "spec/spec_helper.rb", "utils/remap.rb", "utils/subset.rb"]
|
||||||
s.has_rdoc = true
|
|
||||||
s.homepage = %q{http://github.com/jchris/couchrest}
|
s.homepage = %q{http://github.com/jchris/couchrest}
|
||||||
s.require_paths = ["lib"]
|
s.require_paths = ["lib"]
|
||||||
s.rubygems_version = %q{1.3.2}
|
s.rubygems_version = %q{1.3.4}
|
||||||
s.summary = %q{Lean and RESTful interface to CouchDB.}
|
s.summary = %q{Lean and RESTful interface to CouchDB.}
|
||||||
|
|
||||||
if s.respond_to? :specification_version then
|
if s.respond_to? :specification_version then
|
||||||
|
|
33
history.txt
Normal file
33
history.txt
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
== 0.31
|
||||||
|
|
||||||
|
* Major enhancements
|
||||||
|
|
||||||
|
* Created an abstraction HTTP layer to support different http adapters (Matt Aimonetti)
|
||||||
|
* Added ExtendedDocument.create({}) and #create!({}) so you don't have to do Model.new.create (Matt Aimonetti)
|
||||||
|
|
||||||
|
* Minor enhancements
|
||||||
|
|
||||||
|
* Added an init.rb file for easy usage as a Rails plugin (Aaron Quint)
|
||||||
|
* Bug fix: pagination shouldn't die on empty results (Arnaud Berthomier)
|
||||||
|
* Optimized ExtendedDocument.count to run about 3x faster (Matt Aimonetti)
|
||||||
|
* Added Float casting (Ryan Felton & Matt Aimonetti)
|
||||||
|
|
||||||
|
== 0.30
|
||||||
|
|
||||||
|
* Major enhancements
|
||||||
|
|
||||||
|
* Added support for pagination (John Wood)
|
||||||
|
* Improved performance when initializing documents with timestamps (Matt Aimonetti)
|
||||||
|
|
||||||
|
* Minor enhancements
|
||||||
|
|
||||||
|
* Extended the API to retrieve an attachment URI (Matt Aimonetti)
|
||||||
|
* Bug fix: default value should be able to be set as false (Alexander Uvarov)
|
||||||
|
* Bug fix: validates_is_numeric should be able to properly validate a Float instance (Rob Kaufman)
|
||||||
|
* Bug fix: fixed the Timeout implementation (Seth Falcon)
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Unfortunately, before 0.30 we did not keep a track of the modifications made to CouchRest.
|
||||||
|
You can see the full commit history on GitHub: http://github.com/mattetti/couchrest/commits/master/
|
1
init.rb
Normal file
1
init.rb
Normal file
|
@ -0,0 +1 @@
|
||||||
|
require File.join(File.dirname(__FILE__),'lib', 'couchrest.rb')
|
|
@ -28,7 +28,7 @@ require 'couchrest/monkeypatches'
|
||||||
|
|
||||||
# = CouchDB, close to the metal
|
# = CouchDB, close to the metal
|
||||||
module CouchRest
|
module CouchRest
|
||||||
VERSION = '0.29' unless self.const_defined?("VERSION")
|
VERSION = '0.30' unless self.const_defined?("VERSION")
|
||||||
|
|
||||||
autoload :Server, 'couchrest/core/server'
|
autoload :Server, 'couchrest/core/server'
|
||||||
autoload :Database, 'couchrest/core/database'
|
autoload :Database, 'couchrest/core/database'
|
||||||
|
@ -45,6 +45,7 @@ module CouchRest
|
||||||
autoload :ExtendedDocument, 'couchrest/more/extended_document'
|
autoload :ExtendedDocument, 'couchrest/more/extended_document'
|
||||||
autoload :CastedModel, 'couchrest/more/casted_model'
|
autoload :CastedModel, 'couchrest/more/casted_model'
|
||||||
|
|
||||||
|
require File.join(File.dirname(__FILE__), 'couchrest', 'core', 'http_abstraction')
|
||||||
require File.join(File.dirname(__FILE__), 'couchrest', 'mixins')
|
require File.join(File.dirname(__FILE__), 'couchrest', 'mixins')
|
||||||
require File.join(File.dirname(__FILE__), 'couchrest', 'support', 'rails') if defined?(Rails)
|
require File.join(File.dirname(__FILE__), 'couchrest', 'support', 'rails') if defined?(Rails)
|
||||||
|
|
||||||
|
@ -119,9 +120,9 @@ module CouchRest
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
# set proxy for RestClient to use
|
# set proxy to use
|
||||||
def proxy url
|
def proxy url
|
||||||
RestClient.proxy = url
|
HttpAbstraction.proxy = url
|
||||||
end
|
end
|
||||||
|
|
||||||
# ensure that a database exists
|
# ensure that a database exists
|
||||||
|
@ -142,7 +143,7 @@ module CouchRest
|
||||||
def put(uri, doc = nil)
|
def put(uri, doc = nil)
|
||||||
payload = doc.to_json if doc
|
payload = doc.to_json if doc
|
||||||
begin
|
begin
|
||||||
JSON.parse(RestClient.put(uri, payload))
|
JSON.parse(HttpAbstraction.put(uri, payload))
|
||||||
rescue Exception => e
|
rescue Exception => e
|
||||||
if $DEBUG
|
if $DEBUG
|
||||||
raise "Error while sending a PUT request #{uri}\npayload: #{payload.inspect}\n#{e}"
|
raise "Error while sending a PUT request #{uri}\npayload: #{payload.inspect}\n#{e}"
|
||||||
|
@ -154,7 +155,7 @@ module CouchRest
|
||||||
|
|
||||||
def get(uri)
|
def get(uri)
|
||||||
begin
|
begin
|
||||||
JSON.parse(RestClient.get(uri), :max_nesting => false)
|
JSON.parse(HttpAbstraction.get(uri), :max_nesting => false)
|
||||||
rescue => e
|
rescue => e
|
||||||
if $DEBUG
|
if $DEBUG
|
||||||
raise "Error while sending a GET request #{uri}\n: #{e}"
|
raise "Error while sending a GET request #{uri}\n: #{e}"
|
||||||
|
@ -167,7 +168,7 @@ module CouchRest
|
||||||
def post uri, doc = nil
|
def post uri, doc = nil
|
||||||
payload = doc.to_json if doc
|
payload = doc.to_json if doc
|
||||||
begin
|
begin
|
||||||
JSON.parse(RestClient.post(uri, payload))
|
JSON.parse(HttpAbstraction.post(uri, payload))
|
||||||
rescue Exception => e
|
rescue Exception => e
|
||||||
if $DEBUG
|
if $DEBUG
|
||||||
raise "Error while sending a POST request #{uri}\npayload: #{payload.inspect}\n#{e}"
|
raise "Error while sending a POST request #{uri}\npayload: #{payload.inspect}\n#{e}"
|
||||||
|
@ -178,11 +179,11 @@ module CouchRest
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete uri
|
def delete uri
|
||||||
JSON.parse(RestClient.delete(uri))
|
JSON.parse(HttpAbstraction.delete(uri))
|
||||||
end
|
end
|
||||||
|
|
||||||
def copy uri, destination
|
def copy uri, destination
|
||||||
JSON.parse(RestClient.copy(uri, {'Destination' => destination}))
|
JSON.parse(HttpAbstraction.copy(uri, {'Destination' => destination}))
|
||||||
end
|
end
|
||||||
|
|
||||||
def paramify_url url, params = {}
|
def paramify_url url, params = {}
|
||||||
|
|
35
lib/couchrest/core/adapters/restclient.rb
Normal file
35
lib/couchrest/core/adapters/restclient.rb
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
module RestClientAdapter
|
||||||
|
|
||||||
|
module API
|
||||||
|
def proxy=(url)
|
||||||
|
RestClient.proxy = url
|
||||||
|
end
|
||||||
|
|
||||||
|
def proxy
|
||||||
|
RestClient.proxy
|
||||||
|
end
|
||||||
|
|
||||||
|
def get(uri, headers={})
|
||||||
|
RestClient.get(uri, headers)
|
||||||
|
end
|
||||||
|
|
||||||
|
def post(uri, payload, headers={})
|
||||||
|
RestClient.post(uri, payload, headers)
|
||||||
|
end
|
||||||
|
|
||||||
|
def put(uri, payload, headers={})
|
||||||
|
RestClient.put(uri, payload, headers)
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(uri, headers={})
|
||||||
|
RestClient.delete(uri, headers)
|
||||||
|
end
|
||||||
|
|
||||||
|
def copy(uri, headers)
|
||||||
|
RestClient::Request.execute( :method => :copy,
|
||||||
|
:url => uri,
|
||||||
|
:headers => headers)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -58,7 +58,7 @@ module CouchRest
|
||||||
keys = params.delete(:keys)
|
keys = params.delete(:keys)
|
||||||
funcs = funcs.merge({:keys => keys}) if keys
|
funcs = funcs.merge({:keys => keys}) if keys
|
||||||
url = CouchRest.paramify_url "#{@root}/_temp_view", params
|
url = CouchRest.paramify_url "#{@root}/_temp_view", params
|
||||||
JSON.parse(RestClient.post(url, funcs.to_json, {"Content-Type" => 'application/json'}))
|
JSON.parse(HttpAbstraction.post(url, funcs.to_json, {"Content-Type" => 'application/json'}))
|
||||||
end
|
end
|
||||||
|
|
||||||
# backwards compatibility is a plus
|
# backwards compatibility is a plus
|
||||||
|
@ -100,11 +100,8 @@ module CouchRest
|
||||||
|
|
||||||
# GET an attachment directly from CouchDB
|
# GET an attachment directly from CouchDB
|
||||||
def fetch_attachment(doc, name)
|
def fetch_attachment(doc, name)
|
||||||
# slug = escape_docid(docid)
|
|
||||||
# name = CGI.escape(name)
|
|
||||||
uri = url_for_attachment(doc, name)
|
uri = url_for_attachment(doc, name)
|
||||||
RestClient.get uri
|
HttpAbstraction.get uri
|
||||||
# "#{@uri}/#{slug}/#{name}"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# PUT an attachment directly to CouchDB
|
# PUT an attachment directly to CouchDB
|
||||||
|
@ -112,14 +109,14 @@ module CouchRest
|
||||||
docid = escape_docid(doc['_id'])
|
docid = escape_docid(doc['_id'])
|
||||||
name = CGI.escape(name)
|
name = CGI.escape(name)
|
||||||
uri = url_for_attachment(doc, name)
|
uri = url_for_attachment(doc, name)
|
||||||
JSON.parse(RestClient.put(uri, file, options))
|
JSON.parse(HttpAbstraction.put(uri, file, options))
|
||||||
end
|
end
|
||||||
|
|
||||||
# DELETE an attachment directly from CouchDB
|
# DELETE an attachment directly from CouchDB
|
||||||
def delete_attachment doc, name
|
def delete_attachment doc, name
|
||||||
uri = url_for_attachment(doc, name)
|
uri = url_for_attachment(doc, name)
|
||||||
# this needs a rev
|
# this needs a rev
|
||||||
JSON.parse(RestClient.delete(uri))
|
JSON.parse(HttpAbstraction.delete(uri))
|
||||||
end
|
end
|
||||||
|
|
||||||
# Save a document to CouchDB. This will use the <tt>_id</tt> field from
|
# Save a document to CouchDB. This will use the <tt>_id</tt> field from
|
||||||
|
@ -146,7 +143,7 @@ module CouchRest
|
||||||
slug = escape_docid(doc['_id'])
|
slug = escape_docid(doc['_id'])
|
||||||
begin
|
begin
|
||||||
CouchRest.put "#{@root}/#{slug}", doc
|
CouchRest.put "#{@root}/#{slug}", doc
|
||||||
rescue RestClient::ResourceNotFound
|
rescue HttpAbstraction::ResourceNotFound
|
||||||
p "resource not found when saving even tho an id was passed"
|
p "resource not found when saving even tho an id was passed"
|
||||||
slug = doc['_id'] = @server.next_uuid
|
slug = doc['_id'] = @server.next_uuid
|
||||||
CouchRest.put "#{@root}/#{slug}", doc
|
CouchRest.put "#{@root}/#{slug}", doc
|
||||||
|
@ -252,7 +249,7 @@ module CouchRest
|
||||||
def recreate!
|
def recreate!
|
||||||
delete!
|
delete!
|
||||||
create!
|
create!
|
||||||
rescue RestClient::ResourceNotFound
|
rescue HttpAbstraction::ResourceNotFound
|
||||||
ensure
|
ensure
|
||||||
create!
|
create!
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,10 +3,6 @@ require 'delegate'
|
||||||
module CouchRest
|
module CouchRest
|
||||||
class Document < Response
|
class Document < Response
|
||||||
include CouchRest::Mixins::Attachments
|
include CouchRest::Mixins::Attachments
|
||||||
|
|
||||||
# def self.inherited(subklass)
|
|
||||||
# subklass.send(:extlib_inheritable_accessor, :database)
|
|
||||||
# end
|
|
||||||
|
|
||||||
extlib_inheritable_accessor :database
|
extlib_inheritable_accessor :database
|
||||||
attr_accessor :database
|
attr_accessor :database
|
||||||
|
@ -30,6 +26,7 @@ module CouchRest
|
||||||
def new?
|
def new?
|
||||||
!rev
|
!rev
|
||||||
end
|
end
|
||||||
|
alias :new_document? :new?
|
||||||
|
|
||||||
# Saves the document to the db using create or update. Also runs the :save
|
# Saves the document to the db using create or update. Also runs the :save
|
||||||
# callbacks. Sets the <tt>_id</tt> and <tt>_rev</tt> fields based on
|
# callbacks. Sets the <tt>_id</tt> and <tt>_rev</tt> fields based on
|
||||||
|
|
48
lib/couchrest/core/http_abstraction.rb
Normal file
48
lib/couchrest/core/http_abstraction.rb
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
require 'couchrest/core/adapters/restclient'
|
||||||
|
|
||||||
|
# Abstraction layet for HTTP communications.
|
||||||
|
#
|
||||||
|
# By defining a basic API that CouchRest is relying on,
|
||||||
|
# it allows for easy experimentations and implementations of various libraries.
|
||||||
|
#
|
||||||
|
# Most of the API is based on the RestClient API that was used in the early version of CouchRest.
|
||||||
|
#
|
||||||
|
module HttpAbstraction
|
||||||
|
|
||||||
|
# here is the list of exception expected by CouchRest
|
||||||
|
# please convert the underlying errors in this set of known
|
||||||
|
# exceptions.
|
||||||
|
class ResourceNotFound < StandardError; end
|
||||||
|
class RequestFailed < StandardError; end
|
||||||
|
class RequestTimeout < StandardError; end
|
||||||
|
class ServerBrokeConnection < StandardError; end
|
||||||
|
class Conflict < StandardError; end
|
||||||
|
|
||||||
|
|
||||||
|
# # Here is the API you need to implement if you want to write a new adapter
|
||||||
|
# # See adapters/restclient.rb for more information.
|
||||||
|
#
|
||||||
|
# def self.proxy=(url)
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# def self.proxy
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# def self.get(uri, headers=nil)
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# def self.post(uri, payload, headers=nil)
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# def self.put(uri, payload, headers=nil)
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# def self.delete(uri, headers=nil)
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# def self.copy(uri, headers)
|
||||||
|
# end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
HttpAbstraction.extend(RestClientAdapter::API)
|
222
lib/couchrest/mixins/collection.rb
Normal file
222
lib/couchrest/mixins/collection.rb
Normal file
|
@ -0,0 +1,222 @@
|
||||||
|
module CouchRest
|
||||||
|
module Mixins
|
||||||
|
module Collection
|
||||||
|
|
||||||
|
def self.included(base)
|
||||||
|
base.extend(ClassMethods)
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
|
||||||
|
# Creates a new class method, find_all_<collection_name>, that will
|
||||||
|
# execute the view specified with the design_doc and view_name
|
||||||
|
# parameters, along with the specified view_options. This method will
|
||||||
|
# return the results of the view as an Array of objects which are
|
||||||
|
# instances of the class.
|
||||||
|
#
|
||||||
|
# This method is handy for objects that do not use the view_by method
|
||||||
|
# to declare their views.
|
||||||
|
def provides_collection(collection_name, design_doc, view_name, view_options)
|
||||||
|
class_eval <<-END, __FILE__, __LINE__ + 1
|
||||||
|
def self.find_all_#{collection_name}(options = {})
|
||||||
|
view_options = #{view_options.inspect} || {}
|
||||||
|
CollectionProxy.new(@database, "#{design_doc}", "#{view_name}", view_options.merge(options), Kernel.const_get('#{self}'))
|
||||||
|
end
|
||||||
|
END
|
||||||
|
end
|
||||||
|
|
||||||
|
# Fetch a group of objects from CouchDB. Options can include:
|
||||||
|
# :page - Specifies the page to load (starting at 1)
|
||||||
|
# :per_page - Specifies the number of objects to load per page
|
||||||
|
#
|
||||||
|
# Defaults are used if these options are not specified.
|
||||||
|
def paginate(options)
|
||||||
|
proxy = create_collection_proxy(options)
|
||||||
|
proxy.paginate(options)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Iterate over the objects in a collection, fetching them from CouchDB
|
||||||
|
# in groups. Options can include:
|
||||||
|
# :page - Specifies the page to load
|
||||||
|
# :per_page - Specifies the number of objects to load per page
|
||||||
|
#
|
||||||
|
# Defaults are used if these options are not specified.
|
||||||
|
def paginated_each(options, &block)
|
||||||
|
proxy = create_collection_proxy(options)
|
||||||
|
proxy.paginated_each(options, &block)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Create a CollectionProxy for the specified view and options.
|
||||||
|
# CollectionProxy behaves just like an Array, but offers support for
|
||||||
|
# pagination.
|
||||||
|
def collection_proxy_for(design_doc, view_name, view_options = {})
|
||||||
|
options = view_options.merge(:design_doc => design_doc, :view_name => view_name)
|
||||||
|
create_collection_proxy(options)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def create_collection_proxy(options)
|
||||||
|
design_doc, view_name, view_options = parse_view_options(options)
|
||||||
|
CollectionProxy.new(@database, design_doc, view_name, view_options, self)
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_view_options(options)
|
||||||
|
design_doc = options.delete(:design_doc)
|
||||||
|
raise ArgumentError, 'design_doc is required' if design_doc.nil?
|
||||||
|
|
||||||
|
view_name = options.delete(:view_name)
|
||||||
|
raise ArgumentError, 'view_name is required' if view_name.nil?
|
||||||
|
|
||||||
|
default_view_options = (design_doc.class == Design &&
|
||||||
|
design_doc['views'][view_name.to_s] &&
|
||||||
|
design_doc['views'][view_name.to_s]["couchrest-defaults"]) || {}
|
||||||
|
view_options = default_view_options.merge(options)
|
||||||
|
|
||||||
|
[design_doc, view_name, view_options]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class CollectionProxy
|
||||||
|
alias_method :proxy_respond_to?, :respond_to?
|
||||||
|
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ }
|
||||||
|
|
||||||
|
DEFAULT_PAGE = 1
|
||||||
|
DEFAULT_PER_PAGE = 30
|
||||||
|
|
||||||
|
# Create a new CollectionProxy to represent the specified view. If a
|
||||||
|
# container class is specified, the proxy will create an object of the
|
||||||
|
# given type for each row that comes back from the view. If no
|
||||||
|
# container class is specified, the raw results are returned.
|
||||||
|
#
|
||||||
|
# The CollectionProxy provides support for paginating over a collection
|
||||||
|
# via the paginate, and paginated_each methods.
|
||||||
|
def initialize(database, design_doc, view_name, view_options = {}, container_class = nil)
|
||||||
|
raise ArgumentError, "database is a required parameter" if database.nil?
|
||||||
|
|
||||||
|
@database = database
|
||||||
|
@container_class = container_class
|
||||||
|
|
||||||
|
strip_pagination_options(view_options)
|
||||||
|
@view_options = view_options
|
||||||
|
|
||||||
|
if design_doc.class == Design
|
||||||
|
@view_name = "#{design_doc.name}/#{view_name}"
|
||||||
|
else
|
||||||
|
@view_name = "#{design_doc}/#{view_name}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# See Collection.paginate
|
||||||
|
def paginate(options = {})
|
||||||
|
page, per_page = parse_options(options)
|
||||||
|
results = @database.view(@view_name, pagination_options(page, per_page))
|
||||||
|
remember_where_we_left_off(results, page)
|
||||||
|
convert_to_container_array(results)
|
||||||
|
end
|
||||||
|
|
||||||
|
# See Collection.paginated_each
|
||||||
|
def paginated_each(options = {}, &block)
|
||||||
|
page, per_page = parse_options(options)
|
||||||
|
|
||||||
|
begin
|
||||||
|
collection = paginate({:page => page, :per_page => per_page})
|
||||||
|
collection.each(&block)
|
||||||
|
page += 1
|
||||||
|
end until collection.size < per_page
|
||||||
|
end
|
||||||
|
|
||||||
|
def respond_to?(*args)
|
||||||
|
proxy_respond_to?(*args) || (load_target && @target.respond_to?(*args))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Explicitly proxy === because the instance method removal above
|
||||||
|
# doesn't catch it.
|
||||||
|
def ===(other)
|
||||||
|
load_target
|
||||||
|
other === @target
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def method_missing(method, *args)
|
||||||
|
if load_target
|
||||||
|
if block_given?
|
||||||
|
@target.send(method, *args) { |*block_args| yield(*block_args) }
|
||||||
|
else
|
||||||
|
@target.send(method, *args)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_target
|
||||||
|
unless loaded?
|
||||||
|
results = @database.view(@view_name, @view_options)
|
||||||
|
@target = convert_to_container_array(results)
|
||||||
|
end
|
||||||
|
@loaded = true
|
||||||
|
@target
|
||||||
|
end
|
||||||
|
|
||||||
|
def loaded?
|
||||||
|
@loaded
|
||||||
|
end
|
||||||
|
|
||||||
|
def reload
|
||||||
|
reset
|
||||||
|
load_target
|
||||||
|
self unless @target.nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
def reset
|
||||||
|
@loaded = false
|
||||||
|
@target = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def inspect
|
||||||
|
load_target
|
||||||
|
@target.inspect
|
||||||
|
end
|
||||||
|
|
||||||
|
def convert_to_container_array(results)
|
||||||
|
if @container_class.nil?
|
||||||
|
results
|
||||||
|
else
|
||||||
|
results['rows'].collect { |row| @container_class.new(row['doc']) } unless results['rows'].nil?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_options(page, per_page)
|
||||||
|
view_options = @view_options.clone
|
||||||
|
if @last_key && @last_docid && @last_page == page - 1
|
||||||
|
view_options.delete(:key)
|
||||||
|
options = { :startkey => @last_key, :startkey_docid => @last_docid, :limit => per_page, :skip => 1 }
|
||||||
|
else
|
||||||
|
options = { :limit => per_page, :skip => per_page * (page - 1) }
|
||||||
|
end
|
||||||
|
view_options.merge(options)
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_options(options)
|
||||||
|
page = options.delete(:page) || DEFAULT_PAGE
|
||||||
|
per_page = options.delete(:per_page) || DEFAULT_PER_PAGE
|
||||||
|
[page.to_i, per_page.to_i]
|
||||||
|
end
|
||||||
|
|
||||||
|
def strip_pagination_options(options)
|
||||||
|
parse_options(options)
|
||||||
|
end
|
||||||
|
|
||||||
|
def remember_where_we_left_off(results, page)
|
||||||
|
last_row = results['rows'].last
|
||||||
|
if last_row
|
||||||
|
@last_key = last_row['key']
|
||||||
|
@last_docid = last_row['id']
|
||||||
|
end
|
||||||
|
@last_page = page
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -37,9 +37,6 @@ module CouchRest
|
||||||
if (doc['couchrest-type'] == '#{self.to_s}') {
|
if (doc['couchrest-type'] == '#{self.to_s}') {
|
||||||
emit(null,1);
|
emit(null,1);
|
||||||
}
|
}
|
||||||
}",
|
|
||||||
'reduce' => "function(keys, values) {
|
|
||||||
return sum(values);
|
|
||||||
}"
|
}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,9 +19,7 @@ module CouchRest
|
||||||
# equal to the name of the current class. Takes the standard set of
|
# equal to the name of the current class. Takes the standard set of
|
||||||
# CouchRest::Database#view options
|
# CouchRest::Database#view options
|
||||||
def count(opts = {}, &block)
|
def count(opts = {}, &block)
|
||||||
result = all({:reduce => true}.merge(opts), &block)['rows']
|
all({:raw => true, :limit => 0}.merge(opts), &block)['total_rows']
|
||||||
return 0 if result.empty?
|
|
||||||
result.first['value']
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Load the first document that have the "couchrest-type" field equal to
|
# Load the first document that have the "couchrest-type" field equal to
|
||||||
|
|
|
@ -5,3 +5,4 @@ require File.join(File.dirname(__FILE__), 'design_doc')
|
||||||
require File.join(File.dirname(__FILE__), 'validation')
|
require File.join(File.dirname(__FILE__), 'validation')
|
||||||
require File.join(File.dirname(__FILE__), 'extended_attachments')
|
require File.join(File.dirname(__FILE__), 'extended_attachments')
|
||||||
require File.join(File.dirname(__FILE__), 'class_proxy')
|
require File.join(File.dirname(__FILE__), 'class_proxy')
|
||||||
|
require File.join(File.dirname(__FILE__), 'collection')
|
||||||
|
|
|
@ -1,6 +1,25 @@
|
||||||
require 'time'
|
require 'time'
|
||||||
require File.join(File.dirname(__FILE__), '..', 'more', 'property')
|
require File.join(File.dirname(__FILE__), '..', 'more', 'property')
|
||||||
|
|
||||||
|
class Time
|
||||||
|
# returns a local time value much faster than Time.parse
|
||||||
|
def self.mktime_with_offset(string)
|
||||||
|
string =~ /(\d{4})\/(\d{2})\/(\d{2}) (\d{2}):(\d{2}):(\d{2}) ([\+\-])(\d{2})/
|
||||||
|
# $1 = year
|
||||||
|
# $2 = month
|
||||||
|
# $3 = day
|
||||||
|
# $4 = hours
|
||||||
|
# $5 = minutes
|
||||||
|
# $6 = seconds
|
||||||
|
# $7 = time zone direction
|
||||||
|
# $8 = tz difference
|
||||||
|
# utc time with wrong TZ info:
|
||||||
|
time = mktime($1, RFC2822_MONTH_NAME[$2.to_i - 1], $3, $4, $5, $6, $7)
|
||||||
|
tz_difference = ("#{$7 == '-' ? '+' : '-'}#{$8}".to_i * 3600)
|
||||||
|
time + tz_difference + zone_offset(time.zone)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
module CouchRest
|
module CouchRest
|
||||||
module Mixins
|
module Mixins
|
||||||
module Properties
|
module Properties
|
||||||
|
@ -65,6 +84,7 @@ module CouchRest
|
||||||
end
|
end
|
||||||
associate_casted_to_parent(self[property.name], assigned)
|
associate_casted_to_parent(self[property.name], assigned)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def associate_casted_to_parent(casted, assigned)
|
def associate_casted_to_parent(casted, assigned)
|
||||||
|
@ -73,8 +93,12 @@ module CouchRest
|
||||||
end
|
end
|
||||||
|
|
||||||
def convert_property_value(property, klass, value)
|
def convert_property_value(property, klass, value)
|
||||||
if ((property.init_method == 'new') && klass.to_s == 'Time')
|
if ((property.init_method == 'new') && klass.to_s == 'Time')
|
||||||
value.is_a?(String) ? Time.parse(value.dup) : value
|
# Using custom time parsing method because Ruby's default method is toooo slow
|
||||||
|
value.is_a?(String) ? Time.mktime_with_offset(value.dup) : value
|
||||||
|
# Float instances don't get initialized with #new
|
||||||
|
elsif ((property.init_method == 'new') && klass.to_s == 'Float')
|
||||||
|
cast_float(value)
|
||||||
else
|
else
|
||||||
klass.send(property.init_method, value.dup)
|
klass.send(property.init_method, value.dup)
|
||||||
end
|
end
|
||||||
|
@ -87,6 +111,14 @@ module CouchRest
|
||||||
cast_property(property, true)
|
cast_property(property, true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def cast_float(value)
|
||||||
|
begin
|
||||||
|
Float(value)
|
||||||
|
rescue
|
||||||
|
value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
|
|
||||||
def property(name, options={})
|
def property(name, options={})
|
||||||
|
@ -146,4 +178,4 @@ module CouchRest
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
|
@ -72,7 +72,7 @@ module CouchRest
|
||||||
#
|
#
|
||||||
# To understand the capabilities of this view system more completely,
|
# To understand the capabilities of this view system more completely,
|
||||||
# it is recommended that you read the RSpec file at
|
# it is recommended that you read the RSpec file at
|
||||||
# <tt>spec/core/model_spec.rb</tt>.
|
# <tt>spec/couchrest/more/extended_doc_spec.rb</tt>.
|
||||||
|
|
||||||
def view_by(*keys)
|
def view_by(*keys)
|
||||||
opts = keys.pop if keys.last.is_a?(Hash)
|
opts = keys.pop if keys.last.is_a?(Hash)
|
||||||
|
@ -124,14 +124,6 @@ module CouchRest
|
||||||
# potentially large indexes.
|
# potentially large indexes.
|
||||||
def cleanup_design_docs!(db = database)
|
def cleanup_design_docs!(db = database)
|
||||||
save_design_doc_on(db)
|
save_design_doc_on(db)
|
||||||
# db.refresh_design_doc
|
|
||||||
# db.save_design_doc
|
|
||||||
# design_doc = model_design_doc(db)
|
|
||||||
# if design_doc
|
|
||||||
# db.delete_doc(design_doc)
|
|
||||||
# else
|
|
||||||
# false
|
|
||||||
# end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -141,8 +133,12 @@ module CouchRest
|
||||||
fetch_view(db, name, opts, &block)
|
fetch_view(db, name, opts, &block)
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
view = fetch_view db, name, opts.merge({:include_docs => true}), &block
|
if block.nil?
|
||||||
view['rows'].collect{|r|new(r['doc'])} if view['rows']
|
collection_proxy_for(design_doc, name, opts.merge({:include_docs => true}))
|
||||||
|
else
|
||||||
|
view = fetch_view db, name, opts.merge({:include_docs => true}), &block
|
||||||
|
view['rows'].collect{|r|new(r['doc'])} if view['rows']
|
||||||
|
end
|
||||||
rescue
|
rescue
|
||||||
# fallback for old versions of couchdb that don't
|
# fallback for old versions of couchdb that don't
|
||||||
# have include_docs support
|
# have include_docs support
|
||||||
|
@ -158,7 +154,7 @@ module CouchRest
|
||||||
begin
|
begin
|
||||||
design_doc.view_on(db, view_name, opts, &block)
|
design_doc.view_on(db, view_name, opts, &block)
|
||||||
# the design doc may not have been saved yet on this database
|
# the design doc may not have been saved yet on this database
|
||||||
rescue RestClient::ResourceNotFound => e
|
rescue HttpAbstraction::ResourceNotFound => e
|
||||||
if retryable
|
if retryable
|
||||||
save_design_doc_on(db)
|
save_design_doc_on(db)
|
||||||
retryable = false
|
retryable = false
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
require File.join(File.dirname(__FILE__), 'support', 'class')
|
require File.join(File.dirname(__FILE__), 'support', 'class')
|
||||||
require File.join(File.dirname(__FILE__), 'support', 'blank')
|
require File.join(File.dirname(__FILE__), 'support', 'blank')
|
||||||
|
require 'timeout'
|
||||||
|
|
||||||
# This file must be loaded after the JSON gem and any other library that beats up the Time class.
|
# This file must be loaded after the JSON gem and any other library that beats up the Time class.
|
||||||
class Time
|
class Time
|
||||||
|
@ -38,7 +39,7 @@ if RUBY_VERSION.to_f < 1.9
|
||||||
if IO.select([@io], nil, nil, @read_timeout)
|
if IO.select([@io], nil, nil, @read_timeout)
|
||||||
retry
|
retry
|
||||||
else
|
else
|
||||||
raise Timeout::TimeoutError
|
raise Timeout::Error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
@ -50,63 +51,63 @@ if RUBY_VERSION.to_f < 1.9
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module RestClient
|
# module RestClient
|
||||||
def self.copy(url, headers={})
|
# # def self.copy(url, headers={})
|
||||||
Request.execute(:method => :copy,
|
# # Request.execute(:method => :copy,
|
||||||
:url => url,
|
# # :url => url,
|
||||||
:headers => headers)
|
# # :headers => headers)
|
||||||
end
|
# # end
|
||||||
|
|
||||||
# class Request
|
|
||||||
#
|
|
||||||
# def establish_connection(uri)
|
|
||||||
# Thread.current[:connection].finish if (Thread.current[:connection] && Thread.current[:connection].started?)
|
|
||||||
# p net_http_class
|
|
||||||
# net = net_http_class.new(uri.host, uri.port)
|
|
||||||
# net.use_ssl = uri.is_a?(URI::HTTPS)
|
|
||||||
# net.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
||||||
# Thread.current[:connection] = net
|
|
||||||
# Thread.current[:connection].start
|
|
||||||
# Thread.current[:connection]
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# def transmit(uri, req, payload)
|
|
||||||
# setup_credentials(req)
|
|
||||||
#
|
|
||||||
# Thread.current[:host] ||= uri.host
|
|
||||||
# Thread.current[:port] ||= uri.port
|
|
||||||
#
|
|
||||||
# if (Thread.current[:connection].nil? || (Thread.current[:host] != uri.host))
|
|
||||||
# p "establishing a connection"
|
|
||||||
# establish_connection(uri)
|
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# display_log request_log
|
# # class Request
|
||||||
# http = Thread.current[:connection]
|
# #
|
||||||
# http.read_timeout = @timeout if @timeout
|
# # def establish_connection(uri)
|
||||||
#
|
# # Thread.current[:connection].finish if (Thread.current[:connection] && Thread.current[:connection].started?)
|
||||||
# begin
|
# # p net_http_class
|
||||||
# res = http.request(req, payload)
|
# # net = net_http_class.new(uri.host, uri.port)
|
||||||
# rescue
|
# # net.use_ssl = uri.is_a?(URI::HTTPS)
|
||||||
# p "Net::HTTP connection failed, reconnecting"
|
# # net.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
||||||
# establish_connection(uri)
|
# # Thread.current[:connection] = net
|
||||||
# http = Thread.current[:connection]
|
# # Thread.current[:connection].start
|
||||||
# require 'ruby-debug'
|
# # Thread.current[:connection]
|
||||||
# req.body_stream = nil
|
# # end
|
||||||
#
|
# #
|
||||||
# res = http.request(req, payload)
|
# # def transmit(uri, req, payload)
|
||||||
# display_log response_log(res)
|
# # setup_credentials(req)
|
||||||
# result res
|
# #
|
||||||
# else
|
# # Thread.current[:host] ||= uri.host
|
||||||
# display_log response_log(res)
|
# # Thread.current[:port] ||= uri.port
|
||||||
# process_result res
|
# #
|
||||||
# end
|
# # if (Thread.current[:connection].nil? || (Thread.current[:host] != uri.host))
|
||||||
#
|
# # p "establishing a connection"
|
||||||
# rescue EOFError
|
# # establish_connection(uri)
|
||||||
# raise RestClient::ServerBrokeConnection
|
# # end
|
||||||
# rescue Timeout::Error
|
# #
|
||||||
# raise RestClient::RequestTimeout
|
# # display_log request_log
|
||||||
# end
|
# # http = Thread.current[:connection]
|
||||||
# end
|
# # http.read_timeout = @timeout if @timeout
|
||||||
|
# #
|
||||||
end
|
# # begin
|
||||||
|
# # res = http.request(req, payload)
|
||||||
|
# # rescue
|
||||||
|
# # p "Net::HTTP connection failed, reconnecting"
|
||||||
|
# # establish_connection(uri)
|
||||||
|
# # http = Thread.current[:connection]
|
||||||
|
# # require 'ruby-debug'
|
||||||
|
# # req.body_stream = nil
|
||||||
|
# #
|
||||||
|
# # res = http.request(req, payload)
|
||||||
|
# # display_log response_log(res)
|
||||||
|
# # result res
|
||||||
|
# # else
|
||||||
|
# # display_log response_log(res)
|
||||||
|
# # process_result res
|
||||||
|
# # end
|
||||||
|
# #
|
||||||
|
# # rescue EOFError
|
||||||
|
# # raise RestClient::ServerBrokeConnection
|
||||||
|
# # rescue Timeout::Error
|
||||||
|
# # raise RestClient::RequestTimeout
|
||||||
|
# # end
|
||||||
|
# # end
|
||||||
|
#
|
||||||
|
# end
|
||||||
|
|
|
@ -13,10 +13,11 @@ module CouchRest
|
||||||
include CouchRest::Mixins::DesignDoc
|
include CouchRest::Mixins::DesignDoc
|
||||||
include CouchRest::Mixins::ExtendedAttachments
|
include CouchRest::Mixins::ExtendedAttachments
|
||||||
include CouchRest::Mixins::ClassProxy
|
include CouchRest::Mixins::ClassProxy
|
||||||
|
include CouchRest::Mixins::Collection
|
||||||
|
|
||||||
def self.subclasses
|
def self.subclasses
|
||||||
@subclasses ||= []
|
@subclasses ||= []
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.inherited(subklass)
|
def self.inherited(subklass)
|
||||||
subklass.send(:include, CouchRest::Mixins::Properties)
|
subklass.send(:include, CouchRest::Mixins::Properties)
|
||||||
|
@ -51,6 +52,26 @@ module CouchRest
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Defines an instance and save it directly to the database
|
||||||
|
#
|
||||||
|
# ==== Returns
|
||||||
|
# returns the reloaded document
|
||||||
|
def self.create(options)
|
||||||
|
instance = new(options)
|
||||||
|
instance.create
|
||||||
|
instance
|
||||||
|
end
|
||||||
|
|
||||||
|
# Defines an instance and save it directly to the database
|
||||||
|
#
|
||||||
|
# ==== Returns
|
||||||
|
# returns the reloaded document or raises an exception
|
||||||
|
def self.create!(options)
|
||||||
|
instance = new(options)
|
||||||
|
instance.create!
|
||||||
|
instance
|
||||||
|
end
|
||||||
|
|
||||||
# Automatically set <tt>updated_at</tt> and <tt>created_at</tt> fields
|
# Automatically set <tt>updated_at</tt> and <tt>created_at</tt> fields
|
||||||
# on the document whenever saving occurs. CouchRest uses a pretty
|
# on the document whenever saving occurs. CouchRest uses a pretty
|
||||||
# decent time format by default. See Time#to_json
|
# decent time format by default. See Time#to_json
|
||||||
|
|
|
@ -40,7 +40,7 @@ module CouchRest
|
||||||
value = target.send(field_name)
|
value = target.send(field_name)
|
||||||
return true if @options[:allow_nil] && value.nil?
|
return true if @options[:allow_nil] && value.nil?
|
||||||
|
|
||||||
value = value.kind_of?(Float) ? value.to_s('F') : value.to_s
|
value = (defined?(BigDecimal) && value.kind_of?(BigDecimal)) ? value.to_s('F') : value.to_s
|
||||||
|
|
||||||
error_message = @options[:message]
|
error_message = @options[:message]
|
||||||
precision = @options[:precision]
|
precision = @options[:precision]
|
||||||
|
|
|
@ -191,7 +191,7 @@ describe CouchRest do
|
||||||
describe "using a proxy for RestClient connections" do
|
describe "using a proxy for RestClient connections" do
|
||||||
it "should set proxy url for RestClient" do
|
it "should set proxy url for RestClient" do
|
||||||
CouchRest.proxy 'http://localhost:8888/'
|
CouchRest.proxy 'http://localhost:8888/'
|
||||||
proxy_uri = URI.parse(RestClient.proxy)
|
proxy_uri = URI.parse(HttpAbstraction.proxy)
|
||||||
proxy_uri.host.should eql( 'localhost' )
|
proxy_uri.host.should eql( 'localhost' )
|
||||||
proxy_uri.port.should eql( 8888 )
|
proxy_uri.port.should eql( 8888 )
|
||||||
CouchRest.proxy nil
|
CouchRest.proxy nil
|
||||||
|
|
|
@ -690,7 +690,7 @@ describe CouchRest::Database do
|
||||||
|
|
||||||
it "should recreate a db even tho it doesn't exist" do
|
it "should recreate a db even tho it doesn't exist" do
|
||||||
@cr.databases.should_not include(@db2.name)
|
@cr.databases.should_not include(@db2.name)
|
||||||
@db2.recreate!
|
begin @db2.recreate! rescue nil end
|
||||||
@cr.databases.should include(@db2.name)
|
@cr.databases.should include(@db2.name)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -121,15 +121,28 @@ describe "ExtendedDocument" do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "a new model" do
|
describe "a new model" do
|
||||||
it "should be a new_record" do
|
it "should be a new document" do
|
||||||
@obj = Basic.new
|
@obj = Basic.new
|
||||||
@obj.rev.should be_nil
|
@obj.rev.should be_nil
|
||||||
@obj.should be_new
|
@obj.should be_new
|
||||||
|
@obj.should be_new_document
|
||||||
|
@obj.should be_new_record
|
||||||
end
|
end
|
||||||
it "should be a new_document" do
|
end
|
||||||
@obj = Basic.new
|
|
||||||
@obj.rev.should be_nil
|
describe "creating a new document" do
|
||||||
@obj.should be_new
|
it "should instantialize and save a document" do
|
||||||
|
article = Article.create(:title => 'my test')
|
||||||
|
article.title.should == 'my test'
|
||||||
|
article.should_not be_new
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should trigger the create callbacks" do
|
||||||
|
doc = WithCallBacks.create(:name => 'my other test')
|
||||||
|
doc.run_before_create.should be_true
|
||||||
|
doc.run_after_create.should be_true
|
||||||
|
doc.run_before_save.should be_true
|
||||||
|
doc.run_after_save.should be_true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -121,7 +121,7 @@ describe "ExtendedDocument views" do
|
||||||
describe "a model class not tied to a database" do
|
describe "a model class not tied to a database" do
|
||||||
before(:all) do
|
before(:all) do
|
||||||
reset_test_db!
|
reset_test_db!
|
||||||
@db = DB
|
@db = DB
|
||||||
%w{aaa bbb ddd eee}.each do |title|
|
%w{aaa bbb ddd eee}.each do |title|
|
||||||
u = Unattached.new(:title => title)
|
u = Unattached.new(:title => title)
|
||||||
u.database = @db
|
u.database = @db
|
||||||
|
@ -133,14 +133,15 @@ describe "ExtendedDocument views" do
|
||||||
lambda{Unattached.all}.should raise_error
|
lambda{Unattached.all}.should raise_error
|
||||||
end
|
end
|
||||||
it "should query all" do
|
it "should query all" do
|
||||||
rs = Unattached.all :database=>@db
|
Unattached.cleanup_design_docs!(@db)
|
||||||
|
rs = Unattached.all :database => @db
|
||||||
rs.length.should == 4
|
rs.length.should == 4
|
||||||
end
|
end
|
||||||
it "should barf on query if no database given" do
|
it "should barf on query if no database given" do
|
||||||
lambda{Unattached.view :by_title}.should raise_error
|
lambda{Unattached.view :by_title}.should raise_error
|
||||||
end
|
end
|
||||||
it "should make the design doc upon first query" do
|
it "should make the design doc upon first query" do
|
||||||
Unattached.by_title :database=>@db
|
Unattached.by_title :database => @db
|
||||||
doc = Unattached.design_doc
|
doc = Unattached.design_doc
|
||||||
doc['views']['all']['map'].should include('Unattached')
|
doc['views']['all']['map'].should include('Unattached')
|
||||||
end
|
end
|
||||||
|
@ -157,7 +158,7 @@ describe "ExtendedDocument views" do
|
||||||
things = []
|
things = []
|
||||||
Unattached.view(:by_title, :database=>@db) do |thing|
|
Unattached.view(:by_title, :database=>@db) do |thing|
|
||||||
things << thing
|
things << thing
|
||||||
end
|
end
|
||||||
things[0]["doc"]["title"].should =='aaa'
|
things[0]["doc"]["title"].should =='aaa'
|
||||||
end
|
end
|
||||||
it "should yield with by_key method" do
|
it "should yield with by_key method" do
|
||||||
|
@ -337,5 +338,78 @@ describe "ExtendedDocument views" do
|
||||||
Article.design_doc["views"].keys.should include("by_updated_at")
|
Article.design_doc["views"].keys.should include("by_updated_at")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "with a collection" do
|
||||||
|
before(:all) do
|
||||||
|
reset_test_db!
|
||||||
|
@titles = ["very uniq one", "really interesting", "some fun",
|
||||||
|
"really awesome", "crazy bob", "this rocks", "super rad"]
|
||||||
|
@titles.each_with_index do |title,i|
|
||||||
|
a = Article.new(:title => title, :date => Date.today)
|
||||||
|
a.save
|
||||||
|
end
|
||||||
|
end
|
||||||
|
it "should return a proxy that looks like an array of 7 Article objects" do
|
||||||
|
articles = Article.by_date :key => Date.today
|
||||||
|
articles.class.should == Array
|
||||||
|
articles.size.should == 7
|
||||||
|
end
|
||||||
|
it "should get a subset of articles using paginate" do
|
||||||
|
articles = Article.by_date :key => Date.today
|
||||||
|
articles.paginate(:page => 1, :per_page => 3).size.should == 3
|
||||||
|
articles.paginate(:page => 2, :per_page => 3).size.should == 3
|
||||||
|
articles.paginate(:page => 3, :per_page => 3).size.should == 1
|
||||||
|
end
|
||||||
|
it "should get all articles, a few at a time, using paginated each" do
|
||||||
|
articles = Article.by_date :key => Date.today
|
||||||
|
articles.paginated_each(:per_page => 3) do |a|
|
||||||
|
a.should_not be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
it "should provide a class method to access the collection directly" do
|
||||||
|
articles = Article.collection_proxy_for('Article', 'by_date', :descending => true,
|
||||||
|
:key => Date.today, :include_docs => true)
|
||||||
|
articles.class.should == Array
|
||||||
|
articles.size.should == 7
|
||||||
|
end
|
||||||
|
it "should provide a class method for paginate" do
|
||||||
|
articles = Article.paginate(:design_doc => 'Article', :view_name => 'by_date',
|
||||||
|
:per_page => 3, :descending => true, :key => Date.today, :include_docs => true)
|
||||||
|
articles.size.should == 3
|
||||||
|
|
||||||
|
articles = Article.paginate(:design_doc => 'Article', :view_name => 'by_date',
|
||||||
|
:per_page => 3, :page => 2, :descending => true, :key => Date.today, :include_docs => true)
|
||||||
|
articles.size.should == 3
|
||||||
|
|
||||||
|
articles = Article.paginate(:design_doc => 'Article', :view_name => 'by_date',
|
||||||
|
:per_page => 3, :page => 3, :descending => true, :key => Date.today, :include_docs => true)
|
||||||
|
articles.size.should == 1
|
||||||
|
end
|
||||||
|
it "should provide a class method for paginated_each" do
|
||||||
|
options = { :design_doc => 'Article', :view_name => 'by_date',
|
||||||
|
:per_page => 3, :page => 1, :descending => true, :key => Date.today,
|
||||||
|
:include_docs => true }
|
||||||
|
Article.paginated_each(options) do |a|
|
||||||
|
a.should_not be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
it "should provide a class method to get a collection for a view" do
|
||||||
|
class Article
|
||||||
|
provides_collection :article_details, 'Article', 'by_date', :descending => true, :include_docs => true
|
||||||
|
end
|
||||||
|
|
||||||
|
articles = Article.find_all_article_details(:key => Date.today)
|
||||||
|
articles.class.should == Array
|
||||||
|
articles.size.should == 7
|
||||||
|
end
|
||||||
|
it "should raise an exception if design_doc is not provided" do
|
||||||
|
lambda{Article.collection_proxy_for(nil, 'by_date')}.should raise_error
|
||||||
|
lambda{Article.paginate(:view_name => 'by_date')}.should raise_error
|
||||||
|
end
|
||||||
|
it "should raise an exception if view_name is not provided" do
|
||||||
|
lambda{Article.collection_proxy_for('Article', nil)}.should raise_error
|
||||||
|
lambda{Article.paginate(:design_doc => 'Article')}.should raise_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -142,7 +142,29 @@ describe "ExtendedDocument properties" do
|
||||||
@event['occurs_at'].should be_an_instance_of(Time)
|
@event['occurs_at'].should be_an_instance_of(Time)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
describe "casting to Float object" do
|
||||||
|
class RootBeerFloat < CouchRest::ExtendedDocument
|
||||||
|
use_database DB
|
||||||
|
property :price, :cast_as => 'Float'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should convert a string into a float if casted as so" do
|
||||||
|
RootBeerFloat.new(:price => '12.50').price.should == 12.50
|
||||||
|
RootBeerFloat.new(:price => '9').price.should == 9.0
|
||||||
|
RootBeerFloat.new(:price => '-9').price.should == -9.0
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not convert a string if it's not a string that can be cast as a float" do
|
||||||
|
RootBeerFloat.new(:price => 'test').price.should == 'test'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should work fine when a float is being passed" do
|
||||||
|
RootBeerFloat.new(:price => 9.99).price.should == 9.99
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "a newly created casted model" do
|
describe "a newly created casted model" do
|
||||||
|
|
|
@ -20,7 +20,7 @@ class Basic < CouchRest::ExtendedDocument
|
||||||
end
|
end
|
||||||
|
|
||||||
def reset_test_db!
|
def reset_test_db!
|
||||||
DB.recreate! rescue nil
|
DB.recreate! rescue nil
|
||||||
DB
|
DB
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue