Integrated Yehuda's new callback code from rails
This commit is contained in:
parent
1c6e073b47
commit
dc4787e905
12
README.md
12
README.md
|
@ -69,20 +69,20 @@ CouchRest::Model has been deprecated and replaced by CouchRest::ExtendedDocument
|
||||||
### Callbacks
|
### Callbacks
|
||||||
|
|
||||||
`CouchRest::ExtendedDocuments` instances have 4 callbacks already defined for you:
|
`CouchRest::ExtendedDocuments` instances have 4 callbacks already defined for you:
|
||||||
`validate_callback`, `create_callback`, `save_callback`, `update_callback` and `destroy_callback`
|
`:validate`, `:create`, `:save`, `:update` and `:destroy`
|
||||||
|
|
||||||
`CouchRest::CastedModel` instances have 1 callback already defined for you:
|
`CouchRest::CastedModel` instances have 1 callback already defined for you:
|
||||||
`validate_callback`
|
`:validate`
|
||||||
|
|
||||||
Define your callback as follows:
|
Define your callback as follows:
|
||||||
|
|
||||||
save_callback :before, :generate_slug_from_name
|
set_callback :save, :before, :generate_slug_from_name
|
||||||
|
|
||||||
CouchRest uses a mixin you can find in lib/mixins/callbacks which is extracted from Rails 3, here are some simple usage examples:
|
CouchRest uses a mixin you can find in lib/mixins/callbacks which is extracted from Rails 3, here are some simple usage examples:
|
||||||
|
|
||||||
save_callback :before, :before_method
|
set_callback :save, :before, :before_method
|
||||||
save_callback :after, :after_method, :if => :condition
|
set_callback :save, :after, :after_method, :if => :condition
|
||||||
save_callback :around {|r| stuff; yield; stuff }
|
set_callback :save, :around {|r| stuff; yield; stuff }
|
||||||
|
|
||||||
Check the mixin or the ExtendedDocument class to see how to implement your own callbacks.
|
Check the mixin or the ExtendedDocument class to see how to implement your own callbacks.
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
require File.join(File.dirname(__FILE__), '..', 'support', 'class')
|
require File.join(File.dirname(__FILE__), '..', 'support', 'class')
|
||||||
|
|
||||||
# Extracted from ActiveSupport::Callbacks written by Yehuda Katz
|
# Extracted from ActiveSupport::NewCallbacks written by Yehuda Katz
|
||||||
# http://github.com/wycats/rails/raw/abstract_controller/activesupport/lib/active_support/new_callbacks.rb
|
# http://github.com/rails/rails/raw/d6e4113c83a9d55be6f2af247da2cecaa855f43b/activesupport/lib/active_support/new_callbacks.rb
|
||||||
# http://github.com/wycats/rails/raw/18b405f154868204a8f332888871041a7bad95e1/activesupport/lib/active_support/callbacks.rb
|
# http://github.com/rails/rails/commit/1126a85aed576402d978e6f76eb393b6baaa9541
|
||||||
|
|
||||||
module CouchRest
|
module CouchRest
|
||||||
# Callbacks are hooks into the lifecycle of an object that allow you to trigger logic
|
# Callbacks are hooks into the lifecycle of an object that allow you to trigger logic
|
||||||
|
@ -85,19 +85,18 @@ module CouchRest
|
||||||
def self.included(klass)
|
def self.included(klass)
|
||||||
klass.extend ClassMethods
|
klass.extend ClassMethods
|
||||||
end
|
end
|
||||||
|
|
||||||
def run_callbacks(kind, options = {}, &blk)
|
def run_callbacks(kind, options = {}, &blk)
|
||||||
send("_run_#{kind}_callbacks", &blk)
|
send("_run_#{kind}_callbacks", &blk)
|
||||||
end
|
end
|
||||||
|
|
||||||
class Callback
|
class Callback
|
||||||
@@_callback_sequence = 0
|
@@_callback_sequence = 0
|
||||||
|
|
||||||
attr_accessor :filter, :kind, :name, :options, :per_key, :klass
|
attr_accessor :filter, :kind, :name, :options, :per_key, :klass
|
||||||
def initialize(filter, kind, options, klass, name)
|
def initialize(filter, kind, options, klass)
|
||||||
@kind, @klass = kind, klass
|
@kind, @klass = kind, klass
|
||||||
@name = name
|
|
||||||
|
|
||||||
normalize_options!(options)
|
normalize_options!(options)
|
||||||
|
|
||||||
@per_key = options.delete(:per_key)
|
@per_key = options.delete(:per_key)
|
||||||
|
@ -108,7 +107,7 @@ module CouchRest
|
||||||
|
|
||||||
_compile_per_key_options
|
_compile_per_key_options
|
||||||
end
|
end
|
||||||
|
|
||||||
def clone(klass)
|
def clone(klass)
|
||||||
obj = super()
|
obj = super()
|
||||||
obj.klass = klass
|
obj.klass = klass
|
||||||
|
@ -120,7 +119,7 @@ module CouchRest
|
||||||
obj.options[:unless] = @options[:unless].dup
|
obj.options[:unless] = @options[:unless].dup
|
||||||
obj
|
obj
|
||||||
end
|
end
|
||||||
|
|
||||||
def normalize_options!(options)
|
def normalize_options!(options)
|
||||||
options[:if] = Array(options[:if])
|
options[:if] = Array(options[:if])
|
||||||
options[:unless] = Array(options[:unless])
|
options[:unless] = Array(options[:unless])
|
||||||
|
@ -129,14 +128,13 @@ module CouchRest
|
||||||
options[:per_key][:if] = Array(options[:per_key][:if])
|
options[:per_key][:if] = Array(options[:per_key][:if])
|
||||||
options[:per_key][:unless] = Array(options[:per_key][:unless])
|
options[:per_key][:unless] = Array(options[:per_key][:unless])
|
||||||
end
|
end
|
||||||
|
|
||||||
def next_id
|
def next_id
|
||||||
@@_callback_sequence += 1
|
@@_callback_sequence += 1
|
||||||
end
|
end
|
||||||
|
|
||||||
def matches?(_kind, _name, _filter)
|
def matches?(_kind, _filter)
|
||||||
@kind == _kind &&
|
@kind == _kind &&
|
||||||
@name == _name &&
|
|
||||||
@filter == _filter
|
@filter == _filter
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -144,11 +142,11 @@ module CouchRest
|
||||||
filter_options[:if].push(new_options[:unless]) if new_options.key?(:unless)
|
filter_options[:if].push(new_options[:unless]) if new_options.key?(:unless)
|
||||||
filter_options[:unless].push(new_options[:if]) if new_options.key?(:if)
|
filter_options[:unless].push(new_options[:if]) if new_options.key?(:if)
|
||||||
end
|
end
|
||||||
|
|
||||||
def recompile!(_options, _per_key)
|
def recompile!(_options, _per_key)
|
||||||
_update_filter(self.options, _options)
|
_update_filter(self.options, _options)
|
||||||
_update_filter(self.per_key, _per_key)
|
_update_filter(self.per_key, _per_key)
|
||||||
|
|
||||||
@callback_id = next_id
|
@callback_id = next_id
|
||||||
@filter = _compile_filter(@raw_filter)
|
@filter = _compile_filter(@raw_filter)
|
||||||
@compiled_options = _compile_options(@options)
|
@compiled_options = _compile_options(@options)
|
||||||
|
@ -164,19 +162,19 @@ module CouchRest
|
||||||
end
|
end
|
||||||
RUBY_EVAL
|
RUBY_EVAL
|
||||||
end
|
end
|
||||||
|
|
||||||
# This will supply contents for before and around filters, and no
|
# This will supply contents for before and around filters, and no
|
||||||
# contents for after filters (for the forward pass).
|
# contents for after filters (for the forward pass).
|
||||||
def start(key = nil, options = {})
|
def start(key = nil, options = {})
|
||||||
object, terminator = (options || {}).values_at(:object, :terminator)
|
object, terminator = (options || {}).values_at(:object, :terminator)
|
||||||
|
|
||||||
return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
|
return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
|
||||||
|
|
||||||
terminator ||= false
|
terminator ||= false
|
||||||
|
|
||||||
# options[0] is the compiled form of supplied conditions
|
# options[0] is the compiled form of supplied conditions
|
||||||
# options[1] is the "end" for the conditional
|
# options[1] is the "end" for the conditional
|
||||||
|
|
||||||
if @kind == :before || @kind == :around
|
if @kind == :before || @kind == :around
|
||||||
if @kind == :before
|
if @kind == :before
|
||||||
# if condition # before_save :filter_name, :if => :condition
|
# if condition # before_save :filter_name, :if => :condition
|
||||||
|
@ -204,7 +202,7 @@ module CouchRest
|
||||||
# yield self
|
# yield self
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
|
|
||||||
name = "_conditional_callback_#{@kind}_#{next_id}"
|
name = "_conditional_callback_#{@kind}_#{next_id}"
|
||||||
txt = <<-RUBY_EVAL
|
txt = <<-RUBY_EVAL
|
||||||
def #{name}(halted)
|
def #{name}(halted)
|
||||||
|
@ -222,14 +220,14 @@ module CouchRest
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# This will supply contents for around and after filters, but not
|
# This will supply contents for around and after filters, but not
|
||||||
# before filters (for the backward pass).
|
# before filters (for the backward pass).
|
||||||
def end(key = nil, options = {})
|
def end(key = nil, options = {})
|
||||||
object = (options || {})[:object]
|
object = (options || {})[:object]
|
||||||
|
|
||||||
return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
|
return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
|
||||||
|
|
||||||
if @kind == :around || @kind == :after
|
if @kind == :around || @kind == :after
|
||||||
# if condition # after_save :filter_name, :if => :condition
|
# if condition # after_save :filter_name, :if => :condition
|
||||||
# filter_name
|
# filter_name
|
||||||
|
@ -241,27 +239,27 @@ module CouchRest
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
# Options support the same options as filters themselves (and support
|
# Options support the same options as filters themselves (and support
|
||||||
# symbols, string, procs, and objects), so compile a conditional
|
# symbols, string, procs, and objects), so compile a conditional
|
||||||
# expression based on the options
|
# expression based on the options
|
||||||
def _compile_options(options)
|
def _compile_options(options)
|
||||||
return [] if options[:if].empty? && options[:unless].empty?
|
return [] if options[:if].empty? && options[:unless].empty?
|
||||||
|
|
||||||
conditions = []
|
conditions = []
|
||||||
|
|
||||||
unless options[:if].empty?
|
unless options[:if].empty?
|
||||||
conditions << Array(_compile_filter(options[:if]))
|
conditions << Array(_compile_filter(options[:if]))
|
||||||
end
|
end
|
||||||
|
|
||||||
unless options[:unless].empty?
|
unless options[:unless].empty?
|
||||||
conditions << Array(_compile_filter(options[:unless])).map {|f| "!#{f}"}
|
conditions << Array(_compile_filter(options[:unless])).map {|f| "!#{f}"}
|
||||||
end
|
end
|
||||||
|
|
||||||
["if #{conditions.flatten.join(" && ")}", "end"]
|
["if #{conditions.flatten.join(" && ")}", "end"]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Filters support:
|
# Filters support:
|
||||||
# Arrays:: Used in conditions. This is used to specify
|
# Arrays:: Used in conditions. This is used to specify
|
||||||
# multiple conditions. Used internally to
|
# multiple conditions. Used internally to
|
||||||
|
@ -289,7 +287,22 @@ module CouchRest
|
||||||
filter
|
filter
|
||||||
when Proc
|
when Proc
|
||||||
@klass.send(:define_method, method_name, &filter)
|
@klass.send(:define_method, method_name, &filter)
|
||||||
method_name << (filter.arity == 1 ? "(self)" : "")
|
method_name << case filter.arity
|
||||||
|
when 1
|
||||||
|
"(self)"
|
||||||
|
when 2
|
||||||
|
" self, Proc.new "
|
||||||
|
else
|
||||||
|
""
|
||||||
|
end
|
||||||
|
when Method
|
||||||
|
@klass.send(:define_method, "#{method_name}_method") { filter }
|
||||||
|
@klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
||||||
|
def #{method_name}(&blk)
|
||||||
|
#{method_name}_method.call(self, &blk)
|
||||||
|
end
|
||||||
|
RUBY_EVAL
|
||||||
|
method_name
|
||||||
when String
|
when String
|
||||||
@klass.class_eval <<-RUBY_EVAL
|
@klass.class_eval <<-RUBY_EVAL
|
||||||
def #{method_name}
|
def #{method_name}
|
||||||
|
@ -298,52 +311,65 @@ module CouchRest
|
||||||
RUBY_EVAL
|
RUBY_EVAL
|
||||||
method_name
|
method_name
|
||||||
else
|
else
|
||||||
kind, name = @kind, @name
|
kind = @kind
|
||||||
@klass.send(:define_method, method_name) do
|
@klass.send(:define_method, "#{method_name}_object") { filter }
|
||||||
filter.send("#{kind}_#{name}", self)
|
|
||||||
end
|
_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
|
method_name
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def _normalize_legacy_filter(kind, filter)
|
||||||
|
if !filter.respond_to?(kind) && filter.respond_to?(:filter)
|
||||||
|
filter.metaclass.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
|
end
|
||||||
|
|
||||||
# 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/
|
|
||||||
return self.class._create_and_run_keyed_callback($1, $2.to_sym, $3.to_sym, self, &blk)
|
|
||||||
end
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
# An Array with a compile method
|
# An Array with a compile method
|
||||||
class CallbackChain < Array
|
class CallbackChain < Array
|
||||||
def initialize(symbol)
|
def initialize(symbol)
|
||||||
@symbol = symbol
|
@symbol = symbol
|
||||||
end
|
end
|
||||||
|
|
||||||
def compile(key = nil, options = {})
|
def compile(key = nil, options = {})
|
||||||
method = []
|
method = []
|
||||||
method << "halted = false"
|
method << "halted = false"
|
||||||
each do |callback|
|
each do |callback|
|
||||||
method << callback.start(key, options)
|
method << callback.start(key, options)
|
||||||
end
|
end
|
||||||
method << "yield self if block_given?"
|
method << "yield self if block_given? && !halted"
|
||||||
reverse_each do |callback|
|
reverse_each do |callback|
|
||||||
method << callback.end(key, options)
|
method << callback.end(key, options)
|
||||||
end
|
end
|
||||||
method.compact.join("\n")
|
method.compact.join("\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
def clone(klass)
|
def clone(klass)
|
||||||
chain = CallbackChain.new(@symbol)
|
chain = CallbackChain.new(@symbol)
|
||||||
chain.push(*map {|c| c.clone(klass)})
|
chain.push(*map {|c| c.clone(klass)})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
CHAINS = {:before => :before, :around => :before, :after => :after} unless self.const_defined?("CHAINS")
|
CHAINS = {:before => :before, :around => :before, :after => :after}
|
||||||
|
|
||||||
# Make the _run_save_callbacks method. The generated method takes
|
# 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
|
# 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.
|
# in order, yield the block, and then run the after filters.
|
||||||
|
@ -355,43 +381,44 @@ module CouchRest
|
||||||
# The _run_save_callbacks method can optionally take a key, which
|
# The _run_save_callbacks method can optionally take a key, which
|
||||||
# will be used to compile an optimized callback method for each
|
# will be used to compile an optimized callback method for each
|
||||||
# key. See #define_callbacks for more information.
|
# key. See #define_callbacks for more information.
|
||||||
def _define_runner(symbol, str, options)
|
def _define_runner(symbol, callbacks)
|
||||||
str = <<-RUBY_EVAL
|
body = callbacks.compile(nil, :terminator => send("_#{symbol}_terminator"))
|
||||||
def _run_#{symbol}_callbacks(key = nil)
|
|
||||||
|
body, line = <<-RUBY_EVAL, __LINE__
|
||||||
|
def _run_#{symbol}_callbacks(key = nil, &blk)
|
||||||
if key
|
if key
|
||||||
send("_run__\#{self.class.name.split("::").last}__#{symbol}__\#{key}__callbacks") { yield if block_given? }
|
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
|
else
|
||||||
#{str}
|
#{body}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
RUBY_EVAL
|
RUBY_EVAL
|
||||||
|
|
||||||
class_eval str, __FILE__, __LINE__ + 1
|
undef_method "_run_#{symbol}_callbacks" if method_defined?("_run_#{symbol}_callbacks")
|
||||||
|
class_eval body, __FILE__, line
|
||||||
before_name, around_name, after_name =
|
|
||||||
options.values_at(:before, :after, :around)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# This is called the first time a callback is called with a particular
|
# This is called the first time a callback is called with a particular
|
||||||
# key. It creates a new callback method for the key, calculating
|
# key. It creates a new callback method for the key, calculating
|
||||||
# which callbacks can be omitted because of per_key conditions.
|
# which callbacks can be omitted because of per_key conditions.
|
||||||
def _create_and_run_keyed_callback(klass, kind, key, obj, &blk)
|
def _create_keyed_callback(name, kind, obj, &blk)
|
||||||
@_keyed_callbacks ||= {}
|
@_keyed_callbacks ||= {}
|
||||||
@_keyed_callbacks[[kind, key]] ||= begin
|
@_keyed_callbacks[name] ||= begin
|
||||||
str = self.send("_#{kind}_callbacks").compile(key, :object => obj, :terminator => self.send("_#{kind}_terminator"))
|
str = send("_#{kind}_callbacks").
|
||||||
|
compile(name, :object => obj, :terminator => send("_#{kind}_terminator"))
|
||||||
|
|
||||||
|
class_eval "def #{name}() #{str} end", __FILE__, __LINE__
|
||||||
|
|
||||||
self.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
|
||||||
def _run__#{klass.split("::").last}__#{kind}__#{key}__callbacks
|
|
||||||
#{str}
|
|
||||||
end
|
|
||||||
RUBY_EVAL
|
|
||||||
|
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
obj.send("_run__#{klass.split("::").last}__#{kind}__#{key}__callbacks", &blk)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Define callbacks.
|
# Define callbacks.
|
||||||
#
|
#
|
||||||
# Creates a <name>_callback method that you can use to add callbacks.
|
# Creates a <name>_callback method that you can use to add callbacks.
|
||||||
|
@ -423,58 +450,62 @@ module CouchRest
|
||||||
# In that case, each action_name would get its own compiled callback
|
# In that case, each action_name would get its own compiled callback
|
||||||
# method that took into consideration the per_key conditions. This
|
# method that took into consideration the per_key conditions. This
|
||||||
# is a speed improvement for ActionPack.
|
# 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
|
||||||
|
|
||||||
|
responded = self.respond_to?(":_#{name}_callbacks")
|
||||||
|
|
||||||
|
callbacks = send("_#{name}_callbacks")
|
||||||
|
yield callbacks, type, filters, options if block_given?
|
||||||
|
|
||||||
|
_define_runner(name, callbacks)
|
||||||
|
end
|
||||||
|
|
||||||
|
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}_callbacks=", 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)
|
def define_callbacks(*symbols)
|
||||||
terminator = symbols.pop if symbols.last.is_a?(String)
|
terminator = symbols.pop if symbols.last.is_a?(String)
|
||||||
symbols.each do |symbol|
|
symbols.each do |symbol|
|
||||||
self.extlib_inheritable_accessor("_#{symbol}_terminator")
|
extlib_inheritable_accessor("_#{symbol}_terminator") { terminator }
|
||||||
self.send("_#{symbol}_terminator=", terminator)
|
|
||||||
self.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
|
||||||
extlib_inheritable_accessor :_#{symbol}_callbacks
|
|
||||||
self._#{symbol}_callbacks = CallbackChain.new(:#{symbol})
|
|
||||||
|
|
||||||
def self.#{symbol}_callback(*filters, &blk)
|
extlib_inheritable_accessor("_#{symbol}_callbacks") do
|
||||||
type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before
|
CallbackChain.new(symbol)
|
||||||
options = filters.last.is_a?(Hash) ? filters.pop : {}
|
end
|
||||||
filters.unshift(blk) if block_given?
|
|
||||||
|
self.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
||||||
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(nil, :terminator => _#{symbol}_terminator),
|
|
||||||
options)
|
|
||||||
end
|
|
||||||
|
|
||||||
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|
|
|
||||||
self._#{symbol}_callbacks = self._#{symbol}_callbacks.clone(self)
|
|
||||||
|
|
||||||
filter = self._#{symbol}_callbacks.find {|c| c.matches?(type, :#{symbol}, filter) }
|
|
||||||
per_key = options[:per_key] || {}
|
|
||||||
if filter
|
|
||||||
filter.recompile!(options, per_key)
|
|
||||||
else
|
|
||||||
self._#{symbol}_callbacks.delete(filter)
|
|
||||||
end
|
|
||||||
_define_runner(:#{symbol},
|
|
||||||
self._#{symbol}_callbacks.compile(nil, :terminator => _#{symbol}_terminator),
|
|
||||||
options)
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.reset_#{symbol}_callbacks
|
def self.reset_#{symbol}_callbacks
|
||||||
self._#{symbol}_callbacks = CallbackChain.new(:#{symbol})
|
update_callbacks(:#{symbol})
|
||||||
_define_runner(:#{symbol}, self._#{symbol}_callbacks.compile, {})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
self.#{symbol}_callback(:before)
|
self.set_callback(:#{symbol}, :before)
|
||||||
RUBY_EVAL
|
RUBY_EVAL
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -77,7 +77,7 @@ module CouchRest
|
||||||
base.class_eval <<-EOS, __FILE__, __LINE__
|
base.class_eval <<-EOS, __FILE__, __LINE__
|
||||||
define_callbacks :validate
|
define_callbacks :validate
|
||||||
if method_defined?(:_run_save_callbacks)
|
if method_defined?(:_run_save_callbacks)
|
||||||
save_callback :before, :check_validations
|
set_callback :save, :before, :check_validations
|
||||||
end
|
end
|
||||||
EOS
|
EOS
|
||||||
base.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
base.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
||||||
|
|
|
@ -62,7 +62,7 @@ module CouchRest
|
||||||
property(:updated_at, :read_only => true, :cast_as => 'Time', :auto_validation => false)
|
property(:updated_at, :read_only => true, :cast_as => 'Time', :auto_validation => false)
|
||||||
property(:created_at, :read_only => true, :cast_as => 'Time', :auto_validation => false)
|
property(:created_at, :read_only => true, :cast_as => 'Time', :auto_validation => false)
|
||||||
|
|
||||||
save_callback :before do |object|
|
set_callback :save, :before do |object|
|
||||||
object['updated_at'] = Time.now
|
object['updated_at'] = Time.now
|
||||||
object['created_at'] = object['updated_at'] if object.new?
|
object['created_at'] = object['updated_at'] if object.new?
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,96 +1,55 @@
|
||||||
# Copyright (c) 2004-2008 David Heinemeier Hansson
|
# Extracted From
|
||||||
|
# http://github.com/rails/rails/commit/971e2438d98326c994ec6d3ef8e37b7e868ed6e2
|
||||||
|
|
||||||
|
# Extends the class object with class and instance accessors for class attributes,
|
||||||
|
# just like the native attr* accessors for instance attributes.
|
||||||
#
|
#
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining
|
# class Person
|
||||||
# a copy of this software and associated documentation files (the
|
# cattr_accessor :hair_colors
|
||||||
# "Software"), to deal in the Software without restriction, including
|
# end
|
||||||
# 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
|
# Person.hair_colors = [:brown, :black, :blonde, :red]
|
||||||
# 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.
|
|
||||||
|
|
||||||
# Allows attributes to be shared within an inheritance hierarchy, but where
|
|
||||||
# each descendant gets a copy of their parents' attributes, instead of just a
|
|
||||||
# pointer to the same. This means that the child can add elements to, for
|
|
||||||
# example, an array without those additions being shared with either their
|
|
||||||
# parent, siblings, or children, which is unlike the regular class-level
|
|
||||||
# attributes that are shared across the entire hierarchy.
|
|
||||||
class Class
|
class Class
|
||||||
# Defines class-level and instance-level attribute reader.
|
|
||||||
#
|
|
||||||
# @param *syms<Array> Array of attributes to define reader for.
|
|
||||||
# @return <Array[#to_s]> List of attributes that were made into cattr_readers
|
|
||||||
#
|
|
||||||
# @api public
|
|
||||||
#
|
|
||||||
# @todo Is this inconsistent in that it does not allow you to prevent
|
|
||||||
# an instance_reader via :instance_reader => false
|
|
||||||
def cattr_reader(*syms)
|
def cattr_reader(*syms)
|
||||||
syms.flatten.each do |sym|
|
syms.flatten.each do |sym|
|
||||||
next if sym.is_a?(Hash)
|
next if sym.is_a?(Hash)
|
||||||
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
||||||
unless defined? @@#{sym}
|
unless defined? @@#{sym} # unless defined? @@hair_colors
|
||||||
@@#{sym} = nil
|
@@#{sym} = nil # @@hair_colors = nil
|
||||||
end
|
end # end
|
||||||
|
#
|
||||||
def self.#{sym}
|
def self.#{sym} # def self.hair_colors
|
||||||
@@#{sym}
|
@@#{sym} # @@hair_colors
|
||||||
end
|
end # end
|
||||||
|
#
|
||||||
def #{sym}
|
def #{sym} # def hair_colors
|
||||||
@@#{sym}
|
@@#{sym} # @@hair_colors
|
||||||
end
|
end # end
|
||||||
RUBY
|
EOS
|
||||||
end
|
end
|
||||||
end unless Class.respond_to?(:cattr_reader)
|
end unless Class.respond_to?(:cattr_reader)
|
||||||
|
|
||||||
# Defines class-level (and optionally instance-level) attribute writer.
|
|
||||||
#
|
|
||||||
# @param <Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to define writer for.
|
|
||||||
# @option syms :instance_writer<Boolean> if true, instance-level attribute writer is defined.
|
|
||||||
# @return <Array[#to_s]> List of attributes that were made into cattr_writers
|
|
||||||
#
|
|
||||||
# @api public
|
|
||||||
def cattr_writer(*syms)
|
def cattr_writer(*syms)
|
||||||
options = syms.last.is_a?(Hash) ? syms.pop : {}
|
options = syms.extract_options!
|
||||||
syms.flatten.each do |sym|
|
syms.flatten.each do |sym|
|
||||||
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
||||||
unless defined? @@#{sym}
|
unless defined? @@#{sym} # unless defined? @@hair_colors
|
||||||
@@#{sym} = nil
|
@@#{sym} = nil # @@hair_colors = nil
|
||||||
end
|
end # end
|
||||||
|
#
|
||||||
def self.#{sym}=(obj)
|
def self.#{sym}=(obj) # def self.hair_colors=(obj)
|
||||||
@@#{sym} = obj
|
@@#{sym} = obj # @@hair_colors = obj
|
||||||
end
|
end # end
|
||||||
RUBY
|
#
|
||||||
|
#{" #
|
||||||
unless options[:instance_writer] == false
|
def #{sym}=(obj) # def hair_colors=(obj)
|
||||||
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
@@#{sym} = obj # @@hair_colors = obj
|
||||||
def #{sym}=(obj)
|
end # end
|
||||||
@@#{sym} = obj
|
" unless options[:instance_writer] == false } # # instance writer above is generated unless options[:instance_writer] == false
|
||||||
end
|
EOS
|
||||||
RUBY
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end unless Class.respond_to?(:cattr_writer)
|
end unless Class.respond_to?(:cattr_writer)
|
||||||
|
|
||||||
# Defines class-level (and optionally instance-level) attribute accessor.
|
|
||||||
#
|
|
||||||
# @param *syms<Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to define accessor for.
|
|
||||||
# @option syms :instance_writer<Boolean> if true, instance-level attribute writer is defined.
|
|
||||||
# @return <Array[#to_s]> List of attributes that were made into accessors
|
|
||||||
#
|
|
||||||
# @api public
|
|
||||||
def cattr_accessor(*syms)
|
def cattr_accessor(*syms)
|
||||||
cattr_reader(*syms)
|
cattr_reader(*syms)
|
||||||
cattr_writer(*syms)
|
cattr_writer(*syms)
|
||||||
|
@ -156,6 +115,8 @@ class Class
|
||||||
def #{ivar}=(obj) self.class.#{ivar} = obj end
|
def #{ivar}=(obj) self.class.#{ivar} = obj end
|
||||||
RUBY
|
RUBY
|
||||||
end
|
end
|
||||||
|
|
||||||
|
self.send("#{ivar}=", yield) if block_given?
|
||||||
end
|
end
|
||||||
end unless Class.respond_to?(:extlib_inheritable_writer)
|
end unless Class.respond_to?(:extlib_inheritable_writer)
|
||||||
|
|
||||||
|
@ -168,9 +129,24 @@ class Class
|
||||||
# @return <Array[#to_s]> An Array of attributes turned into inheritable accessors.
|
# @return <Array[#to_s]> An Array of attributes turned into inheritable accessors.
|
||||||
#
|
#
|
||||||
# @api public
|
# @api public
|
||||||
def extlib_inheritable_accessor(*syms)
|
def extlib_inheritable_accessor(*syms, &block)
|
||||||
extlib_inheritable_reader(*syms)
|
extlib_inheritable_reader(*syms)
|
||||||
extlib_inheritable_writer(*syms)
|
extlib_inheritable_writer(*syms, &block)
|
||||||
end unless Class.respond_to?(:extlib_inheritable_accessor)
|
end unless Class.respond_to?(:extlib_inheritable_accessor)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class Array
|
||||||
|
# Extracts options from a set of arguments. Removes and returns the last
|
||||||
|
# element in the array if it's a hash, otherwise returns a blank hash.
|
||||||
|
#
|
||||||
|
# def options(*args)
|
||||||
|
# args.extract_options!
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# options(1, 2) # => {}
|
||||||
|
# options(1, 2, :a => :b) # => {:a=>:b}
|
||||||
|
def extract_options!
|
||||||
|
last.is_a?(::Hash) ? pop : {}
|
||||||
|
end unless Array.respond_to?(:extract_options!)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
|
@ -35,10 +35,10 @@ class WithCastedCallBackModel < Hash
|
||||||
property :run_before_validate
|
property :run_before_validate
|
||||||
property :run_after_validate
|
property :run_after_validate
|
||||||
|
|
||||||
validate_callback :before do |object|
|
set_callback :validate, :before do |object|
|
||||||
object.run_before_validate = true
|
object.run_before_validate = true
|
||||||
end
|
end
|
||||||
validate_callback :after do |object|
|
set_callback :validate, :after do |object|
|
||||||
object.run_after_validate = true
|
object.run_after_validate = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -29,28 +29,28 @@ describe "ExtendedDocument" do
|
||||||
property :run_before_update
|
property :run_before_update
|
||||||
property :run_after_update
|
property :run_after_update
|
||||||
|
|
||||||
validate_callback :before do |object|
|
set_callback :validate, :before do |object|
|
||||||
object.run_before_validate = true
|
object.run_before_validate = true
|
||||||
end
|
end
|
||||||
validate_callback :after do |object|
|
set_callback :validate, :after do |object|
|
||||||
object.run_after_validate = true
|
object.run_after_validate = true
|
||||||
end
|
end
|
||||||
save_callback :before do |object|
|
set_callback :save, :before do |object|
|
||||||
object.run_before_save = true
|
object.run_before_save = true
|
||||||
end
|
end
|
||||||
save_callback :after do |object|
|
set_callback :save, :after do |object|
|
||||||
object.run_after_save = true
|
object.run_after_save = true
|
||||||
end
|
end
|
||||||
create_callback :before do |object|
|
set_callback :create, :before do |object|
|
||||||
object.run_before_create = true
|
object.run_before_create = true
|
||||||
end
|
end
|
||||||
create_callback :after do |object|
|
set_callback :create, :after do |object|
|
||||||
object.run_after_create = true
|
object.run_after_create = true
|
||||||
end
|
end
|
||||||
update_callback :before do |object|
|
set_callback :update, :before do |object|
|
||||||
object.run_before_update = true
|
object.run_before_update = true
|
||||||
end
|
end
|
||||||
update_callback :after do |object|
|
set_callback :update, :after do |object|
|
||||||
object.run_after_update = true
|
object.run_after_update = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
2
spec/fixtures/more/article.rb
vendored
2
spec/fixtures/more/article.rb
vendored
|
@ -26,7 +26,7 @@ class Article < CouchRest::ExtendedDocument
|
||||||
|
|
||||||
timestamps!
|
timestamps!
|
||||||
|
|
||||||
save_callback :before, :generate_slug_from_title
|
set_callback :save, :before, :generate_slug_from_title
|
||||||
|
|
||||||
def generate_slug_from_title
|
def generate_slug_from_title
|
||||||
self['slug'] = title.downcase.gsub(/[^a-z0-9]/,'-').squeeze('-').gsub(/^\-|\-$/,'') if new?
|
self['slug'] = title.downcase.gsub(/[^a-z0-9]/,'-').squeeze('-').gsub(/^\-|\-$/,'') if new?
|
||||||
|
|
Loading…
Reference in a new issue