diff --git a/README.md b/README.md
index a169b70..c212ba5 100644
--- a/README.md
+++ b/README.md
@@ -12,14 +12,17 @@ Note: CouchRest only support CouchDB 0.9.0 or newer.
## 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
-The core of Couchrest is Heroku’s excellent REST Client Ruby HTTP wrapper.
-REST Client takes all the nastyness of Net::HTTP and gives is a pretty face,
-while still giving you more control than Open-URI. I recommend it anytime
-you’re interfacing with a well-defined web service.
+CouchRest rests on top of a HTTP abstraction layer using by default Heroku’s excellent REST Client Ruby HTTP wrapper.
+Other adapters can be added to support more http libraries.
### 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`
(requires RSpec and optionally ZenTest for autotest support).
-## Examples
+## Examples (CouchRest Core)
Quick Start:
@@ -59,12 +62,50 @@ Creating and Querying Views:
})
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
@@ -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, :around {|r| stuff; yield; stuff }
- Or the new shorter version:
+ Or the aliased short version:
before_save :before_method, :another_method
after_save :after_method, :another_method, :if => :condition
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.
### 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"]
+### 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)
diff --git a/Rakefile b/Rakefile
index ddea44d..90b0653 100644
--- a/Rakefile
+++ b/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.has_rdoc = true
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["spec/tmp"]
s.extra_rdoc_files = %w( README.md LICENSE THANKS.md )
diff --git a/couchrest.gemspec b/couchrest.gemspec
index 13b4be7..8708ec6 100644
--- a/couchrest.gemspec
+++ b/couchrest.gemspec
@@ -2,7 +2,7 @@
Gem::Specification.new do |s|
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.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.email = %q{jchris@apache.org}
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.has_rdoc = true
+ 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.homepage = %q{http://github.com/jchris/couchrest}
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.}
if s.respond_to? :specification_version then
diff --git a/history.txt b/history.txt
new file mode 100644
index 0000000..1d886de
--- /dev/null
+++ b/history.txt
@@ -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/
\ No newline at end of file
diff --git a/init.rb b/init.rb
new file mode 100644
index 0000000..976b492
--- /dev/null
+++ b/init.rb
@@ -0,0 +1 @@
+require File.join(File.dirname(__FILE__),'lib', 'couchrest.rb')
\ No newline at end of file
diff --git a/lib/couchrest.rb b/lib/couchrest.rb
index c6e5e34..edd4453 100644
--- a/lib/couchrest.rb
+++ b/lib/couchrest.rb
@@ -28,7 +28,7 @@ require 'couchrest/monkeypatches'
# = CouchDB, close to the metal
module CouchRest
- VERSION = '0.29' unless self.const_defined?("VERSION")
+ VERSION = '0.30' unless self.const_defined?("VERSION")
autoload :Server, 'couchrest/core/server'
autoload :Database, 'couchrest/core/database'
@@ -45,6 +45,7 @@ module CouchRest
autoload :ExtendedDocument, 'couchrest/more/extended_document'
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', 'support', 'rails') if defined?(Rails)
@@ -119,9 +120,9 @@ module CouchRest
}
end
- # set proxy for RestClient to use
+ # set proxy to use
def proxy url
- RestClient.proxy = url
+ HttpAbstraction.proxy = url
end
# ensure that a database exists
@@ -142,7 +143,7 @@ module CouchRest
def put(uri, doc = nil)
payload = doc.to_json if doc
begin
- JSON.parse(RestClient.put(uri, payload))
+ JSON.parse(HttpAbstraction.put(uri, payload))
rescue Exception => e
if $DEBUG
raise "Error while sending a PUT request #{uri}\npayload: #{payload.inspect}\n#{e}"
@@ -154,7 +155,7 @@ module CouchRest
def get(uri)
begin
- JSON.parse(RestClient.get(uri), :max_nesting => false)
+ JSON.parse(HttpAbstraction.get(uri), :max_nesting => false)
rescue => e
if $DEBUG
raise "Error while sending a GET request #{uri}\n: #{e}"
@@ -167,7 +168,7 @@ module CouchRest
def post uri, doc = nil
payload = doc.to_json if doc
begin
- JSON.parse(RestClient.post(uri, payload))
+ JSON.parse(HttpAbstraction.post(uri, payload))
rescue Exception => e
if $DEBUG
raise "Error while sending a POST request #{uri}\npayload: #{payload.inspect}\n#{e}"
@@ -178,11 +179,11 @@ module CouchRest
end
def delete uri
- JSON.parse(RestClient.delete(uri))
+ JSON.parse(HttpAbstraction.delete(uri))
end
def copy uri, destination
- JSON.parse(RestClient.copy(uri, {'Destination' => destination}))
+ JSON.parse(HttpAbstraction.copy(uri, {'Destination' => destination}))
end
def paramify_url url, params = {}
diff --git a/lib/couchrest/core/adapters/restclient.rb b/lib/couchrest/core/adapters/restclient.rb
new file mode 100644
index 0000000..ed02228
--- /dev/null
+++ b/lib/couchrest/core/adapters/restclient.rb
@@ -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
\ No newline at end of file
diff --git a/lib/couchrest/core/database.rb b/lib/couchrest/core/database.rb
index 6627117..08eb4cb 100644
--- a/lib/couchrest/core/database.rb
+++ b/lib/couchrest/core/database.rb
@@ -58,7 +58,7 @@ module CouchRest
keys = params.delete(:keys)
funcs = funcs.merge({:keys => keys}) if keys
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
# backwards compatibility is a plus
@@ -100,11 +100,8 @@ module CouchRest
# GET an attachment directly from CouchDB
def fetch_attachment(doc, name)
- # slug = escape_docid(docid)
- # name = CGI.escape(name)
uri = url_for_attachment(doc, name)
- RestClient.get uri
- # "#{@uri}/#{slug}/#{name}"
+ HttpAbstraction.get uri
end
# PUT an attachment directly to CouchDB
@@ -112,14 +109,14 @@ module CouchRest
docid = escape_docid(doc['_id'])
name = CGI.escape(name)
uri = url_for_attachment(doc, name)
- JSON.parse(RestClient.put(uri, file, options))
+ JSON.parse(HttpAbstraction.put(uri, file, options))
end
# DELETE an attachment directly from CouchDB
def delete_attachment doc, name
uri = url_for_attachment(doc, name)
# this needs a rev
- JSON.parse(RestClient.delete(uri))
+ JSON.parse(HttpAbstraction.delete(uri))
end
# Save a document to CouchDB. This will use the _id field from
@@ -146,7 +143,7 @@ module CouchRest
slug = escape_docid(doc['_id'])
begin
CouchRest.put "#{@root}/#{slug}", doc
- rescue RestClient::ResourceNotFound
+ rescue HttpAbstraction::ResourceNotFound
p "resource not found when saving even tho an id was passed"
slug = doc['_id'] = @server.next_uuid
CouchRest.put "#{@root}/#{slug}", doc
@@ -252,7 +249,7 @@ module CouchRest
def recreate!
delete!
create!
- rescue RestClient::ResourceNotFound
+ rescue HttpAbstraction::ResourceNotFound
ensure
create!
end
diff --git a/lib/couchrest/core/document.rb b/lib/couchrest/core/document.rb
index b2d903a..cd66285 100644
--- a/lib/couchrest/core/document.rb
+++ b/lib/couchrest/core/document.rb
@@ -3,10 +3,6 @@ require 'delegate'
module CouchRest
class Document < Response
include CouchRest::Mixins::Attachments
-
- # def self.inherited(subklass)
- # subklass.send(:extlib_inheritable_accessor, :database)
- # end
extlib_inheritable_accessor :database
attr_accessor :database
@@ -30,6 +26,7 @@ module CouchRest
def new?
!rev
end
+ alias :new_document? :new?
# Saves the document to the db using create or update. Also runs the :save
# callbacks. Sets the _id and _rev fields based on
diff --git a/lib/couchrest/core/http_abstraction.rb b/lib/couchrest/core/http_abstraction.rb
new file mode 100644
index 0000000..529866d
--- /dev/null
+++ b/lib/couchrest/core/http_abstraction.rb
@@ -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)
\ No newline at end of file
diff --git a/lib/couchrest/mixins/collection.rb b/lib/couchrest/mixins/collection.rb
new file mode 100644
index 0000000..b492dbe
--- /dev/null
+++ b/lib/couchrest/mixins/collection.rb
@@ -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_, 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
diff --git a/lib/couchrest/mixins/design_doc.rb b/lib/couchrest/mixins/design_doc.rb
index 4ed6fdf..661b6ef 100644
--- a/lib/couchrest/mixins/design_doc.rb
+++ b/lib/couchrest/mixins/design_doc.rb
@@ -37,9 +37,6 @@ module CouchRest
if (doc['couchrest-type'] == '#{self.to_s}') {
emit(null,1);
}
- }",
- 'reduce' => "function(keys, values) {
- return sum(values);
}"
}
}
diff --git a/lib/couchrest/mixins/document_queries.rb b/lib/couchrest/mixins/document_queries.rb
index 3ea516c..defff3d 100644
--- a/lib/couchrest/mixins/document_queries.rb
+++ b/lib/couchrest/mixins/document_queries.rb
@@ -19,9 +19,7 @@ module CouchRest
# equal to the name of the current class. Takes the standard set of
# CouchRest::Database#view options
def count(opts = {}, &block)
- result = all({:reduce => true}.merge(opts), &block)['rows']
- return 0 if result.empty?
- result.first['value']
+ all({:raw => true, :limit => 0}.merge(opts), &block)['total_rows']
end
# Load the first document that have the "couchrest-type" field equal to
diff --git a/lib/couchrest/mixins/extended_document_mixins.rb b/lib/couchrest/mixins/extended_document_mixins.rb
index f5aa8f9..89b25d6 100644
--- a/lib/couchrest/mixins/extended_document_mixins.rb
+++ b/lib/couchrest/mixins/extended_document_mixins.rb
@@ -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__), 'extended_attachments')
require File.join(File.dirname(__FILE__), 'class_proxy')
+require File.join(File.dirname(__FILE__), 'collection')
diff --git a/lib/couchrest/mixins/properties.rb b/lib/couchrest/mixins/properties.rb
index 3c0451b..79d6984 100644
--- a/lib/couchrest/mixins/properties.rb
+++ b/lib/couchrest/mixins/properties.rb
@@ -1,6 +1,25 @@
require 'time'
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 Mixins
module Properties
@@ -65,6 +84,7 @@ module CouchRest
end
associate_casted_to_parent(self[property.name], assigned)
end
+
end
def associate_casted_to_parent(casted, assigned)
@@ -73,8 +93,12 @@ module CouchRest
end
def convert_property_value(property, klass, value)
- if ((property.init_method == 'new') && klass.to_s == 'Time')
- value.is_a?(String) ? Time.parse(value.dup) : value
+ if ((property.init_method == 'new') && klass.to_s == 'Time')
+ # 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
klass.send(property.init_method, value.dup)
end
@@ -87,6 +111,14 @@ module CouchRest
cast_property(property, true)
end
+ def cast_float(value)
+ begin
+ Float(value)
+ rescue
+ value
+ end
+ end
+
module ClassMethods
def property(name, options={})
@@ -146,4 +178,4 @@ module CouchRest
end
end
-end
+end
\ No newline at end of file
diff --git a/lib/couchrest/mixins/views.rb b/lib/couchrest/mixins/views.rb
index 55c1613..f520998 100644
--- a/lib/couchrest/mixins/views.rb
+++ b/lib/couchrest/mixins/views.rb
@@ -72,7 +72,7 @@ module CouchRest
#
# To understand the capabilities of this view system more completely,
# it is recommended that you read the RSpec file at
- # spec/core/model_spec.rb.
+ # spec/couchrest/more/extended_doc_spec.rb.
def view_by(*keys)
opts = keys.pop if keys.last.is_a?(Hash)
@@ -124,14 +124,6 @@ module CouchRest
# potentially large indexes.
def cleanup_design_docs!(db = database)
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
private
@@ -141,8 +133,12 @@ module CouchRest
fetch_view(db, name, opts, &block)
else
begin
- view = fetch_view db, name, opts.merge({:include_docs => true}), &block
- view['rows'].collect{|r|new(r['doc'])} if view['rows']
+ if block.nil?
+ 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
# fallback for old versions of couchdb that don't
# have include_docs support
@@ -158,7 +154,7 @@ module CouchRest
begin
design_doc.view_on(db, view_name, opts, &block)
# the design doc may not have been saved yet on this database
- rescue RestClient::ResourceNotFound => e
+ rescue HttpAbstraction::ResourceNotFound => e
if retryable
save_design_doc_on(db)
retryable = false
diff --git a/lib/couchrest/monkeypatches.rb b/lib/couchrest/monkeypatches.rb
index 2fad1f3..95c52c3 100644
--- a/lib/couchrest/monkeypatches.rb
+++ b/lib/couchrest/monkeypatches.rb
@@ -1,5 +1,6 @@
require File.join(File.dirname(__FILE__), 'support', 'class')
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.
class Time
@@ -38,7 +39,7 @@ if RUBY_VERSION.to_f < 1.9
if IO.select([@io], nil, nil, @read_timeout)
retry
else
- raise Timeout::TimeoutError
+ raise Timeout::Error
end
end
else
@@ -50,63 +51,63 @@ if RUBY_VERSION.to_f < 1.9
end
end
-module RestClient
- def self.copy(url, headers={})
- Request.execute(:method => :copy,
- :url => url,
- :headers => headers)
- 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
+# module RestClient
+# # def self.copy(url, headers={})
+# # Request.execute(:method => :copy,
+# # :url => url,
+# # :headers => headers)
+# # end
#
-# display_log request_log
-# http = Thread.current[:connection]
-# http.read_timeout = @timeout if @timeout
-#
-# 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
\ No newline at end of file
+# # 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
+# # http = Thread.current[:connection]
+# # http.read_timeout = @timeout if @timeout
+# #
+# # 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
diff --git a/lib/couchrest/more/extended_document.rb b/lib/couchrest/more/extended_document.rb
index 635604d..80a255c 100644
--- a/lib/couchrest/more/extended_document.rb
+++ b/lib/couchrest/more/extended_document.rb
@@ -13,10 +13,11 @@ module CouchRest
include CouchRest::Mixins::DesignDoc
include CouchRest::Mixins::ExtendedAttachments
include CouchRest::Mixins::ClassProxy
+ include CouchRest::Mixins::Collection
- def self.subclasses
- @subclasses ||= []
- end
+ def self.subclasses
+ @subclasses ||= []
+ end
def self.inherited(subklass)
subklass.send(:include, CouchRest::Mixins::Properties)
@@ -51,6 +52,26 @@ module CouchRest
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 updated_at and created_at fields
# on the document whenever saving occurs. CouchRest uses a pretty
# decent time format by default. See Time#to_json
diff --git a/lib/couchrest/validation/validators/numeric_validator.rb b/lib/couchrest/validation/validators/numeric_validator.rb
index 57a711f..05269b3 100644
--- a/lib/couchrest/validation/validators/numeric_validator.rb
+++ b/lib/couchrest/validation/validators/numeric_validator.rb
@@ -40,7 +40,7 @@ module CouchRest
value = target.send(field_name)
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]
precision = @options[:precision]
diff --git a/spec/couchrest/core/couchrest_spec.rb b/spec/couchrest/core/couchrest_spec.rb
index 2ed8bb9..35e48e6 100644
--- a/spec/couchrest/core/couchrest_spec.rb
+++ b/spec/couchrest/core/couchrest_spec.rb
@@ -191,7 +191,7 @@ describe CouchRest do
describe "using a proxy for RestClient connections" do
it "should set proxy url for RestClient" do
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.port.should eql( 8888 )
CouchRest.proxy nil
diff --git a/spec/couchrest/core/database_spec.rb b/spec/couchrest/core/database_spec.rb
index bfbf6ad..ecdb02f 100644
--- a/spec/couchrest/core/database_spec.rb
+++ b/spec/couchrest/core/database_spec.rb
@@ -690,7 +690,7 @@ describe CouchRest::Database do
it "should recreate a db even tho it doesn't exist" do
@cr.databases.should_not include(@db2.name)
- @db2.recreate!
+ begin @db2.recreate! rescue nil end
@cr.databases.should include(@db2.name)
end
diff --git a/spec/couchrest/more/extended_doc_spec.rb b/spec/couchrest/more/extended_doc_spec.rb
index a9d6e1c..bc5ee84 100644
--- a/spec/couchrest/more/extended_doc_spec.rb
+++ b/spec/couchrest/more/extended_doc_spec.rb
@@ -121,15 +121,28 @@ describe "ExtendedDocument" do
end
describe "a new model" do
- it "should be a new_record" do
+ it "should be a new document" do
@obj = Basic.new
@obj.rev.should be_nil
@obj.should be_new
+ @obj.should be_new_document
+ @obj.should be_new_record
end
- it "should be a new_document" do
- @obj = Basic.new
- @obj.rev.should be_nil
- @obj.should be_new
+ end
+
+ describe "creating a new document" do
+ 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
diff --git a/spec/couchrest/more/extended_doc_view_spec.rb b/spec/couchrest/more/extended_doc_view_spec.rb
index 4d797e5..7c5bbd2 100644
--- a/spec/couchrest/more/extended_doc_view_spec.rb
+++ b/spec/couchrest/more/extended_doc_view_spec.rb
@@ -121,7 +121,7 @@ describe "ExtendedDocument views" do
describe "a model class not tied to a database" do
before(:all) do
reset_test_db!
- @db = DB
+ @db = DB
%w{aaa bbb ddd eee}.each do |title|
u = Unattached.new(:title => title)
u.database = @db
@@ -133,14 +133,15 @@ describe "ExtendedDocument views" do
lambda{Unattached.all}.should raise_error
end
it "should query all" do
- rs = Unattached.all :database=>@db
+ Unattached.cleanup_design_docs!(@db)
+ rs = Unattached.all :database => @db
rs.length.should == 4
end
it "should barf on query if no database given" do
lambda{Unattached.view :by_title}.should raise_error
end
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['views']['all']['map'].should include('Unattached')
end
@@ -157,7 +158,7 @@ describe "ExtendedDocument views" do
things = []
Unattached.view(:by_title, :database=>@db) do |thing|
things << thing
- end
+ end
things[0]["doc"]["title"].should =='aaa'
end
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")
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
diff --git a/spec/couchrest/more/property_spec.rb b/spec/couchrest/more/property_spec.rb
index 647aa69..58bf887 100644
--- a/spec/couchrest/more/property_spec.rb
+++ b/spec/couchrest/more/property_spec.rb
@@ -142,7 +142,29 @@ describe "ExtendedDocument properties" do
@event['occurs_at'].should be_an_instance_of(Time)
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
describe "a newly created casted model" do
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index cafdcc5..e750258 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -20,7 +20,7 @@ class Basic < CouchRest::ExtendedDocument
end
def reset_test_db!
- DB.recreate! rescue nil
+ DB.recreate! rescue nil
DB
end