[activecouch commit] r64 - in trunk: . active_couch active_couch/support spec/base

0 views
Skip to first unread message

codesite...@google.com

unread,
Jan 22, 2008, 6:05:20 AM1/22/08
to activ...@googlegroups.com
Author: arun.thampi
Date: Tue Jan 22 03:04:24 2008
New Revision: 64

Added:
trunk/active_couch/callbacks.rb
trunk/spec/base/after_delete_spec.rb
trunk/spec/base/after_save_spec.rb
trunk/spec/base/before_save_spec.rb
Modified:
trunk/active_couch.rb
trunk/active_couch/base.rb
trunk/active_couch/support/extensions.rb

Log:
- Working commit for callbacks


Modified: trunk/active_couch.rb
==============================================================================
--- trunk/active_couch.rb (original)
+++ trunk/active_couch.rb Tue Jan 22 03:04:24 2008
@@ -10,4 +10,9 @@
require 'active_couch/errors'
require 'active_couch/base'
require 'active_couch/connection'
-require 'active_couch/migrations'
\ No newline at end of file
+require 'active_couch/migrations'
+require 'active_couch/callbacks'
+
+#ActiveCouch::Base.class_eval do
+# include ActiveCouch::Callbacks
+#end
\ No newline at end of file

Modified: trunk/active_couch/base.rb
==============================================================================
--- trunk/active_couch/base.rb (original)
+++ trunk/active_couch/base.rb Tue Jan 22 03:04:24 2008
@@ -1,11 +1,12 @@
module ActiveCouch
class Base
- SPECIAL_MEMBERS = %w(attributes associations connection)
+ SPECIAL_MEMBERS = %w(attributes associations connection callbacks)
DEFAULT_ATTRIBUTES = %w(id rev)
-
+
def initialize(params = {})
# Object instance variable
- @attributes, @associations, @connection, klass_atts,
klass_assocs = {}, {}, self.class.connection, self.class.attributes, self.class.associations
+ @attributes = {}; @associations = {}; @callbacks = Hash.new;
@connection = self.class.connection
+ klass_atts = self.class.attributes; klass_assocs =
self.class.associations; klass_callbacks = self.class.callbacks
# ActiveCouch::Connection object will be readable in every
# object instantiated from a subclass of ActiveCouch::Base
SPECIAL_MEMBERS.each do |k|
@@ -18,11 +19,6 @@
self.instance_eval "def #{k}=(val); attributes[:#{k}].value =
val; end"
end

