Browse files

Moving to own repo without the base couchrest

  • Loading branch information...
1 parent e6604a0 commit 3894579304731bf965483de8ecd81561d545fcce @samlown samlown committed May 10, 2010
Showing with 557 additions and 4,448 deletions.
  1. +2 −1 .gitignore
  2. +16 −16 README.md
  3. +7 −7 Rakefile
  4. +0 −182 couchrest.gemspec
  5. +0 −38 examples/word_count/markov
  6. +0 −3 examples/word_count/views/books/chunked-map.js
  7. +0 −1 examples/word_count/views/books/united-map.js
  8. +0 −6 examples/word_count/views/markov/chain-map.js
  9. +0 −7 examples/word_count/views/markov/chain-reduce.js
  10. +0 −6 examples/word_count/views/word_count/count-map.js
  11. +0 −3 examples/word_count/views/word_count/count-reduce.js
  12. +0 −46 examples/word_count/word_count.rb
  13. +0 −40 examples/word_count/word_count_query.rb
  14. +0 −26 examples/word_count/word_count_views.rb
  15. +5 −0 history.txt
  16. +1 −1 init.rb
  17. +0 −162 lib/couchrest.rb
  18. 0 lib/couchrest/{more → }/casted_array.rb
  19. +4 −3 lib/couchrest/{more → }/casted_model.rb
  20. +0 −71 lib/couchrest/commands/generate.rb
  21. +0 −103 lib/couchrest/commands/push.rb
  22. +0 −35 lib/couchrest/core/adapters/restclient.rb
  23. +0 −377 lib/couchrest/core/database.rb
  24. +0 −79 lib/couchrest/core/design.rb
  25. +0 −84 lib/couchrest/core/document.rb
  26. +0 −48 lib/couchrest/core/http_abstraction.rb
  27. +0 −16 lib/couchrest/core/response.rb
  28. +0 −49 lib/couchrest/core/rest_api.rb
  29. +0 −88 lib/couchrest/core/server.rb
  30. +0 −4 lib/couchrest/core/view.rb
  31. +11 −3 lib/couchrest/{more → }/extended_document.rb
  32. +0 −103 lib/couchrest/helper/pager.rb
  33. +0 −51 lib/couchrest/helper/streamer.rb
  34. +0 −51 lib/couchrest/helper/upgrade.rb
  35. +0 −263 lib/couchrest/middlewares/logger.rb
  36. +10 −2 lib/couchrest/mixins.rb
  37. +0 −31 lib/couchrest/mixins/attachments.rb
  38. +425 −425 lib/couchrest/mixins/callbacks.rb
  39. +0 −9 lib/couchrest/mixins/extended_document_mixins.rb
  40. +3 −3 lib/couchrest/mixins/properties.rb
  41. +0 −1 lib/couchrest/mixins/validation.rb
  42. +3 −111 lib/couchrest/monkeypatches.rb
  43. 0 lib/couchrest/{more → }/property.rb
  44. +0 −42 lib/couchrest/support/blank.rb
  45. +0 −190 lib/couchrest/support/class.rb
  46. +56 −0 lib/couchrest/support/couchrest.rb
  47. +1 −1 lib/couchrest/{more → }/typecast.rb
  48. +0 −1 lib/couchrest/validation/auto_validate.rb
  49. +1 −1 spec/couchrest/{more → }/attribute_protection_spec.rb
  50. +1 −1 spec/couchrest/{more → }/casted_extended_doc_spec.rb
  51. +2 −2 spec/couchrest/{more → }/casted_model_spec.rb
  52. +0 −184 spec/couchrest/core/couchrest_spec.rb
  53. +0 −840 spec/couchrest/core/database_spec.rb
  54. +0 −138 spec/couchrest/core/design_spec.rb
  55. +0 −275 spec/couchrest/core/document_spec.rb
  56. +0 −35 spec/couchrest/core/server_spec.rb
  57. +1 −1 spec/couchrest/{more → }/extended_doc_attachment_spec.rb
  58. +1 −1 spec/couchrest/{more → }/extended_doc_inherited_spec.rb
  59. +1 −1 spec/couchrest/{more → }/extended_doc_spec.rb
  60. +1 −1 spec/couchrest/{more → }/extended_doc_subclass_spec.rb
  61. +1 −1 spec/couchrest/{more → }/extended_doc_view_spec.rb
  62. +0 −122 spec/couchrest/helpers/pager_spec.rb
  63. +0 −52 spec/couchrest/helpers/streamer_spec.rb
  64. +1 −1 spec/couchrest/{more → }/property_spec.rb
  65. +2 −2 spec/fixtures/more/cat.rb
  66. +1 −1 spec/spec_helper.rb
