Permalink
Browse files

Refactored hooks code. Hooks are now inheritable, and can be defined …

…by supplying a block or a method name, or by overriding the hook instance method. Hook chains can now be broken by returning false.
  • Loading branch information...
ciconia committed Jan 1, 2008
1 parent 6b25eab commit 0b9b536cab6ef8da434f09c4d0132db61a0c59ce
Showing with 278 additions and 213 deletions.
  1. +4 −0 model/CHANGELOG
  2. +1 −5 model/lib/sequel_model.rb
  3. +47 −114 model/lib/sequel_model/hooks.rb
  4. +9 −8 model/lib/sequel_model/record.rb
  5. +213 −74 model/spec/hooks_spec.rb
  6. +4 −12 model/spec/model_spec.rb
View
@@ -1,3 +1,7 @@
+=== SVN
+
+* Refactored hooks code. Hooks are now inheritable, and can be defined by supplying a block or a method name, or by overriding the hook instance method. Hook chains can now be broken by returning false.
+
=== 0.1 (2007-12-30)
* Moved model code from sequel into separate model sub-project.
@@ -287,11 +287,7 @@ def self.delete_all
# Like delete_all, but invokes before_destroy and after_destroy hooks if used.
def self.destroy_all
- if has_hooks?(:before_destroy) || has_hooks?(:after_destroy)
- dataset.destroy
- else
- dataset.delete
- end
+ dataset.destroy
end
def self.is_dataset_magic_method?(m)
@@ -1,122 +1,55 @@
module Sequel
class Model
- # This Hash translates verbs to methodnames used in chain manipulation
- # methods.
- VERB_TO_METHOD = {:prepend => :unshift, :append => :push}
-
- # Returns @hooks which is an instance of Hash with its hook identifier
- # (Symbol) as key and the chain of hooks (Array) as value.
- #
- # If it is not already set it'll be with an empty set of hooks.
- # This behaviour will change in the future to allow inheritance.
- #
- # For the time being, you should be able to do:
- #
- # class A < Sequel::Model(:a)
- # before_save { 'Do something...' }
- # end
- #
- # class B < A
- # @hooks = superclass.hooks.clone
- # before_save # => [#<Proc:0x0000c6e8@(example.rb):123>]
- # end
- #
- # In this case you should remember that the clone doesn't create any new
- # instances of your chains, so if you change the chain here it changes in
- # its superclass, too.
- def self.hooks
- @hooks ||= Hash.new { |h, k| h[k] = [] }
- end
-
- # Adds block to chain of Hooks for <tt>:before_save</tt>.
- # It can either be prepended (default) or appended.
- #
- # Returns the chain itself.
- #
- # Valid verbs are <tt>:prepend</tt> and <tt>:append</tt>.
- def self.before_save(verb = :prepend, &block)
- hooks[:before_save].send VERB_TO_METHOD.fetch(verb), block if block
- hooks[:before_save]
- end
- # Adds block to chain of Hooks for <tt>:before_create</tt>.
- # It can either be prepended (default) or appended.
- #
- # Returns the chain itself.
- #
- # Valid verbs are <tt>:prepend</tt> and <tt>:append</tt>.
- def self.before_create(verb = :prepend, &block)
- hooks[:before_create].send VERB_TO_METHOD.fetch(verb), block if block
- hooks[:before_create]
- end
- # Adds block to chain of Hooks for <tt>:before_update</tt>.
- # It can either be prepended (default) or appended.
- #
- # Returns the chain itself.
- #
- # Valid verbs are <tt>:prepend</tt> and <tt>:append</tt>.
- def self.before_update(verb = :prepend, &block)
- hooks[:before_update].send VERB_TO_METHOD.fetch(verb), block if block
- hooks[:before_update]
- end
- # Adds block to chain of Hooks for <tt>:before_destroy</tt>.
- # It can either be prepended (default) or appended.
- #
- # Returns the chain itself.
- #
- # Valid verbs are <tt>:prepend</tt> and <tt>:append</tt>.
- def self.before_destroy(verb = :prepend, &block)
- hooks[:before_destroy].send VERB_TO_METHOD.fetch(verb), block if block
- hooks[:before_destroy]
- end
-
- # Adds block to chain of Hooks for <tt>:after_save</tt>.
- # It can either be prepended or appended (default).
- #
- # Returns the chain itself.
- #
- # Valid verbs are <tt>:prepend</tt> and <tt>:append</tt>.
- def self.after_save(verb = :append, &block)
- hooks[:after_save].send VERB_TO_METHOD.fetch(verb), block if block
- hooks[:after_save]
- end
- # Adds block to chain of Hooks for <tt>:after_create</tt>.
- # It can either be prepended or appended (default).
- #
- # Returns the chain itself.
- #
- # Valid verbs are <tt>:prepend</tt> and <tt>:append</tt>.
- def self.after_create(verb = :append, &block)
- hooks[:after_create].send VERB_TO_METHOD.fetch(verb), block if block
- hooks[:after_create]
- end
- # Adds block to chain of Hooks for <tt>:after_update</tt>.
- # It can either be prepended or appended (default).
- #
- # Returns the chain itself.
- #
- # Valid verbs are <tt>:prepend</tt> and <tt>:append</tt>.
- def self.after_update(verb = :append, &block)
- hooks[:after_update].send VERB_TO_METHOD.fetch(verb), block if block
- hooks[:after_update]
- end
- # Adds block to chain of Hooks for <tt>:after_destroy</tt>.
- # It can either be prepended or appended (default).
- #
- # Returns the chain itself.
- #
- # Valid verbs are <tt>:prepend</tt> and <tt>:append</tt>.
- def self.after_destroy(verb = :append, &block)
- hooks[:after_destroy].send VERB_TO_METHOD.fetch(verb), block if block
- hooks[:after_destroy]
+ HOOKS = [
+ :after_initialize,
+ :before_create,
+ :after_create,
+ :before_update,
+ :after_update,
+ :before_save,
+ :after_save,
+ :before_destroy,
+ :after_destroy
+ ]
+
+ def self.def_hook_method(m) #:nodoc:
+ # write hook def
+ hook_def = "
+ def self.#{m}(method = nil, &block)
+ unless block
+ (raise SequelError, 'No hook method specified') unless method
+ block = proc {send method}
+ end
+ add_hook(#{m.inspect}, &block)
+ end
+ "
+
+ instance_eval(hook_def)
end
-
- # Evaluates specified chain of Hooks through <tt>instance_eval</tt>.
- def run_hooks(key)
- model.hooks[key].each {|h| instance_eval(&h)}
+
+ HOOKS.each {|h| define_method(h) {}}
+ HOOKS.each {|h| def_hook_method(h)}
+
+ # Returns the hooks hash for the model class.
+ def self.hooks
+ @hooks ||= Hash.new {|h, k| h[k] = []}
end
+ # Returns true if the model class or any of its ancestors have defined
+ # hooks for the given hook key. Notice that this method cannot detect
+ # hooks defined using overridden methods.
def self.has_hooks?(key)
- hooks[key] && !hooks[key].empty?
+ has = hooks[key] && !hooks[key].empty?
+ has || ((self != Model) && superclass.has_hooks?(key))
+ end
+
+ def self.add_hook(hook, &block) #:nodoc:
+ chain = hooks[hook]
+ chain << block
+ define_method(hook) do
+ return false if super == false
+ chain.each {|h| break false if instance_eval(&h) == false}
+ end
end
end
-end
+end
@@ -192,6 +192,7 @@ def initialize(values = {}, new_record = false, &block)
end
block[self] if block
+ after_initialize
end
# Returns true if the current instance represents a new record.
@@ -208,9 +209,9 @@ def exists?
# Creates or updates the associated record. This method can also
# accept a list of specific columns to update.
def save(*columns)
- run_hooks(:before_save)
+ before_save
if @new
- run_hooks(:before_create)
+ before_create
iid = model.dataset.insert(@values)
# if we have a regular primary key and it's not set in @values,
# we assume it's the last inserted id
@@ -222,19 +223,19 @@ def save(*columns)
refresh
end
@new = false
- run_hooks(:after_create)
+ after_create
else
- run_hooks(:before_update)
+ before_update
if columns.empty?
this.update(@values)
@changed_columns = []
else # update only the specified columns
this.update(@values.reject {|k, v| !columns.include?(k)})
@changed_columns.reject! {|c| columns.include?(c)}
end
- run_hooks(:after_update)
+ after_update
end
- run_hooks(:after_save)
+ after_save
self
end
@@ -260,9 +261,9 @@ def refresh
# Like delete but runs hooks before and after delete.
def destroy
db.transaction do
- run_hooks(:before_destroy)
+ before_destroy
delete
- run_hooks(:after_destroy)
+ after_destroy
end
end
Oops, something went wrong.

0 comments on commit 0b9b536

Please sign in to comment.