Permalink
Browse files

Removing ExtendedDocument into own gem and removing HttpAbstraction

  • Loading branch information...
1 parent e6604a0 commit e35ad4de067183be311f917ff19dff782bbb235e @samlown samlown committed May 10, 2010
Showing with 121 additions and 7,478 deletions.
  1. +2 −1 .gitignore
  2. +10 −4 README.md
  3. +2 −2 Rakefile
  4. +0 −182 couchrest.gemspec
  5. +0 −144 examples/model/example.rb
  6. +8 −0 history.txt
  7. +27 −49 lib/couchrest.rb
  8. +0 −35 lib/couchrest/core/adapters/restclient.rb
  9. +0 −48 lib/couchrest/core/http_abstraction.rb
  10. +0 −4 lib/couchrest/core/view.rb
  11. +6 −11 lib/couchrest/{core → }/database.rb
  12. 0 lib/couchrest/{core → }/design.rb
  13. +1 −1 lib/couchrest/{core → }/document.rb
  14. +29 −0 lib/couchrest/helper/attachments.rb
  15. +3 −3 lib/couchrest/middlewares/logger.rb
  16. +0 −4 lib/couchrest/mixins.rb
  17. +0 −31 lib/couchrest/mixins/attachments.rb
  18. +0 −74 lib/couchrest/mixins/attribute_protection.rb
  19. +0 −532 lib/couchrest/mixins/callbacks.rb
  20. +0 −120 lib/couchrest/mixins/class_proxy.rb
  21. +0 −260 lib/couchrest/mixins/collection.rb
  22. +0 −121 lib/couchrest/mixins/design_doc.rb
  23. +0 −80 lib/couchrest/mixins/document_queries.rb
  24. +0 −70 lib/couchrest/mixins/extended_attachments.rb
  25. +0 −9 lib/couchrest/mixins/extended_document_mixins.rb
  26. +0 −154 lib/couchrest/mixins/properties.rb
  27. +0 −246 lib/couchrest/mixins/validation.rb
  28. +0 −148 lib/couchrest/mixins/views.rb
  29. +1 −71 lib/couchrest/monkeypatches.rb
  30. +0 −25 lib/couchrest/more/casted_array.rb
  31. +0 −58 lib/couchrest/more/casted_model.rb
  32. +0 −310 lib/couchrest/more/extended_document.rb
  33. +0 −50 lib/couchrest/more/property.rb
  34. +0 −175 lib/couchrest/more/typecast.rb
  35. 0 lib/couchrest/{core → }/response.rb
  36. +9 −6 lib/couchrest/{core → }/rest_api.rb
  37. 0 lib/couchrest/{core → }/server.rb
  38. +0 −42 lib/couchrest/support/blank.rb
  39. +0 −42 lib/couchrest/support/rails.rb
  40. +0 −157 lib/couchrest/validation/auto_validate.rb
  41. +0 −78 lib/couchrest/validation/contextual_validators.rb
  42. +0 −125 lib/couchrest/validation/validation_errors.rb
  43. +0 −74 lib/couchrest/validation/validators/absent_field_validator.rb
  44. +0 −107 lib/couchrest/validation/validators/confirmation_validator.rb
  45. +0 −122 lib/couchrest/validation/validators/format_validator.rb
  46. +0 −66 lib/couchrest/validation/validators/formats/email.rb
  47. +0 −43 lib/couchrest/validation/validators/formats/url.rb
  48. +0 −120 lib/couchrest/validation/validators/generic_validator.rb
  49. +0 −139 lib/couchrest/validation/validators/length_validator.rb
  50. +0 −89 lib/couchrest/validation/validators/method_validator.rb
  51. +0 −109 lib/couchrest/validation/validators/numeric_validator.rb
  52. +0 −114 lib/couchrest/validation/validators/required_field_validator.rb
  53. +16 −3 spec/couchrest/{core → }/couchrest_spec.rb
  54. +2 −2 spec/couchrest/{core → }/database_spec.rb
  55. +2 −2 spec/couchrest/{core → }/design_spec.rb
  56. +1 −1 spec/couchrest/{core → }/document_spec.rb
  57. +0 −150 spec/couchrest/more/attribute_protection_spec.rb
  58. +0 −79 spec/couchrest/more/casted_extended_doc_spec.rb
  59. +0 −406 spec/couchrest/more/casted_model_spec.rb
  60. +0 −135 spec/couchrest/more/extended_doc_attachment_spec.rb
  61. +0 −40 spec/couchrest/more/extended_doc_inherited_spec.rb
  62. +0 −824 spec/couchrest/more/extended_doc_spec.rb
  63. +0 −99 spec/couchrest/more/extended_doc_subclass_spec.rb
  64. +0 −473 spec/couchrest/more/extended_doc_view_spec.rb
  65. +0 −628 spec/couchrest/more/property_spec.rb
  66. +2 −2 spec/couchrest/{core → }/server_spec.rb
  67. +0 −35 spec/fixtures/more/article.rb
  68. +0 −22 spec/fixtures/more/card.rb
  69. +0 −22 spec/fixtures/more/cat.rb
  70. +0 −22 spec/fixtures/more/course.rb
  71. +0 −8 spec/fixtures/more/event.rb
  72. +0 −17 spec/fixtures/more/invoice.rb
  73. +0 −9 spec/fixtures/more/person.rb
  74. +0 −6 spec/fixtures/more/question.rb
  75. +0 −12 spec/fixtures/more/service.rb
  76. +0 −22 spec/fixtures/more/user.rb
  77. +0 −4 spec/spec_helper.rb
