From 5607936540458ba488a51adf655bef61bb724f30 Mon Sep 17 00:00:00 2001 From: Matt Aimonetti Date: Thu, 5 Mar 2009 23:52:48 -0800 Subject: [PATCH] updated the callback system using Yehuda's latest version and bumped the release to make the latest fixes available --- couchrest.gemspec | 4 +- lib/couchrest.rb | 2 +- lib/couchrest/mixins/callbacks.rb | 105 ++++++++++++------ spec/couchrest/more/extended_doc_view_spec.rb | 23 ++-- 4 files changed, 87 insertions(+), 47 deletions(-) diff --git a/couchrest.gemspec b/couchrest.gemspec index c126abb..935c543 100644 --- a/couchrest.gemspec +++ b/couchrest.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |s| s.name = %q{couchrest} - s.version = "0.2" + s.version = "0.2.1" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["J. Chris Anderson", "Matt Aimonetti"] @@ -10,7 +10,7 @@ Gem::Specification.new do |s| s.description = %q{CouchRest provides a simple interface on top of CouchDB's RESTful HTTP API, as well as including some utility scripts for managing views and attachments.} s.email = %q{jchris@apache.org} s.extra_rdoc_files = ["README.md", "LICENSE", "THANKS.md"] - s.files = ["LICENSE", "README.md", "Rakefile", "THANKS.md", "examples/model", "examples/model/example.rb", "examples/word_count", "examples/word_count/markov", "examples/word_count/views", "examples/word_count/views/books", "examples/word_count/views/books/chunked-map.js", "examples/word_count/views/books/united-map.js", "examples/word_count/views/markov", "examples/word_count/views/markov/chain-map.js", "examples/word_count/views/markov/chain-reduce.js", "examples/word_count/views/word_count", "examples/word_count/views/word_count/count-map.js", "examples/word_count/views/word_count/count-reduce.js", "examples/word_count/word_count.rb", "examples/word_count/word_count_query.rb", "examples/word_count/word_count_views.rb", "lib/couchrest", "lib/couchrest/commands", "lib/couchrest/commands/generate.rb", "lib/couchrest/commands/push.rb", "lib/couchrest/core", "lib/couchrest/core/database.rb", "lib/couchrest/core/design.rb", "lib/couchrest/core/document.rb", "lib/couchrest/core/response.rb", "lib/couchrest/core/server.rb", "lib/couchrest/core/view.rb", "lib/couchrest/helper", "lib/couchrest/helper/pager.rb", "lib/couchrest/helper/streamer.rb", "lib/couchrest/mixins", "lib/couchrest/mixins/attachments.rb", "lib/couchrest/mixins/callbacks.rb", "lib/couchrest/mixins/design_doc.rb", "lib/couchrest/mixins/document_queries.rb", "lib/couchrest/mixins/extended_attachments.rb", "lib/couchrest/mixins/extended_document_mixins.rb", "lib/couchrest/mixins/properties.rb", "lib/couchrest/mixins/validation.rb", "lib/couchrest/mixins/views.rb", "lib/couchrest/mixins.rb", "lib/couchrest/monkeypatches.rb", "lib/couchrest/more", "lib/couchrest/more/casted_model.rb", "lib/couchrest/more/extended_document.rb", "lib/couchrest/more/property.rb", "lib/couchrest/support", "lib/couchrest/support/blank.rb", "lib/couchrest/support/class.rb", "lib/couchrest/validation", "lib/couchrest/validation/auto_validate.rb", "lib/couchrest/validation/contextual_validators.rb", "lib/couchrest/validation/validation_errors.rb", "lib/couchrest/validation/validators", "lib/couchrest/validation/validators/absent_field_validator.rb", "lib/couchrest/validation/validators/confirmation_validator.rb", "lib/couchrest/validation/validators/format_validator.rb", "lib/couchrest/validation/validators/formats", "lib/couchrest/validation/validators/formats/email.rb", "lib/couchrest/validation/validators/formats/url.rb", "lib/couchrest/validation/validators/generic_validator.rb", "lib/couchrest/validation/validators/length_validator.rb", "lib/couchrest/validation/validators/method_validator.rb", "lib/couchrest/validation/validators/numeric_validator.rb", "lib/couchrest/validation/validators/required_field_validator.rb", "lib/couchrest.rb", "spec/couchrest", "spec/couchrest/core", "spec/couchrest/core/couchrest_spec.rb", "spec/couchrest/core/database_spec.rb", "spec/couchrest/core/design_spec.rb", "spec/couchrest/core/document_spec.rb", "spec/couchrest/core/server_spec.rb", "spec/couchrest/helpers", "spec/couchrest/helpers/pager_spec.rb", "spec/couchrest/helpers/streamer_spec.rb", "spec/couchrest/more", "spec/couchrest/more/casted_extended_doc_spec.rb", "spec/couchrest/more/casted_model_spec.rb", "spec/couchrest/more/extended_doc_attachment_spec.rb", "spec/couchrest/more/extended_doc_spec.rb", "spec/couchrest/more/extended_doc_view_spec.rb", "spec/couchrest/more/property_spec.rb", "spec/fixtures", "spec/fixtures/attachments", "spec/fixtures/attachments/couchdb.png", "spec/fixtures/attachments/README", "spec/fixtures/attachments/test.html", "spec/fixtures/couchapp", "spec/fixtures/couchapp/_attachments", "spec/fixtures/couchapp/_attachments/index.html", "spec/fixtures/couchapp/doc.json", "spec/fixtures/couchapp/foo", "spec/fixtures/couchapp/foo/bar.txt", "spec/fixtures/couchapp/foo/test.json", "spec/fixtures/couchapp/test.json", "spec/fixtures/couchapp/views", "spec/fixtures/couchapp/views/example-map.js", "spec/fixtures/couchapp/views/example-reduce.js", "spec/fixtures/couchapp-test", "spec/fixtures/couchapp-test/my-app", "spec/fixtures/couchapp-test/my-app/_attachments", "spec/fixtures/couchapp-test/my-app/_attachments/index.html", "spec/fixtures/couchapp-test/my-app/foo", "spec/fixtures/couchapp-test/my-app/foo/bar.txt", "spec/fixtures/couchapp-test/my-app/views", "spec/fixtures/couchapp-test/my-app/views/example-map.js", "spec/fixtures/couchapp-test/my-app/views/example-reduce.js", "spec/fixtures/more", "spec/fixtures/more/article.rb", "spec/fixtures/more/card.rb", "spec/fixtures/more/course.rb", "spec/fixtures/more/event.rb", "spec/fixtures/more/invoice.rb", "spec/fixtures/more/person.rb", "spec/fixtures/more/question.rb", "spec/fixtures/more/service.rb", "spec/fixtures/views", "spec/fixtures/views/lib.js", "spec/fixtures/views/test_view", "spec/fixtures/views/test_view/lib.js", "spec/fixtures/views/test_view/only-map.js", "spec/fixtures/views/test_view/test-map.js", "spec/fixtures/views/test_view/test-reduce.js", "spec/spec.opts", "spec/spec_helper.rb", "utils/remap.rb", "utils/subset.rb"] + s.files = ["LICENSE", "README.md", "Rakefile", "THANKS.md", "examples/model", "examples/model/example.rb", "examples/word_count", "examples/word_count/markov", "examples/word_count/views", "examples/word_count/views/books", "examples/word_count/views/books/chunked-map.js", "examples/word_count/views/books/united-map.js", "examples/word_count/views/markov", "examples/word_count/views/markov/chain-map.js", "examples/word_count/views/markov/chain-reduce.js", "examples/word_count/views/word_count", "examples/word_count/views/word_count/count-map.js", "examples/word_count/views/word_count/count-reduce.js", "examples/word_count/word_count.rb", "examples/word_count/word_count_query.rb", "examples/word_count/word_count_views.rb", "lib/couchrest", "lib/couchrest/commands", "lib/couchrest/commands/generate.rb", "lib/couchrest/commands/push.rb", "lib/couchrest/core", "lib/couchrest/core/database.rb", "lib/couchrest/core/design.rb", "lib/couchrest/core/document.rb", "lib/couchrest/core/response.rb", "lib/couchrest/core/server.rb", "lib/couchrest/core/view.rb", "lib/couchrest/helper", "lib/couchrest/helper/pager.rb", "lib/couchrest/helper/streamer.rb", "lib/couchrest/mixins", "lib/couchrest/mixins/attachments.rb", "lib/couchrest/mixins/callbacks.rb", "lib/couchrest/mixins/design_doc.rb", "lib/couchrest/mixins/document_queries.rb", "lib/couchrest/mixins/extended_attachments.rb", "lib/couchrest/mixins/extended_document_mixins.rb", "lib/couchrest/mixins/properties.rb", "lib/couchrest/mixins/validation.rb", "lib/couchrest/mixins/views.rb", "lib/couchrest/mixins.rb", "lib/couchrest/monkeypatches.rb", "lib/couchrest/more", "lib/couchrest/more/casted_model.rb", "lib/couchrest/more/extended_document.rb", "lib/couchrest/more/property.rb", "lib/couchrest/support", "lib/couchrest/support/blank.rb", "lib/couchrest/support/class.rb", "lib/couchrest/validation", "lib/couchrest/validation/auto_validate.rb", "lib/couchrest/validation/contextual_validators.rb", "lib/couchrest/validation/validation_errors.rb", "lib/couchrest/validation/validators", "lib/couchrest/validation/validators/absent_field_validator.rb", "lib/couchrest/validation/validators/confirmation_validator.rb", "lib/couchrest/validation/validators/format_validator.rb", "lib/couchrest/validation/validators/formats", "lib/couchrest/validation/validators/formats/email.rb", "lib/couchrest/validation/validators/formats/url.rb", "lib/couchrest/validation/validators/generic_validator.rb", "lib/couchrest/validation/validators/length_validator.rb", "lib/couchrest/validation/validators/method_validator.rb", "lib/couchrest/validation/validators/numeric_validator.rb", "lib/couchrest/validation/validators/required_field_validator.rb", "lib/couchrest.rb", "spec/couchrest", "spec/couchrest/core", "spec/couchrest/core/couchrest_spec.rb", "spec/couchrest/core/database_spec.rb", "spec/couchrest/core/design_spec.rb", "spec/couchrest/core/document_spec.rb", "spec/couchrest/core/server_spec.rb", "spec/couchrest/helpers", "spec/couchrest/helpers/pager_spec.rb", "spec/couchrest/helpers/streamer_spec.rb", "spec/couchrest/more", "spec/couchrest/more/casted_extended_doc_spec.rb", "spec/couchrest/more/casted_model_spec.rb", "spec/couchrest/more/extended_doc_attachment_spec.rb", "spec/couchrest/more/extended_doc_spec.rb", "spec/couchrest/more/extended_doc_view_spec.rb", "spec/couchrest/more/property_spec.rb", "spec/couchrest/support", "spec/couchrest/support/class_spec.rb", "spec/fixtures", "spec/fixtures/attachments", "spec/fixtures/attachments/couchdb.png", "spec/fixtures/attachments/README", "spec/fixtures/attachments/test.html", "spec/fixtures/couchapp", "spec/fixtures/couchapp/_attachments", "spec/fixtures/couchapp/_attachments/index.html", "spec/fixtures/couchapp/doc.json", "spec/fixtures/couchapp/foo", "spec/fixtures/couchapp/foo/bar.txt", "spec/fixtures/couchapp/foo/test.json", "spec/fixtures/couchapp/test.json", "spec/fixtures/couchapp/views", "spec/fixtures/couchapp/views/example-map.js", "spec/fixtures/couchapp/views/example-reduce.js", "spec/fixtures/couchapp-test", "spec/fixtures/couchapp-test/my-app", "spec/fixtures/couchapp-test/my-app/_attachments", "spec/fixtures/couchapp-test/my-app/_attachments/index.html", "spec/fixtures/couchapp-test/my-app/foo", "spec/fixtures/couchapp-test/my-app/foo/bar.txt", "spec/fixtures/couchapp-test/my-app/views", "spec/fixtures/couchapp-test/my-app/views/example-map.js", "spec/fixtures/couchapp-test/my-app/views/example-reduce.js", "spec/fixtures/more", "spec/fixtures/more/article.rb", "spec/fixtures/more/card.rb", "spec/fixtures/more/course.rb", "spec/fixtures/more/event.rb", "spec/fixtures/more/invoice.rb", "spec/fixtures/more/person.rb", "spec/fixtures/more/question.rb", "spec/fixtures/more/service.rb", "spec/fixtures/views", "spec/fixtures/views/lib.js", "spec/fixtures/views/test_view", "spec/fixtures/views/test_view/lib.js", "spec/fixtures/views/test_view/only-map.js", "spec/fixtures/views/test_view/test-map.js", "spec/fixtures/views/test_view/test-reduce.js", "spec/spec.opts", "spec/spec_helper.rb", "utils/remap.rb", "utils/subset.rb"] s.has_rdoc = true s.homepage = %q{http://github.com/jchris/couchrest} s.require_paths = ["lib"] diff --git a/lib/couchrest.rb b/lib/couchrest.rb index 7b1e3c9..ae0c946 100644 --- a/lib/couchrest.rb +++ b/lib/couchrest.rb @@ -28,7 +28,7 @@ require 'couchrest/monkeypatches' # = CouchDB, close to the metal module CouchRest - VERSION = '0.2' unless self.const_defined?("VERSION") + VERSION = '0.2.1' unless self.const_defined?("VERSION") autoload :Server, 'couchrest/core/server' autoload :Database, 'couchrest/core/database' diff --git a/lib/couchrest/mixins/callbacks.rb b/lib/couchrest/mixins/callbacks.rb index 28eca50..cd22a74 100644 --- a/lib/couchrest/mixins/callbacks.rb +++ b/lib/couchrest/mixins/callbacks.rb @@ -1,6 +1,7 @@ require File.join(File.dirname(__FILE__), '..', 'support', 'class') # Extracted from ActiveSupport::Callbacks written by Yehuda Katz +# http://github.com/wycats/rails/raw/abstract_controller/activesupport/lib/active_support/new_callbacks.rb # http://github.com/wycats/rails/raw/18b405f154868204a8f332888871041a7bad95e1/activesupport/lib/active_support/callbacks.rb module CouchRest @@ -11,7 +12,7 @@ module CouchRest # # Example: # class Storage - # include CouchRest::Callbacks + # include ActiveSupport::Callbacks # # define_callbacks :save # end @@ -45,7 +46,7 @@ module CouchRest # # Example: # class Storage - # include CouchRest::Callbacks + # include ActiveSupport::Callbacks # # define_callbacks :save # @@ -85,8 +86,8 @@ module CouchRest klass.extend ClassMethods end - def run_callbacks(kind, options = {}) - send("_run_#{kind}_callbacks") + def run_callbacks(kind, options = {}, &blk) + send("_run_#{kind}_callbacks", &blk) end class Callback @@ -166,9 +167,13 @@ module CouchRest # This will supply contents for before and around filters, and no # contents for after filters (for the forward pass). - def start(key = nil, object = nil) + 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 @@ -177,8 +182,14 @@ module CouchRest # if condition # before_save :filter_name, :if => :condition # filter_name # end - [@compiled_options[0], @filter, @compiled_options[1]].compact.join("\n") - elsif @compiled_options[0] + 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. # @@ -196,8 +207,8 @@ module CouchRest name = "_conditional_callback_#{@kind}_#{next_id}" txt = <<-RUBY_EVAL - def #{name} - #{@compiled_options[0]} + def #{name}(halted) + #{@compiled_options[0] || "if true"} && !halted #{@filter} do yield self end @@ -207,16 +218,16 @@ module CouchRest end RUBY_EVAL @klass.class_eval(txt) - "#{name} do" - else - "#{@filter} do" + "#{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, object = nil) + def end(key = nil, options = {}) + object = (options || {})[:object] + return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?") if @kind == :around || @kind == :after @@ -299,7 +310,7 @@ module CouchRest # This method_missing is supplied to catch callbacks with keys and create # the appropriate callback for future use. def method_missing(meth, *args, &blk) - if meth.to_s =~ /_run_(\w+)_(\w+)_(\w+)_callbacks/ + if meth.to_s =~ /_run__([\w:]+)__(\w+)__(\w+)__callbacks/ return self.class._create_and_run_keyed_callback($1, $2.to_sym, $3.to_sym, self, &blk) end super @@ -307,20 +318,26 @@ module CouchRest # An Array with a compile method class CallbackChain < Array - def compile(key = nil, object = nil) + def initialize(symbol) + @symbol = symbol + end + + def compile(key = nil, options = {}) method = [] + method << "halted = false" each do |callback| - method << callback.start(key, object) + method << callback.start(key, options) end - method << "yield self" + method << "yield self if block_given?" reverse_each do |callback| - method << callback.end(key, object) + method << callback.end(key, options) end method.compact.join("\n") end def clone(klass) - CallbackChain.new(map {|c| c.clone(klass)}) + chain = CallbackChain.new(@symbol) + chain.push(*map {|c| c.clone(klass)}) end end @@ -338,16 +355,18 @@ module CouchRest # 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, str, options) - self.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 + def _define_runner(symbol, str, options) + str = <<-RUBY_EVAL def _run_#{symbol}_callbacks(key = nil) if key - send("_run_\#{self.class}_#{symbol}_\#{key}_callbacks") { yield } + send("_run__\#{self.class.name.split("::").last}__#{symbol}__\#{key}__callbacks") { yield if block_given? } else #{str} end end RUBY_EVAL + + class_eval str, __FILE__, __LINE__ + 1 before_name, around_name, after_name = options.values_at(:before, :after, :around) @@ -359,15 +378,18 @@ module CouchRest def _create_and_run_keyed_callback(klass, kind, key, obj, &blk) @_keyed_callbacks ||= {} @_keyed_callbacks[[kind, key]] ||= begin - str = self.send("_#{kind}_callbacks").compile(key, obj) + str = self.send("_#{kind}_callbacks").compile(key, :object => obj, :terminator => self.send("_#{kind}_terminator")) + self.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 - def _run_#{klass}_#{kind}_#{key}_callbacks + def _run__#{klass.split("::").last}__#{kind}__#{key}__callbacks #{str} end RUBY_EVAL + true end - obj.send("_run_#{klass}_#{kind}_#{key}_callbacks", &blk) + + obj.send("_run__#{klass.split("::").last}__#{kind}__#{key}__callbacks", &blk) end # Define callbacks. @@ -402,20 +424,32 @@ module CouchRest # method that took into consideration the per_key conditions. This # is a speed improvement for ActionPack. def define_callbacks(*symbols) + terminator = symbols.pop if symbols.last.is_a?(String) symbols.each do |symbol| + self.class_inheritable_accessor("_#{symbol}_terminator") + self.send("_#{symbol}_terminator=", terminator) self.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 class_inheritable_accessor :_#{symbol}_callbacks - self._#{symbol}_callbacks = CallbackChain.new + self._#{symbol}_callbacks = CallbackChain.new(:#{symbol}) - def self.#{symbol}_callback(type, *filters, &blk) + def self.#{symbol}_callback(*filters, &blk) + type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before options = filters.last.is_a?(Hash) ? filters.pop : {} filters.unshift(blk) if block_given? - filters.map! {|f| Callback.new(f, type, options.dup, self, :#{symbol})} + + filters.map! do |filter| + # overrides parent class + self._#{symbol}_callbacks.delete_if {|c| c.matches?(type, :#{symbol}, filter)} + Callback.new(filter, type, options.dup, self, :#{symbol}) + end self._#{symbol}_callbacks.push(*filters) - _define_runner(:#{symbol}, self._#{symbol}_callbacks.compile, options) + _define_runner(:#{symbol}, + self._#{symbol}_callbacks.compile(nil, :terminator => _#{symbol}_terminator), + options) end - def self.skip_#{symbol}_callback(type, *filters, &blk) + def self.skip_#{symbol}_callback(*filters, &blk) + type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before options = filters.last.is_a?(Hash) ? filters.pop : {} filters.unshift(blk) if block_given? filters.each do |filter| @@ -428,15 +462,22 @@ module CouchRest else self._#{symbol}_callbacks.delete(filter) end - _define_runner(:#{symbol}, self._#{symbol}_callbacks.compile, options) + _define_runner(:#{symbol}, + self._#{symbol}_callbacks.compile(nil, :terminator => _#{symbol}_terminator), + options) end end + def self.reset_#{symbol}_callbacks + self._#{symbol}_callbacks = CallbackChain.new(:#{symbol}) + _define_runner(:#{symbol}, self._#{symbol}_callbacks.compile, {}) + end + self.#{symbol}_callback(:before) RUBY_EVAL end end end end -end +end \ No newline at end of file diff --git a/spec/couchrest/more/extended_doc_view_spec.rb b/spec/couchrest/more/extended_doc_view_spec.rb index 46e8534..9045021 100644 --- a/spec/couchrest/more/extended_doc_view_spec.rb +++ b/spec/couchrest/more/extended_doc_view_spec.rb @@ -167,24 +167,24 @@ describe "ExtendedDocument views" do # TODO: moved to Design, delete describe "adding a view" do before(:each) do + reset_test_db! Article.by_date - @design_docs = Article.database.documents :startkey => "_design/", - :endkey => "_design/\u9999" + @design_docs = Article.database.documents :startkey => "_design/", :endkey => "_design/\u9999" end it "should not create a design doc on view definition" do Article.view_by :created_at - newdocs = Article.database.documents :startkey => "_design/", - :endkey => "_design/\u9999" + newdocs = Article.database.documents :startkey => "_design/", :endkey => "_design/\u9999" newdocs["rows"].length.should == @design_docs["rows"].length end - it "should create a new design document on view access" do + it "should create a new version of the design document on view access" do + old_design_doc = Article.database.documents(:key => @design_docs["rows"].first["key"], :include_docs => true)["rows"][0]["doc"] Article.view_by :updated_at Article.by_updated_at - newdocs = Article.database.documents :startkey => "_design/", - :endkey => "_design/\u9999" - # puts @design_docs.inspect - # puts newdocs.inspect - newdocs["rows"].length.should == @design_docs["rows"].length + 1 + newdocs = Article.database.documents({:startkey => "_design/", :endkey => "_design/\u9999"}) + + doc = Article.database.documents(:key => @design_docs["rows"].first["key"], :include_docs => true)["rows"][0]["doc"] + doc["_rev"].should_not == old_design_doc["_rev"] + doc["views"].keys.should include("by_updated_at") end end @@ -195,10 +195,9 @@ describe "ExtendedDocument views" do Article.by_field end it "should clean them up" do + ddocs = Article.all_design_doc_versions Article.view_by :stream Article.by_stream - ddocs = Article.all_design_doc_versions - ddocs["rows"].length.should > 1 Article.cleanup_design_docs! ddocs = Article.all_design_doc_versions ddocs["rows"].length.should == 1