- DEFAULT_ATTRIBUTES.each do |x|
- self.instance_eval "def #{x}; _#{x}; end"
- self.instance_eval "def #{x}=(val); self._#{x}=(val); end"
- end
-
klass_assocs.each_key do |k|
@associations[k] =
HasManyAssociation.new(klass_assocs[k].name, :class => klass_assocs[k].klass)
self.instance_eval "def #{k}; associations[:#{k}].container; end"
@@ -30,6 +26,16 @@
# from the class
self.instance_eval "def add_#{Inflector.singularize(k)}(val);
associations[:#{k}].push(val); end"
end
+
+ klass_callbacks.each_key do |k|
+ @callbacks[k] = klass_callbacks[k].dup
+ end
+
+ DEFAULT_ATTRIBUTES.each do |x|
+ self.instance_eval "def #{x}; _#{x}; end"
+ self.instance_eval "def #{x}=(val); self._#{x}=(val); end"
+ end
+
# Set any instance variables if any, which are present in the
params hash
from_hash(params)
end
@@ -357,10 +363,15 @@
end

def inherited(subklass)
+ subklass.class_eval do
+ include ActiveCouch::Callbacks
+ end
+
# TODO: Need a cleaner way to do this
subklass.instance_variable_set "@attributes", { :_id =>
Attribute.new(:_id, :with_default_value => nil),
:_rev =>
Attribute.new(:_rev, :with_default_value => nil) }
subklass.instance_variable_set "@associations", {}
+ subklass.instance_variable_set "@callbacks", Hash.new([])
subklass.instance_variable_set "@connections", nil

SPECIAL_MEMBERS.each do |k|

Added: trunk/active_couch/callbacks.rb
==============================================================================
--- (empty file)
+++ trunk/active_couch/callbacks.rb Tue Jan 22 03:04:24 2008
@@ -0,0 +1,81 @@
+module ActiveCouch
+ module Callbacks
+ CALLBACKS = %w(before_save after_save before_delete after_delete)
+
+ def self.included(base)
+ # Alias methods which will have callbacks, (for now only save
and delete).
+ # This creates 2 sets of methods: save_with_callbacks, save_without_callbacks,
+ # delete_with_callbacks, delete_without_callbacks
+ #
+ # save_without_callbacks and delete_without_callbacks have the
same behaviour
+ # as the save and delete methods, respectively
+ [:save, :delete].each do |method|
+ base.send :alias_method_chain, method, :callbacks
+ end
+
+ CALLBACKS.each do |method|
+ base.class_eval <<-"end_eval"
+ def self.#{method}(*callbacks, &block)
+ callbacks << block if block_given?
+ # Assumes that the default value for the callbacks hash in the
+ # including class is an empty array
+ self.callbacks[#{method.to_sym.inspect}] =
self.callbacks[#{method.to_sym.inspect}] + callbacks
+ end
+ end_eval
+ end
+ end # end method self.included
+
+ def before_save() end
+
+ def after_save() end
+
+ def before_delete() end
+
+ def after_delete() end
+
+ def save_with_callbacks
+ return false if callback(:before_save) == false
+ result = save_without_callbacks
+ callback(:after_save)
+ result
+ end
+ private :save_with_callbacks
+
+ def delete_with_callbacks
+ return false if callback(:before_delete) == false
+ result = delete_without_callbacks
+ callback(:after_delete)
+ result
+ end
+ private :delete_with_callbacks
+
+ private
+ def callback(method)
+ callbacks_for(method).each do |callback|
+ result = case callback
+ when Symbol
+ self.send(callback)
+ when String
+ eval(callback, binding)
+ when Proc, Method
+ callback.call(self)
+ else
+ if callback.respond_to?(method)
+ callback.send(method, self)
+ else
+ raise ActiveCouchError, "Callbacks must be a symbol
denoting the method to call, a string to be evaluated, a block to be
invoked, or an object responding to the callback method."
+ end
+ end
+ return false if result == false
+ end
+
+ result = send(method) if respond_to?(method)
+
+ return result
+ end
+
+ def callbacks_for(method)
+ self.class.callbacks[method.to_sym]
+ end
+ end # end module Callbacks
+end # end module ActiveCouch
\ No newline at end of file

Modified: trunk/active_couch/support/extensions.rb
==============================================================================
--- trunk/active_couch/support/extensions.rb (original)
+++ trunk/active_couch/support/extensions.rb Tue Jan 22 03:04:24 2008
@@ -46,6 +46,47 @@
parent_name = name.split('::')[0..-2] * '::'
parent_name.empty? ? Object : Inflector.constantize(parent_name)
end
+
+ # Encapsulates the common pattern of:
+ #
+ # alias_method :foo_without_feature, :foo
+ # alias_method :foo, :foo_with_feature
+ #
+ # With this, you simply do:
+ #
+ # alias_method_chain :foo, :feature
+ #
+ # And both aliases are set up for you.
+ #
+ # Query and bang methods (foo?, foo!) keep the same punctuation:
+ #
+ # alias_method_chain :foo?, :feature
+ #
+ # is equivalent to
+ #
+ # alias_method :foo_without_feature?, :foo?
+ # alias_method :foo?, :foo_with_feature?
+ #
+ # so you can safely chain foo, foo?, and foo! with the same feature.
+ def alias_method_chain(target, feature)
+ # Strip out punctuation on predicates or bang methods since
+ # e.g. target?_without_feature is not a valid method name.
+ aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
+ yield(aliased_target, punctuation) if block_given?
+
+ with_method, without_method = "#{aliased_target}_with_#{feature}#{punctuation}", "#{aliased_target}_without_#{feature}#{punctuation}"
+
+ alias_method without_method, target
+ alias_method target, with_method
+
+ case
+ when public_method_defined?(without_method)
+ public target
+ when protected_method_defined?(without_method)
+ protected target
+ when private_method_defined?(without_method)
+ private target
+ end
+ end
end
-
end

Added: trunk/spec/base/after_delete_spec.rb
==============================================================================
--- (empty file)
+++ trunk/spec/base/after_delete_spec.rb Tue Jan 22 03:04:24 2008
@@ -0,0 +1,48 @@
+require File.dirname(__FILE__) + '/../spec_helper.rb'
+
+describe "ActiveCouch::Base #after_delete method" do
+ before(:each) do
+ class Person < ActiveCouch::Base
+ site 'http://localhost:5984/'
+ has :name
+ has :delete_status
+ # Callback, after the actual save happens
+ after_delete :print_message
+
+ private
+ def print_message
+ self.delete_status = "Deleted McLovin"
+ end
+ end
+ # Migration needed for this spec
+ ActiveCouch::Migrator.create_database('http://localhost:5984/', 'people')
+ end
+
+ after(:each) do
+ # Migration needed for this spec
+ ActiveCouch::Migrator.delete_database('http://localhost:5984/', 'people')
+ end
+
+ it "should have a class method called after_delete" do
+ Person.methods.include?('after_delete').should == true
+ end
+
+ it "should call the method specified as an argument to after_delete,
*after_delete* the object has been deleted from CouchDB" do
+ p = Person.new(:name => 'McLovin')
+ # First, it must be empty...
+ p.delete_status.should == ""
+ # then it must be saved...
+ p.save
+ # Delete should return true...
+ p.delete.should == true
+ p.delete_status.should == "Deleted McLovin"
+ end
+end
+
+describe "ActiveCouch::Base #after_save method with a block as
argument" do
+ it "should execute the block as a param to after_save"
+end
+
+describe "ActiveCouch::Base #after_save method with an Object (which
implements after_save) as argument" do
+ it "should call before_save in the object passed as a param to after_save"
+end

Added: trunk/spec/base/after_save_spec.rb
==============================================================================
--- (empty file)
+++ trunk/spec/base/after_save_spec.rb Tue Jan 22 03:04:24 2008
@@ -0,0 +1,47 @@
+require File.dirname(__FILE__) + '/../spec_helper.rb'
+
+describe "ActiveCouch::Base #after_save method" do
+ before(:each) do
+ class Person < ActiveCouch::Base
+ site 'http://localhost:5984/'
+ has :name
+ has :saved_revision
+ # Callback, after the actual save happens
+ after_save :print_message
+
+ private
+ def print_message
+ # This can only be set if the object has been saved.
+ # Otherwise it will be nil
+ self.saved_revision = self.rev
+ end
+ end
+ # Migration needed for this spec
+ ActiveCouch::Migrator.create_database('http://localhost:5984/', 'people')
+ end
+
+ after(:each) do
+ # Migration needed for this spec
+ ActiveCouch::Migrator.delete_database('http://localhost:5984/', 'people')
+ end
+
+ it "should have a class method called after_save" do
+ Person.methods.include?('after_save').should == true
+ end
+
+ it "should call the method specified as an argument to after_save,
*after* the object has been persisted in CouchDB" do
+ p = Person.new(:name => 'McLovin')
+ # Save should return true...
+ p.save.should == true
+ # ...and saved_id must not be nil (because it is set to the revision)
+ p.saved_revision.should_not == nil
+ end
+end
+
+describe "ActiveCouch::Base #after_save method with a block as
argument" do
+ it "should execute the block as a param to after_save"
+end
+
+describe "ActiveCouch::Base #after_save method with an Object (which
implements after_save) as argument" do
+ it "should call before_save in the object passed as a param to after_save"
+end