View
3 .gitignore
@@ -1,4 +1,5 @@
.DS_Store
html/*
pkg
-*.swp
+*.swp
+*.gemspec
View
14 README.md
@@ -14,12 +14,17 @@ Note: CouchRest only support CouchDB 0.9.0 or newer.
$ sudo gem install couchrest
-### Relax, it's RESTful
+## Relax, it's RESTful
CouchRest rests on top of a HTTP abstraction layer using by default Heroku’s excellent REST Client Ruby HTTP wrapper.
-Other adapters can be added to support more http libraries.
-### Running the Specs
+## Extended Document
+
+As of May 2010 support for the popular CouchRest::ExtendedDocument mixin has been moved to its own gem: couchrest_extended_document.
+
+Most people will probably want to use this library (or one of the alternatives) to make it slightly easier to access your documents.
+
+## Running the Specs
The most complete documentation is the spec/ directory. To validate your
CouchRest install, from the project root directory run `rake`, or `autotest`
@@ -38,7 +43,8 @@ Please post bugs, suggestions and patches to the bug tracker at [http://github.c
Follow us on Twitter: [http://twitter.com/couchrest](http://twitter.com/couchrest)
Also, check [http://twitter.com/#search?q=%23couchrest](http://twitter.com/#search?q=%23couchrest)
-
+
+
## Ruby on Rails
CouchRest is compatible with rails and can even be used a Rails plugin.
View
4 Rakefile
@@ -15,7 +15,7 @@ end
begin
require 'jeweler'
Jeweler::Tasks.new do |gemspec|
- gemspec.name = "couchrest"
+ gemspec.name = "samlown-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"
@@ -25,7 +25,7 @@ begin
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("mime-types", ">= 1.15")
+# gemspec.add_dependency("couchrest_extended_document", ">= 1.0.0")
gemspec.version = CouchRest::VERSION
gemspec.date = "2008-11-22"
gemspec.require_path = "lib"
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
144 examples/model/example.rb
@@ -1,144 +0,0 @@
-require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'couchrest')
-
-def show obj
- puts obj.inspect
- puts
-end
-
-SERVER = CouchRest.new
-SERVER.default_database = 'couchrest-extendeddoc-example'
-
-class Author < CouchRest::ExtendedDocument
- use_database SERVER.default_database
- property :name
-
- def drink_scotch
- puts "... glug type glug ... I'm #{name} ... type glug glug ..."
- end
-end
-
-class Post < CouchRest::ExtendedDocument
- use_database SERVER.default_database
-
- property :title
- property :body
- property :author, :cast_as => 'Author'
-
- timestamps!
-end
-
-class Comment < CouchRest::ExtendedDocument
- use_database SERVER.default_database
-
- property :commenter, :cast_as => 'Author'
- timestamps!
-
- def post= post
- self["post_id"] = post.id
- end
- def post
- Post.get(self['post_id']) if self['post_id']
- end
-
-end
-
-puts "Act I: CRUD"
-puts
-puts "(pause for dramatic effect)"
-puts
-sleep 2
-
-puts "Create an author."
-quentin = Author.new("name" => "Quentin Hazel")
-show quentin
-
-puts "Create a new post."
-post = Post.new(:title => "First Post", :body => "Lorem ipsum dolor sit amet, consectetur adipisicing elit...")
-show post
-
-puts "Add the author to the post."
-post.author = quentin
-show post
-
-puts "Save the post."
-post.save
-show post
-
-puts "Load the post."
-reloaded = Post.get(post.id)
-show reloaded
-
-puts "The author of the post is an instance of Author."
-reloaded.author.drink_scotch
-
-puts "\nAdd some comments to the post."
-comment_one = Comment.new :text => "Blah blah blah", :commenter => {:name => "Joe Sixpack"}
-comment_two = Comment.new :text => "Yeah yeah yeah", :commenter => {:name => "Jane Doe"}
-comment_three = Comment.new :text => "Whatever...", :commenter => {:name => "John Stewart"}
-
-# TODO - maybe add some magic here?
-comment_one.post = post
-comment_two.post = post
-comment_three.post = post
-comment_one.save
-comment_two.save
-comment_three.save
-
-show comment_one
-show comment_two
-show comment_three
-
-puts "We can load a post through its comment (no magic here)."
-show post = comment_one.post
-
-puts "Commenters are also authors."
-comment_two['commenter'].drink_scotch
-comment_one['commenter'].drink_scotch
-comment_three['commenter'].drink_scotch
-
-puts "\nLet's save an author to her own document."
-jane = comment_two['commenter']
-jane.save
-show jane
-
-puts "Oh, that's neat! Because Ruby passes hash valuee by reference, Jane's new id has been added to the comment she left."
-show comment_two
-
-puts "Of course, we'd better remember to save it."
-comment_two.save
-show comment_two
-
-puts "Oooh, denormalized... feel the burn!"
-puts
-puts
-puts
-puts "Act II: Views"
-puts
-puts
-sleep 2
-
-puts "Let's find all the comments that go with our post."
-puts "Our post has id #{post.id}, so lets find all the comments with that post_id."
-puts
-
-class Comment
- view_by :post_id
-end
-
-comments = Comment.by_post_id :key => post.id
-show comments
-
-puts "That was too easy."
-puts "We can even wrap it up in a finder on the Post class."
-puts
-
-class Post
- def comments
- Comment.by_post_id :key => id
- end
-end
-
-show post.comments
-puts "Gimme 5 minutes and I'll roll this into the framework. ;)"
-puts
-puts "There is a lot more that can be done with views, but a lot of the interesting stuff is joins, which of course range across types. We'll pick up where we left off, next time."
View
8 history.txt
@@ -4,6 +4,14 @@
* Minor enhancements
+== 1.0.0
+
+* Major enhancements
+ * Moved ExtendedDocument and friends into own library, couchrest_extended_document. (Sam Lown)
+ * Removed HttpAbstraction component for direct interface with RestClient. (Sam Lown)
+ * Changed version to more conventional format starting from 1.0.0 to avoid ambiguity issues with order. (Sam Lown)
+
+
== 0.37
* Minor enhancements
View
76 lib/couchrest.rb
@@ -25,31 +25,23 @@
$:.include?(File.expand_path(File.dirname(__FILE__)))
require 'couchrest/monkeypatches'
+require 'couchrest/rest_api'
# = CouchDB, close to the metal
module CouchRest
- VERSION = '0.37.4' unless self.const_defined?("VERSION")
+ VERSION = '1.0.0' 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 :Server, 'couchrest/server'
+ autoload :Database, 'couchrest/database'
+ autoload :Response, 'couchrest/response'
+ autoload :Document, 'couchrest/document'
+ autoload :Design, 'couchrest/design'
+ autoload :Model, 'couchrest/model'
autoload :Pager, 'couchrest/helper/pager'
- autoload :FileManager, 'couchrest/helper/file_manager'
autoload :Streamer, 'couchrest/helper/streamer'
+ autoload :Attachments, 'couchrest/helper/attachments'
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)
@@ -59,35 +51,6 @@ module CouchRest
# 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)
@@ -130,9 +93,9 @@ def parse url
# set proxy to use
def proxy url
- HttpAbstraction.proxy = url
+ RestClient.proxy = url
end
-
+
# ensure that a database exists
# creates it if it isn't already there
# returns it after it's been created
@@ -160,3 +123,18 @@ def paramify_url url, params = {}
end
end # class << self
end
+
+# For the sake of backwards compatability, generate a dummy ExtendedDocument class
+# which should be replaced by real library: couchrest_extended_document.
+#
+# Added 2010-05-10 by Sam Lown. Please remove at some point in the future.
+#
+class CouchRest::ExtendedDocument < CouchRest::Document
+
+ def self.inherited(subclass)
+ raise "ExtendedDocument is no longer included in CouchRest base driver, see couchrest_extended_document gem"
+ 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
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
4 lib/couchrest/core/view.rb
@@ -1,4 +0,0 @@
-module CouchRest
- class View
- end
-end
View
17 lib/couchrest/core/database.rb → lib/couchrest/database.rb
@@ -65,7 +65,7 @@ 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'}))
+ JSON.parse(RestClient.post(url, funcs.to_json, {"Content-Type" => 'application/json'}))
end
# backwards compatibility is a plus
@@ -108,28 +108,28 @@ def get(id, params = {})
# GET an attachment directly from CouchDB
def fetch_attachment(doc, name)
uri = url_for_attachment(doc, name)
- HttpAbstraction.get uri
+ RestClient.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))
+ JSON.parse(RestClient.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))
+ JSON.parse(RestClient.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))
+ JSON.parse(RestClient.delete(uri))
else
error
end
@@ -174,7 +174,7 @@ def save_doc(doc, bulk = false, batch = false)
uri = "#{@root}/#{slug}"
uri << "?batch=ok" if batch
CouchRest.put uri, doc
- rescue HttpAbstraction::ResourceNotFound
+ rescue RestClient::ResourceNotFound
p "resource not found when saving even tho an id was passed"
slug = doc['_id'] = @server.next_uuid
CouchRest.put "#{@root}/#{slug}", doc
@@ -316,7 +316,6 @@ def replicate_to other_db, continuous=false
# 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
@@ -334,10 +333,6 @@ def replicate other_db, continuous, options
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)
View
0 lib/couchrest/core/design.rb → lib/couchrest/design.rb
File renamed without changes.
View
2 lib/couchrest/core/document.rb → lib/couchrest/document.rb
@@ -2,7 +2,7 @@
module CouchRest
class Document < Response
- include CouchRest::Mixins::Attachments
+ include CouchRest::Attachments
extlib_inheritable_accessor :database
attr_accessor :database
View
29 lib/couchrest/helper/attachments.rb
@@ -0,0 +1,29 @@
+module CouchRest
+ module Attachments
+
+ # saves an attachment directly to couchdb
+ def put_attachment(name, file, options={})
+ raise ArgumentError, "doc must be saved" unless self.rev
+ raise ArgumentError, "doc.database required to put_attachment" unless database
+ result = database.put_attachment(self, name, file, options)
+ self['_rev'] = result['rev']
+ result['ok']
+ end
+
+ # returns an attachment's data
+ def fetch_attachment(name)
+ raise ArgumentError, "doc must be saved" unless self.rev
+ raise ArgumentError, "doc.database required to put_attachment" unless database
+ database.fetch_attachment(self, name)
+ end
+
+ # deletes an attachment directly from couchdb
+ def delete_attachment(name, force=false)
+ raise ArgumentError, "doc.database required to delete_attachment" unless database
+ result = database.delete_attachment(self, name, force)
+ self['_rev'] = result['rev']
+ result['ok']
+ end
+
+ end
+end
View
6 lib/couchrest/middlewares/logger.rb
@@ -53,8 +53,8 @@ def call(env)
end
end
-# inject our logger into CouchRest HTTP abstraction layer
-module HttpAbstraction
+# inject our logger into key RestClient methods
+module RestClient
def self.get(uri, headers=nil)
start_query = Time.now
@@ -260,4 +260,4 @@ def self.delete(uri, headers=nil)
# end
#
# end
-# end
+# end
View
4 lib/couchrest/mixins.rb
@@ -1,4 +0,0 @@
-mixins_dir = File.join(File.dirname(__FILE__), 'mixins')
-
-require File.join(mixins_dir, 'attachments')
-require File.join(mixins_dir, 'callbacks')
View
31 lib/couchrest/mixins/attachments.rb
@@ -1,31 +0,0 @@
-module CouchRest
- module Mixins
- module Attachments
-
- # saves an attachment directly to couchdb
- def put_attachment(name, file, options={})
- raise ArgumentError, "doc must be saved" unless self.rev
- raise ArgumentError, "doc.database required to put_attachment" unless database
- result = database.put_attachment(self, name, file, options)
- self['_rev'] = result['rev']
- result['ok']
- end
-
- # returns an attachment's data
- def fetch_attachment(name)
- raise ArgumentError, "doc must be saved" unless self.rev
- raise ArgumentError, "doc.database required to put_attachment" unless database
- database.fetch_attachment(self, name)
- end
-
- # deletes an attachment directly from couchdb
- def delete_attachment(name, force=false)
- raise ArgumentError, "doc.database required to delete_attachment" unless database
- result = database.delete_attachment(self, name, force)
- self['_rev'] = result['rev']
- result['ok']
- end
-
- end
- end
-end
View
74 lib/couchrest/mixins/attribute_protection.rb
@@ -1,74 +0,0 @@
-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
View
532 lib/couchrest/mixins/callbacks.rb
@@ -1,532 +0,0 @@
-# Copyright (c) 2006-2009 David Heinemeier Hansson
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-#
-# Extracted from ActiveSupport::NewCallbacks written by Yehuda Katz
-# http://github.com/rails/rails/raw/d6e4113c83a9d55be6f2af247da2cecaa855f43b/activesupport/lib/active_support/new_callbacks.rb
-# http://github.com/rails/rails/commit/1126a85aed576402d978e6f76eb393b6baaa9541
-
-require File.join(File.dirname(__FILE__), '..', 'support', 'class')
-
-module CouchRest
- # Callbacks are hooks into the lifecycle of an object that allow you to trigger logic
- # before or after an alteration of the object state.
- #
- # Mixing in this module allows you to define callbacks in your class.
- #
- # Example:
- # class Storage
- # include ActiveSupport::Callbacks
- #
- # define_callbacks :save
- # end
- #
- # class ConfigStorage < Storage
- # save_callback :before, :saving_message
- # def saving_message
- # puts "saving..."
- # end
- #
- # save_callback :after do |object|
- # puts "saved"
- # end
- #
- # def save
- # _run_save_callbacks do
- # puts "- save"
- # end
- # end
- # end
- #
- # config = ConfigStorage.new
- # config.save
- #
- # Output:
- # saving...
- # - save
- # saved
- #
- # Callbacks from parent classes are inherited.
- #
- # Example:
- # class Storage
- # include ActiveSupport::Callbacks
- #
- # define_callbacks :save
- #
- # save_callback :before, :prepare
- # def prepare
- # puts "preparing save"
- # end
- # end
- #
- # class ConfigStorage < Storage
- # save_callback :before, :saving_message
- # def saving_message
- # puts "saving..."
- # end
- #
- # save_callback :after do |object|
- # puts "saved"
- # end
- #
- # def save
- # _run_save_callbacks do
- # puts "- save"
- # end
- # end
- # end
- #
- # config = ConfigStorage.new
- # config.save
- #
- # Output:
- # preparing save
- # saving...
- # - save
- # saved
- module Callbacks
- def self.included(klass)
- klass.extend ClassMethods
- end
-
- def run_callbacks(kind, options = {}, &blk)
- send("_run_#{kind}_callbacks", &blk)
- end
-
- class Callback
- @@_callback_sequence = 0
-
- attr_accessor :filter, :kind, :name, :options, :per_key, :klass
- def initialize(filter, kind, options, klass)
- @kind, @klass = kind, klass
-
- normalize_options!(options)
-
- @per_key = options.delete(:per_key)
- @raw_filter, @options = filter, options
- @filter = _compile_filter(filter)
- @compiled_options = _compile_options(options)
- @callback_id = next_id
-
- _compile_per_key_options
- end
-
- def clone(klass)
- obj = super()
- obj.klass = klass
- obj.per_key = @per_key.dup
- obj.options = @options.dup
- obj.per_key[:if] = @per_key[:if].dup
- obj.per_key[:unless] = @per_key[:unless].dup
- obj.options[:if] = @options[:if].dup
- obj.options[:unless] = @options[:unless].dup
- obj
- end
-
- def normalize_options!(options)
- options[:if] = Array.wrap(options[:if])
- options[:unless] = Array.wrap(options[:unless])
-
- options[:per_key] ||= {}
- options[:per_key][:if] = Array.wrap(options[:per_key][:if])
- options[:per_key][:unless] = Array.wrap(options[:per_key][:unless])
- end
-
- def next_id
- @@_callback_sequence += 1
- end
-
- def matches?(_kind, _filter)
- @kind == _kind &&
- @filter == _filter
- end
-
- def _update_filter(filter_options, new_options)
- filter_options[:if].push(new_options[:unless]) if new_options.key?(:unless)
- filter_options[:unless].push(new_options[:if]) if new_options.key?(:if)
- end
-
- def recompile!(_options, _per_key)
- _update_filter(self.options, _options)
- _update_filter(self.per_key, _per_key)
-
- @callback_id = next_id
- @filter = _compile_filter(@raw_filter)
- @compiled_options = _compile_options(@options)
- _compile_per_key_options
- end
-
- def _compile_per_key_options
- key_options = _compile_options(@per_key)
-
- @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def _one_time_conditions_valid_#{@callback_id}?
- true #{key_options[0]}
- end
- RUBY_EVAL
- end
-
- # This will supply contents for before and around filters, and no
- # contents for after filters (for the forward pass).
- def start(key = nil, options = {})
- object, terminator = (options || {}).values_at(:object, :terminator)
-
- return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
-
- terminator ||= false
-
- # options[0] is the compiled form of supplied conditions
- # options[1] is the "end" for the conditional
-
- if @kind == :before || @kind == :around
- if @kind == :before
- # if condition # before_save :filter_name, :if => :condition
- # filter_name
- # end
- filter = <<-RUBY_EVAL
- unless halted
- result = #{@filter}
- halted = (#{terminator})
- end
- RUBY_EVAL
-
- [@compiled_options[0], filter, @compiled_options[1]].compact.join("\n")
- else
- # Compile around filters with conditions into proxy methods
- # that contain the conditions.
- #
- # For `around_save :filter_name, :if => :condition':
- #
- # def _conditional_callback_save_17
- # if condition
- # filter_name do
- # yield self
- # end
- # else
- # yield self
- # end
- # end
-
- name = "_conditional_callback_#{@kind}_#{next_id}"
- txt, line = <<-RUBY_EVAL, __LINE__ + 1
- def #{name}(halted)
- #{@compiled_options[0] || "if true"} && !halted
- #{@filter} do
- yield self
- end
- else
- yield self
- end
- end
- RUBY_EVAL
- @klass.class_eval(txt, __FILE__, line)
- "#{name}(halted) do"
- end
- end
- end
-
- # This will supply contents for around and after filters, but not
- # before filters (for the backward pass).
- def end(key = nil, options = {})
- object = (options || {})[:object]
-
- return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
-
- if @kind == :around || @kind == :after
- # if condition # after_save :filter_name, :if => :condition
- # filter_name
- # end
- if @kind == :after
- [@compiled_options[0], @filter, @compiled_options[1]].compact.join("\n")
- else
- "end"
- end
- end
- end
-
- private
- # Options support the same options as filters themselves (and support
- # symbols, string, procs, and objects), so compile a conditional
- # expression based on the options
- def _compile_options(options)
- return [] if options[:if].empty? && options[:unless].empty?
-
- conditions = []
-
- unless options[:if].empty?
- conditions << Array.wrap(_compile_filter(options[:if]))
- end
-
- unless options[:unless].empty?
- conditions << Array.wrap(_compile_filter(options[:unless])).map {|f| "!#{f}"}
- end
-
- ["if #{conditions.flatten.join(" && ")}", "end"]
- end
-
- # Filters support:
- # Arrays:: Used in conditions. This is used to specify
- # multiple conditions. Used internally to
- # merge conditions from skip_* filters
- # Symbols:: A method to call
- # Strings:: Some content to evaluate
- # Procs:: A proc to call with the object
- # Objects:: An object with a before_foo method on it to call
- #
- # All of these objects are compiled into methods and handled
- # the same after this point:
- # Arrays:: Merged together into a single filter
- # Symbols:: Already methods
- # Strings:: class_eval'ed into methods
- # Procs:: define_method'ed into methods
- # Objects::
- # a method is created that calls the before_foo method
- # on the object.
- def _compile_filter(filter)
- method_name = "_callback_#{@kind}_#{next_id}"
- case filter
- when Array
- filter.map {|f| _compile_filter(f)}
- when Symbol
- filter
- when String
- "(#{filter})"
- when Proc
- @klass.send(:define_method, method_name, &filter)
- return method_name if filter.arity == 0
-
- method_name << (filter.arity == 1 ? "(self)" : " self, Proc.new ")
- else
- @klass.send(:define_method, "#{method_name}_object") { filter }
-
- _normalize_legacy_filter(kind, filter)
-
- @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def #{method_name}(&blk)
- #{method_name}_object.send(:#{kind}, self, &blk)
- end
- RUBY_EVAL
-
- method_name
- end
- end
-
- def _normalize_legacy_filter(kind, filter)
- if !filter.respond_to?(kind) && filter.respond_to?(:filter)
- filter.class_eval(
- "def #{kind}(context, &block) filter(context, &block) end",
- __FILE__, __LINE__ - 1)
- elsif filter.respond_to?(:before) && filter.respond_to?(:after) && kind == :around
- def filter.around(context)
- should_continue = before(context)
- yield if should_continue
- after(context)
- end
- end
- end
-
- end
-
- # An Array with a compile method
- class CallbackChain < Array
- def initialize(symbol)
- @symbol = symbol
- end
-
- def compile(key = nil, options = {})
- method = []
- method << "halted = false"
- each do |callback|
- method << callback.start(key, options)
- end
- method << "yield self if block_given? && !halted"
- reverse_each do |callback|
- method << callback.end(key, options)
- end
- method.compact.join("\n")
- end
-
- def clone(klass)
- chain = CallbackChain.new(@symbol)
- chain.push(*map {|c| c.clone(klass)})
- end
- end
-
- module ClassMethods
- #CHAINS = {:before => :before, :around => :before, :after => :after}
-
- # Make the _run_save_callbacks method. The generated method takes
- # a block that it'll yield to. It'll call the before and around filters
- # in order, yield the block, and then run the after filters.
- #
- # _run_save_callbacks do
- # save
- # end
- #
- # The _run_save_callbacks method can optionally take a key, which
- # will be used to compile an optimized callback method for each
- # key. See #define_callbacks for more information.
- def _define_runner(symbol)
- body = send("_#{symbol}_callback").
- compile(nil, :terminator => send("_#{symbol}_terminator"))
-
- body, line = <<-RUBY_EVAL, __LINE__ + 1
- def _run_#{symbol}_callbacks(key = nil, &blk)
- if key
- name = "_run__\#{self.class.name.hash.abs}__#{symbol}__\#{key.hash.abs}__callbacks"
-
- unless respond_to?(name)
- self.class._create_keyed_callback(name, :#{symbol}, self, &blk)
- end
-
- send(name, &blk)
- else
- #{body}
- end
- end
- RUBY_EVAL
-
- undef_method "_run_#{symbol}_callbacks" if method_defined?("_run_#{symbol}_callbacks")
- class_eval body, __FILE__, line
- end
-
- # This is called the first time a callback is called with a particular
- # key. It creates a new callback method for the key, calculating
- # which callbacks can be omitted because of per_key conditions.
- def _create_keyed_callback(name, kind, obj, &blk)
- @_keyed_callbacks ||= {}
- @_keyed_callbacks[name] ||= begin
- str = send("_#{kind}_callback").
- compile(name, :object => obj, :terminator => send("_#{kind}_terminator"))
-
- class_eval "def #{name}() #{str} end", __FILE__, __LINE__
-
- true
- end
- end
-
- # Define callbacks.
- #
- # Creates a <name>_callback method that you can use to add callbacks.
- #
- # Syntax:
- # save_callback :before, :before_meth
- # save_callback :after, :after_meth, :if => :condition
- # save_callback :around {|r| stuff; yield; stuff }
- #
- # The <name>_callback method also updates the _run_<name>_callbacks
- # method, which is the public API to run the callbacks.
- #
- # Also creates a skip_<name>_callback method that you can use to skip
- # callbacks.
- #
- # When creating or skipping callbacks, you can specify conditions that
- # are always the same for a given key. For instance, in ActionPack,
- # we convert :only and :except conditions into per-key conditions.
- #
- # before_filter :authenticate, :except => "index"
- # becomes
- # dispatch_callback :before, :authenticate, :per_key => {:unless => proc {|c| c.action_name == "index"}}
- #
- # Per-Key conditions are evaluated only once per use of a given key.
- # In the case of the above example, you would do:
- #
- # run_dispatch_callbacks(action_name) { ... dispatch stuff ... }
- #
- # In that case, each action_name would get its own compiled callback
- # method that took into consideration the per_key conditions. This
- # is a speed improvement for ActionPack.
- def _update_callbacks(name, filters = CallbackChain.new(name), block = nil)
- type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before
- options = filters.last.is_a?(Hash) ? filters.pop : {}
- filters.unshift(block) if block
-
- callbacks = send("_#{name}_callback")
- yield callbacks, type, filters, options if block_given?
-
- _define_runner(name)
- end
-
- alias_method :_reset_callbacks, :_update_callbacks
-
- def set_callback(name, *filters, &block)
- _update_callbacks(name, filters, block) do |callbacks, type, filters, options|
- filters.map! do |filter|
- # overrides parent class
- callbacks.delete_if {|c| c.matches?(type, filter) }
- Callback.new(filter, type, options.dup, self)
- end
-
- options[:prepend] ? callbacks.unshift(*filters) : callbacks.push(*filters)
- end
- end
-
- def skip_callback(name, *filters, &block)
- _update_callbacks(name, filters, block) do |callbacks, type, filters, options|
- filters.each do |filter|
- callbacks = send("_#{name}_callback=", callbacks.clone(self))
-
- filter = callbacks.find {|c| c.matches?(type, filter) }
-
- if filter && options.any?
- filter.recompile!(options, options[:per_key] || {})
- else
- callbacks.delete(filter)
- end
- end
- end
- end
-
- def define_callbacks(*symbols)
- terminator = symbols.pop if symbols.last.is_a?(String)
- symbols.each do |symbol|
- extlib_inheritable_accessor("_#{symbol}_terminator") { terminator }
-
- extlib_inheritable_accessor("_#{symbol}_callback") do
- CallbackChain.new(symbol)
- end
-
- _define_runner(symbol)
-
- # Define more convenient callback methods
- # set_callback(:save, :before) becomes before_save
- [:before, :after, :around].each do |filter|
- self.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def self.#{filter}_#{symbol}(*symbols, &blk)
- _alias_callbacks(symbols, blk) do |callback, options|
- set_callback(:#{symbol}, :#{filter}, callback, options)
- end
- end
- RUBY_EVAL
- end
- end
- end
-
- def _alias_callbacks(callbacks, block)
- options = callbacks.last.is_a?(Hash) ? callbacks.pop : {}
- callbacks.push(block) if block
- callbacks.each do |callback|
- yield callback, options
- end
- end
- end
- end
-end
View
120 lib/couchrest/mixins/class_proxy.rb
@@ -1,120 +0,0 @@
-module CouchRest
- module Mixins
- module ClassProxy
-
- def self.included(base)
- base.extend(ClassMethods)
- end
-
- module ClassMethods
-
- # Return a proxy object which represents a model class on a
- # chosen database instance. This allows you to DRY operations
- # where a database is chosen dynamically.
- #
- # ==== Example:
- #
- # db = CouchRest::Database.new(...)
- # articles = Article.on(db)
- #
- # articles.all { ... }
- # articles.by_title { ... }
- #
- # u = articles.get("someid")
- #
- # u = articles.new(:title => "I like plankton")
- # u.save # saved on the correct database
-
- def on(database)
- Proxy.new(self, database)
- end
- end
-
- class Proxy #:nodoc:
- def initialize(klass, database)
- @klass = klass
- @database = database
- end
-
- # ExtendedDocument
-
- def new(*args)
- doc = @klass.new(*args)
- doc.database = @database
- doc
- end
-
- def method_missing(m, *args, &block)
- if has_view?(m)
- query = args.shift || {}
- view(m, query, *args, &block)
- else
- super
- end
- end
-
- # Mixins::DocumentQueries
-
- def all(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)
- @klass.all({:database => @database, :raw => true, :limit => 0}.merge(opts), &block)['total_rows']
- end
-
- def first(opts = {})
- doc = @klass.first({:database => @database}.merge(opts))
- doc.database = @database if doc && doc.respond_to?(:database)
- doc
- end
-
- def get(id)
- doc = @klass.get(id, @database)
- doc.database = @database if doc && doc.respond_to?(:database)
- doc
- end
-
- # Mixins::Views
-
- def has_view?(view)
- @klass.has_view?(view)
- end
-
- def view(name, 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
-
-
- # Mixins::DesignDoc
-
- def design_doc
- @klass.design_doc
- end
-
- def refresh_design_doc
- @klass.refresh_design_doc(@database)
- end
-
- def save_design_doc
- @klass.save_design_doc(@database)
- end
-
- # DEPRICATED
- def all_design_doc_versions
- @klass.all_design_doc_versions(@database)
- end
-
- def stored_design_doc
- @klass.stored_design_doc(@database)
- end
- alias :model_design_doc :stored_design_doc
-
- end
- end
- end
-end
View
260 lib/couchrest/mixins/collection.rb
@@ -1,260 +0,0 @@
-module CouchRest
- module Mixins
- module Collection
-
- def self.included(base)
- base.extend(ClassMethods)
- end
-
- module ClassMethods
-
- # Creates a new class method, find_all_<collection_name>, that will
- # execute the view specified with the design_doc and view_name
- # parameters, along with the specified view_options. This method will
- # return the results of the view as an Array of objects which are
- # instances of the class.
- #
- # This method is handy for objects that do not use the view_by method
- # to declare their views.
- def provides_collection(collection_name, design_doc, view_name, view_options)
- class_eval <<-END, __FILE__, __LINE__ + 1
- def self.find_all_#{collection_name}(options = {})
- view_options = #{view_options.inspect} || {}
- CollectionProxy.new(options[:database] || database, "#{design_doc}", "#{view_name}", view_options.merge(options), Kernel.const_get('#{self}'))
- end
- END
- end
-
- # Fetch a group of objects from CouchDB. Options can include:
- # :page - Specifies the page to load (starting at 1)
- # :per_page - Specifies the number of objects to load per page
- #
- # Defaults are used if these options are not specified.
- def paginate(options)
- proxy = create_collection_proxy(options)
- proxy.paginate(options)
- end
-
- # Iterate over the objects in a collection, fetching them from CouchDB
- # in groups. Options can include:
- # :page - Specifies the page to load
- # :per_page - Specifies the number of objects to load per page
- #
- # Defaults are used if these options are not specified.
- def paginated_each(options, &block)
- search = options.delete(:search)
- unless search == true
- proxy = create_collection_proxy(options)
- else
- proxy = create_search_collection_proxy(options)
- end
- proxy.paginated_each(options, &block)
- end
-
- # Create a CollectionProxy for the specified view and options.
- # CollectionProxy behaves just like an Array, but offers support for
- # pagination.
- def collection_proxy_for(design_doc, view_name, view_options = {})
- options = view_options.merge(:design_doc => design_doc, :view_name => view_name)
- create_collection_proxy(options)
- end
-
- private
-
- def create_collection_proxy(options)
- design_doc, view_name, view_options = parse_view_options(options)
- CollectionProxy.new(options[:database] || database, design_doc, view_name, view_options, self)
- end
-
- def create_search_collection_proxy(options)
- design_doc, search_name, search_options = parse_search_options(options)
- CollectionProxy.new(options[:database] || database, design_doc, search_name, search_options, self, :search)
- end
-
- def parse_view_options(options)
- design_doc = options.delete(:design_doc)
- raise ArgumentError, 'design_doc is required' if design_doc.nil?
-
- view_name = options.delete(:view_name)
- raise ArgumentError, 'view_name is required' if view_name.nil?
-
- default_view_options = (design_doc.class == Design &&
- design_doc['views'][view_name.to_s] &&
- design_doc['views'][view_name.to_s]["couchrest-defaults"]) || {}
- view_options = default_view_options.merge(options)
-
- [design_doc, view_name, view_options]
- end
-
- def parse_search_options(options)
- design_doc = options.delete(:design_doc)
- raise ArgumentError, 'design_doc is required' if design_doc.nil?
-
- search_name = options.delete(:view_name)
- raise ArgumentError, 'search_name is required' if search_name.nil?
-
- search_options = options.clone
- [design_doc, search_name, search_options]
- end
-
- end
-
- class CollectionProxy
- alias_method :proxy_respond_to?, :respond_to?
- instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ }
-
- DEFAULT_PAGE = 1
- DEFAULT_PER_PAGE = 30
-
- # Create a new CollectionProxy to represent the specified view. If a
- # container class is specified, the proxy will create an object of the
- # given type for each row that comes back from the view. If no
- # container class is specified, the raw results are returned.
- #
- # The CollectionProxy provides support for paginating over a collection
- # via the paginate, and paginated_each methods.
- def initialize(database, design_doc, view_name, view_options = {}, container_class = nil, query_type = :view)
- raise ArgumentError, "database is a required parameter" if database.nil?
-
- @database = database
- @container_class = container_class
- @query_type = query_type
-
- strip_pagination_options(view_options)
- @view_options = view_options
-
- if design_doc.class == Design
- @view_name = "#{design_doc.name}/#{view_name}"
- else
- @view_name = "#{design_doc}/#{view_name}"
- end
- end
-
- # See Collection.paginate
- def paginate(options = {})
- page, per_page = parse_options(options)
- results = @database.send(@query_type, @view_name, pagination_options(page, per_page))
- remember_where_we_left_off(results, page)
- instances = convert_to_container_array(results)
-
- begin
- if Kernel.const_get('WillPaginate')
- total_rows = results['total_rows'].to_i
- paginated = WillPaginate::Collection.create(page, per_page, total_rows) do |pager|
- pager.replace(instances)
- end
- return paginated
- end
- rescue NameError
- # When not using will_paginate, not much we could do about this. :x
- end
- return instances
- end
-
- # See Collection.paginated_each
- def paginated_each(options = {}, &block)
- page, per_page = parse_options(options)
-
- begin
- collection = paginate({:page => page, :per_page => per_page})
- collection.each(&block)
- page += 1
- end until collection.size < per_page
- end
-
- def respond_to?(*args)
- proxy_respond_to?(*args) || (load_target && @target.respond_to?(*args))
- end
-
- # Explicitly proxy === because the instance method removal above
- # doesn't catch it.
- def ===(other)
- load_target
- other === @target
- end
-
- private
-
- def method_missing(method, *args)
- if load_target
- if block_given?
- @target.send(method, *args) { |*block_args| yield(*block_args) }
- else
- @target.send(method, *args)
- end
- end
- end
-
- def load_target
- unless loaded?
- @view_options.merge!({:include_docs => true}) if @query_type == :search
- results = @database.send(@query_type, @view_name, @view_options)
- @target = convert_to_container_array(results)
- end
- @loaded = true
- @target
- end
-
- def loaded?
- @loaded
- end
-
- def reload
- reset
- load_target
- self unless @target.nil?
- end
-
- def reset
- @loaded = false
- @target = nil
- end
-
- def inspect
- load_target
- @target.inspect
- end
-
- def convert_to_container_array(results)
- if @container_class.nil?
- results
- else
- results['rows'].collect { |row| @container_class.create_from_database(row['doc']) } unless results['rows'].nil?
- end
- end
-
- def pagination_options(page, per_page)
- view_options = @view_options.clone
- if @query_type == :view && @last_key && @last_docid && @last_page == page - 1
- key = view_options.delete(:key)
- end_key = view_options[:endkey] || key
- options = { :startkey => @last_key, :endkey => end_key, :startkey_docid => @last_docid, :limit => per_page, :skip => 1 }
- else
- options = { :limit => per_page, :skip => per_page * (page - 1) }
- end
- view_options.merge(options)
- end
-
- def parse_options(options)
- page = options.delete(:page) || DEFAULT_PAGE
- per_page = options.delete(:per_page) || DEFAULT_PER_PAGE
- [page.to_i, per_page.to_i]
- end
-
- def strip_pagination_options(options)
- parse_options(options)
- end
-
- def remember_where_we_left_off(results, page)
- last_row = results['rows'].last
- if last_row
- @last_key = last_row['key']
- @last_docid = last_row['id']
- end
- @last_page = page
- end
- end
-
- end
- end
-end
View
121 lib/couchrest/mixins/design_doc.rb
@@ -1,121 +0,0 @@
-require 'digest/md5'
-
-module CouchRest
- module Mixins
- module DesignDoc
-
- def self.included(base)
- base.extend(ClassMethods)
- end
-
- module ClassMethods
-
- def design_doc
- @design_doc ||= Design.new(default_design_doc)
- end
-
- # Use when something has been changed, like a view, so that on the next request
- # the design docs will be updated (if changed!)
- def req_design_doc_refresh
- @design_doc_fresh = { }
- end
-
- def design_doc_id
- "_design/#{design_doc_slug}"
- end
-
- def design_doc_slug
- self.to_s
- end
-
- def default_design_doc
- {
- "_id" => design_doc_id,
- "language" => "javascript",
- "views" => {
- 'all' => {
- 'map' => "function(doc) {
- if (doc['couchrest-type'] == '#{self.to_s}') {
- emit(doc['_id'],1);
- }
- }"
- }
- }
- }
- end
-
- # DEPRECATED
- # use stored_design_doc to retrieve the current design doc
- def all_design_doc_versions(db = database)
- db.documents :startkey => "_design/#{self.to_s}",
- :endkey => "_design/#{self.to_s}-\u9999"
- end
-
- # Retreive the latest version of the design document directly
- # from the database.
- def stored_design_doc(db = database)
- db.get(design_doc_id) rescue nil
- end
- alias :model_design_doc :stored_design_doc
-
- def refresh_design_doc(db = database)
- raise "Database missing for design document refresh" if db.nil?
- unless design_doc_fresh(db)
- save_design_doc(db)
- design_doc_fresh(db, true)
- end
- end
-
- # Save the design doc onto a target database in a thread-safe way,
- # not modifying the model's design_doc
- #
- # See also save_design_doc! to always save the design doc even if there
- # are no changes.
- def save_design_doc(db = database, force = false)
- update_design_doc(Design.new(design_doc), db, force)
- end
-
- # Force the update of the model's design_doc even if it hasn't changed.
- def save_design_doc!(db = database)
- save_design_doc(db, true)
- end
-
- protected
-
- def design_doc_fresh(db, fresh = nil)
- @design_doc_fresh ||= {}
- if fresh.nil?
- @design_doc_fresh[db.uri] || false
- else
- @design_doc_fresh[db.uri] = fresh
- end
- end
-
- # Writes out a design_doc to a given database, returning the
- # updated design doc
- def update_design_doc(design_doc, db, force = false)
- saved = stored_design_doc(db)
- if saved
- changes = force
- design_doc['views'].each do |name, view|
- if saved['views'][name] != view
- changes = true
- saved['views'][name] = view
- end
- end
- if changes
- db.save_doc(saved)
- end
- design_doc
- else
- design_doc.database = db
- design_doc.save
- design_doc
- end
- end
-
- end # module ClassMethods
-
- end
- end
-end
View
80 lib/couchrest/mixins/document_queries.rb
@@ -1,80 +0,0 @@
-module CouchRest
- module Mixins
- module DocumentQueries
-
- def self.included(base)
- base.extend(ClassMethods)
- end
-
- module ClassMethods
-
- # Load all documents that have the "couchrest-type" field equal to the
- # name of the current class. Take the standard set of
- # CouchRest::Database#view options.
- def all(opts = {}, &block)
- view(:all, opts, &block)
- end
-
- # Returns the number of documents that have the "couchrest-type" field
- # equal to the name of the current class. Takes the standard set of
- # CouchRest::Database#view options
- def count(opts = {}, &block)
- all({:raw => true, :limit => 0}.merge(opts), &block)['total_rows']
- end
-
- # Load the first document that have the "couchrest-type" field equal to
- # the name of the current class.
- #
- # ==== Returns
- # Object:: The first object instance available
- # or
- # Nil:: if no instances available
- #
- # ==== Parameters
- # opts<Hash>::
- # View options, see <tt>CouchRest::Database#view</tt> options for more info.
- def first(opts = {})
- first_instance = self.all(opts.merge!(:limit => 1))
- first_instance.empty? ? nil : first_instance.first
- end
-
- # Load a document from the database by id
- # No exceptions will be raised if the document isn't found
- #
- # ==== Returns
- # Object:: if the document was found
- # or
- # Nil::
- #
- # === Parameters
- # id<String, Integer>:: Document ID
- # db<Database>:: optional option to pass a custom database to use
- def get(id, db = database)
- begin
- get!(id, db)
- rescue
- nil
- end
- end
-
- # Load a document from the database by id
- # An exception will be raised if the document isn't found
- #
- # ==== Returns
- # Object:: if the document was found
- # or
- # Exception
- #
- # === Parameters
- # id<String, Integer>:: Document ID
- # db<Database>:: optional option to pass a custom database to use
- def get!(id, db = database)
- doc = db.get id
- create_from_database(doc)
- end
-
- end
-
- end
- end
-end
View
70 lib/couchrest/mixins/extended_attachments.rb
@@ -1,70 +0,0 @@
-module CouchRest
- module Mixins
- module ExtendedAttachments
-
- # creates a file attachment to the current doc
- def create_attachment(args={})
- raise ArgumentError unless args[:file] && args[:name]
- return if has_attachment?(args[:name])
- self['_attachments'] ||= {}
- set_attachment_attr(args)
- rescue ArgumentError => e
- raise ArgumentError, 'You must specify :file and :name'
- end
-
- # reads the data from an attachment
- def read_attachment(attachment_name)
- database.fetch_attachment(self, attachment_name)
- end
-
- # modifies a file attachment on the current doc
- def update_attachment(args={})
- raise ArgumentError unless args[:file] && args[:name]
- return unless has_attachment?(args[:name])
- delete_attachment(args[:name])
- set_attachment_attr(args)
- rescue ArgumentError => e
- raise ArgumentError, 'You must specify :file and :name'
- end
-
- # deletes a file attachment from the current doc
- def delete_attachment(attachment_name)
- return unless self['_attachments']
- self['_attachments'].delete attachment_name
- end
-
- # returns true if attachment_name exists
- def has_attachment?(attachment_name)
- !!(self['_attachments'] && self['_attachments'][attachment_name] && !self['_attachments'][attachment_name].empty?)
- end
-
- # returns URL to fetch the attachment from
- def attachment_url(attachment_name)
- return unless has_attachment?(attachment_name)
- "#{database.root}/#{self.id}/#{attachment_name}"
- end
-
- # returns URI to fetch the attachment from
- def attachment_uri(attachment_name)
- return unless has_attachment?(attachment_name)
- "#{database.uri}/#{self.id}/#{attachment_name}"
- end
-
- private
-
- def get_mime_type(file)
- ::MIME::Types.type_for(file.path).empty? ?
- 'text\/plain' : MIME::Types.type_for(file.path).first.content_type.gsub(/\//,'\/')
- end
-
- def set_attachment_attr(args)
- content_type = args[:content_type] ? args[:content_type] : get_mime_type(args[:file])
- self['_attachments'][args[:name]] = {
- 'content_type' => content_type,
- 'data' => args[:file].read
- }
- end
-
- end # module ExtendedAttachments
- end
-end
View
9 lib/couchrest/mixins/extended_document_mixins.rb
@@ -1,9 +0,0 @@
-require File.join(File.dirname(__FILE__), 'properties')
-require File.join(File.dirname(__FILE__), 'document_queries')
-require File.join(File.dirname(__FILE__), 'views')
-require File.join(File.dirname(__FILE__), 'design_doc')
-require File.join(File.dirname(__FILE__), 'validation')
-require File.join(File.dirname(__FILE__), 'extended_attachments')
-require File.join(File.dirname(__FILE__), 'class_proxy')
-require File.join(File.dirname(__FILE__), 'collection')
-require File.join(File.dirname(__FILE__), 'attribute_protection')
View
154 lib/couchrest/mixins/properties.rb
@@ -1,154 +0,0 @@
-require 'time'
-require File.join(File.dirname(__FILE__), '..', 'more', 'property')
-require File.join(File.dirname(__FILE__), '..', 'more', 'casted_array')
-require File.join(File.dirname(__FILE__), '..', 'more', 'typecast')
-
-module CouchRest
- module Mixins
- module Properties
-
- class IncludeError < StandardError; end
-
- include ::CouchRest::More::Typecast
-
- def self.included(base)
- base.class_eval <<-EOS, __FILE__, __LINE__ + 1
- extlib_inheritable_accessor(:properties) unless self.respond_to?(:properties)
- self.properties ||= []
- EOS
- base.extend(ClassMethods)
- raise CouchRest::Mixins::Properties::IncludeError, "You can only mixin Properties in a class responding to [] and []=, if you tried to mixin CastedModel, make sure your class inherits from Hash or responds to the proper methods" unless (base.new.respond_to?(:[]) && base.new.respond_to?(:[]=))
- end
-
- def apply_defaults
- return if self.respond_to?(:new?) && (new? == false)
- return unless self.class.respond_to?(:properties)
- return if self.class.properties.empty?
- # TODO: cache the default object
- self.class.properties.each do |property|
- key = property.name.to_s
- # let's make sure we have a default
- unless property.default.nil?
- if property.default.class == Proc
- self[key] = property.default.call
- else
- self[key] = Marshal.load(Marshal.dump(property.default))
- end
- end
- end
- end
-
- def cast_keys
- return unless self.class.properties
- self.class.properties.each do |property|
- cast_property(property)
- end
- end
-
- def cast_property(property, assigned=false)
- return unless property.casted
- key = self.has_key?(property.name) ? property.name : property.name.to_sym
- # Don't cast the property unless it has a value
- return unless self[key]
- if property.type.is_a?(Array)
- klass = property.type[0]
- self[key] = [self[key]] unless self[key].is_a?(Array)
- arr = self[key].collect do |value|
- value = typecast_value(value, klass, property.init_method)
- associate_casted_to_parent(value, assigned)
- value
- end
- # allow casted_by calls to be passed up chain by wrapping in CastedArray
- self[key] = klass != String ? ::CouchRest::CastedArray.new(arr) : arr
- self[key].casted_by = self if self[key].respond_to?(:casted_by)
- else
- self[key] = typecast_value(self[key], property.type, property.init_method)
- associate_casted_to_parent(self[key], assigned)
- end
- end
-
- def associate_casted_to_parent(casted, assigned)
- casted.casted_by = self if casted.respond_to?(:casted_by)
- casted.document_saved = true if !assigned && casted.respond_to?(:document_saved)
- end