Merge branch 'master' of git://github.com/couchrest/couchrest
This commit is contained in:
commit
857695e219
44 changed files with 798 additions and 371 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,3 +1,5 @@
|
|||
.DS_Store
|
||||
html/*
|
||||
pkg
|
||||
*.swp
|
||||
couchrest.gemspec
|
||||
|
|
152
README.md
152
README.md
|
@ -14,11 +14,6 @@ Note: CouchRest only support CouchDB 0.9.0 or newer.
|
|||
|
||||
$ 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 couchrest-couchrest
|
||||
|
||||
### Relax, it's RESTful
|
||||
|
||||
CouchRest rests on top of a HTTP abstraction layer using by default Heroku’s excellent REST Client Ruby HTTP wrapper.
|
||||
|
@ -30,152 +25,19 @@ 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 (CouchRest Core)
|
||||
## Docs
|
||||
|
||||
Quick Start:
|
||||
API: [http://rdoc.info/projects/couchrest/couchrest](http://rdoc.info/projects/couchrest/couchrest)
|
||||
|
||||
# with !, it creates the database if it doesn't already exist
|
||||
@db = CouchRest.database!("http://127.0.0.1:5984/couchrest-test")
|
||||
response = @db.save_doc({:key => 'value', 'another key' => 'another value'})
|
||||
doc = @db.get(response['id'])
|
||||
puts doc.inspect
|
||||
Check the wiki for documentation and examples [http://wiki.github.com/couchrest/couchrest](http://wiki.github.com/couchrest/couchrest)
|
||||
|
||||
Bulk Save:
|
||||
## Contact
|
||||
|
||||
@db.bulk_save([
|
||||
{"wild" => "and random"},
|
||||
{"mild" => "yet local"},
|
||||
{"another" => ["set","of","keys"]}
|
||||
])
|
||||
# returns ids and revs of the current docs
|
||||
puts @db.documents.inspect
|
||||
Please post bugs, suggestions and patches to the bug tracker at <http://jchris.lighthouseapp.com/projects/17807-couchrest/overview>.
|
||||
|
||||
Creating and Querying Views:
|
||||
Follow us on Twitter: http://twitter.com/couchrest
|
||||
|
||||
@db.save_doc({
|
||||
"_id" => "_design/first",
|
||||
:views => {
|
||||
:test => {
|
||||
:map => "function(doc){for(var w in doc){ if(!w.match(/^_/))emit(w,doc[w])}}"
|
||||
}
|
||||
}
|
||||
})
|
||||
puts @db.view('first/test')['rows'].inspect
|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
### 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
|
||||
|
||||
`CouchRest::ExtendedDocuments` instances have 4 callbacks already defined for you:
|
||||
`:validate`, `:create`, `:save`, `:update` and `:destroy`
|
||||
|
||||
`CouchRest::CastedModel` instances have 1 callback already defined for you:
|
||||
`:validate`
|
||||
|
||||
Define your callback as follows:
|
||||
|
||||
set_callback :save, :before, :generate_slug_from_name
|
||||
|
||||
CouchRest uses a mixin you can find in lib/mixins/callbacks which is extracted from Rails 3, here are some simple usage examples:
|
||||
|
||||
set_callback :save, :before, :before_method
|
||||
set_callback :save, :after, :after_method, :if => :condition
|
||||
set_callback :save, :around {|r| stuff; yield; stuff }
|
||||
|
||||
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.
|
||||
|
||||
### Properties
|
||||
|
||||
property :last_name, :alias => :family_name
|
||||
property :read_only_value, :read_only => true
|
||||
property :name, :length => 4...20
|
||||
property :price, :type => Integer
|
||||
|
||||
### Casting
|
||||
|
||||
Often, you will want to store multiple objects within a document, to be able to retrieve your objects when you load the document,
|
||||
you can define some casting rules.
|
||||
|
||||
property :casted_attribute, :cast_as => 'WithCastedModelMixin'
|
||||
property :keywords, :cast_as => ["String"]
|
||||
property :occurs_at, :cast_as => 'Time', :send => 'parse
|
||||
property :end_date, :cast_as => 'Date', :send => 'parse
|
||||
|
||||
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)
|
||||
Also, check http://twitter.com/#search?q=%23couchrest
|
||||
|
||||
## Ruby on Rails
|
||||
|
||||
|
|
64
Rakefile
64
Rakefile
|
@ -1,9 +1,7 @@
|
|||
require 'rake'
|
||||
require "rake/rdoctask"
|
||||
require 'rake/gempackagetask'
|
||||
require File.join(File.expand_path(File.dirname(__FILE__)),'lib','couchrest')
|
||||
|
||||
|
||||
begin
|
||||
require 'spec/rake/spectask'
|
||||
rescue LoadError
|
||||
|
@ -14,41 +12,26 @@ EOS
|
|||
exit(0)
|
||||
end
|
||||
|
||||
spec = Gem::Specification.new do |s|
|
||||
s.name = "couchrest"
|
||||
s.version = CouchRest::VERSION
|
||||
s.date = "2008-11-22"
|
||||
s.summary = "Lean and RESTful interface to CouchDB."
|
||||
s.email = "jchris@apache.org"
|
||||
s.homepage = "http://github.com/jchris/couchrest"
|
||||
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 history.txt) +
|
||||
Dir["{examples,lib,spec,utils}/**/*"] -
|
||||
Dir["spec/tmp"]
|
||||
s.extra_rdoc_files = %w( README.md LICENSE THANKS.md )
|
||||
s.require_path = "lib"
|
||||
s.add_dependency("rest-client", ">= 0.5")
|
||||
s.add_dependency("mime-types", ">= 1.15")
|
||||
end
|
||||
|
||||
|
||||
desc "Create .gemspec file (useful for github)"
|
||||
task :gemspec do
|
||||
filename = "#{spec.name}.gemspec"
|
||||
File.open(filename, "w") do |f|
|
||||
f.puts spec.to_ruby
|
||||
begin
|
||||
require 'jeweler'
|
||||
Jeweler::Tasks.new do |gemspec|
|
||||
gemspec.name = "couchrest"
|
||||
gemspec.summary = "Lean and RESTful interface to CouchDB."
|
||||
gemspec.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."
|
||||
gemspec.email = "jchris@apache.org"
|
||||
gemspec.homepage = "http://github.com/couchrest/couchrest"
|
||||
gemspec.authors = ["J. Chris Anderson", "Matt Aimonetti", "Marcos Tapajos"]
|
||||
gemspec.extra_rdoc_files = %w( README.md LICENSE THANKS.md )
|
||||
gemspec.files = %w( LICENSE README.md Rakefile THANKS.md history.txt) + Dir["{examples,lib,spec,utils}/**/*"] - Dir["spec/tmp"]
|
||||
gemspec.has_rdoc = true
|
||||
gemspec.add_dependency("rest-client", ">= 0.5")
|
||||
gemspec.add_dependency("mime-types", ">= 1.15")
|
||||
gemspec.version = CouchRest::VERSION
|
||||
gemspec.date = "2008-11-22"
|
||||
gemspec.require_path = "lib"
|
||||
end
|
||||
end
|
||||
|
||||
Rake::GemPackageTask.new(spec) do |pkg|
|
||||
pkg.gem_spec = spec
|
||||
end
|
||||
|
||||
desc "Install the gem locally"
|
||||
task :install => [:package] do
|
||||
sh %{sudo gem install pkg/couchrest-#{CouchRest::VERSION}}
|
||||
rescue LoadError
|
||||
puts "Jeweler not available. Install it with: gem install jeweler"
|
||||
end
|
||||
|
||||
desc "Run all specs"
|
||||
|
@ -73,3 +56,12 @@ end
|
|||
|
||||
desc "Run the rspec"
|
||||
task :default => :spec
|
||||
|
||||
module Rake
|
||||
def self.remove_task(task_name)
|
||||
Rake.application.instance_variable_get('@tasks').delete(task_name.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
Rake.remove_task("github:release")
|
||||
Rake.remove_task("release")
|
|
@ -14,6 +14,6 @@ changes. A list of these people is included below.
|
|||
* Simon Rozet (simon /at/ rozet /dot/ name)
|
||||
* [Marcos Tapajós](http://tapajos.me)
|
||||
|
||||
Patches are welcome. The primary source for this software project is [on Github](http://github.com/jchris/couchrest/tree/master)
|
||||
Patches are welcome. The primary source for this software project is [on Github](http://github.com/couchrest/couchrest)
|
||||
|
||||
A lot of people have active forks - thank you all - even the patches I don't end up using are helpful.
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
|
||||
Gem::Specification.new do |s|
|
||||
s.name = %q{couchrest}
|
||||
s.version = "0.34"
|
||||
|
||||
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
||||
s.authors = ["J. Chris Anderson", "Matt Aimonetti"]
|
||||
s.date = %q{2008-11-22}
|
||||
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", "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/adapters", "lib/couchrest/core/adapters/restclient.rb", "lib/couchrest/core/database.rb", "lib/couchrest/core/design.rb", "lib/couchrest/core/document.rb", "lib/couchrest/core/http_abstraction.rb", "lib/couchrest/core/response.rb", "lib/couchrest/core/rest_api.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/middlewares", "lib/couchrest/middlewares/logger.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.require_paths = ["lib"]
|
||||
s.rubygems_version = %q{1.3.1}
|
||||
s.summary = %q{Lean and RESTful interface to CouchDB.}
|
||||
|
||||
if s.respond_to? :specification_version then
|
||||
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
||||
s.specification_version = 2
|
||||
|
||||
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
||||
s.add_runtime_dependency(%q<rest-client>, [">= 0.5"])
|
||||
s.add_runtime_dependency(%q<mime-types>, [">= 1.15"])
|
||||
else
|
||||
s.add_dependency(%q<rest-client>, [">= 0.5"])
|
||||
s.add_dependency(%q<mime-types>, [">= 1.15"])
|
||||
end
|
||||
else
|
||||
s.add_dependency(%q<rest-client>, [">= 0.5"])
|
||||
s.add_dependency(%q<mime-types>, [">= 1.15"])
|
||||
end
|
||||
end
|
|
@ -1,20 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
require 'yaml'
|
||||
|
||||
if ARGV.size < 1
|
||||
puts "Usage: github-test.rb my-project.gemspec"
|
||||
exit
|
||||
end
|
||||
|
||||
require 'rubygems/specification'
|
||||
data = File.read(ARGV[0])
|
||||
spec = nil
|
||||
|
||||
if data !~ %r{!ruby/object:Gem::Specification}
|
||||
Thread.new { spec = eval("$SAFE = 3\n#{data}") }.join
|
||||
else
|
||||
spec = YAML.load(data)
|
||||
end
|
||||
|
||||
puts spec
|
||||
puts "OK"
|
49
history.txt
49
history.txt
|
@ -1,3 +1,50 @@
|
|||
== Next Version
|
||||
|
||||
* Major enhancements
|
||||
|
||||
* Minor enhancements
|
||||
|
||||
== 0.35
|
||||
|
||||
* Major enhancements
|
||||
* CouchRest::ExtendedDocument allow chaining the inherit class callback (Kenneth Kalmer) - http://github.com/couchrest/couchrest/issues#issue/8
|
||||
|
||||
* Minor enhancements
|
||||
* Fix attachment bug (Johannes Jörg Schmidt)
|
||||
* Fix create database exception bug (Damien Mathieu)
|
||||
* Compatible with restclient >= 1.4.0 new responses (Julien Kirch)
|
||||
* Bug fix: Attribute protection no longer strips attributes coming from the database (Will Leinweber)
|
||||
* Bug fix: Remove double CGI escape when PUTting an attachment (nzoschke)
|
||||
* Bug fix: Changing Class proxy to set database on result sets (Peter Gumeson)
|
||||
* Bug fix: Updated time regexp (Nolan Darilek)
|
||||
* Added an update_doc method to database to handle conflicts during atomic updates. (Pierre Larochelle)
|
||||
* Bug fix: http://github.com/couchrest/couchrest/issues/#issue/2 (Luke Burton)
|
||||
|
||||
== 0.34
|
||||
|
||||
* Major enhancements
|
||||
|
||||
* Added support for https database URIs. (Mathias Meyer)
|
||||
* Changing some validations to be compatible with activemodel. (Marcos Tapajós)
|
||||
* Adds attribute protection to properties. (Will Leinweber)
|
||||
* Improved CouchRest::Database#save_doc, added "batch" mode to significantly speed up saves at cost of lower durability gurantees. (Igal Koshevoy)
|
||||
* Added CouchRest::Database#bulk_save_doc and #batch_save_doc as human-friendlier wrappers around #save_doc. (Igal Koshevoy)
|
||||
|
||||
* Minor enhancements
|
||||
|
||||
* Fix content_type handling for attachments
|
||||
* Fixed a bug in the pagination code that caused it to paginate over records outside of the scope of the view parameters.(John Wood)
|
||||
* Removed amount_pages calculation for the pagination collection, since it cannot be reliably calculated without a view.(John Wood)
|
||||
* Bug fix: http://github.com/couchrest/couchrest/issues/#issue/2 (Luke Burton)
|
||||
* Bug fix: http://github.com/couchrest/couchrest/issues/#issue/1 (Marcos Tapajós)
|
||||
* Removed the Database class deprecation notices (Matt Aimonetti)
|
||||
* Adding support to :cast_as => 'Date'. (Marcos Tapajós)
|
||||
* Improve documentation (Marcos Tapajós)
|
||||
* Streamer fixes (Julien Sanchez)
|
||||
* Fix Save on Document & ExtendedDocument crashed if bulk (Julien Sanchez)
|
||||
* Fix Initialization of ExtendentDocument model shouldn't failed on a nil value in argument (deepj)
|
||||
* Change to use Jeweler and Gemcutter (Marcos Tapajós)
|
||||
|
||||
== 0.33
|
||||
|
||||
* Major enhancements
|
||||
|
@ -60,4 +107,4 @@
|
|||
---
|
||||
|
||||
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/couchrest/couchrest/commits/master/
|
||||
You can see the full commit history on GitHub: http://github.com/couchrest/couchrest/commits/master/
|
||||
|
|
|
@ -28,7 +28,7 @@ require 'couchrest/monkeypatches'
|
|||
|
||||
# = CouchDB, close to the metal
|
||||
module CouchRest
|
||||
VERSION = '0.34' unless self.const_defined?("VERSION")
|
||||
VERSION = '0.35' unless self.const_defined?("VERSION")
|
||||
|
||||
autoload :Server, 'couchrest/core/server'
|
||||
autoload :Database, 'couchrest/core/database'
|
||||
|
@ -96,14 +96,14 @@ module CouchRest
|
|||
|
||||
def parse url
|
||||
case url
|
||||
when /^http:\/\/(.*)\/(.*)\/(.*)/
|
||||
when /^https?:\/\/(.*)\/(.*)\/(.*)/
|
||||
host = $1
|
||||
db = $2
|
||||
docid = $3
|
||||
when /^http:\/\/(.*)\/(.*)/
|
||||
when /^https?:\/\/(.*)\/(.*)/
|
||||
host = $1
|
||||
db = $2
|
||||
when /^http:\/\/(.*)/
|
||||
when /^https?:\/\/(.*)/
|
||||
host = $1
|
||||
when /(.*)\/(.*)\/(.*)/
|
||||
host = $1
|
||||
|
|
|
@ -10,25 +10,25 @@ module RestClientAdapter
|
|||
end
|
||||
|
||||
def get(uri, headers={})
|
||||
RestClient.get(uri, headers)
|
||||
RestClient.get(uri, headers).to_s
|
||||
end
|
||||
|
||||
def post(uri, payload, headers={})
|
||||
RestClient.post(uri, payload, headers)
|
||||
RestClient.post(uri, payload, headers).to_s
|
||||
end
|
||||
|
||||
def put(uri, payload, headers={})
|
||||
RestClient.put(uri, payload, headers)
|
||||
RestClient.put(uri, payload, headers).to_s
|
||||
end
|
||||
|
||||
def delete(uri, headers={})
|
||||
RestClient.delete(uri, headers)
|
||||
RestClient.delete(uri, headers).to_s
|
||||
end
|
||||
|
||||
def copy(uri, headers)
|
||||
RestClient::Request.execute( :method => :copy,
|
||||
:url => uri,
|
||||
:headers => headers)
|
||||
:headers => headers).to_s
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -107,7 +107,6 @@ module CouchRest
|
|||
# PUT an attachment directly to CouchDB
|
||||
def put_attachment(doc, name, file, options = {})
|
||||
docid = escape_docid(doc['_id'])
|
||||
name = CGI.escape(name)
|
||||
uri = url_for_attachment(doc, name)
|
||||
JSON.parse(HttpAbstraction.put(uri, file, options))
|
||||
end
|
||||
|
@ -129,7 +128,7 @@ module CouchRest
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Save a document to CouchDB. This will use the <tt>_id</tt> field from
|
||||
# the document as the id for PUT, or request a new UUID from CouchDB, if
|
||||
# no <tt>_id</tt> is present on the document. IDs are attached to
|
||||
|
@ -139,13 +138,25 @@ module CouchRest
|
|||
#
|
||||
# If <tt>bulk</tt> is true (false by default) the document is cached for bulk-saving later.
|
||||
# Bulk saving happens automatically when #bulk_save_cache limit is exceded, or on the next non bulk save.
|
||||
def save_doc(doc, bulk = false)
|
||||
#
|
||||
# If <tt>batch</tt> is true (false by default) the document is saved in
|
||||
# batch mode, "used to achieve higher throughput at the cost of lower
|
||||
# guarantees. When [...] sent using this option, it is not immediately
|
||||
# written to disk. Instead it is stored in memory on a per-user basis for a
|
||||
# second or so (or the number of docs in memory reaches a certain point).
|
||||
# After the threshold has passed, the docs are committed to disk. Instead
|
||||
# of waiting for the doc to be written to disk before responding, CouchDB
|
||||
# sends an HTTP 202 Accepted response immediately. batch=ok is not suitable
|
||||
# for crucial data, but it ideal for applications like logging which can
|
||||
# accept the risk that a small proportion of updates could be lost due to a
|
||||
# crash."
|
||||
def save_doc(doc, bulk = false, batch = false)
|
||||
if doc['_attachments']
|
||||
doc['_attachments'] = encode_attachments(doc['_attachments'])
|
||||
end
|
||||
if bulk
|
||||
@bulk_save_cache << doc
|
||||
return bulk_save if @bulk_save_cache.length >= @bulk_save_cache_limit
|
||||
bulk_save if @bulk_save_cache.length >= @bulk_save_cache_limit
|
||||
return {"ok" => true} # Compatibility with Document#save
|
||||
elsif !bulk && @bulk_save_cache.length > 0
|
||||
bulk_save
|
||||
|
@ -153,7 +164,9 @@ module CouchRest
|
|||
result = if doc['_id']
|
||||
slug = escape_docid(doc['_id'])
|
||||
begin
|
||||
CouchRest.put "#{@root}/#{slug}", doc
|
||||
uri = "#{@root}/#{slug}"
|
||||
uri << "?batch=ok" if batch
|
||||
CouchRest.put uri, doc
|
||||
rescue HttpAbstraction::ResourceNotFound
|
||||
p "resource not found when saving even tho an id was passed"
|
||||
slug = doc['_id'] = @server.next_uuid
|
||||
|
@ -175,6 +188,15 @@ module CouchRest
|
|||
result
|
||||
end
|
||||
|
||||
# Save a document to CouchDB in bulk mode. See #save_doc's +bulk+ argument.
|
||||
def bulk_save_doc(doc)
|
||||
save_doc(doc, true)
|
||||
end
|
||||
|
||||
# Save a document to CouchDB in batch mode. See #save_doc's +batch+ argument.
|
||||
def batch_save_doc(doc)
|
||||
save_doc(doc, false, true)
|
||||
end
|
||||
|
||||
# POST an array of documents to CouchDB. If any of the documents are
|
||||
# missing ids, supply one from the uuid cache.
|
||||
|
@ -227,6 +249,33 @@ module CouchRest
|
|||
CouchRest.copy "#{@root}/#{slug}", destination
|
||||
end
|
||||
|
||||
# Updates the given doc by yielding the current state of the doc
|
||||
# and trying to update update_limit times. Returns the new doc
|
||||
# if the doc was successfully updated without hitting the limit
|
||||
def update_doc(doc_id, params = {}, update_limit=10)
|
||||
resp = {'ok' => false}
|
||||
new_doc = nil
|
||||
last_fail = nil
|
||||
|
||||
until resp['ok'] or update_limit <= 0
|
||||
doc = self.get(doc_id, params) # grab the doc
|
||||
new_doc = yield doc # give it to the caller to be updated
|
||||
begin
|
||||
resp = self.save_doc new_doc # try to PUT the updated doc into the db
|
||||
rescue RestClient::RequestFailed => e
|
||||
if e.http_code == 409 # Update collision
|
||||
update_limit -= 1
|
||||
last_fail = e
|
||||
else # some other error
|
||||
raise e
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
raise last_fail unless resp['ok']
|
||||
new_doc
|
||||
end
|
||||
|
||||
# Compact the database, removing old document revisions and optimizing space use.
|
||||
def compact!
|
||||
CouchRest.post "#{@root}/_compact"
|
||||
|
@ -242,7 +291,7 @@ module CouchRest
|
|||
def recreate!
|
||||
delete!
|
||||
create!
|
||||
rescue HttpAbstraction::ResourceNotFound
|
||||
rescue RestClient::ResourceNotFound
|
||||
ensure
|
||||
create!
|
||||
end
|
||||
|
|
|
@ -7,15 +7,22 @@ module CouchRest
|
|||
|
||||
# Stream a view, yielding one row at a time. Shells out to <tt>curl</tt> to keep RAM usage low when you have millions of rows.
|
||||
def view name, params = nil, &block
|
||||
urlst = /^_/.match(name) ? "#{@db.root}/#{name}" : "#{@db.root}/_view/#{name}"
|
||||
urlst = if /^_/.match(name) then
|
||||
"#{@db.root}/#{name}"
|
||||
else
|
||||
name = name.split('/')
|
||||
dname = name.shift
|
||||
vname = name.join('/')
|
||||
"#{@db.root}/_design/#{dname}/_view/#{vname}"
|
||||
end
|
||||
url = CouchRest.paramify_url urlst, params
|
||||
# puts "stream #{url}"
|
||||
first = nil
|
||||
IO.popen("curl --silent #{url}") do |view|
|
||||
IO.popen("curl --silent \"#{url}\"") do |view|
|
||||
first = view.gets # discard header
|
||||
while line = view.gets
|
||||
row = parse_line(line)
|
||||
block.call row
|
||||
block.call row unless row.nil? # last line "}]" discarded
|
||||
end
|
||||
end
|
||||
parse_first(first)
|
||||
|
@ -41,4 +48,4 @@ module CouchRest
|
|||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
74
lib/couchrest/mixins/attribute_protection.rb
Normal file
74
lib/couchrest/mixins/attribute_protection.rb
Normal file
|
@ -0,0 +1,74 @@
|
|||
module CouchRest
|
||||
module Mixins
|
||||
module AttributeProtection
|
||||
# Attribute protection from mass assignment to CouchRest properties
|
||||
#
|
||||
# Protected methods will be removed from
|
||||
# * new
|
||||
# * update_attributes
|
||||
# * upate_attributes_without_saving
|
||||
# * attributes=
|
||||
#
|
||||
# There are two modes of protection
|
||||
# 1) Declare accessible poperties, assume all the rest are protected
|
||||
# property :name, :accessible => true
|
||||
# property :admin # this will be automatically protected
|
||||
#
|
||||
# 2) Declare protected properties, assume all the rest are accessible
|
||||
# property :name # this will not be protected
|
||||
# property :admin, :protected => true
|
||||
#
|
||||
# Note: you cannot set both flags in a single class
|
||||
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def accessible_properties
|
||||
properties.select { |prop| prop.options[:accessible] }
|
||||
end
|
||||
|
||||
def protected_properties
|
||||
properties.select { |prop| prop.options[:protected] }
|
||||
end
|
||||
end
|
||||
|
||||
def accessible_properties
|
||||
self.class.accessible_properties
|
||||
end
|
||||
|
||||
def protected_properties
|
||||
self.class.protected_properties
|
||||
end
|
||||
|
||||
def remove_protected_attributes(attributes)
|
||||
protected_names = properties_to_remove_from_mass_assignment.map { |prop| prop.name }
|
||||
return attributes if protected_names.empty?
|
||||
|
||||
attributes.reject! do |property_name, property_value|
|
||||
protected_names.include?(property_name.to_s)
|
||||
end
|
||||
|
||||
attributes || {}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def properties_to_remove_from_mass_assignment
|
||||
has_protected = !protected_properties.empty?
|
||||
has_accessible = !accessible_properties.empty?
|
||||
|
||||
if !has_protected && !has_accessible
|
||||
[]
|
||||
elsif has_protected && !has_accessible
|
||||
protected_properties
|
||||
elsif has_accessible && !has_protected
|
||||
properties.reject { |prop| prop.options[:accessible] }
|
||||
else
|
||||
raise "Set either :accessible or :protected for #{self.class}, but not both"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -56,7 +56,9 @@ module CouchRest
|
|||
# Mixins::DocumentQueries
|
||||
|
||||
def all(opts = {}, &block)
|
||||
@klass.all({:database => @database}.merge(opts), &block)
|
||||
docs = @klass.all({:database => @database}.merge(opts), &block)
|
||||
docs.each { |doc| doc.database = @database if doc.respond_to?(:database) } if docs
|
||||
docs
|
||||
end
|
||||
|
||||
def count(opts = {}, &block)
|
||||
|
@ -64,11 +66,15 @@ module CouchRest
|
|||
end
|
||||
|
||||
def first(opts = {})
|
||||
@klass.first({:database => @database}.merge(opts))
|
||||
doc = @klass.first({:database => @database}.merge(opts))
|
||||
doc.database = @database if doc && doc.respond_to?(:database)
|
||||
doc
|
||||
end
|
||||
|
||||
def get(id)
|
||||
@klass.get(id, @database)
|
||||
doc = @klass.get(id, @database)
|
||||
doc.database = @database if doc && doc.respond_to?(:database)
|
||||
doc
|
||||
end
|
||||
|
||||
# Mixins::Views
|
||||
|
@ -78,7 +84,9 @@ module CouchRest
|
|||
end
|
||||
|
||||
def view(name, query={}, &block)
|
||||
@klass.view(name, {:database => @database}.merge(query), &block)
|
||||
docs = @klass.view(name, {:database => @database}.merge(query), &block)
|
||||
docs.each { |doc| doc.database = @database if doc.respond_to?(:database) } if docs
|
||||
docs
|
||||
end
|
||||
|
||||
def all_design_doc_versions
|
||||
|
|
|
@ -183,7 +183,7 @@ module CouchRest
|
|||
if @container_class.nil?
|
||||
results
|
||||
else
|
||||
results['rows'].collect { |row| @container_class.new(row['doc']) } unless results['rows'].nil?
|
||||
results['rows'].collect { |row| @container_class.create_from_database(row['doc']) } unless results['rows'].nil?
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -51,11 +51,9 @@ module CouchRest
|
|||
# db<Database>:: optional option to pass a custom database to use
|
||||
def get(id, db = database)
|
||||
begin
|
||||
doc = db.get id
|
||||
get!(id, db)
|
||||
rescue
|
||||
nil
|
||||
else
|
||||
new(doc)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -72,11 +70,11 @@ module CouchRest
|
|||
# db<Database>:: optional option to pass a custom database to use
|
||||
def get!(id, db = database)
|
||||
doc = db.get id
|
||||
new(doc)
|
||||
create_from_database(doc)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,7 +14,7 @@ module CouchRest
|
|||
|
||||
# reads the data from an attachment
|
||||
def read_attachment(attachment_name)
|
||||
Base64.decode64(database.fetch_attachment(self, attachment_name))
|
||||
database.fetch_attachment(self, attachment_name)
|
||||
end
|
||||
|
||||
# modifies a file attachment on the current doc
|
||||
|
@ -52,10 +52,6 @@ module CouchRest
|
|||
|
||||
private
|
||||
|
||||
def encode_attachment(data)
|
||||
::Base64.encode64(data).gsub(/\r|\n/,'')
|
||||
end
|
||||
|
||||
def get_mime_type(file)
|
||||
::MIME::Types.type_for(file.path).empty? ?
|
||||
'text\/plain' : MIME::Types.type_for(file.path).first.content_type.gsub(/\//,'\/')
|
||||
|
@ -65,10 +61,10 @@ module CouchRest
|
|||
content_type = args[:content_type] ? args[:content_type] : get_mime_type(args[:file])
|
||||
self['_attachments'][args[:name]] = {
|
||||
'content_type' => content_type,
|
||||
'data' => encode_attachment(args[:file].read)
|
||||
'data' => args[:file].read
|
||||
}
|
||||
end
|
||||
|
||||
end # module ExtendedAttachments
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,3 +6,4 @@ 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')
|
||||
require File.join(File.dirname(__FILE__), 'attribute_protection')
|
||||
|
|
|
@ -4,7 +4,7 @@ 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})/
|
||||
string =~ /(\d{4})[\-|\/](\d{2})[\-|\/](\d{2})[T|\s](\d{2}):(\d{2}):(\d{2})([\+|\s|\-])*(\d{2}):?(\d{2})/
|
||||
# $1 = year
|
||||
# $2 = month
|
||||
# $3 = day
|
||||
|
@ -197,4 +197,4 @@ module CouchRest
|
|||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -137,13 +137,13 @@ module CouchRest
|
|||
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']
|
||||
view['rows'].collect{|r|create_from_database(r['doc'])} if view['rows']
|
||||
end
|
||||
rescue
|
||||
# fallback for old versions of couchdb that don't
|
||||
# have include_docs support
|
||||
view = fetch_view(db, name, opts, &block)
|
||||
view['rows'].collect{|r|new(db.get(r['id']))} if view['rows']
|
||||
view['rows'].collect{|r|create_from_database(db.get(r['id']))} if view['rows']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -170,4 +170,4 @@ module CouchRest
|
|||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,15 +14,18 @@ module CouchRest
|
|||
include CouchRest::Mixins::ExtendedAttachments
|
||||
include CouchRest::Mixins::ClassProxy
|
||||
include CouchRest::Mixins::Collection
|
||||
include CouchRest::Mixins::AttributeProtection
|
||||
|
||||
def self.subclasses
|
||||
@subclasses ||= []
|
||||
end
|
||||
|
||||
def self.inherited(subklass)
|
||||
super
|
||||
subklass.send(:include, CouchRest::Mixins::Properties)
|
||||
subklass.class_eval <<-EOS, __FILE__, __LINE__ + 1
|
||||
def self.inherited(subklass)
|
||||
super
|
||||
subklass.properties = self.properties.dup
|
||||
end
|
||||
EOS
|
||||
|
@ -37,15 +40,20 @@ module CouchRest
|
|||
define_callbacks :save, "result == :halt"
|
||||
define_callbacks :update, "result == :halt"
|
||||
define_callbacks :destroy, "result == :halt"
|
||||
|
||||
# Creates a new instance, bypassing attribute protection
|
||||
#
|
||||
# ==== Returns
|
||||
# a document instance
|
||||
def self.create_from_database(passed_keys={})
|
||||
new(passed_keys, :directly_set_attributes => true)
|
||||
end
|
||||
|
||||
def initialize(passed_keys={})
|
||||
def initialize(passed_keys={}, options={})
|
||||
apply_defaults # defined in CouchRest::Mixins::Properties
|
||||
passed_keys.each do |k,v|
|
||||
if self.respond_to?("#{k}=")
|
||||
self.send("#{k}=", passed_keys.delete(k))
|
||||
end
|
||||
end if passed_keys
|
||||
super
|
||||
remove_protected_attributes(passed_keys) unless options[:directly_set_attributes]
|
||||
directly_set_attributes(passed_keys) unless passed_keys.nil?
|
||||
super(passed_keys)
|
||||
cast_keys # defined in CouchRest::Mixins::Properties
|
||||
unless self['_id'] && self['_rev']
|
||||
self['couchrest-type'] = self.class.to_s
|
||||
|
@ -150,12 +158,8 @@ module CouchRest
|
|||
# make a copy, we don't want to change arguments
|
||||
attrs = hash.dup
|
||||
%w[_id _rev created_at updated_at].each {|attr| attrs.delete(attr)}
|
||||
attrs.each do |k, v|
|
||||
raise NoMethodError, "#{k}= method not available, use property :#{k}" unless self.respond_to?("#{k}=")
|
||||
end
|
||||
attrs.each do |k, v|
|
||||
self.send("#{k}=",v)
|
||||
end
|
||||
check_properties_exist(attrs)
|
||||
set_attributes(attrs)
|
||||
end
|
||||
alias :attributes= :update_attributes_without_saving
|
||||
|
||||
|
@ -238,7 +242,7 @@ module CouchRest
|
|||
set_unique_id if new? && self.respond_to?(:set_unique_id)
|
||||
result = database.save_doc(self, bulk)
|
||||
mark_as_saved
|
||||
true
|
||||
result["ok"] == true
|
||||
end
|
||||
|
||||
# Saves the document to the db using save. Raises an exception
|
||||
|
@ -280,6 +284,26 @@ module CouchRest
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_properties_exist(attrs)
|
||||
attrs.each do |attribute_name, attribute_value|
|
||||
raise NoMethodError, "#{attribute_name}= method not available, use property :#{attribute_name}" unless self.respond_to?("#{attribute_name}=")
|
||||
end
|
||||
end
|
||||
|
||||
def directly_set_attributes(hash)
|
||||
hash.each do |attribute_name, attribute_value|
|
||||
if self.respond_to?("#{attribute_name}=")
|
||||
self.send("#{attribute_name}=", hash.delete(attribute_name))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def set_attributes(hash)
|
||||
attrs = remove_protected_attributes(hash)
|
||||
directly_set_attributes(attrs)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -32,7 +32,7 @@ module CouchRest
|
|||
@alias = options.delete(:alias) if options[:alias]
|
||||
@default = options.delete(:default) unless options[:default].nil?
|
||||
@casted = options[:casted] ? true : false
|
||||
@init_method = options[:send] ? options.delete(:send) : 'new'
|
||||
@init_method = options[:init_method] ? options.delete(:init_method) : 'new'
|
||||
@options = options
|
||||
end
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ module CouchRest
|
|||
# validator to be automatically created on the property
|
||||
#
|
||||
# Integer type
|
||||
# Using a Integer type causes a validates_is_number
|
||||
# Using a Integer type causes a validates_numericality_of
|
||||
# validator to be created for the property. integer_only
|
||||
# is set to true
|
||||
#
|
||||
|
@ -97,8 +97,7 @@ module CouchRest
|
|||
|
||||
# presence
|
||||
if opts[:allow_nil] == false
|
||||
# validates_present property.name, opts
|
||||
validates_present property.name, options_with_message(opts, property, :presence)
|
||||
validates_presence_of property.name, options_with_message(opts, property, :presence)
|
||||
end
|
||||
|
||||
# length
|
||||
|
@ -111,8 +110,7 @@ module CouchRest
|
|||
else
|
||||
opts[:maximum] = len
|
||||
end
|
||||
# validates_length property.name, opts
|
||||
validates_length property.name, options_with_message(opts, property, :length)
|
||||
validates_length_of property.name, options_with_message(opts, property, :length)
|
||||
end
|
||||
|
||||
# format
|
||||
|
@ -142,13 +140,11 @@ module CouchRest
|
|||
# numeric validator
|
||||
if "Integer" == property.type
|
||||
opts[:integer_only] = true
|
||||
# validates_is_number property.name, opts
|
||||
validates_is_number property.name, options_with_message(opts, property, :is_number)
|
||||
validates_numericality_of property.name, options_with_message(opts, property, :is_number)
|
||||
elsif Float == property.type
|
||||
opts[:precision] = property.precision
|
||||
opts[:scale] = property.scale
|
||||
# validates_is_number property.name, opts
|
||||
validates_is_number property.name, options_with_message(opts, property, :is_number)
|
||||
validates_numericality_of property.name, options_with_message(opts, property, :is_number)
|
||||
end
|
||||
|
||||
# marked the property has checked
|
||||
|
|
|
@ -81,18 +81,26 @@ module CouchRest
|
|||
# attr_accessor :password_confirmation
|
||||
# attr_accessor :email_repeated
|
||||
#
|
||||
# validates_is_confirmed :password
|
||||
# validates_is_confirmed :email, :confirm => :email_repeated
|
||||
# validates_confirmation_of :password
|
||||
# validates_confirmation_of :email, :confirm => :email_repeated
|
||||
#
|
||||
# # a call to valid? will return false unless:
|
||||
# # password == password_confirmation
|
||||
# # and
|
||||
# # email == email_repeated
|
||||
#
|
||||
def validates_is_confirmed(*fields)
|
||||
def validates_confirmation_of(*fields)
|
||||
opts = opts_from_validator_args(fields)
|
||||
add_validator_to_context(opts, fields, CouchRest::Validation::ConfirmationValidator)
|
||||
end
|
||||
|
||||
def validates_is_confirmed(*fields)
|
||||
warn "[DEPRECATION] `validates_is_confirmed` is deprecated. Please use `validates_confirmation_of` instead."
|
||||
validates_confirmation_of(*fields)
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
end # module ValidatesIsConfirmed
|
||||
end # module Validation
|
||||
|
|
|
@ -99,18 +99,23 @@ module CouchRest
|
|||
# property :email, String
|
||||
# property :zip_code, String
|
||||
#
|
||||
# validates_format :email, :as => :email_address
|
||||
# validates_format :zip_code, :with => /^\d{5}$/
|
||||
# validates_format_of :email, :as => :email_address
|
||||
# validates_format_of :zip_code, :with => /^\d{5}$/
|
||||
#
|
||||
# # a call to valid? will return false unless:
|
||||
# # email is formatted like an email address
|
||||
# # and
|
||||
# # zip_code is a string of 5 digits
|
||||
#
|
||||
def validates_format(*fields)
|
||||
def validates_format_of(*fields)
|
||||
opts = opts_from_validator_args(fields)
|
||||
add_validator_to_context(opts, fields, CouchRest::Validation::FormatValidator)
|
||||
end
|
||||
|
||||
def validates_format(*fields)
|
||||
warn "[DEPRECATION] `validates_format` is deprecated. Please use `validates_format_of` instead."
|
||||
validates_format_of(*fields)
|
||||
end
|
||||
|
||||
end # module ValidatesFormat
|
||||
end # module Validation
|
||||
|
|
|
@ -115,20 +115,25 @@ module CouchRest
|
|||
# property low, Integer
|
||||
# property just_right, Integer
|
||||
#
|
||||
# validates_length :high, :min => 100000000000
|
||||
# validates_length :low, :equals => 0
|
||||
# validates_length :just_right, :within => 1..10
|
||||
# validates_length_of :high, :min => 100000000000
|
||||
# validates_length_of :low, :equals => 0
|
||||
# validates_length_of :just_right, :within => 1..10
|
||||
#
|
||||
# # a call to valid? will return false unless:
|
||||
# # high is greater than or equal to 100000000000
|
||||
# # low is equal to 0
|
||||
# # just_right is between 1 and 10 (inclusive of both 1 and 10)
|
||||
#
|
||||
def validates_length(*fields)
|
||||
def validates_length_of(*fields)
|
||||
opts = opts_from_validator_args(fields)
|
||||
add_validator_to_context(opts, fields, CouchRest::Validation::LengthValidator)
|
||||
end
|
||||
|
||||
|
||||
def validates_length(*fields)
|
||||
warn "[DEPRECATION] `validates_length` is deprecated. Please use `validates_length_of` instead."
|
||||
validates_length_of(*fields)
|
||||
end
|
||||
|
||||
end # module ValidatesLength
|
||||
end # module Validation
|
||||
end # module CouchRest
|
||||
|
|
|
@ -94,10 +94,15 @@ module CouchRest
|
|||
|
||||
# Validate whether a field is numeric
|
||||
#
|
||||
def validates_is_number(*fields)
|
||||
def validates_numericality_of(*fields)
|
||||
opts = opts_from_validator_args(fields)
|
||||
add_validator_to_context(opts, fields, CouchRest::Validation::NumericValidator)
|
||||
end
|
||||
|
||||
def validates_is_number(*fields)
|
||||
warn "[DEPRECATION] `validates_is_number` is deprecated. Please use `validates_numericality_of` instead."
|
||||
validates_numericality_of(*fields)
|
||||
end
|
||||
|
||||
end # module ValidatesIsNumber
|
||||
end # module Validation
|
||||
|
|
|
@ -93,16 +93,21 @@ module CouchRest
|
|||
# property :another_required, String
|
||||
# property :yet_again, String
|
||||
#
|
||||
# validates_present :required_attribute
|
||||
# validates_present :another_required, :yet_again
|
||||
# validates_presence_of :required_attribute
|
||||
# validates_presence_of :another_required, :yet_again
|
||||
#
|
||||
# # a call to valid? will return false unless
|
||||
# # all three attributes are !blank?
|
||||
# end
|
||||
def validates_present(*fields)
|
||||
def validates_presence_of(*fields)
|
||||
opts = opts_from_validator_args(fields)
|
||||
add_validator_to_context(opts, fields, CouchRest::Validation::RequiredFieldValidator)
|
||||
end
|
||||
|
||||
def validates_present(*fields)
|
||||
warn "[DEPRECATION] `validates_present` is deprecated. Please use `validates_presence_of` instead."
|
||||
validates_presence_of(*fields)
|
||||
end
|
||||
|
||||
end # module ValidatesPresent
|
||||
end # module Validation
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
git clean -fxd
|
||||
rake gemspec
|
|
@ -54,7 +54,7 @@ describe CouchRest do
|
|||
db[:host].should == "127.0.0.1"
|
||||
end
|
||||
it "should parse a host and db with http" do
|
||||
db = CouchRest.parse "http://127.0.0.1/my-db"
|
||||
db = CouchRest.parse "https://127.0.0.1/my-db"
|
||||
db[:database].should == "my-db"
|
||||
db[:host].should == "127.0.0.1"
|
||||
end
|
||||
|
@ -68,16 +68,31 @@ describe CouchRest do
|
|||
db[:database].should == "my-db"
|
||||
db[:host].should == "127.0.0.1:5555"
|
||||
end
|
||||
it "should parse a host with a port and db with https" do
|
||||
db = CouchRest.parse "https://127.0.0.1:5555/my-db"
|
||||
db[:database].should == "my-db"
|
||||
db[:host].should == "127.0.0.1:5555"
|
||||
end
|
||||
it "should parse just a host" do
|
||||
db = CouchRest.parse "http://127.0.0.1:5555/"
|
||||
db[:database].should be_nil
|
||||
db[:host].should == "127.0.0.1:5555"
|
||||
end
|
||||
it "should parse just a host with https" do
|
||||
db = CouchRest.parse "https://127.0.0.1:5555/"
|
||||
db[:database].should be_nil
|
||||
db[:host].should == "127.0.0.1:5555"
|
||||
end
|
||||
it "should parse just a host no slash" do
|
||||
db = CouchRest.parse "http://127.0.0.1:5555"
|
||||
db[:host].should == "127.0.0.1:5555"
|
||||
db[:database].should be_nil
|
||||
end
|
||||
it "should parse just a host no slash and https" do
|
||||
db = CouchRest.parse "https://127.0.0.1:5555"
|
||||
db[:host].should == "127.0.0.1:5555"
|
||||
db[:database].should be_nil
|
||||
end
|
||||
it "should get docid" do
|
||||
db = CouchRest.parse "127.0.0.1:5555/my-db/my-doc"
|
||||
db[:database].should == "my-db"
|
||||
|
@ -90,7 +105,12 @@ describe CouchRest do
|
|||
db[:host].should == "127.0.0.1:5555"
|
||||
db[:doc].should == "my-doc"
|
||||
end
|
||||
|
||||
it "should get docid with https" do
|
||||
db = CouchRest.parse "https://127.0.0.1:5555/my-db/my-doc"
|
||||
db[:database].should == "my-db"
|
||||
db[:host].should == "127.0.0.1:5555"
|
||||
db[:doc].should == "my-doc"
|
||||
end
|
||||
it "should parse a host and db" do
|
||||
db = CouchRest.parse "127.0.0.1/my-db"
|
||||
db[:database].should == "my-db"
|
||||
|
@ -101,6 +121,11 @@ describe CouchRest do
|
|||
db[:database].should == "my-db"
|
||||
db[:host].should == "127.0.0.1"
|
||||
end
|
||||
it "should parse a host and db with https" do
|
||||
db = CouchRest.parse "https://127.0.0.1/my-db"
|
||||
db[:database].should == "my-db"
|
||||
db[:host].should == "127.0.0.1"
|
||||
end
|
||||
it "should parse a host with a port and db" do
|
||||
db = CouchRest.parse "127.0.0.1:5555/my-db"
|
||||
db[:database].should == "my-db"
|
||||
|
@ -111,16 +136,31 @@ describe CouchRest do
|
|||
db[:database].should == "my-db"
|
||||
db[:host].should == "127.0.0.1:5555"
|
||||
end
|
||||
it "should parse a host with a port and db with https" do
|
||||
db = CouchRest.parse "http://127.0.0.1:5555/my-db"
|
||||
db[:database].should == "my-db"
|
||||
db[:host].should == "127.0.0.1:5555"
|
||||
end
|
||||
it "should parse just a host" do
|
||||
db = CouchRest.parse "http://127.0.0.1:5555/"
|
||||
db[:database].should be_nil
|
||||
db[:host].should == "127.0.0.1:5555"
|
||||
end
|
||||
it "should parse just a host with https" do
|
||||
db = CouchRest.parse "https://127.0.0.1:5555/"
|
||||
db[:database].should be_nil
|
||||
db[:host].should == "127.0.0.1:5555"
|
||||
end
|
||||
it "should parse just a host no slash" do
|
||||
db = CouchRest.parse "http://127.0.0.1:5555"
|
||||
db[:host].should == "127.0.0.1:5555"
|
||||
db[:database].should be_nil
|
||||
end
|
||||
it "should parse just a host no slash and https" do
|
||||
db = CouchRest.parse "https://127.0.0.1:5555"
|
||||
db[:host].should == "127.0.0.1:5555"
|
||||
db[:database].should be_nil
|
||||
end
|
||||
it "should get docid" do
|
||||
db = CouchRest.parse "127.0.0.1:5555/my-db/my-doc"
|
||||
db[:database].should == "my-db"
|
||||
|
@ -133,6 +173,12 @@ describe CouchRest do
|
|||
db[:host].should == "127.0.0.1:5555"
|
||||
db[:doc].should == "my-doc"
|
||||
end
|
||||
it "should get docid with https" do
|
||||
db = CouchRest.parse "https://127.0.0.1:5555/my-db/my-doc"
|
||||
db[:database].should == "my-db"
|
||||
db[:host].should == "127.0.0.1:5555"
|
||||
db[:doc].should == "my-doc"
|
||||
end
|
||||
end
|
||||
|
||||
describe "easy initializing a database adapter" do
|
||||
|
|
|
@ -65,22 +65,27 @@ describe CouchRest::Database do
|
|||
|
||||
describe "saving a view" do
|
||||
before(:each) do
|
||||
@view = {'test' => {'map' => 'function(doc) {
|
||||
if (doc.word && !/\W/.test(doc.word)) {
|
||||
emit(doc.word,null);
|
||||
@view = {'test' => {'map' => <<-JS
|
||||
function(doc) {
|
||||
var reg = new RegExp("\\\\W");
|
||||
if (doc.word && !reg.test(doc.word)) {
|
||||
emit(doc.word,null);
|
||||
}
|
||||
}
|
||||
}'}}
|
||||
JS
|
||||
}}
|
||||
@db.save_doc({
|
||||
"_id" => "_design/test",
|
||||
:views => @view
|
||||
})
|
||||
end
|
||||
it "should work properly" do
|
||||
@db.bulk_save([
|
||||
r = @db.bulk_save([
|
||||
{"word" => "once"},
|
||||
{"word" => "and again"}
|
||||
])
|
||||
@db.view('test/test')['total_rows'].should == 1
|
||||
r = @db.view('test/test')
|
||||
r['total_rows'].should == 1
|
||||
end
|
||||
it "should round trip" do
|
||||
@db.get("_design/test")['views'].should == @view
|
||||
|
@ -131,9 +136,16 @@ describe CouchRest::Database do
|
|||
rs = @db.view('first/test', :include_docs => true) do |row|
|
||||
rows << row
|
||||
end
|
||||
rows.length.should == 4
|
||||
rows.length.should == 3
|
||||
rs["total_rows"].should == 3
|
||||
end
|
||||
it "should accept a block with several params" do
|
||||
rows = []
|
||||
rs = @db.view('first/test', :include_docs => true, :limit => 2) do |row|
|
||||
rows << row
|
||||
end
|
||||
rows.length.should == 2
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET (document by id) when the doc exists" do
|
||||
|
@ -539,6 +551,53 @@ describe CouchRest::Database do
|
|||
|
||||
end
|
||||
|
||||
describe "UPDATE existing document" do
|
||||
before :each do
|
||||
@id = @db.save_doc({
|
||||
'article' => 'Pete Doherty Kicked Out For Nazi Anthem',
|
||||
'upvotes' => 10,
|
||||
'link' => 'http://beatcrave.com/2009-11-30/pete-doherty-kicked-out-for-nazi-anthem/'})['id']
|
||||
end
|
||||
it "should work under normal conditions" do
|
||||
@db.update_doc @id do |doc|
|
||||
doc['upvotes'] += 1
|
||||
doc
|
||||
end
|
||||
@db.get(@id)['upvotes'].should == 11
|
||||
end
|
||||
it "should fail if update_limit is reached" do
|
||||
lambda do
|
||||
@db.update_doc @id do |doc|
|
||||
# modify and save the doc so that a collision happens
|
||||
conflicting_doc = @db.get @id
|
||||
conflicting_doc['upvotes'] += 1
|
||||
@db.save_doc conflicting_doc
|
||||
|
||||
# then try saving it through the update
|
||||
doc['upvotes'] += 1
|
||||
doc
|
||||
end
|
||||
end.should raise_error(RestClient::RequestFailed)
|
||||
end
|
||||
it "should not fail if update_limit is not reached" do
|
||||
limit = 5
|
||||
lambda do
|
||||
@db.update_doc @id do |doc|
|
||||
# same as the last spec except we're only forcing 5 conflicts
|
||||
if limit > 0
|
||||
conflicting_doc = @db.get @id
|
||||
conflicting_doc['upvotes'] += 1
|
||||
@db.save_doc conflicting_doc
|
||||
limit -= 1
|
||||
end
|
||||
doc['upvotes'] += 1
|
||||
doc
|
||||
end
|
||||
end.should_not raise_error
|
||||
@db.get(@id)['upvotes'].should == 16
|
||||
end
|
||||
end
|
||||
|
||||
describe "COPY existing document" do
|
||||
before :each do
|
||||
@r = @db.save_doc({'artist' => 'Zappa', 'title' => 'Muffin Man'})
|
||||
|
@ -704,11 +763,11 @@ describe CouchRest::Database do
|
|||
|
||||
it "should recreate a db even tho it doesn't exist" do
|
||||
@cr.databases.should_not include(@db2.name)
|
||||
begin @db2.recreate! rescue nil end
|
||||
@db2.recreate!
|
||||
@cr.databases.should include(@db2.name)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,6 +9,14 @@ describe CouchRest::Streamer do
|
|||
@streamer = CouchRest::Streamer.new(@db)
|
||||
@docs = (1..1000).collect{|i| {:integer => i, :string => i.to_s}}
|
||||
@db.bulk_save(@docs)
|
||||
@db.save_doc({
|
||||
"_id" => "_design/first",
|
||||
:views => {
|
||||
:test => {
|
||||
:map => "function(doc){for(var w in doc){ if(!w.match(/^_/))emit(w,doc[w])}}"
|
||||
}
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
it "should yield each row in a view" do
|
||||
|
@ -19,5 +27,26 @@ describe CouchRest::Streamer do
|
|||
end
|
||||
count.should == 1001
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
it "should accept several params" do
|
||||
count = 0
|
||||
@streamer.view("_design/first/_view/test", :include_docs => true, :limit => 5) do |row|
|
||||
count += 1
|
||||
end
|
||||
count.should == 5
|
||||
end
|
||||
|
||||
it "should accept both view formats" do
|
||||
count = 0
|
||||
@streamer.view("_design/first/_view/test") do |row|
|
||||
count += 1
|
||||
end
|
||||
count.should == 2000
|
||||
count = 0
|
||||
@streamer.view("first/test") do |row|
|
||||
count += 1
|
||||
end
|
||||
count.should == 2000
|
||||
end
|
||||
|
||||
end
|
||||
|
|
150
spec/couchrest/more/attribute_protection_spec.rb
Normal file
150
spec/couchrest/more/attribute_protection_spec.rb
Normal file
|
@ -0,0 +1,150 @@
|
|||
require File.expand_path("../../../spec_helper", __FILE__)
|
||||
|
||||
describe "ExtendedDocument", "no declarations" do
|
||||
class NoProtection < CouchRest::ExtendedDocument
|
||||
use_database TEST_SERVER.default_database
|
||||
property :name
|
||||
property :phone
|
||||
end
|
||||
|
||||
it "should not protect anything through new" do
|
||||
user = NoProtection.new(:name => "will", :phone => "555-5555")
|
||||
|
||||
user.name.should == "will"
|
||||
user.phone.should == "555-5555"
|
||||
end
|
||||
|
||||
it "should not protect anything through attributes=" do
|
||||
user = NoProtection.new
|
||||
user.attributes = {:name => "will", :phone => "555-5555"}
|
||||
|
||||
user.name.should == "will"
|
||||
user.phone.should == "555-5555"
|
||||
end
|
||||
|
||||
it "should recreate from the database properly" do
|
||||
user = NoProtection.new
|
||||
user.name = "will"
|
||||
user.phone = "555-5555"
|
||||
user.save!
|
||||
|
||||
user = NoProtection.get(user.id)
|
||||
user.name.should == "will"
|
||||
user.phone.should == "555-5555"
|
||||
end
|
||||
end
|
||||
|
||||
describe "ExtendedDocument", "accessible flag" do
|
||||
class WithAccessible < CouchRest::ExtendedDocument
|
||||
use_database TEST_SERVER.default_database
|
||||
property :name, :accessible => true
|
||||
property :admin, :default => false
|
||||
end
|
||||
|
||||
it "should recognize accessible properties" do
|
||||
props = WithAccessible.accessible_properties.map { |prop| prop.name}
|
||||
props.should include("name")
|
||||
props.should_not include("admin")
|
||||
end
|
||||
|
||||
it "should protect non-accessible properties set through new" do
|
||||
user = WithAccessible.new(:name => "will", :admin => true)
|
||||
|
||||
user.name.should == "will"
|
||||
user.admin.should == false
|
||||
end
|
||||
|
||||
it "should protect non-accessible properties set through attributes=" do
|
||||
user = WithAccessible.new
|
||||
user.attributes = {:name => "will", :admin => true}
|
||||
|
||||
user.name.should == "will"
|
||||
user.admin.should == false
|
||||
end
|
||||
end
|
||||
|
||||
describe "ExtendedDocument", "protected flag" do
|
||||
class WithProtected < CouchRest::ExtendedDocument
|
||||
use_database TEST_SERVER.default_database
|
||||
property :name
|
||||
property :admin, :default => false, :protected => true
|
||||
end
|
||||
|
||||
it "should recognize protected properties" do
|
||||
props = WithProtected.protected_properties.map { |prop| prop.name}
|
||||
props.should_not include("name")
|
||||
props.should include("admin")
|
||||
end
|
||||
|
||||
it "should protect non-accessible properties set through new" do
|
||||
user = WithProtected.new(:name => "will", :admin => true)
|
||||
|
||||
user.name.should == "will"
|
||||
user.admin.should == false
|
||||
end
|
||||
|
||||
it "should protect non-accessible properties set through attributes=" do
|
||||
user = WithProtected.new
|
||||
user.attributes = {:name => "will", :admin => true}
|
||||
|
||||
user.name.should == "will"
|
||||
user.admin.should == false
|
||||
end
|
||||
end
|
||||
|
||||
describe "ExtendedDocument", "protected flag" do
|
||||
class WithBoth < CouchRest::ExtendedDocument
|
||||
use_database TEST_SERVER.default_database
|
||||
property :name, :accessible => true
|
||||
property :admin, :default => false, :protected => true
|
||||
end
|
||||
|
||||
it "should raise an error when both are set" do
|
||||
lambda { WithBoth.new }.should raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe "ExtendedDocument", "from database" do
|
||||
class WithProtected < CouchRest::ExtendedDocument
|
||||
use_database TEST_SERVER.default_database
|
||||
property :name
|
||||
property :admin, :default => false, :protected => true
|
||||
view_by :name
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
@user = WithProtected.new
|
||||
@user.name = "will"
|
||||
@user.admin = true
|
||||
@user.save!
|
||||
end
|
||||
|
||||
def verify_attrs(user)
|
||||
user.name.should == "will"
|
||||
user.admin.should == true
|
||||
end
|
||||
|
||||
it "ExtendedDocument#get should not strip protected attributes" do
|
||||
reloaded = WithProtected.get( @user.id )
|
||||
verify_attrs reloaded
|
||||
end
|
||||
|
||||
it "ExtendedDocument#get! should not strip protected attributes" do
|
||||
reloaded = WithProtected.get!( @user.id )
|
||||
verify_attrs reloaded
|
||||
end
|
||||
|
||||
it "ExtendedDocument#all should not strip protected attributes" do
|
||||
# all creates a CollectionProxy
|
||||
docs = WithProtected.all(:key => @user.id)
|
||||
docs.size.should == 1
|
||||
reloaded = docs.first
|
||||
verify_attrs reloaded
|
||||
end
|
||||
|
||||
it "views should not strip protected attributes" do
|
||||
docs = WithProtected.by_name(:startkey => "will", :endkey => "will")
|
||||
reloaded = docs.first
|
||||
verify_attrs reloaded
|
||||
end
|
||||
end
|
|
@ -49,7 +49,7 @@ describe "assigning a value to casted attribute after initializing an object" do
|
|||
end
|
||||
|
||||
it "should cast attribute" do
|
||||
@car.driver = JSON.parse(JSON.generate(@driver))
|
||||
@car.driver = JSON.parse(@driver.to_json)
|
||||
@car.driver.should be_instance_of(Driver)
|
||||
end
|
||||
|
||||
|
@ -60,7 +60,7 @@ describe "casting an extended document from parsed JSON" do
|
|||
before(:each) do
|
||||
@driver = Driver.new(:name => 'Matt')
|
||||
@car = Car.new(:name => 'Renault 306', :driver => @driver)
|
||||
@new_car = Car.new(JSON.parse(JSON.generate(@car)))
|
||||
@new_car = Car.new(JSON.parse(@car.to_json))
|
||||
end
|
||||
|
||||
it "should cast casted attribute" do
|
||||
|
|
|
@ -224,7 +224,7 @@ describe CouchRest::CastedModel do
|
|||
|
||||
it "should not fail if the casted model doesn't have validation" do
|
||||
Cat.property :masters, :cast_as => ['Person'], :default => []
|
||||
Cat.validates_present :name
|
||||
Cat.validates_presence_of :name
|
||||
cat = Cat.new(:name => 'kitty')
|
||||
cat.should be_valid
|
||||
cat.masters.push Person.new
|
||||
|
|
40
spec/couchrest/more/extended_doc_inherited_spec.rb
Normal file
40
spec/couchrest/more/extended_doc_inherited_spec.rb
Normal file
|
@ -0,0 +1,40 @@
|
|||
require File.expand_path('../../../spec_helper', __FILE__)
|
||||
|
||||
begin
|
||||
require 'rubygems' unless ENV['SKIP_RUBYGEMS']
|
||||
require 'activesupport'
|
||||
ActiveSupport::JSON.backend = :JSONGem
|
||||
|
||||
class PlainParent
|
||||
class_inheritable_accessor :foo
|
||||
self.foo = :bar
|
||||
end
|
||||
|
||||
class PlainChild < PlainParent
|
||||
end
|
||||
|
||||
class ExtendedParent < CouchRest::ExtendedDocument
|
||||
class_inheritable_accessor :foo
|
||||
self.foo = :bar
|
||||
end
|
||||
|
||||
class ExtendedChild < ExtendedParent
|
||||
end
|
||||
|
||||
describe "Using chained inheritance without CouchRest::ExtendedDocument" do
|
||||
it "should preserve inheritable attributes" do
|
||||
PlainParent.foo.should == :bar
|
||||
PlainChild.foo.should == :bar
|
||||
end
|
||||
end
|
||||
|
||||
describe "Using chained inheritance with CouchRest::ExtendedDocument" do
|
||||
it "should preserve inheritable attributes" do
|
||||
ExtendedParent.foo.should == :bar
|
||||
ExtendedChild.foo.should == :bar
|
||||
end
|
||||
end
|
||||
|
||||
rescue LoadError
|
||||
puts "This spec requires 'active_support' to be loaded"
|
||||
end
|
|
@ -1,3 +1,5 @@
|
|||
# encoding: utf-8
|
||||
|
||||
require File.expand_path("../../../spec_helper", __FILE__)
|
||||
require File.join(FIXTURE_PATH, 'more', 'article')
|
||||
require File.join(FIXTURE_PATH, 'more', 'course')
|
||||
|
@ -127,6 +129,11 @@ describe "ExtendedDocument" do
|
|||
@obj.should be_new_document
|
||||
@obj.should be_new_record
|
||||
end
|
||||
|
||||
it "should not failed on a nil value in argument" do
|
||||
@obj = Basic.new(nil)
|
||||
@obj.should == { 'couchrest-type' => 'Basic' }
|
||||
end
|
||||
end
|
||||
|
||||
describe "creating a new document" do
|
||||
|
@ -728,7 +735,7 @@ describe "ExtendedDocument" do
|
|||
|
||||
it "should not fail if the nested casted model doesn't have validation" do
|
||||
Cat.property :trainer, :cast_as => 'Person'
|
||||
Cat.validates_present :name
|
||||
Cat.validates_presence_of :name
|
||||
cat = Cat.new(:name => 'Mr Bigglesworth')
|
||||
cat.trainer = Person.new
|
||||
cat.trainer.validatable?.should be_false
|
||||
|
|
|
@ -25,39 +25,33 @@ describe "ExtendedDocument views" do
|
|||
written_at += 24 * 3600
|
||||
end
|
||||
end
|
||||
|
||||
it "should have a design doc" do
|
||||
Article.design_doc["views"]["by_date"].should_not be_nil
|
||||
end
|
||||
|
||||
it "should save the design doc" do
|
||||
Article.by_date #rescue nil
|
||||
doc = Article.database.get Article.design_doc.id
|
||||
doc['views']['by_date'].should_not be_nil
|
||||
end
|
||||
|
||||
it "should return the matching raw view result" do
|
||||
view = Article.by_date :raw => true
|
||||
view['rows'].length.should == 4
|
||||
end
|
||||
|
||||
it "should not include non-Articles" do
|
||||
Article.database.save_doc({"date" => 1})
|
||||
view = Article.by_date :raw => true
|
||||
view['rows'].length.should == 4
|
||||
end
|
||||
|
||||
it "should return the matching objects (with default argument :descending => true)" do
|
||||
articles = Article.by_date
|
||||
articles.collect{|a|a.title}.should == @titles.reverse
|
||||
end
|
||||
|
||||
it "should allow you to override default args" do
|
||||
articles = Article.by_date :descending => false
|
||||
articles.collect{|a|a.title}.should == @titles
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "another model with a simple view" do
|
||||
before(:all) do
|
||||
reset_test_db!
|
||||
|
@ -96,8 +90,7 @@ describe "ExtendedDocument views" do
|
|||
courses[0]["doc"]["title"].should =='aaa'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
describe "a ducktype view" do
|
||||
before(:all) do
|
||||
reset_test_db!
|
||||
|
@ -117,7 +110,7 @@ describe "ExtendedDocument views" do
|
|||
@as[0]['_id'].should == @id
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "a model class not tied to a database" do
|
||||
before(:all) do
|
||||
reset_test_db!
|
||||
|
@ -198,7 +191,7 @@ describe "ExtendedDocument views" do
|
|||
Unattached.model_design_doc(@db)['_rev'].should_not == original_revision
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "class proxy" do
|
||||
before(:all) do
|
||||
reset_test_db!
|
||||
|
@ -254,6 +247,36 @@ describe "ExtendedDocument views" do
|
|||
u = @us.first
|
||||
u.title.should =~ /\A...\z/
|
||||
end
|
||||
it "should set database on first retreived document" do
|
||||
u = @us.first
|
||||
u.database.should === DB
|
||||
end
|
||||
it "should set database on all retreived documents" do
|
||||
@us.all.each do |u|
|
||||
u.database.should === DB
|
||||
end
|
||||
end
|
||||
it "should set database on each retreived document" do
|
||||
rs = @us.by_title :startkey=>"bbb", :endkey=>"eee"
|
||||
rs.length.should == 3
|
||||
rs.each do |u|
|
||||
u.database.should === DB
|
||||
end
|
||||
end
|
||||
it "should set database on document retreived by id" do
|
||||
u = @us.get(@first_id)
|
||||
u.database.should === DB
|
||||
end
|
||||
it "should not attempt to set database on raw results using :all" do
|
||||
@us.all(:raw => true).each do |u|
|
||||
u.respond_to?(:database).should be_false
|
||||
end
|
||||
end
|
||||
it "should not attempt to set database on raw results using view" do
|
||||
@us.by_title(:raw => true).each do |u|
|
||||
u.respond_to?(:database).should be_false
|
||||
end
|
||||
end
|
||||
it "should clean up design docs left around on specific database" do
|
||||
@us.by_title
|
||||
original_id = @us.model_design_doc['_rev']
|
||||
|
@ -262,7 +285,7 @@ describe "ExtendedDocument views" do
|
|||
@us.model_design_doc['_rev'].should_not == original_id
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "a model with a compound key view" do
|
||||
before(:all) do
|
||||
Article.by_user_id_and_date.each{|a| a.destroy(true)}
|
||||
|
@ -295,7 +318,7 @@ describe "ExtendedDocument views" do
|
|||
articles[0].title.should == "even more interesting"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "with a custom view" do
|
||||
before(:all) do
|
||||
@titles = ["very uniq one", "even less interesting", "some fun",
|
||||
|
@ -311,18 +334,18 @@ describe "ExtendedDocument views" do
|
|||
view = Article.by_tags :raw => true
|
||||
view['rows'].length.should == 5
|
||||
end
|
||||
|
||||
|
||||
it "should be default to :reduce => false" do
|
||||
ars = Article.by_tags
|
||||
ars.first.tags.first.should == 'cool'
|
||||
end
|
||||
|
||||
|
||||
it "should be raw when reduce is true" do
|
||||
view = Article.by_tags :reduce => true, :group => true
|
||||
view['rows'].find{|r|r['key'] == 'cool'}['value'].should == 3
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# TODO: moved to Design, delete
|
||||
describe "adding a view" do
|
||||
before(:each) do
|
||||
|
@ -344,7 +367,7 @@ 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!
|
||||
|
@ -354,7 +377,7 @@ describe "ExtendedDocument views" do
|
|||
a = Article.new(:title => title, :date => Date.today)
|
||||
a.save
|
||||
end
|
||||
|
||||
|
||||
titles = ["yesterday very uniq one", "yesterday really interesting", "yesterday some fun",
|
||||
"yesterday really awesome", "yesterday crazy bob", "yesterday this rocks"]
|
||||
titles.each_with_index do |title,i|
|
||||
|
@ -390,11 +413,11 @@ describe "ExtendedDocument views" 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
|
||||
|
@ -408,10 +431,6 @@ describe "ExtendedDocument views" do
|
|||
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
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# encoding: utf-8
|
||||
require File.expand_path('../../../spec_helper', __FILE__)
|
||||
require File.join(FIXTURE_PATH, 'more', 'person')
|
||||
require File.join(FIXTURE_PATH, 'more', 'card')
|
||||
|
@ -5,6 +6,7 @@ require File.join(FIXTURE_PATH, 'more', 'invoice')
|
|||
require File.join(FIXTURE_PATH, 'more', 'service')
|
||||
require File.join(FIXTURE_PATH, 'more', 'event')
|
||||
require File.join(FIXTURE_PATH, 'more', 'cat')
|
||||
require File.join(FIXTURE_PATH, 'more', 'user')
|
||||
|
||||
|
||||
describe "ExtendedDocument properties" do
|
||||
|
@ -55,6 +57,30 @@ describe "ExtendedDocument properties" do
|
|||
@card.updated_at.should_not be_nil
|
||||
end
|
||||
|
||||
|
||||
describe "mass assignment protection" do
|
||||
|
||||
it "should not store protected attribute using mass assignment" do
|
||||
cat_toy = CatToy.new(:name => "Zorro")
|
||||
cat = Cat.create(:name => "Helena", :toys => [cat_toy], :favorite_toy => cat_toy, :number => 1)
|
||||
cat.number.should be_nil
|
||||
cat.number = 1
|
||||
cat.save
|
||||
cat.number.should == 1
|
||||
end
|
||||
|
||||
it "should not store protected attribute when 'declare accessible poperties, assume all the rest are protected'" do
|
||||
user = User.create(:name => "Marcos Tapajós", :admin => true)
|
||||
user.admin.should be_nil
|
||||
end
|
||||
|
||||
it "should not store protected attribute when 'declare protected properties, assume all the rest are accessible'" do
|
||||
user = SpecialUser.create(:name => "Marcos Tapajós", :admin => true)
|
||||
user.admin.should be_nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "validation" do
|
||||
before(:each) do
|
||||
@invoice = Invoice.new(:client_name => "matt", :employee_name => "Chris", :location => "San Diego, CA")
|
||||
|
|
3
spec/fixtures/more/article.rb
vendored
3
spec/fixtures/more/article.rb
vendored
|
@ -2,6 +2,7 @@ class Article < CouchRest::ExtendedDocument
|
|||
use_database DB
|
||||
unique_id :slug
|
||||
|
||||
provides_collection :article_details, 'Article', 'by_date', :descending => true, :include_docs => true
|
||||
view_by :date, :descending => true
|
||||
view_by :user_id, :date
|
||||
|
||||
|
@ -31,4 +32,4 @@ class Article < CouchRest::ExtendedDocument
|
|||
def generate_slug_from_title
|
||||
self['slug'] = title.downcase.gsub(/[^a-z0-9]/,'-').squeeze('-').gsub(/^\-|\-$/,'') if new?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
2
spec/fixtures/more/card.rb
vendored
2
spec/fixtures/more/card.rb
vendored
|
@ -17,6 +17,6 @@ class Card < CouchRest::ExtendedDocument
|
|||
timestamps!
|
||||
|
||||
# Validation
|
||||
validates_present :first_name
|
||||
validates_presence_of :first_name
|
||||
|
||||
end
|
9
spec/fixtures/more/cat.rb
vendored
9
spec/fixtures/more/cat.rb
vendored
|
@ -4,9 +4,10 @@ class Cat < CouchRest::ExtendedDocument
|
|||
# Set the default database to use
|
||||
use_database DB
|
||||
|
||||
property :name
|
||||
property :toys, :cast_as => ['CatToy'], :default => []
|
||||
property :favorite_toy, :cast_as => 'CatToy'
|
||||
property :name, :accessible => true
|
||||
property :toys, :cast_as => ['CatToy'], :default => [], :accessible => true
|
||||
property :favorite_toy, :cast_as => 'CatToy', :accessible => true
|
||||
property :number
|
||||
end
|
||||
|
||||
class CatToy < Hash
|
||||
|
@ -15,5 +16,5 @@ class CatToy < Hash
|
|||
|
||||
property :name
|
||||
|
||||
validates_present :name
|
||||
validates_presence_of :name
|
||||
end
|
4
spec/fixtures/more/event.rb
vendored
4
spec/fixtures/more/event.rb
vendored
|
@ -2,8 +2,8 @@ class Event < CouchRest::ExtendedDocument
|
|||
use_database DB
|
||||
|
||||
property :subject
|
||||
property :occurs_at, :cast_as => 'Time', :send => 'parse'
|
||||
property :end_date, :cast_as => 'Date', :send => 'parse'
|
||||
property :occurs_at, :cast_as => 'Time', :init_method => 'parse'
|
||||
property :end_date, :cast_as => 'Date', :init_method => 'parse'
|
||||
|
||||
|
||||
end
|
4
spec/fixtures/more/invoice.rb
vendored
4
spec/fixtures/more/invoice.rb
vendored
|
@ -11,7 +11,7 @@ class Invoice < CouchRest::ExtendedDocument
|
|||
property :location
|
||||
|
||||
# Validation
|
||||
validates_present :client_name, :employee_name
|
||||
validates_present :location, :message => "Hey stupid!, you forgot the location"
|
||||
validates_presence_of :client_name, :employee_name
|
||||
validates_presence_of :location, :message => "Hey stupid!, you forgot the location"
|
||||
|
||||
end
|
22
spec/fixtures/more/user.rb
vendored
Normal file
22
spec/fixtures/more/user.rb
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
class User < CouchRest::ExtendedDocument
|
||||
# Set the default database to use
|
||||
use_database DB
|
||||
property :name, :accessible => true
|
||||
property :admin # this will be automatically protected
|
||||
end
|
||||
|
||||
class SpecialUser < CouchRest::ExtendedDocument
|
||||
# Set the default database to use
|
||||
use_database DB
|
||||
property :name # this will not be protected
|
||||
property :admin, :protected => true
|
||||
end
|
||||
|
||||
# There are two modes of protection
|
||||
# 1) Declare accessible poperties, assume all the rest are protected
|
||||
# property :name, :accessible => true
|
||||
# property :admin # this will be automatically protected
|
||||
#
|
||||
# 2) Declare protected properties, assume all the rest are accessible
|
||||
# property :name # this will not be protected
|
||||
# property :admin, :protected => true
|
Loading…
Reference in a new issue