merged Peter's stuff
This commit is contained in:
commit
64a51b73ae
20 changed files with 944 additions and 334 deletions
|
@ -23,9 +23,10 @@ module CouchRest
|
|||
end
|
||||
|
||||
# returns true if the document has never been saved
|
||||
def new_document?
|
||||
def new?
|
||||
!rev
|
||||
end
|
||||
alias :new_document? :new?
|
||||
|
||||
# Saves the document to the db using create or update. Also runs the :save
|
||||
# callbacks. Sets the <tt>_id</tt> and <tt>_rev</tt> fields based on
|
||||
|
@ -63,7 +64,7 @@ module CouchRest
|
|||
|
||||
# Returns the CouchDB uri for the document
|
||||
def uri(append_rev = false)
|
||||
return nil if new_document?
|
||||
return nil if new?
|
||||
couch_uri = "http://#{database.root}/#{CGI.escape(id)}"
|
||||
if append_rev == true
|
||||
couch_uri << "?rev=#{rev}"
|
||||
|
|
|
@ -1,8 +1,29 @@
|
|||
require File.join(File.dirname(__FILE__), '..', 'support', 'class')
|
||||
# Copyright (c) 2006-2009 David Heinemeier Hansson
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# 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
|
||||
# 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.
|
||||
#
|
||||
# Extracted from ActiveSupport::NewCallbacks written by Yehuda Katz
|
||||
# http://github.com/rails/rails/raw/d6e4113c83a9d55be6f2af247da2cecaa855f43b/activesupport/lib/active_support/new_callbacks.rb
|
||||
# http://github.com/rails/rails/commit/1126a85aed576402d978e6f76eb393b6baaa9541
|
||||
|
||||
# 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
|
||||
require File.join(File.dirname(__FILE__), '..', 'support', 'class')
|
||||
|
||||
module CouchRest
|
||||
# Callbacks are hooks into the lifecycle of an object that allow you to trigger logic
|
||||
|
@ -85,19 +106,18 @@ module CouchRest
|
|||
def self.included(klass)
|
||||
klass.extend ClassMethods
|
||||
end
|
||||
|
||||
|
||||
def run_callbacks(kind, options = {}, &blk)
|
||||
send("_run_#{kind}_callbacks", &blk)
|
||||
end
|
||||
|
||||
|
||||
class Callback
|
||||
@@_callback_sequence = 0
|
||||
|
||||
|
||||
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
|
||||
@name = name
|
||||
|
||||
|
||||
normalize_options!(options)
|
||||
|
||||
@per_key = options.delete(:per_key)
|
||||
|
@ -108,7 +128,7 @@ module CouchRest
|
|||
|
||||
_compile_per_key_options
|
||||
end
|
||||
|
||||
|
||||
def clone(klass)
|
||||
obj = super()
|
||||
obj.klass = klass
|
||||
|
@ -120,23 +140,22 @@ module CouchRest
|
|||
obj.options[:unless] = @options[:unless].dup
|
||||
obj
|
||||
end
|
||||
|
||||
|
||||
def normalize_options!(options)
|
||||
options[:if] = Array(options[:if])
|
||||
options[:unless] = Array(options[:unless])
|
||||
options[:if] = Array.wrap(options[:if])
|
||||
options[:unless] = Array.wrap(options[:unless])
|
||||
|
||||
options[:per_key] ||= {}
|
||||
options[:per_key][:if] = Array(options[:per_key][:if])
|
||||
options[:per_key][:unless] = Array(options[:per_key][:unless])
|
||||
options[:per_key][:if] = Array.wrap(options[:per_key][:if])
|
||||
options[:per_key][:unless] = Array.wrap(options[:per_key][:unless])
|
||||
end
|
||||
|
||||
|
||||
def next_id
|
||||
@@_callback_sequence += 1
|
||||
end
|
||||
|
||||
def matches?(_kind, _name, _filter)
|
||||
|
||||
def matches?(_kind, _filter)
|
||||
@kind == _kind &&
|
||||
@name == _name &&
|
||||
@filter == _filter
|
||||
end
|
||||
|
||||
|
@ -144,11 +163,11 @@ module CouchRest
|
|||
filter_options[:if].push(new_options[:unless]) if new_options.key?(:unless)
|
||||
filter_options[:unless].push(new_options[:if]) if new_options.key?(:if)
|
||||
end
|
||||
|
||||
|
||||
def recompile!(_options, _per_key)
|
||||
_update_filter(self.options, _options)
|
||||
_update_filter(self.per_key, _per_key)
|
||||
|
||||
|
||||
@callback_id = next_id
|
||||
@filter = _compile_filter(@raw_filter)
|
||||
@compiled_options = _compile_options(@options)
|
||||
|
@ -164,19 +183,19 @@ module CouchRest
|
|||
end
|
||||
RUBY_EVAL
|
||||
end
|
||||
|
||||
|
||||
# This will supply contents for before and around filters, and no
|
||||
# contents for after filters (for the forward pass).
|
||||
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
|
||||
|
||||
|
||||
if @kind == :before || @kind == :around
|
||||
if @kind == :before
|
||||
# if condition # before_save :filter_name, :if => :condition
|
||||
|
@ -185,9 +204,10 @@ module CouchRest
|
|||
filter = <<-RUBY_EVAL
|
||||
unless halted
|
||||
result = #{@filter}
|
||||
halted ||= (#{terminator})
|
||||
halted = (#{terminator})
|
||||
end
|
||||
RUBY_EVAL
|
||||
|
||||
[@compiled_options[0], filter, @compiled_options[1]].compact.join("\n")
|
||||
else
|
||||
# Compile around filters with conditions into proxy methods
|
||||
|
@ -204,9 +224,9 @@ module CouchRest
|
|||
# yield self
|
||||
# end
|
||||
# end
|
||||
|
||||
|
||||
name = "_conditional_callback_#{@kind}_#{next_id}"
|
||||
txt = <<-RUBY_EVAL
|
||||
txt, line = <<-RUBY_EVAL, __LINE__ + 1
|
||||
def #{name}(halted)
|
||||
#{@compiled_options[0] || "if true"} && !halted
|
||||
#{@filter} do
|
||||
|
@ -217,19 +237,19 @@ module CouchRest
|
|||
end
|
||||
end
|
||||
RUBY_EVAL
|
||||
@klass.class_eval(txt)
|
||||
@klass.class_eval(txt, __FILE__, line)
|
||||
"#{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, options = {})
|
||||
object = (options || {})[:object]
|
||||
|
||||
|
||||
return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
|
||||
|
||||
|
||||
if @kind == :around || @kind == :after
|
||||
# if condition # after_save :filter_name, :if => :condition
|
||||
# filter_name
|
||||
|
@ -241,27 +261,27 @@ module CouchRest
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
# Options support the same options as filters themselves (and support
|
||||
# symbols, string, procs, and objects), so compile a conditional
|
||||
# expression based on the options
|
||||
def _compile_options(options)
|
||||
return [] if options[:if].empty? && options[:unless].empty?
|
||||
|
||||
|
||||
conditions = []
|
||||
|
||||
|
||||
unless options[:if].empty?
|
||||
conditions << Array(_compile_filter(options[:if]))
|
||||
conditions << Array.wrap(_compile_filter(options[:if]))
|
||||
end
|
||||
|
||||
|
||||
unless options[:unless].empty?
|
||||
conditions << Array(_compile_filter(options[:unless])).map {|f| "!#{f}"}
|
||||
conditions << Array.wrap(_compile_filter(options[:unless])).map {|f| "!#{f}"}
|
||||
end
|
||||
|
||||
|
||||
["if #{conditions.flatten.join(" && ")}", "end"]
|
||||
end
|
||||
|
||||
|
||||
# Filters support:
|
||||
# Arrays:: Used in conditions. This is used to specify
|
||||
# multiple conditions. Used internally to
|
||||
|
@ -287,63 +307,72 @@ module CouchRest
|
|||
filter.map {|f| _compile_filter(f)}
|
||||
when Symbol
|
||||
filter
|
||||
when String
|
||||
"(#{filter})"
|
||||
when Proc
|
||||
@klass.send(:define_method, method_name, &filter)
|
||||
method_name << (filter.arity == 1 ? "(self)" : "")
|
||||
when String
|
||||
@klass.class_eval <<-RUBY_EVAL
|
||||
def #{method_name}
|
||||
#{filter}
|
||||
return method_name if filter.arity == 0
|
||||
|
||||
method_name << (filter.arity == 1 ? "(self)" : " self, Proc.new ")
|
||||
else
|
||||
@klass.send(:define_method, "#{method_name}_object") { filter }
|
||||
|
||||
_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
|
||||
else
|
||||
kind, name = @kind, @name
|
||||
@klass.send(:define_method, method_name) do
|
||||
filter.send("#{kind}_#{name}", self)
|
||||
end
|
||||
|
||||
method_name
|
||||
end
|
||||
end
|
||||
|
||||
def _normalize_legacy_filter(kind, filter)
|
||||
if !filter.respond_to?(kind) && filter.respond_to?(:filter)
|
||||
filter.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
|
||||
|
||||
# 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
|
||||
class CallbackChain < Array
|
||||
def initialize(symbol)
|
||||
@symbol = symbol
|
||||
end
|
||||
|
||||
|
||||
def compile(key = nil, options = {})
|
||||
method = []
|
||||
method << "halted = false"
|
||||
each do |callback|
|
||||
method << callback.start(key, options)
|
||||
end
|
||||
method << "yield self if block_given?"
|
||||
method << "yield self if block_given? && !halted"
|
||||
reverse_each do |callback|
|
||||
method << callback.end(key, options)
|
||||
end
|
||||
method.compact.join("\n")
|
||||
end
|
||||
|
||||
|
||||
def clone(klass)
|
||||
chain = CallbackChain.new(@symbol)
|
||||
chain.push(*map {|c| c.clone(klass)})
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
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
|
||||
# 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.
|
||||
|
@ -355,43 +384,45 @@ 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)
|
||||
str = <<-RUBY_EVAL
|
||||
def _run_#{symbol}_callbacks(key = nil)
|
||||
def _define_runner(symbol)
|
||||
body = send("_#{symbol}_callback").
|
||||
compile(nil, :terminator => send("_#{symbol}_terminator"))
|
||||
|
||||
body, line = <<-RUBY_EVAL, __LINE__ + 1
|
||||
def _run_#{symbol}_callbacks(key = nil, &blk)
|
||||
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
|
||||
#{str}
|
||||
#{body}
|
||||
end
|
||||
end
|
||||
RUBY_EVAL
|
||||
|
||||
class_eval str, __FILE__, __LINE__ + 1
|
||||
|
||||
before_name, around_name, after_name =
|
||||
options.values_at(:before, :after, :around)
|
||||
|
||||
undef_method "_run_#{symbol}_callbacks" if method_defined?("_run_#{symbol}_callbacks")
|
||||
class_eval body, __FILE__, line
|
||||
end
|
||||
|
||||
|
||||
# This is called the first time a callback is called with a particular
|
||||
# key. It creates a new callback method for the key, calculating
|
||||
# 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[[kind, key]] ||= begin
|
||||
str = self.send("_#{kind}_callbacks").compile(key, :object => obj, :terminator => self.send("_#{kind}_terminator"))
|
||||
@_keyed_callbacks[name] ||= begin
|
||||
str = send("_#{kind}_callback").
|
||||
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
|
||||
end
|
||||
|
||||
obj.send("_run__#{klass.split("::").last}__#{kind}__#{key}__callbacks", &blk)
|
||||
end
|
||||
|
||||
|
||||
# Define callbacks.
|
||||
#
|
||||
# Creates a <name>_callback method that you can use to add callbacks.
|
||||
|
@ -423,59 +454,77 @@ module CouchRest
|
|||
# In that case, each action_name would get its own compiled callback
|
||||
# method that took into consideration the per_key conditions. This
|
||||
# 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
|
||||
|
||||
callbacks = send("_#{name}_callback")
|
||||
yield callbacks, type, filters, options if block_given?
|
||||
|
||||
_define_runner(name)
|
||||
end
|
||||
|
||||
alias_method :_reset_callbacks, :_update_callbacks
|
||||
|
||||
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}_callback=", 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)
|
||||
terminator = symbols.pop if symbols.last.is_a?(String)
|
||||
symbols.each do |symbol|
|
||||
self.extlib_inheritable_accessor("_#{symbol}_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})
|
||||
extlib_inheritable_accessor("_#{symbol}_terminator") { terminator }
|
||||
|
||||
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! 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)
|
||||
extlib_inheritable_accessor("_#{symbol}_callback") do
|
||||
CallbackChain.new(symbol)
|
||||
end
|
||||
|
||||
_define_runner(symbol)
|
||||
|
||||
# Define more convenient callback methods
|
||||
# set_callback(:save, :before) becomes before_save
|
||||
[:before, :after, :around].each do |filter|
|
||||
self.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
||||
def self.#{filter}_#{symbol}(*symbols, &blk)
|
||||
_alias_callbacks(symbols, blk) do |callback, options|
|
||||
set_callback(:#{symbol}, :#{filter}, callback, options)
|
||||
end
|
||||
_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
|
||||
RUBY_EVAL
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def _alias_callbacks(callbacks, block)
|
||||
options = callbacks.last.is_a?(Hash) ? callbacks.pop : {}
|
||||
callbacks.push(block) if block
|
||||
callbacks.each do |callback|
|
||||
yield callback, options
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -27,7 +27,7 @@ module CouchRest
|
|||
class IncludeError < StandardError; end
|
||||
|
||||
def self.included(base)
|
||||
base.class_eval <<-EOS, __FILE__, __LINE__
|
||||
base.class_eval <<-EOS, __FILE__, __LINE__ + 1
|
||||
extlib_inheritable_accessor(:properties) unless self.respond_to?(:properties)
|
||||
self.properties ||= []
|
||||
EOS
|
||||
|
@ -36,7 +36,7 @@ module CouchRest
|
|||
end
|
||||
|
||||
def apply_defaults
|
||||
return if self.respond_to?(:new_document?) && (new_document? == false)
|
||||
return if self.respond_to?(:new?) && (new? == false)
|
||||
return unless self.class.respond_to?(:properties)
|
||||
return if self.class.properties.empty?
|
||||
# TODO: cache the default object
|
||||
|
@ -56,50 +56,76 @@ module CouchRest
|
|||
def cast_keys
|
||||
return unless self.class.properties
|
||||
self.class.properties.each do |property|
|
||||
next unless property.casted
|
||||
key = self.has_key?(property.name) ? property.name : property.name.to_sym
|
||||
# Don't cast the property unless it has a value
|
||||
next unless self[key]
|
||||
target = property.type
|
||||
if target.is_a?(Array)
|
||||
klass = ::CouchRest.constantize(target[0])
|
||||
self[property.name] = self[key].collect do |value|
|
||||
# Auto parse Time objects
|
||||
obj = ( (property.init_method == 'new') && klass == Time) ? Time.parse(value) : klass.send(property.init_method, value)
|
||||
obj.casted_by = self if obj.respond_to?(:casted_by)
|
||||
obj
|
||||
end
|
||||
else
|
||||
# Auto parse Time objects
|
||||
self[property.name] = if ((property.init_method == 'new') && target == 'Time')
|
||||
# Using custom time parsing method because Ruby's default method is toooo slow
|
||||
self[key].is_a?(String) ? Time.mktime_with_offset(self[key].dup) : self[key]
|
||||
# Float instances don't get initialized with #new
|
||||
elsif ((property.init_method == 'new') && target == 'Float')
|
||||
cast_float(self[key])
|
||||
# 'boolean' type is simply used to generate a property? accessor method
|
||||
elsif ((property.init_method == 'new') && target == 'boolean')
|
||||
self[key]
|
||||
else
|
||||
# Let people use :send as a Time parse arg
|
||||
klass = ::CouchRest.constantize(target)
|
||||
klass.send(property.init_method, self[key].dup)
|
||||
end
|
||||
self[property.name].casted_by = self if self[property.name].respond_to?(:casted_by)
|
||||
end
|
||||
|
||||
cast_property(property)
|
||||
end
|
||||
|
||||
def cast_float(value)
|
||||
begin
|
||||
Float(value)
|
||||
rescue
|
||||
end
|
||||
|
||||
def cast_property(property, assigned=false)
|
||||
return unless property.casted
|
||||
key = self.has_key?(property.name) ? property.name : property.name.to_sym
|
||||
# Don't cast the property unless it has a value
|
||||
return unless self[key]
|
||||
if property.type.is_a?(Array)
|
||||
klass = ::CouchRest.constantize(property.type[0])
|
||||
arr = self[key].dup.collect do |value|
|
||||
unless value.instance_of?(klass)
|
||||
value = convert_property_value(property, klass, value)
|
||||
end
|
||||
associate_casted_to_parent(value, assigned)
|
||||
value
|
||||
end
|
||||
self[key] = klass != String ? CastedArray.new(arr) : arr
|
||||
self[key].casted_by = self if self[key].respond_to?(:casted_by)
|
||||
else
|
||||
if property.type == 'boolean'
|
||||
klass = TrueClass
|
||||
else
|
||||
klass = ::CouchRest.constantize(property.type)
|
||||
end
|
||||
|
||||
unless self[key].instance_of?(klass)
|
||||
self[key] = convert_property_value(property, klass, self[property.name])
|
||||
end
|
||||
associate_casted_to_parent(self[property.name], assigned)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def associate_casted_to_parent(casted, assigned)
|
||||
casted.casted_by = self if casted.respond_to?(:casted_by)
|
||||
casted.document_saved = true if !assigned && casted.respond_to?(:document_saved)
|
||||
end
|
||||
|
||||
def convert_property_value(property, klass, value)
|
||||
if ((property.init_method == 'new') && klass == Time)
|
||||
# Using custom time parsing method because Ruby's default method is toooo slow
|
||||
value.is_a?(String) ? Time.mktime_with_offset(value.dup) : value
|
||||
# Float instances don't get initialized with #new
|
||||
elsif ((property.init_method == 'new') && klass == Float)
|
||||
cast_float(value)
|
||||
# 'boolean' type is simply used to generate a property? accessor method
|
||||
elsif ((property.init_method == 'new') && klass == TrueClass)
|
||||
value
|
||||
else
|
||||
klass.send(property.init_method, value.dup)
|
||||
end
|
||||
end
|
||||
|
||||
def cast_property_by_name(property_name)
|
||||
return unless self.class.properties
|
||||
property = self.class.properties.detect{|property| property.name == property_name}
|
||||
return unless property
|
||||
cast_property(property, true)
|
||||
end
|
||||
|
||||
def cast_float(value)
|
||||
begin
|
||||
Float(value)
|
||||
rescue
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
|
||||
def property(name, options={})
|
||||
|
@ -125,7 +151,7 @@ module CouchRest
|
|||
# defines the getter for the property (and optional aliases)
|
||||
def create_property_getter(property)
|
||||
# meth = property.name
|
||||
class_eval <<-EOS, __FILE__, __LINE__
|
||||
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
||||
def #{property.name}
|
||||
self['#{property.name}']
|
||||
end
|
||||
|
@ -144,7 +170,7 @@ module CouchRest
|
|||
end
|
||||
|
||||
if property.alias
|
||||
class_eval <<-EOS, __FILE__, __LINE__
|
||||
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
||||
alias #{property.alias.to_sym} #{property.name.to_sym}
|
||||
EOS
|
||||
end
|
||||
|
@ -152,16 +178,17 @@ module CouchRest
|
|||
|
||||
# defines the setter for the property (and optional aliases)
|
||||
def create_property_setter(property)
|
||||
meth = property.name
|
||||
property_name = property.name
|
||||
class_eval <<-EOS
|
||||
def #{meth}=(value)
|
||||
self['#{meth}'] = value
|
||||
def #{property_name}=(value)
|
||||
self['#{property_name}'] = value
|
||||
cast_property_by_name('#{property_name}')
|
||||
end
|
||||
EOS
|
||||
|
||||
if property.alias
|
||||
class_eval <<-EOS
|
||||
alias #{property.alias.to_sym}= #{meth.to_sym}=
|
||||
alias #{property.alias.to_sym}= #{property_name.to_sym}=
|
||||
EOS
|
||||
end
|
||||
end
|
||||
|
|
|
@ -50,7 +50,10 @@ module CouchRest
|
|||
|
||||
def self.included(base)
|
||||
base.extlib_inheritable_accessor(:auto_validation)
|
||||
base.class_eval <<-EOS, __FILE__, __LINE__
|
||||
base.class_eval <<-EOS, __FILE__, __LINE__ + 1
|
||||
# Callbacks
|
||||
define_callbacks :validate
|
||||
|
||||
# Turn off auto validation by default
|
||||
self.auto_validation ||= false
|
||||
|
||||
|
@ -71,9 +74,10 @@ module CouchRest
|
|||
EOS
|
||||
|
||||
base.extend(ClassMethods)
|
||||
base.class_eval <<-EOS, __FILE__, __LINE__
|
||||
base.class_eval <<-EOS, __FILE__, __LINE__ + 1
|
||||
define_callbacks :validate
|
||||
if method_defined?(:_run_save_callbacks)
|
||||
save_callback :before, :check_validations
|
||||
set_callback :save, :before, :check_validations
|
||||
end
|
||||
EOS
|
||||
base.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
||||
|
@ -115,8 +119,7 @@ module CouchRest
|
|||
# Check if a resource is valid in a given context
|
||||
#
|
||||
def valid?(context = :default)
|
||||
result = self.class.validators.execute(context, self)
|
||||
result && validate_casted_arrays
|
||||
recursive_valid?(self, context, true)
|
||||
end
|
||||
|
||||
# checking on casted objects
|
||||
|
@ -133,29 +136,24 @@ module CouchRest
|
|||
result
|
||||
end
|
||||
|
||||
# Begin a recursive walk of the model checking validity
|
||||
#
|
||||
def all_valid?(context = :default)
|
||||
recursive_valid?(self, context, true)
|
||||
end
|
||||
|
||||
# Do recursive validity checking
|
||||
#
|
||||
def recursive_valid?(target, context, state)
|
||||
valid = state
|
||||
target.instance_variables.each do |ivar|
|
||||
ivar_value = target.instance_variable_get(ivar)
|
||||
if ivar_value.validatable?
|
||||
valid = valid && recursive_valid?(ivar_value, context, valid)
|
||||
elsif ivar_value.respond_to?(:each)
|
||||
ivar_value.each do |item|
|
||||
target.each do |key, prop|
|
||||
if prop.is_a?(Array)
|
||||
prop.each do |item|
|
||||
if item.validatable?
|
||||
valid = valid && recursive_valid?(item, context, valid)
|
||||
valid = recursive_valid?(item, context, valid) && valid
|
||||
end
|
||||
end
|
||||
elsif prop.validatable?
|
||||
valid = recursive_valid?(prop, context, valid) && valid
|
||||
end
|
||||
end
|
||||
return valid && target.valid?
|
||||
target._run_validate_callbacks do
|
||||
target.class.validators.execute(context, target) && valid
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
@ -212,21 +210,12 @@ module CouchRest
|
|||
def create_context_instance_methods(context)
|
||||
name = "valid_for_#{context.to_s}?" # valid_for_signup?
|
||||
if !self.instance_methods.include?(name)
|
||||
class_eval <<-EOS, __FILE__, __LINE__
|
||||
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
||||
def #{name} # def valid_for_signup?
|
||||
valid?('#{context.to_s}'.to_sym) # valid?('signup'.to_sym)
|
||||
end # end
|
||||
EOS
|
||||
end
|
||||
|
||||
all = "all_valid_for_#{context.to_s}?" # all_valid_for_signup?
|
||||
if !self.instance_methods.include?(all)
|
||||
class_eval <<-EOS, __FILE__, __LINE__
|
||||
def #{all} # def all_valid_for_signup?
|
||||
all_valid?('#{context.to_s}'.to_sym) # all_valid?('signup'.to_sym)
|
||||
end # end
|
||||
EOS
|
||||
end
|
||||
end
|
||||
|
||||
# Create a new validator of the given klazz and push it onto the
|
||||
|
|
|
@ -5,8 +5,10 @@ module CouchRest
|
|||
module CastedModel
|
||||
|
||||
def self.included(base)
|
||||
base.send(:include, ::CouchRest::Callbacks)
|
||||
base.send(:include, ::CouchRest::Mixins::Properties)
|
||||
base.send(:attr_accessor, :casted_by)
|
||||
base.send(:attr_accessor, :document_saved)
|
||||
end
|
||||
|
||||
def initialize(keys={})
|
||||
|
@ -26,5 +28,31 @@ module CouchRest
|
|||
def [] key
|
||||
super(key.to_s)
|
||||
end
|
||||
|
||||
# Gets a reference to the top level extended
|
||||
# document that a model is saved inside of
|
||||
def base_doc
|
||||
return nil unless @casted_by
|
||||
@casted_by.base_doc
|
||||
end
|
||||
|
||||
# False if the casted model has already
|
||||
# been saved in the containing document
|
||||
def new?
|
||||
!@document_saved
|
||||
end
|
||||
alias :new_record? :new?
|
||||
|
||||
# Sets the attributes from a hash
|
||||
def update_attributes_without_saving(hash)
|
||||
hash.each do |k, v|
|
||||
raise NoMethodError, "#{k}= method not available, use property :#{k}" unless self.respond_to?("#{k}=")
|
||||
end
|
||||
hash.each do |k, v|
|
||||
self.send("#{k}=",v)
|
||||
end
|
||||
end
|
||||
alias :attributes= :update_attributes_without_saving
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,7 +21,7 @@ module CouchRest
|
|||
|
||||
def self.inherited(subklass)
|
||||
subklass.send(:include, CouchRest::Mixins::Properties)
|
||||
subklass.class_eval <<-EOS, __FILE__, __LINE__
|
||||
subklass.class_eval <<-EOS, __FILE__, __LINE__ + 1
|
||||
def self.inherited(subklass)
|
||||
subklass.properties = self.properties.dup
|
||||
end
|
||||
|
@ -33,10 +33,10 @@ module CouchRest
|
|||
attr_accessor :casted_by
|
||||
|
||||
# Callbacks
|
||||
define_callbacks :create
|
||||
define_callbacks :save
|
||||
define_callbacks :update
|
||||
define_callbacks :destroy
|
||||
define_callbacks :create, "result == :halt"
|
||||
define_callbacks :save, "result == :halt"
|
||||
define_callbacks :update, "result == :halt"
|
||||
define_callbacks :destroy, "result == :halt"
|
||||
|
||||
def initialize(passed_keys={})
|
||||
apply_defaults # defined in CouchRest::Mixins::Properties
|
||||
|
@ -76,17 +76,17 @@ module CouchRest
|
|||
# on the document whenever saving occurs. CouchRest uses a pretty
|
||||
# decent time format by default. See Time#to_json
|
||||
def self.timestamps!
|
||||
class_eval <<-EOS, __FILE__, __LINE__
|
||||
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
||||
property(:updated_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['created_at'] = object['updated_at'] if object.new_document?
|
||||
object['created_at'] = object['updated_at'] if object.new?
|
||||
end
|
||||
EOS
|
||||
end
|
||||
|
||||
|
||||
# Name a method that will be called before the document is first saved,
|
||||
# which returns a string to be used for the document's <tt>_id</tt>.
|
||||
# Because CouchDB enforces a constraint that each id must be unique,
|
||||
|
@ -128,17 +128,36 @@ module CouchRest
|
|||
self.class.properties
|
||||
end
|
||||
|
||||
# Gets a reference to the actual document in the DB
|
||||
# Calls up to the next document if there is one,
|
||||
# Otherwise we're at the top and we return self
|
||||
def base_doc
|
||||
return self if base_doc?
|
||||
@casted_by.base_doc
|
||||
end
|
||||
|
||||
# Checks if we're the top document
|
||||
def base_doc?
|
||||
!@casted_by
|
||||
end
|
||||
|
||||
# Takes a hash as argument, and applies the values by using writer methods
|
||||
# for each key. It doesn't save the document at the end. Raises a NoMethodError if the corresponding methods are
|
||||
# missing. In case of error, no attributes are changed.
|
||||
def update_attributes_without_saving(hash)
|
||||
hash.each do |k, v|
|
||||
# remove attributes that cannot be updated, silently ignoring them
|
||||
# which matches Rails behavior when, for instance, setting created_at.
|
||||
# make a copy, we don't want to change arguments
|
||||
attrs = hash.dup
|
||||
%w[_id _rev created_at updated_at].each {|attr| attrs.delete(attr)}
|
||||
attrs.each do |k, v|
|
||||
raise NoMethodError, "#{k}= method not available, use property :#{k}" unless self.respond_to?("#{k}=")
|
||||
end
|
||||
hash.each do |k, v|
|
||||
attrs.each do |k, v|
|
||||
self.send("#{k}=",v)
|
||||
end
|
||||
end
|
||||
alias :attributes= :update_attributes_without_saving
|
||||
|
||||
# Takes a hash as argument, and applies the values by using writer methods
|
||||
# for each key. Raises a NoMethodError if the corresponding methods are
|
||||
|
@ -217,7 +236,8 @@ module CouchRest
|
|||
raise ArgumentError, "a document requires a database to be saved to (The document or the #{self.class} default database were not set)" unless database
|
||||
set_unique_id if new_document? && self.respond_to?(:set_unique_id)
|
||||
result = database.save_doc(self, bulk)
|
||||
return true
|
||||
mark_as_saved
|
||||
true
|
||||
end
|
||||
|
||||
# Saves the document to the db using save. Raises an exception
|
||||
|
@ -242,5 +262,22 @@ module CouchRest
|
|||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Set document_saved flag on all casted models to true
|
||||
def mark_as_saved
|
||||
self.each do |key, prop|
|
||||
if prop.is_a?(Array)
|
||||
prop.each do |item|
|
||||
if item.respond_to?(:document_saved)
|
||||
item.send(:document_saved=, true)
|
||||
end
|
||||
end
|
||||
elsif prop.respond_to?(:document_saved)
|
||||
prop.send(:document_saved=, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -38,3 +38,22 @@ module CouchRest
|
|||
|
||||
end
|
||||
end
|
||||
|
||||
class CastedArray < Array
|
||||
attr_accessor :casted_by
|
||||
|
||||
def << obj
|
||||
obj.casted_by = self.casted_by if obj.respond_to?(:casted_by)
|
||||
super(obj)
|
||||
end
|
||||
|
||||
def push(obj)
|
||||
obj.casted_by = self.casted_by if obj.respond_to?(:casted_by)
|
||||
super(obj)
|
||||
end
|
||||
|
||||
def []= index, obj
|
||||
obj.casted_by = self.casted_by if obj.respond_to?(:casted_by)
|
||||
super(index, obj)
|
||||
end
|
||||
end
|
|
@ -1,5 +1,5 @@
|
|||
# Copyright (c) 2004-2008 David Heinemeier Hansson
|
||||
#
|
||||
# Copyright (c) 2006-2009 David Heinemeier Hansson
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
|
@ -7,10 +7,10 @@
|
|||
# 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
|
||||
# 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
|
||||
|
@ -18,79 +18,59 @@
|
|||
# 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.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# class Person
|
||||
# cattr_accessor :hair_colors
|
||||
# end
|
||||
#
|
||||
# Person.hair_colors = [:brown, :black, :blonde, :red]
|
||||
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)
|
||||
syms.flatten.each do |sym|
|
||||
next if sym.is_a?(Hash)
|
||||
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
||||
unless defined? @@#{sym}
|
||||
@@#{sym} = nil
|
||||
end
|
||||
|
||||
def self.#{sym}
|
||||
@@#{sym}
|
||||
end
|
||||
|
||||
def #{sym}
|
||||
@@#{sym}
|
||||
end
|
||||
RUBY
|
||||
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
||||
unless defined? @@#{sym} # unless defined? @@hair_colors
|
||||
@@#{sym} = nil # @@hair_colors = nil
|
||||
end # end
|
||||
#
|
||||
def self.#{sym} # def self.hair_colors
|
||||
@@#{sym} # @@hair_colors
|
||||
end # end
|
||||
#
|
||||
def #{sym} # def hair_colors
|
||||
@@#{sym} # @@hair_colors
|
||||
end # end
|
||||
EOS
|
||||
end
|
||||
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)
|
||||
options = syms.last.is_a?(Hash) ? syms.pop : {}
|
||||
options = syms.extract_options!
|
||||
syms.flatten.each do |sym|
|
||||
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
||||
unless defined? @@#{sym}
|
||||
@@#{sym} = nil
|
||||
end
|
||||
|
||||
def self.#{sym}=(obj)
|
||||
@@#{sym} = obj
|
||||
end
|
||||
RUBY
|
||||
|
||||
unless options[:instance_writer] == false
|
||||
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
||||
def #{sym}=(obj)
|
||||
@@#{sym} = obj
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
||||
unless defined? @@#{sym} # unless defined? @@hair_colors
|
||||
@@#{sym} = nil # @@hair_colors = nil
|
||||
end # end
|
||||
#
|
||||
def self.#{sym}=(obj) # def self.hair_colors=(obj)
|
||||
@@#{sym} = obj # @@hair_colors = obj
|
||||
end # end
|
||||
#
|
||||
#{" #
|
||||
def #{sym}=(obj) # def hair_colors=(obj)
|
||||
@@#{sym} = obj # @@hair_colors = obj
|
||||
end # end
|
||||
" unless options[:instance_writer] == false } # # instance writer above is generated unless options[:instance_writer] == false
|
||||
EOS
|
||||
end
|
||||
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)
|
||||
cattr_reader(*syms)
|
||||
cattr_writer(*syms)
|
||||
|
@ -156,6 +136,8 @@ class Class
|
|||
def #{ivar}=(obj) self.class.#{ivar} = obj end
|
||||
RUBY
|
||||
end
|
||||
|
||||
self.send("#{ivar}=", yield) if block_given?
|
||||
end
|
||||
end unless Class.respond_to?(:extlib_inheritable_writer)
|
||||
|
||||
|
@ -168,9 +150,41 @@ class Class
|
|||
# @return <Array[#to_s]> An Array of attributes turned into inheritable accessors.
|
||||
#
|
||||
# @api public
|
||||
def extlib_inheritable_accessor(*syms)
|
||||
def extlib_inheritable_accessor(*syms, &block)
|
||||
extlib_inheritable_reader(*syms)
|
||||
extlib_inheritable_writer(*syms)
|
||||
extlib_inheritable_writer(*syms, &block)
|
||||
end unless Class.respond_to?(:extlib_inheritable_accessor)
|
||||
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.new.respond_to?(:extract_options!)
|
||||
|
||||
# Wraps the object in an Array unless it's an Array. Converts the
|
||||
# object to an Array using #to_ary if it implements that.
|
||||
def self.wrap(object)
|
||||
case object
|
||||
when nil
|
||||
[]
|
||||
when self
|
||||
object
|
||||
else
|
||||
if object.respond_to?(:to_ary)
|
||||
object.to_ary
|
||||
else
|
||||
[object]
|
||||
end
|
||||
end
|
||||
end unless Array.respond_to?(:wrap)
|
||||
end
|
||||
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
# This file contains various hacks for Rails compatibility.
|
||||
# To use, just require in environment.rb, like so:
|
||||
#
|
||||
# require 'couchrest/support/rails'
|
||||
|
||||
class Hash
|
||||
# Hack so that CouchRest::Document, which descends from Hash,
|
||||
# doesn't appear to Rails routing as a Hash of options
|
||||
|
@ -12,8 +8,10 @@ class Hash
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
CouchRest::Document.class_eval do
|
||||
# Need this when passing doc to a resourceful route
|
||||
alias_method :to_param, :id
|
||||
|
||||
# Hack so that CouchRest::Document, which descends from Hash,
|
||||
# doesn't appear to Rails routing as a Hash of options
|
||||
def is_a?(o)
|
||||
|
@ -23,6 +21,15 @@ CouchRest::Document.class_eval do
|
|||
alias_method :kind_of?, :is_a?
|
||||
end
|
||||
|
||||
CouchRest::CastedModel.class_eval do
|
||||
# The to_param method is needed for rails to generate resourceful routes.
|
||||
# In your controller, remember that it's actually the id of the document.
|
||||
def id
|
||||
return nil if base_doc.nil?
|
||||
base_doc.id
|
||||
end
|
||||
alias_method :to_param, :id
|
||||
end
|
||||
|
||||
require Pathname.new(File.dirname(__FILE__)).join('..', 'validation', 'validation_errors')
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue