Integrated Yehuda's new callback code from rails

This commit is contained in:
Peter Gumeson 2009-06-07 02:57:22 -07:00
parent 1c6e073b47
commit dc4787e905
8 changed files with 228 additions and 221 deletions

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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?