View
3 .gitignore
@@ -1,4 +1,5 @@
.DS_Store
html/*
pkg
-*.swp
+*.swp
+*.gemspec
View
32 README.md
@@ -1,33 +1,33 @@
-# CouchRest: CouchDB, close to the metal
+# CouchRest::ExtendedDocument: CouchDB, not too close to the metal
-CouchRest is based on [CouchDB's couch.js test
-library](http://svn.apache.org/repos/asf/couchdb/trunk/share/www/script/couch.js),
-which I find to be concise, clear, and well designed. CouchRest lightly wraps
-CouchDB's HTTP API, managing JSON serialization, and remembering the URI-paths
-to CouchDB's API endpoints so you don't have to.
+CouchRest::ExtendedDocument adds additional functionality to the standard CouchRest Document class such as
+setting properties, callbacks, typecasting, and validations.
-CouchRest is designed to make a simple base for application and framework-specific object oriented APIs. CouchRest is Object-Mapper agnostic, the parsed JSON it returns from CouchDB shows up as subclasses of Ruby's Hash. Naked JSON, just as it was mean to be.
+Note: CouchRest only supports CouchDB 0.9.0 or newer.
-Note: CouchRest only support CouchDB 0.9.0 or newer.
+## Install
-## Easy Install
-
- $ sudo gem install couchrest
+ $ sudo gem install couchrest_extended_document
-### Relax, it's RESTful
+## Usage
+
+ require 'couchrest/extended_document'
+
+ class Cat < CouchRest::ExtendedDocument
+
+ property :name, :type => String
-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.
+ end
-### Running the Specs
+## Testing
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).
## Docs
-API: [http://rdoc.info/projects/couchrest/couchrest](http://rdoc.info/projects/couchrest/couchrest)
+API: [http://rdoc.info/projects/couchrest/couchrest_extended_document](http://rdoc.info/projects/couchrest/couchrest_extended_document)
Check the wiki for documentation and examples [http://wiki.github.com/couchrest/couchrest](http://wiki.github.com/couchrest/couchrest)
View
14 Rakefile
@@ -1,6 +1,6 @@
require 'rake'
require "rake/rdoctask"
-require File.join(File.expand_path(File.dirname(__FILE__)),'lib','couchrest')
+require File.join(File.expand_path(File.dirname(__FILE__)),'lib','couchrest','extended_document')
begin
require 'spec/rake/spectask'
@@ -15,18 +15,18 @@ end
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.name = "samlown-couchrest_extended_document"
+ gemspec.summary = "Extend CouchRest Document class with useful features."
+ gemspec.description = "CouchRest::ExtendedDocument provides aditional features to the standard CouchRest::Document class such as properties, view designs, callbacks, typecasting and validations."
gemspec.email = "jchris@apache.org"
- gemspec.homepage = "http://github.com/couchrest/couchrest"
+ gemspec.homepage = "http://github.com/samlown/couchrest_extended_document"
gemspec.authors = ["J. Chris Anderson", "Matt Aimonetti", "Marcos Tapajos", "Will Leinweber"]
gemspec.extra_rdoc_files = %w( README.md LICENSE THANKS.md )
gemspec.files = %w( LICENSE README.md Rakefile THANKS.md history.txt couchrest.gemspec) + Dir["{examples,lib,spec,utils}/**/*"] - Dir["spec/tmp"]
gemspec.has_rdoc = true
- gemspec.add_dependency("rest-client", ">= 0.5")
+ gemspec.add_dependency("samlown-couchrest", ">= 1.0.0")
gemspec.add_dependency("mime-types", ">= 1.15")
- gemspec.version = CouchRest::VERSION
+ gemspec.version = CouchRest::ExtendedDocument::VERSION
gemspec.date = "2008-11-22"
gemspec.require_path = "lib"
end
View
182 couchrest.gemspec
@@ -1,182 +0,0 @@
-# Generated by jeweler
-# DO NOT EDIT THIS FILE DIRECTLY
-# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
-# -*- encoding: utf-8 -*-
-
-Gem::Specification.new do |s|
- s.name = %q{couchrest}
- s.version = "0.37"
-
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
- s.authors = ["J. Chris Anderson", "Matt Aimonetti", "Marcos Tapajos", "Will Leinweber"]
- s.date = %q{2010-03-30}
- 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 = [
- "LICENSE",
- "README.md",
- "THANKS.md"
- ]
- s.files = [
- "LICENSE",
- "README.md",
- "Rakefile",
- "THANKS.md",
- "couchrest.gemspec",
- "examples/model/example.rb",
- "examples/word_count/markov",
- "examples/word_count/views/books/chunked-map.js",
- "examples/word_count/views/books/united-map.js",
- "examples/word_count/views/markov/chain-map.js",
- "examples/word_count/views/markov/chain-reduce.js",
- "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",
- "history.txt",
- "lib/couchrest.rb",
- "lib/couchrest/commands/generate.rb",
- "lib/couchrest/commands/push.rb",
- "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/pager.rb",
- "lib/couchrest/helper/streamer.rb",
- "lib/couchrest/helper/upgrade.rb",
- "lib/couchrest/middlewares/logger.rb",
- "lib/couchrest/mixins.rb",
- "lib/couchrest/mixins/attachments.rb",
- "lib/couchrest/mixins/attribute_protection.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/monkeypatches.rb",
- "lib/couchrest/more/casted_model.rb",
- "lib/couchrest/more/extended_document.rb",
- "lib/couchrest/more/property.rb",
- "lib/couchrest/more/typecast.rb",
- "lib/couchrest/support/blank.rb",
- "lib/couchrest/support/class.rb",
- "lib/couchrest/support/rails.rb",
- "lib/couchrest/validation/auto_validate.rb",
- "lib/couchrest/validation/contextual_validators.rb",
- "lib/couchrest/validation/validation_errors.rb",
- "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/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",
- "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/pager_spec.rb",
- "spec/couchrest/helpers/streamer_spec.rb",
- "spec/couchrest/more/attribute_protection_spec.rb",
- "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_inherited_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/attachments/README",
- "spec/fixtures/attachments/couchdb.png",
- "spec/fixtures/attachments/test.html",
- "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/more/user.rb",
- "spec/fixtures/views/lib.js",
- "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/couchrest/couchrest}
- s.rdoc_options = ["--charset=UTF-8"]
- s.require_paths = ["lib"]
- s.rubygems_version = %q{1.3.6}
- s.summary = %q{Lean and RESTful interface to CouchDB.}
- s.test_files = [
- "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/pager_spec.rb",
- "spec/couchrest/helpers/streamer_spec.rb",
- "spec/couchrest/more/attribute_protection_spec.rb",
- "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_inherited_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/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/more/user.rb",
- "spec/spec_helper.rb",
- "examples/model/example.rb",
- "examples/word_count/word_count.rb",
- "examples/word_count/word_count_query.rb",
- "examples/word_count/word_count_views.rb"
- ]
-
- if s.respond_to? :specification_version then
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
- s.specification_version = 3
-
- 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
-
View
38 examples/word_count/markov
@@ -1,38 +0,0 @@
-#!/usr/bin/env ruby
-
-require File.expand_path(File.dirname(__FILE__)) + '/../../couchrest'
-
-cr = CouchRest.new("http://127.0.0.1:5984")
-@db = cr.database('word-count-example')
-@word_memoizer = {}
-
-def probable_follower_for(word)
- @word_memoizer[word] ||= @db.view('markov/chain-reduce', :startkey => [word,nil], :endkey => [word,{}],:group_level => 2)
-
- # puts
- # puts "search #{word} #{wprobs[word]['rows'].length}"
- # @word_memoizer[word]['rows'].sort_by{|r|r['value']}.each{|r|puts [r['value'],r['key']].inspect}
-
- rows = @word_memoizer[word]['rows'].select{|r|(r['key'][1]!='')}.sort_by{|r|r['value']}
- row = rows[(-1*[rows.length,5].min)..-1].sort_by{rand}[0]
- row ? row['key'][1] : nil
-end
-
-
-word = ARGV[0]
-words = [word]
-
-while word
- $stdout.print ' ' if words.length > 1
- $stdout.print word
- $stdout.flush
- word = probable_follower_for(word)
- words << word
-end
-
-$stdout.print '.'
-$stdout.flush
-puts
-
-# `say #{words.join(' ')}`
-
View
3 examples/word_count/views/books/chunked-map.js
@@ -1,3 +0,0 @@
-function(doc) {
- doc.title && doc.chunk && emit([doc.title, doc.chunk],null);
-}
View
1 examples/word_count/views/books/united-map.js
@@ -1 +0,0 @@
-function(doc){if(doc.text && doc.text.match(/united/)) emit([doc.title, doc.chunk],null)}
View
6 examples/word_count/views/markov/chain-map.js
@@ -1,6 +0,0 @@
-function(doc){
- var words = doc.text.split(/\W/).filter(function(w) {return w.length > 0}).map(function(w){return w.toLowerCase()});
- for (var i = 0, l = words.length; i < l; i++) {
- emit(words.slice(i,4),doc.title);
- }
-}
View
7 examples/word_count/views/markov/chain-reduce.js
@@ -1,7 +0,0 @@
-function(key,vs,c){
- if (c) {
- return sum(vs);
- } else {
- return vs.length;
- }
-}
View
6 examples/word_count/views/word_count/count-map.js
@@ -1,6 +0,0 @@
-function(doc){
- var words = doc.text.split(/\W/).map(function(w){return w.toLowerCase()});
- words.forEach(function(word){
- if (word.length > 0) emit([word,doc.title],1);
- });
-}
View
3 examples/word_count/views/word_count/count-reduce.js
@@ -1,3 +0,0 @@
-function(key,values){
- return sum(values);
-}
View
46 examples/word_count/word_count.rb
@@ -1,46 +0,0 @@
-require 'rubygems'
-require 'couchrest'
-
-couch = CouchRest.new("http://127.0.0.1:5984")
-db = couch.database('word-count-example')
-db.delete! rescue nil
-db = couch.create_db('word-count-example')
-
-books = {
- 'outline-of-science.txt' => 'http://www.gutenberg.org/files/20417/20417.txt',
- 'ulysses.txt' => 'http://www.gutenberg.org/dirs/etext03/ulyss12.txt',
- 'america.txt' => 'http://www.gutenberg.org/files/16960/16960.txt',
- 'da-vinci.txt' => 'http://www.gutenberg.org/dirs/etext04/7ldv110.txt'
-}
-
-books.each do |file, url|
- pathfile = File.join(File.dirname(__FILE__),file)
- `curl #{url} > #{pathfile}` unless File.exists?(pathfile)
-end
-
-
-books.keys.each do |book|
- title = book.split('.')[0]
- puts title
- File.open(File.join(File.dirname(__FILE__),book),'r') do |file|
- lines = []
- chunk = 0
- while line = file.gets
- lines << line
- if lines.length > 10
- db.save({
- :title => title,
- :chunk => chunk,
- :text => lines.join('')
- })
- chunk += 1
- puts chunk
- lines = []
- end
- end
- end
-end
-
-# puts "The books have been stored in your CouchDB. To initiate the MapReduce process, visit http://127.0.0.1:5984/_utils/ in your browser and click 'word-count-example', then select view 'words' or 'count'. The process could take about 15 minutes on an average MacBook."
-
-
View
40 examples/word_count/word_count_query.rb
@@ -1,40 +0,0 @@
-require 'rubygems'
-require 'couchrest'
-
-couch = CouchRest.new("http://127.0.0.1:5984")
-db = couch.database('word-count-example')
-
-puts "Now that we've parsed all those books into CouchDB, the queries we can run are incredibly flexible."
-puts "\nThe simplest query we can run is the total word count for all words in all documents:"
-puts "this will take a few minutes the first time. if it times out, just rerun this script in a few few minutes."
-puts db.view('word_count/words').inspect
-
-puts "\nWe can also narrow the query down to just one word, across all documents. Here is the count for 'flight' in all three books:"
-
-word = 'flight'
-params = {
- :startkey => [word],
- :endkey => [word,{}]
- }
-
-puts db.view('word_count/words',params).inspect
-
-puts "\nWe scope the query using startkey and endkey params to take advantage of CouchDB's collation ordering. Here are the params for the last query:"
-puts params.inspect
-
-puts "\nWe can also count words on a per-title basis."
-
-title = 'da-vinci'
-params = {
- :key => [word, title]
- }
-
-puts db.view('word_count/words',params).inspect
-
-
-puts "\nHere are the params for 'flight' in the da-vinci book:"
-puts params.inspect
-puts
-puts 'The url looks like this:'
-puts 'http://127.0.0.1:5984/word-count-example/_view/word_count/count?key=["flight","da-vinci"]'
-puts "\nTry dropping that in your browser..."
View
26 examples/word_count/word_count_views.rb
@@ -1,26 +0,0 @@
-require 'rubygems'
-require 'couchrest'
-
-couch = CouchRest.new("http://127.0.0.1:5984")
-db = couch.database('word-count-example')
-
-word_count = {
- :map => 'function(doc){
- var words = doc.text.split(/\W/);
- words.forEach(function(word){
- if (word.length > 0) emit([word,doc.title],1);
- });
- }',
- :reduce => 'function(key,combine){
- return sum(combine);
- }'
-}
-
-db.delete db.get("_design/word_count") rescue nil
-
-db.save({
- "_id" => "_design/word_count",
- :views => {
- :words => word_count
- }
-})
View
5 history.txt
@@ -4,6 +4,11 @@
* Minor enhancements
+== 0.4.0
+
+* Major enhancements
+ * Separated ExtendedDocument from main CouchRest gem (Sam Lown)
+
== 0.37
* Minor enhancements
View
2 init.rb
@@ -1 +1 @@
-require File.join(File.dirname(__FILE__),'lib', 'couchrest.rb')
+require File.join(File.dirname(__FILE__),'lib', 'couchrest', 'extended_document')
View
162 lib/couchrest.rb
@@ -1,162 +0,0 @@
-# Copyright 2008 J. Chris Anderson
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-require 'rubygems'
-begin
- require 'json'
-rescue LoadError
- raise "You need install and require your own json compatible library since couchrest rest couldn't load the json/json_pure gem" unless Kernel.const_defined?("JSON")
-end
-require 'rest_client'
-
-$:.unshift File.dirname(__FILE__) unless
- $:.include?(File.dirname(__FILE__)) ||
- $:.include?(File.expand_path(File.dirname(__FILE__)))
-
-require 'couchrest/monkeypatches'
-
-# = CouchDB, close to the metal
-module CouchRest
- VERSION = '0.37.4' unless self.const_defined?("VERSION")
-
- autoload :Server, 'couchrest/core/server'
- autoload :Database, 'couchrest/core/database'
- autoload :Response, 'couchrest/core/response'
- autoload :Document, 'couchrest/core/document'
- autoload :Design, 'couchrest/core/design'
- autoload :View, 'couchrest/core/view'
- autoload :Model, 'couchrest/core/model'
- autoload :Pager, 'couchrest/helper/pager'
- autoload :FileManager, 'couchrest/helper/file_manager'
- autoload :Streamer, 'couchrest/helper/streamer'
- autoload :Upgrade, 'couchrest/helper/upgrade'
-
- autoload :ExtendedDocument, 'couchrest/more/extended_document'
- autoload :CastedModel, 'couchrest/more/casted_model'
-
- require File.join(File.dirname(__FILE__), 'couchrest', 'core', 'rest_api')
- 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)
-
- # we extend CouchRest with the RestAPI module which gives us acess to
- # the get, post, put, delete and copy
- CouchRest.extend(::RestAPI)
-
- # The CouchRest module methods handle the basic JSON serialization
- # and deserialization, as well as query parameters. The module also includes
- # some helpers for tasks like instantiating a new Database or Server instance.
- class << self
-
- # extracted from Extlib
- #
- # Constantize tries to find a declared constant with the name specified
- # in the string. It raises a NameError when the name is not in CamelCase
- # or is not initialized.
- #
- # @example
- # "Module".constantize #=> Module
- # "Class".constantize #=> Class
- def constantize(camel_cased_word)
- unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word
- raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!"
- end
-
- Object.module_eval("::#{$1}", __FILE__, __LINE__)
- end
-
- # extracted from Extlib
- #
- # Capitalizes the first word and turns underscores into spaces and strips _id.
- # Like titleize, this is meant for creating pretty output.
- #
- # @example
- # "employee_salary" #=> "Employee salary"
- # "author_id" #=> "Author"
- def humanize(lower_case_and_underscored_word)
- lower_case_and_underscored_word.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize
- end
-
- # todo, make this parse the url and instantiate a Server or Database instance
- # depending on the specificity.
- def new(*opts)
- Server.new(*opts)
- end
-
- def parse url
- case url
- when /^(https?:\/\/)(.*)\/(.*)\/(.*)/
- scheme = $1
- host = $2
- db = $3
- docid = $4
- when /^(https?:\/\/)(.*)\/(.*)/
- scheme = $1
- host = $2
- db = $3
- when /^(https?:\/\/)(.*)/
- scheme = $1
- host = $2
- when /(.*)\/(.*)\/(.*)/
- host = $1
- db = $2
- docid = $3
- when /(.*)\/(.*)/
- host = $1
- db = $2
- else
- db = url
- end
-
- db = nil if db && db.empty?
-
- {
- :host => (scheme || "http://") + (host || "127.0.0.1:5984"),
- :database => db,
- :doc => docid
- }
- end
-
- # set proxy to use
- def proxy url
- HttpAbstraction.proxy = url
- end
-
- # ensure that a database exists
- # creates it if it isn't already there
- # returns it after it's been created
- def database! url
- parsed = parse url
- cr = CouchRest.new(parsed[:host])
- cr.database!(parsed[:database])
- end
-
- def database url
- parsed = parse url
- cr = CouchRest.new(parsed[:host])
- cr.database(parsed[:database])
- end
-
- def paramify_url url, params = {}
- if params && !params.empty?
- query = params.collect do |k,v|
- v = v.to_json if %w{key startkey endkey}.include?(k.to_s)
- "#{k}=#{CGI.escape(v.to_s)}"
- end.join("&")
- url = "#{url}?#{query}"
- end
- url
- end
- end # class << self
-end
View
0 lib/couchrest/more/casted_array.rb → lib/couchrest/casted_array.rb
File renamed without changes.
View
7 lib/couchrest/more/casted_model.rb → lib/couchrest/casted_model.rb
@@ -1,11 +1,12 @@
-require File.expand_path('../../mixins/properties', __FILE__)
-
+require 'couchrest'
+require File.join(File.dirname(__FILE__), 'mixins/callbacks')
+require File.join(File.dirname(__FILE__), 'mixins/properties')
module CouchRest
module CastedModel
def self.included(base)
- base.send(:include, ::CouchRest::Callbacks)
+ base.send(:include, ::CouchRest::Mixins::Callbacks)
base.send(:include, ::CouchRest::Mixins::Properties)
base.send(:attr_accessor, :casted_by)
base.send(:attr_accessor, :document_saved)
View
71 lib/couchrest/commands/generate.rb
@@ -1,71 +0,0 @@
-require 'fileutils'
-
-module CouchRest
- module Commands
- module Generate
-
- def self.run(options)
- directory = options[:directory]
- design_names = options[:trailing_args]
-
- FileUtils.mkdir_p(directory)
- filename = File.join(directory, "lib.js")
- self.write(filename, <<-FUNC)
- // Put global functions here.
- // Include in your views with
- //
- // //include-lib
- FUNC
-
- design_names.each do |design_name|
- subdirectory = File.join(directory, design_name)
- FileUtils.mkdir_p(subdirectory)
- filename = File.join(subdirectory, "sample-map.js")
- self.write(filename, <<-FUNC)
- function(doc) {
- // Keys is first letter of _id
- emit(doc._id[0], doc);
- }
- FUNC
-
- filename = File.join(subdirectory, "sample-reduce.js")
- self.write(filename, <<-FUNC)
- function(keys, values) {
- // Count the number of keys starting with this letter
- return values.length;
- }
- FUNC
-
- filename = File.join(subdirectory, "lib.js")
- self.write(filename, <<-FUNC)
- // Put functions specific to '#{design_name}' here.
- // Include in your views with
- //
- // //include-lib
- FUNC
- end
- end
-
- def self.help
- helpstring = <<-GEN
-
- Usage: couchview generate directory design1 design2 design3 ...
-
- Couchview will create directories and example views for the design documents you specify.
-
- GEN
- helpstring.gsub(/^ /, '')
- end
-
- def self.write(filename, contents)
- puts "Writing #{filename}"
- File.open(filename, "w") do |f|
- # Remove leading spaces
- contents.gsub!(/^ ( )?/, '')
- f.write contents
- end
- end
-
- end
- end
-end
View
103 lib/couchrest/commands/push.rb
@@ -1,103 +0,0 @@
-module CouchRest
-
- module Commands
-
- module Push
-
- def self.run(options)
- directory = options[:directory]
- database = options[:trailing_args].first
-
- fm = CouchRest::FileManager.new(database)
- fm.loud = options[:loud]
-
- if options[:loud]
- puts "Pushing views from directory #{directory} to database #{fm.db}"
- end
-
- fm.push_views(directory)
- end
-
- def self.help
- helpstring = <<-GEN
-
- == Pushing views with Couchview ==
-
- Usage: couchview push directory dbname
-
- Couchview expects a specific filesystem layout for your CouchDB views (see
- example below). It also supports advanced features like inlining of library
- code (so you can keep DRY) as well as avoiding unnecessary document
- modification.
-
- Couchview also solves a problem with CouchDB's view API, which only provides
- access to the final reduce side of any views which have both a map and a
- reduce function defined. The intermediate map results are often useful for
- development and production. CouchDB is smart enough to reuse map indexes for
- functions duplicated across views within the same design document.
-
- For views with a reduce function defined, Couchview creates both a reduce view
- and a map-only view, so that you can browse and query the map side as well as
- the reduction, with no performance penalty.
-
- == Example ==
-
- couchview push foo-project/bar-views baz-database
-
- This will push the views defined in foo-project/bar-views into a database
- called baz-database. Couchview expects the views to be defined in files with
- names like:
-
- foo-project/bar-views/my-design/viewname-map.js
- foo-project/bar-views/my-design/viewname-reduce.js
- foo-project/bar-views/my-design/noreduce-map.js
-
- Pushed to => http://127.0.0.1:5984/baz-database/_design/my-design
-
- And the design document:
- {
- "views" : {
- "viewname-map" : {
- "map" : "### contents of view-name-map.js ###"
- },
- "viewname-reduce" : {
- "map" : "### contents of view-name-map.js ###",
- "reduce" : "### contents of view-name-reduce.js ###"
- },
- "noreduce-map" : {
- "map" : "### contents of noreduce-map.js ###"
- }
- }
- }
-
- Couchview will create a design document for each subdirectory of the views
- directory specified on the command line.
-
- == Library Inlining ==
-
- Couchview can optionally inline library code into your views so you only have
- to maintain it in one place. It looks for any files named lib.* in your
- design-doc directory (for doc specific libs) and in the parent views directory
- (for project global libs). These libraries are only inserted into views which
- include the text
-
- // !include lib
-
- or
-
- # !include lib
-
- Couchview is a result of scratching my own itch. I'd be happy to make it more
- general, so please contact me at jchris@grabb.it if you'd like to see anything
- added or changed.
-
- GEN
- helpstring.gsub(/^ /, '')
- end
-
- end
-
-
- end
-
-end
View
35 lib/couchrest/core/adapters/restclient.rb
@@ -1,35 +0,0 @@
-module RestClientAdapter
-
- module API
- def proxy=(url)
- RestClient.proxy = url
- end
-
- def proxy
- RestClient.proxy
- end
-
- def get(uri, headers={})
- RestClient.get(uri, headers).to_s
- end
-
- def post(uri, payload, headers={})
- RestClient.post(uri, payload, headers).to_s
- end
-
- def put(uri, payload, headers={})
- RestClient.put(uri, payload, headers).to_s
- end
-
- def delete(uri, headers={})
- RestClient.delete(uri, headers).to_s
- end
-
- def copy(uri, headers)
- RestClient::Request.execute( :method => :copy,
- :url => uri,
- :headers => headers).to_s
- end
- end
-
-end
View
377 lib/couchrest/core/database.rb
@@ -1,377 +0,0 @@
-require 'cgi'
-require "base64"
-
-module CouchRest
- class Database
- attr_reader :server, :host, :name, :root, :uri
- attr_accessor :bulk_save_cache_limit
-
- # Create a CouchRest::Database adapter for the supplied CouchRest::Server
- # and database name.
- #
- # ==== Parameters
- # server<CouchRest::Server>:: database host
- # name<String>:: database name
- #
- def initialize(server, name)
- @name = name
- @server = server
- @host = server.uri
- @uri = "/#{name.gsub('/','%2F')}"
- @root = host + uri
- @streamer = Streamer.new(self)
- @bulk_save_cache = []
- @bulk_save_cache_limit = 500 # must be smaller than the uuid count
- end
-
- # returns the database's uri
- def to_s
- @root
- end
-
- # GET the database info from CouchDB
- def info
- CouchRest.get @root
- end
-
- # Query the <tt>_all_docs</tt> view. Accepts all the same arguments as view.
- def documents(params = {})
- keys = params.delete(:keys)
- url = CouchRest.paramify_url "#{@root}/_all_docs", params
- if keys
- CouchRest.post(url, {:keys => keys})
- else
- CouchRest.get url
- end
- end
-
- # Query a CouchDB-Lucene search view
- def search(name, params={})
- # -> http://localhost:5984/yourdb/_fti/YourDesign/by_name?include_docs=true&q=plop*'
- url = CouchRest.paramify_url "#{root}/_fti/#{name}", params
- CouchRest.get url
- end
-
- # load a set of documents by passing an array of ids
- def get_bulk(ids)
- documents(:keys => ids, :include_docs => true)
- end
- alias :bulk_load :get_bulk
-
- # POST a temporary view function to CouchDB for querying. This is not
- # recommended, as you don't get any performance benefit from CouchDB's
- # materialized views. Can be quite slow on large databases.
- def slow_view(funcs, params = {})
- keys = params.delete(:keys)
- funcs = funcs.merge({:keys => keys}) if keys
- url = CouchRest.paramify_url "#{@root}/_temp_view", params
- JSON.parse(HttpAbstraction.post(url, funcs.to_json, {"Content-Type" => 'application/json'}))
- end
-
- # backwards compatibility is a plus
- alias :temp_view :slow_view
-
- # Query a CouchDB view as defined by a <tt>_design</tt> document. Accepts
- # paramaters as described in http://wiki.apache.org/couchdb/HttpViewApi
- def view(name, params = {}, &block)
- keys = params.delete(:keys)
- name = name.split('/') # I think this will always be length == 2, but maybe not...
- dname = name.shift
- vname = name.join('/')
- url = CouchRest.paramify_url "#{@root}/_design/#{dname}/_view/#{vname}", params
- if keys
- CouchRest.post(url, {:keys => keys})
- else
- if block_given?
- @streamer.view("_design/#{dname}/_view/#{vname}", params, &block)
- else
- CouchRest.get url
- end
- end
- end
-
- # GET a document from CouchDB, by id. Returns a Ruby Hash.
- def get(id, params = {})
- slug = escape_docid(id)
- url = CouchRest.paramify_url("#{@root}/#{slug}", params)
- result = CouchRest.get(url)
- return result unless result.is_a?(Hash)
- doc = if /^_design/ =~ result["_id"]
- Design.new(result)
- else
- Document.new(result)
- end
- doc.database = self
- doc
- end
-
- # GET an attachment directly from CouchDB
- def fetch_attachment(doc, name)
- uri = url_for_attachment(doc, name)
- HttpAbstraction.get uri
- end
-
- # PUT an attachment directly to CouchDB
- def put_attachment(doc, name, file, options = {})
- docid = escape_docid(doc['_id'])
- uri = url_for_attachment(doc, name)
- JSON.parse(HttpAbstraction.put(uri, file, options))
- end
-
- # DELETE an attachment directly from CouchDB
- def delete_attachment(doc, name, force=false)
- uri = url_for_attachment(doc, name)
- # this needs a rev
- begin
- JSON.parse(HttpAbstraction.delete(uri))
- rescue Exception => error
- if force
- # get over a 409
- doc = get(doc['_id'])
- uri = url_for_attachment(doc, name)
- JSON.parse(HttpAbstraction.delete(uri))
- else
- error
- 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
- # documents on the client side because POST has the curious property of
- # being automatically retried by proxies in the event of network
- # segmentation and lost responses.
- #
- # 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.
- #
- # 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
- 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
- end
- result = if doc['_id']
- slug = escape_docid(doc['_id'])
- begin
- 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
- CouchRest.put "#{@root}/#{slug}", doc
- end
- else
- begin
- slug = doc['_id'] = @server.next_uuid
- CouchRest.put "#{@root}/#{slug}", doc
- rescue #old version of couchdb
- CouchRest.post @root, doc
- end
- end
- if result['ok']
- doc['_id'] = result['id']
- doc['_rev'] = result['rev']
- doc.database = self if doc.respond_to?(:database=)
- end
- 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.
- #
- # If called with no arguments, bulk saves the cache of documents to be bulk saved.
- def bulk_save(docs = nil, use_uuids = true)
- if docs.nil?
- docs = @bulk_save_cache
- @bulk_save_cache = []
- end
- if (use_uuids)
- ids, noids = docs.partition{|d|d['_id']}
- uuid_count = [noids.length, @server.uuid_batch_count].max
- noids.each do |doc|
- nextid = @server.next_uuid(uuid_count) rescue nil
- doc['_id'] = nextid if nextid
- end
- end
- CouchRest.post "#{@root}/_bulk_docs", {:docs => docs}
- end
- alias :bulk_delete :bulk_save
-
- # DELETE the document from CouchDB that has the given <tt>_id</tt> and
- # <tt>_rev</tt>.
- #
- # If <tt>bulk</tt> is true (false by default) the deletion is recorded for bulk-saving (bulk-deletion :) later.
- # Bulk saving happens automatically when #bulk_save_cache limit is exceded, or on the next non bulk save.
- def delete_doc(doc, bulk = false)
- raise ArgumentError, "_id and _rev required for deleting" unless doc['_id'] && doc['_rev']
- if bulk
- @bulk_save_cache << { '_id' => doc['_id'], '_rev' => doc['_rev'], '_deleted' => true }
- return bulk_save if @bulk_save_cache.length >= @bulk_save_cache_limit
- return { "ok" => true } # Mimic the non-deferred version
- end
- slug = escape_docid(doc['_id'])
- CouchRest.delete "#{@root}/#{slug}?rev=#{doc['_rev']}"
- end
-
- # COPY an existing document to a new id. If the destination id currently exists, a rev must be provided.
- # <tt>dest</tt> can take one of two forms if overwriting: "id_to_overwrite?rev=revision" or the actual doc
- # hash with a '_rev' key
- def copy_doc(doc, dest)
- raise ArgumentError, "_id is required for copying" unless doc['_id']
- slug = escape_docid(doc['_id'])
- destination = if dest.respond_to?(:has_key?) && dest['_id'] && dest['_rev']
- "#{dest['_id']}?rev=#{dest['_rev']}"
- else
- dest
- end
- 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"
- end
-
- # Create the database
- def create!
- bool = server.create_db(@name) rescue false
- bool && true
- end
-
- # Delete and re create the database
- def recreate!
- delete!
- create!
- rescue RestClient::ResourceNotFound
- ensure
- create!
- end
-
- # Replicates via "pulling" from another database to this database. Makes no attempt to deal with conflicts.
- def replicate_from other_db, continuous=false
- replicate other_db, continuous, :target => name
- end
-
- # Replicates via "pushing" to another database. Makes no attempt to deal with conflicts.
- def replicate_to other_db, continuous=false
- replicate other_db, continuous, :source => name
- end
-
- # DELETE the database itself. This is not undoable and could be rather
- # catastrophic. Use with care!
- def delete!
- clear_extended_doc_fresh_cache
- CouchRest.delete @root
- end
-
- private
-
- def replicate other_db, continuous, options
- raise ArgumentError, "must provide a CouchReset::Database" unless other_db.kind_of?(CouchRest::Database)
- raise ArgumentError, "must provide a target or source option" unless (options.key?(:target) || options.key?(:source))
- payload = options
- if options.has_key?(:target)
- payload[:source] = other_db.root
- else
- payload[:target] = other_db.root
- end
- payload[:continuous] = continuous
- CouchRest.post "#{@host}/_replicate", payload
- end
-
- def clear_extended_doc_fresh_cache
- ::CouchRest::ExtendedDocument.subclasses.each{|klass| klass.req_design_doc_refresh if klass.respond_to?(:req_design_doc_refresh)}
- end
-
- def uri_for_attachment(doc, name)
- if doc.is_a?(String)
- puts "CouchRest::Database#fetch_attachment will eventually require a doc as the first argument, not a doc.id"
- docid = doc
- rev = nil
- else
- docid = doc['_id']
- rev = doc['_rev']
- end
- docid = escape_docid(docid)
- name = CGI.escape(name)
- rev = "?rev=#{doc['_rev']}" if rev
- "/#{docid}/#{name}#{rev}"
- end
-
- def url_for_attachment(doc, name)
- @root + uri_for_attachment(doc, name)
- end
-
- def escape_docid id
- /^_design\/(.*)/ =~ id ? "_design/#{CGI.escape($1)}" : CGI.escape(id)
- end
-
- def encode_attachments(attachments)
- attachments.each do |k,v|
- next if v['stub']
- v['data'] = base64(v['data'])
- end
- attachments
- end
-
- def base64(data)
- Base64.encode64(data).gsub(/\s/,'')
- end
- end
-end
View
79 lib/couchrest/core/design.rb
@@ -1,79 +0,0 @@
-module CouchRest
- class Design < Document
- def view_by *keys
- opts = keys.pop if keys.last.is_a?(Hash)
- opts ||= {}
- self['views'] ||= {}
- method_name = "by_#{keys.join('_and_')}"
-
- if opts[:map]
- view = {}
- view['map'] = opts.delete(:map)
- if opts[:reduce]
- view['reduce'] = opts.delete(:reduce)
- opts[:reduce] = false
- end
- self['views'][method_name] = view
- else
- doc_keys = keys.collect{|k|"doc['#{k}']"} # this is where :require => 'doc.x == true' would show up
- key_emit = doc_keys.length == 1 ? "#{doc_keys.first}" : "[#{doc_keys.join(', ')}]"
- guards = opts.delete(:guards) || []
- guards.concat doc_keys
- map_function = <<-JAVASCRIPT
-function(doc) {
- if (#{guards.join(' && ')}) {
- emit(#{key_emit}, null);
- }
-}
-JAVASCRIPT
- self['views'][method_name] = {
- 'map' => map_function
- }
- end
- self['views'][method_name]['couchrest-defaults'] = opts unless opts.empty?
- method_name
- end
-
- # Dispatches to any named view.
- # (using the database where this design doc was saved)
- def view view_name, query={}, &block
- view_on database, view_name, query, &block
- end
-
- # Dispatches to any named view in a specific database
- def view_on db, view_name, query={}, &block
- view_name = view_name.to_s
- view_slug = "#{name}/#{view_name}"
- defaults = (self['views'][view_name] && self['views'][view_name]["couchrest-defaults"]) || {}
- db.view(view_slug, defaults.merge(query), &block)
- end
-
- def name
- id.sub('_design/','') if id
- end
-
- def name= newname
- self['_id'] = "_design/#{newname}"
- end
-
- def save
- raise ArgumentError, "_design docs require a name" unless name && name.length > 0
- super
- end
-
- private
-
- # returns stored defaults if the there is a view named this in the design doc
- def has_view?(view)
- view = view.to_s
- self['views'][view] &&
- (self['views'][view]["couchrest-defaults"] || {})
- end
-
- def fetch_view view_name, opts, &block
- database.view(view_name, opts, &block)
- end
-
- end
-
-end
View
84 lib/couchrest/core/document.rb
@@ -1,84 +0,0 @@
-require 'delegate'
-
-module CouchRest
- class Document < Response
- include CouchRest::Mixins::Attachments
-
- extlib_inheritable_accessor :database
- attr_accessor :database
-
- # override the CouchRest::Model-wide default_database
- # This is not a thread safe operation, do not change the model
- # database at runtime.
- def self.use_database(db)
- self.database = db
- end
-
- def id
- self['_id']
- end
-
- def rev
- self['_rev']
- end
-
- # returns true if the document has never been saved
- 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 <tt>_id</tt> and <tt>_rev</tt> fields based on
- # CouchDB's response.
- # If <tt>bulk</tt> is <tt>true</tt> (defaults to false) the document is cached for bulk save.
- def save(bulk = false)
- raise ArgumentError, "doc.database required for saving" unless database
- result = database.save_doc self, bulk
- result['ok']
- end
-
- # Deletes the document from the database. Runs the :delete callbacks.
- # Removes the <tt>_id</tt> and <tt>_rev</tt> fields, preparing the
- # document to be saved to a new <tt>_id</tt>.
- # If <tt>bulk</tt> is <tt>true</tt> (defaults to false) the document won't
- # actually be deleted from the db until bulk save.
- def destroy(bulk = false)
- raise ArgumentError, "doc.database required to destroy" unless database
- result = database.delete_doc(self, bulk)
- if result['ok']
- self['_rev'] = nil
- self['_id'] = nil
- end
- result['ok']
- end
-
- # copies the document to a new id. If the destination id currently exists, a rev must be provided.
- # <tt>dest</tt> can take one of two forms if overwriting: "id_to_overwrite?rev=revision" or the actual doc
- # hash with a '_rev' key
- def copy(dest)
- raise ArgumentError, "doc.database required to copy" unless database
- result = database.copy_doc(self, dest)
- result['ok']
- end
-
- # Returns the CouchDB uri for the document
- def uri(append_rev = false)
- return nil if new?
- couch_uri = "#{database.root}/#{CGI.escape(id)}"
- if append_rev == true
- couch_uri << "?rev=#{rev}"
- elsif append_rev.kind_of?(Integer)
- couch_uri << "?rev=#{append_rev}"
- end
- couch_uri
- end
-
- # Returns the document's database
- def database
- @database || self.class.database
- end
-
- end
-
-end
View
48 lib/couchrest/core/http_abstraction.rb
@@ -1,48 +0,0 @@
-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)
View
16 lib/couchrest/core/response.rb
@@ -1,16 +0,0 @@
-module CouchRest
- class Response < Hash
- def initialize(pkeys = {})
- pkeys ||= {}
- pkeys.each do |k,v|
- self[k.to_s] = v
- end
- end
- def []=(key, value)
- super(key.to_s, value)
- end
- def [](key)
- super(key.to_s)
- end
- end
-end
View
49 lib/couchrest/core/rest_api.rb
@@ -1,49 +0,0 @@
-module RestAPI
-
- def put(uri, doc = nil)
- payload = doc.to_json if doc
- begin
- JSON.parse(HttpAbstraction.put(uri, payload))
- rescue Exception => e
- if $DEBUG
- raise "Error while sending a PUT request #{uri}\npayload: #{payload.inspect}\n#{e}"
- else
- raise e
- end
- end
- end
-
- def get(uri)
- begin
- JSON.parse(HttpAbstraction.get(uri), :max_nesting => false)
- rescue => e
- if $DEBUG
- raise "Error while sending a GET request #{uri}\n: #{e}"
- else
- raise e
- end
- end
- end
-
- def post(uri, doc = nil)
- payload = doc.to_json if doc
- begin
- JSON.parse(HttpAbstraction.post(uri, payload))
- rescue Exception => e
- if $DEBUG
- raise "Error while sending a POST request #{uri}\npayload: #{payload.inspect}\n#{e}"
- else
- raise e
- end
- end
- end
-
- def delete(uri)
- JSON.parse(HttpAbstraction.delete(uri))
- end
-
- def copy(uri, destination)
- JSON.parse(HttpAbstraction.copy(uri, {'Destination' => destination}))
- end
-
-end
View
88 lib/couchrest/core/server.rb
@@ -1,88 +0,0 @@
-module CouchRest
- class Server
- attr_accessor :uri, :uuid_batch_count, :available_databases
- def initialize(server = 'http://127.0.0.1:5984', uuid_batch_count = 1000)
- @uri = server
- @uuid_batch_count = uuid_batch_count
- end
-
- # Lists all "available" databases.
- # An available database, is a database that was specified
- # as avaiable by your code.
- # It allows to define common databases to use and reuse in your code
- def available_databases
- @available_databases ||= {}
- end
-
- # Adds a new available database and create it unless it already exists
- #
- # Example:
- #
- # @couch = CouchRest::Server.new
- # @couch.define_available_database(:default, "tech-blog")
- #
- def define_available_database(reference, db_name, create_unless_exists = true)
- available_databases[reference.to_sym] = create_unless_exists ? database!(db_name) : database(db_name)
- end
-
- # Checks that a database is set as available
- #
- # Example:
- #
- # @couch.available_database?(:default)
- #
- def available_database?(ref_or_name)
- ref_or_name.is_a?(Symbol) ? available_databases.keys.include?(ref_or_name) : available_databases.values.map{|db| db.name}.include?(ref_or_name)
- end
-
- def default_database=(name, create_unless_exists = true)
- define_available_database(:default, name, create_unless_exists = true)
- end
-
- def default_database
- available_databases[:default]
- end
-
- # Lists all databases on the server
- def databases
- CouchRest.get "#{@uri}/_all_dbs"
- end
-
- # Returns a CouchRest::Database for the given name
- def database(name)
- CouchRest::Database.new(self, name)
- end
-
- # Creates the database if it doesn't exist
- def database!(name)
- create_db(name) rescue nil
- database(name)
- end
-
- # GET the welcome message
- def info
- CouchRest.get "#{@uri}/"
- end
-
- # Create a database
- def create_db(name)
- CouchRest.put "#{@uri}/#{name}"
- database(name)
- end
-
- # Restart the CouchDB instance
- def restart!
- CouchRest.post "#{@uri}/_restart"
- end
-
- # Retrive an unused UUID from CouchDB. Server instances manage caching a list of unused UUIDs.
- def next_uuid(count = @uuid_batch_count)
- @uuids ||= []
- if @uuids.empty?
- @uuids = CouchRest.get("#{@uri}/_uuids?count=#{count}")["uuids"]
- end
- @uuids.pop
- end
-
- end
-end
View
4 lib/couchrest/core/view.rb
@@ -1,4 +0,0 @@
-module CouchRest
- class View
- end
-end
View
14 lib/couchrest/more/extended_document.rb → lib/couchrest/extended_document.rb
@@ -1,13 +1,21 @@
+gem 'samlown-couchrest'
+require 'couchrest'
+require 'active_support'
require 'mime/types'
-require File.join(File.dirname(__FILE__), "property")
-require File.join(File.dirname(__FILE__), '..', 'mixins', 'extended_document_mixins')
require "enumerator"
+require File.join(File.dirname(__FILE__), "monkeypatches")
+require File.join(File.dirname(__FILE__), "property")
+require File.join(File.dirname(__FILE__), 'mixins')
+require File.join(File.dirname(__FILE__), 'casted_model')
module CouchRest
# Same as CouchRest::Document but with properties and validations
class ExtendedDocument < Document
- include CouchRest::Callbacks
+
+ VERSION = "1.0.0"
+
+ include CouchRest::Mixins::Callbacks
include CouchRest::Mixins::DocumentQueries
include CouchRest::Mixins::Views
include CouchRest::Mixins::DesignDoc
View
103 lib/couchrest/helper/pager.rb
@@ -1,103 +0,0 @@
-module CouchRest
- class Pager
- attr_accessor :db
- def initialize db
- @db = db
- end
-
- def all_docs(limit=100, &block)
- startkey = nil
- oldend = nil
-
- while docrows = request_all_docs(limit+1, startkey)
- startkey = docrows.last['key']
- docrows.pop if docrows.length > limit
- if oldend == startkey
- break
- end
- yield(docrows)
- oldend = startkey
- end
- end
-
- def key_reduce(view, limit=2000, firstkey = nil, lastkey = nil, &block)
- # start with no keys
- startkey = firstkey
- # lastprocessedkey = nil
- keepgoing = true
-
- while keepgoing && viewrows = request_view(view, limit, startkey)
- startkey = viewrows.first['key']
- endkey = viewrows.last['key']
-
- if (startkey == endkey)
- # we need to rerequest to get a bigger page
- # so we know we have all the rows for that key
- viewrows = @db.view(view, :key => startkey)['rows']
- # we need to do an offset thing to find the next startkey
- # otherwise we just get stuck
- lastdocid = viewrows.last['id']
- fornextloop = @db.view(view, :startkey => startkey, :startkey_docid => lastdocid, :limit => 2)['rows']
-
- newendkey = fornextloop.last['key']
- if (newendkey == endkey)
- keepgoing = false
- else
- startkey = newendkey
- end
- rows = viewrows
- else
- rows = []
- for r in viewrows
- if (lastkey && r['key'] == lastkey)
- keepgoing = false
- break
- end
- break if (r['key'] == endkey)
- rows << r
- end
- startkey = endkey
- end
-
- key = :begin
- values = []
-
- rows.each do |r|
- if key != r['key']
- # we're on a new key, yield the old first and then reset
- yield(key, values) if key != :begin
- key = r['key']
- values = []
- end
- # keep accumulating
- values << r['value']
- end
- yield(key, values)
-
- end
- end
-
- private
-
- def request_all_docs limit, startkey = nil
- opts = {}
- opts[:limit] = limit if limit
- opts[:startkey] = startkey if startkey
- results = @db.documents(opts)
- rows = results['rows']
- rows unless rows.length == 0
- end
-
- def request_view view, limit = nil, startkey = nil, endkey = nil
- opts = {}
- opts[:limit] = limit if limit
- opts[:startkey] = startkey if startkey
- opts[:endkey] = endkey if endkey
-
- results = @db.view(view, opts)
- rows = results['rows']
- rows unless rows.length == 0
- end
-
- end
-end
View
51 lib/couchrest/helper/streamer.rb
@@ -1,51 +0,0 @@
-module CouchRest
- class Streamer
- attr_accessor :db
- def initialize db
- @db = db
- end
-
- # 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 = 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|
- first = view.gets # discard header
- while line = view.gets
- row = parse_line(line)
- block.call row unless row.nil? # last line "}]" discarded
- end
- end
- parse_first(first)
- end
-
- private
-
- def parse_line line
- return nil unless line
- if /(\{.*\}),?/.match(line.chomp)
- JSON.parse($1)
- end
- end
-
- def parse_first first
- return nil unless first
- parts = first.split(',')
- parts.pop
- line = parts.join(',')
- JSON.parse("#{line}}")
- rescue
- nil
- end
-
- end
-end
View
51 lib/couchrest/helper/upgrade.rb
@@ -1,51 +0,0 @@
-module CouchRest
- class Upgrade
- attr_accessor :olddb, :newdb, :dbname
- def initialize dbname, old_couch, new_couch
- @dbname = dbname
- @olddb = old_couch.database dbname
- @newdb = new_couch.database!(dbname)
- @bulk_docs = []
- end
- def clone!
- puts "#{dbname} - #{olddb.info['doc_count']} docs"
- streamer = CouchRest::Streamer.new(olddb)
- streamer.view("_all_docs_by_seq") do |row|
- load_row_docs(row) if row
- maybe_flush_bulks
- end
- flush_bulks!
- end
-
- private
-
- def maybe_flush_bulks
- flush_bulks! if (@bulk_docs.length > 99)
- end
-
- def flush_bulks!
- url = CouchRest.paramify_url "#{@newdb.uri}/_bulk_docs", {:all_or_nothing => true}
- puts "posting #{@bulk_docs.length} bulk docs to #{url}"
- begin
- CouchRest.post url, {:docs => @bulk_docs}
- @bulk_docs = []
- rescue Exception => e
- puts e.response
- raise e
- end
- end
-
- def load_row_docs(row)
- results = @olddb.get(row["id"], {:open_revs => "all", :attachments => true})
- results.select{|r|r["ok"]}.each do |r|
- doc = r["ok"]
- if /^_/.match(doc["_id"]) && !/^_design/.match(doc["_id"])
- puts "invalid docid #{doc["_id"]} -- trimming"
- doc["_id"] = doc["_id"].sub('_','')
- end
- doc.delete('_rev')
- @bulk_docs << doc
- end
- end
- end
-end
View
263 lib/couchrest/middlewares/logger.rb
@@ -1,263 +0,0 @@
-####################################
-# USAGE
-#
-# in your rack.rb file
-# require this file and then:
-#
-# couch = CouchRest.new
-# LOG_DB = couch.database!('couchrest-logger')
-# use CouchRest::Logger, LOG_DB
-#
-# Note:
-# to require just this middleware, if you have the gem installed do:
-# require 'couchrest/middlewares/logger'
-#
-# For log processing examples, see examples at the bottom of this file
-
-module CouchRest
- class Logger
-
- def self.log
- Thread.current["couchrest.logger"] ||= {:queries => []}
- end
-
- def initialize(app, db=nil)
- @app = app
- @db = db
- end
-
- def self.record(log_info)
- log[:queries] << log_info
- end
-
- def log
- Thread.current["couchrest.logger"] ||= {:queries => []}
- end
-
- def reset_log
- Thread.current["couchrest.logger"] = nil
- end
-
- def call(env)
- reset_log
- log['started_at'] = Time.now
- log['env'] = env
- log['url'] = 'http://' + env['HTTP_HOST'] + env['REQUEST_URI']
- response = @app.call(env)
- log['ended_at'] = Time.now
- log['duration'] = log['ended_at'] - log['started_at']
- # let's report the log in a different thread so we don't slow down the app
- @db ? Thread.new(@db, log){|db, rlog| db.save_doc(rlog);} : p(log.inspect)
- response
- end
- end
-end
-
-# inject our logger into CouchRest HTTP abstraction layer
-module HttpAbstraction
-
- def self.get(uri, headers=nil)
- start_query = Time.now
- log = {:method => :get, :uri => uri, :headers => headers}
- response = super(uri, headers=nil)
- end_query = Time.now
- log[:duration] = (end_query - start_query)
- CouchRest::Logger.record(log)
- response
- end
-
- def self.post(uri, payload, headers=nil)
- start_query = Time.now
- log = {:method => :post, :uri => uri, :payload => (payload ? (JSON.load(payload) rescue 'parsing error') : nil), :headers => headers}
- response = super(uri, payload, headers=nil)
- end_query = Time.now
- log[:duration] = (end_query - start_query)
- CouchRest::Logger.record(log)
- response
- end
-
- def self.put(uri, payload, headers=nil)
- start_query = Time.now
- log = {:method => :put, :uri => uri, :payload => (payload ? (JSON.load(payload) rescue 'parsing error') : nil), :headers => headers}
- response = super(uri, payload, headers=nil)
- end_query = Time.now
- log[:duration] = (end_query - start_query)
- CouchRest::Logger.record(log)
- response
- end
-
- def self.delete(uri, headers=nil)
- start_query = Time.now
- log = {:method => :delete, :uri => uri, :headers => headers}
- response = super(uri, headers=nil)
- end_query = Time.now
- log[:duration] = (end_query - start_query)
- CouchRest::Logger.record(log)
- response
- end
-
-end
-
-
-# Advanced usage example
-#
-#
-# # DB VIEWS
-# by_url = {
-# :map =>
-# "function(doc) {
-# if(doc['url']){ emit(doc['url'], 1) };
-# }",
-# :reduce =>
-# 'function (key, values, rereduce) {
-# return(sum(values));
-# };'
-# }
-# req_duration = {
-# :map =>
-# "function(doc) {
-# if(doc['duration']){ emit(doc['url'], doc['duration']) };
-# }",
-# :reduce =>
-# 'function (key, values, rereduce) {
-# return(sum(values)/values.length);
-# };'
-# }
-#
-# query_duration = {
-# :map =>
-# "function(doc) {
-# if(doc['queries']){
-# doc.queries.forEach(function(query){
-# if(query['duration'] && query['method']){
-# emit(query['method'], query['duration'])
-# }
-# });
-# };
-# }" ,
-# :reduce =>
-# 'function (key, values, rereduce) {
-# return(sum(values)/values.length);
-# };'
-# }
-#
-# action_queries = {
-# :map =>
-# "function(doc) {
-# if(doc['queries']){
-# emit(doc['url'], doc['queries'].length)
-# };
-# }",
-# :reduce =>
-# 'function (key, values, rereduce) {
-# return(sum(values)/values.length);
-# };'
-# }
-#
-# action_time_spent_in_db = {
-# :map =>
-# "function(doc) {
-# if(doc['queries']){
-# var totalDuration = 0;
-# doc.queries.forEach(function(query){
-# totalDuration += query['duration']
-# })
-# emit(doc['url'], totalDuration)
-# };
-# }",
-# :reduce =>
-# 'function (key, values, rereduce) {
-# return(sum(values)/values.length);
-# };'
-# }
-#
-# show_queries = %Q~function(doc, req) {
-# var body = ""
-# body += "<h1>" + doc['url'] + "</h1>"
-# body += "<h2>Request duration in seconds: " + doc['duration'] + "</h2>"
-# body += "<h3>" + doc['queries'].length + " queries</h3><ul>"
-# if (doc.queries){
-# doc.queries.forEach(function(query){
-# body += "<li>"+ query['uri'] +"</li>"
-# });
-# };
-# body += "</ul>"
-# if(doc){ return { body: body} }
-# }~
-#
-#
-# couch = CouchRest.new
-# LOG_DB = couch.database!('couchrest-logger')
-# design_doc = LOG_DB.get("_design/stats") rescue nil
-# LOG_DB.delete_doc design_doc rescue nil
-# LOG_DB.save_doc({
-# "_id" => "_design/stats",
-# :views => {
-# :by_url => by_url,
-# :request_duration => req_duration,
-# :query_duration => query_duration,
-# :action_queries => action_queries,
-# :action_time_spent_in_db => action_time_spent_in_db
-# },
-# :shows => {
-# :queries => show_queries
-# }
-# })
-#
-# module CouchRest
-# class Logger
-#
-# def self.roundup(value)
-# begin
-# value = Float(value)
-# (value * 100).round.to_f / 100
-# rescue
-# value
-# end
-# end
-#
-# # Usage example:
-# # CouchRest::Logger.average_request_duration(LOG_DB)['rows'].first['value']
-# def self.average_request_duration(db)
-# raw = db.view('stats/request_duration', :reduce => true)
-# (raw.has_key?('rows') && !raw['rows'].empty?) ? roundup(raw['rows'].first['value']) : 'not available yet'
-# end
-#
-# def self.average_query_duration(db)
-# raw = db.view('stats/query_duration', :reduce => true)
-# (raw.has_key?('rows') && !raw['rows'].empty?) ? roundup(raw['rows'].first['value']) : 'not available yet'
-# end
-#
-# def self.average_get_query_duration(db)
-# raw = db.view('stats/query_duration', :key => 'get', :reduce => true)
-# (raw.has_key?('rows') && !raw['rows'].empty?) ? roundup(raw['rows'].first['value']) : 'not available yet'
-# end
-#
-# def self.average_post_query_duration(db)
-# raw = db.view('stats/query_duration', :key => 'post', :reduce => true)
-# (raw.has_key?('rows') && !raw['rows'].empty?) ? roundup(raw['rows'].first['value']) : 'not available yet'
-# end
-#
-# def self.average_queries_per_action(db)
-# raw = db.view('stats/action_queries', :reduce => true)
-# (raw.has_key?('rows') && !raw['rows'].empty?) ? roundup(raw['rows'].first['value']) : 'not available yet'
-# end
-#
-# def self.average_db_time_per_action(db)
-# raw = db.view('stat