Added: trunk/spec/base/before_save_spec.rb
==============================================================================
--- (empty file)
+++ trunk/spec/base/before_save_spec.rb Tue Jan 22 03:04:24 2008
@@ -0,0 +1,45 @@
+require File.dirname(__FILE__) + '/../spec_helper.rb'
+
+describe "ActiveCouch::Base #before_save method with a Symbol as
argument" do
+ before(:each) do
+ class Person < ActiveCouch::Base
+ site 'http://localhost:5984/'
+ has :first_name
+ has :last_name
+ # Callback, before the actual save happens
+ before_save :set_first_name
+
+ private
+ def set_first_name
+ self.first_name = 'Seth'
+ end
+ end
+ # Migration needed for this spec
+ ActiveCouch::Migrator.create_database('http://localhost:5984/', 'people')
+ end
+
+ after(:each) do
+ # Migration needed for this spec
+ ActiveCouch::Migrator.delete_database('http://localhost:5984/', 'people')
+ end
+
+ it "should have a class method called before_save" do
+ Person.methods.include?('before_save').should == true
+ end
+
+ it "should call the method specified as an argument to before_save,
*before* the object has been persisted in CouchDB" do
+ p = Person.new(:last_name => 'McLovin')
+ # Save should return true...
+ p.save.should == true
+ # ...and the first_name must be set to 'Seth'
+ p.first_name.should == 'Seth'
+ end
+end
+
+describe "ActiveCouch::Base #before_save method with a block as
argument" do
+ it "should execute the block as a param to before_save"
+end
+
+describe "ActiveCouch::Base #before_save method with an Object (which
implements before_save) as argument" do
+ it "should call before_save in the object passed as a param to before_save"
+end
\ No newline at end of file

codesite...@google.com

unread,
Jan 22, 2008, 6:05:20 AM1/22/08
to activ...@googlegroups.com
Reply all
Reply to author
Forward
0 new messages