Merge remote branch 'samlown/master' into official-master
This commit is contained in:
commit
ef6739774b
15 changed files with 683 additions and 180 deletions
|
@ -20,7 +20,7 @@ module CouchRest
|
|||
class_eval <<-END, __FILE__, __LINE__ + 1
|
||||
def self.find_all_#{collection_name}(options = {})
|
||||
view_options = #{view_options.inspect} || {}
|
||||
CollectionProxy.new(@database, "#{design_doc}", "#{view_name}", view_options.merge(options), Kernel.const_get('#{self}'))
|
||||
CollectionProxy.new(database, "#{design_doc}", "#{view_name}", view_options.merge(options), Kernel.const_get('#{self}'))
|
||||
end
|
||||
END
|
||||
end
|
||||
|
@ -58,7 +58,7 @@ module CouchRest
|
|||
|
||||
def create_collection_proxy(options)
|
||||
design_doc, view_name, view_options = parse_view_options(options)
|
||||
CollectionProxy.new(@database, design_doc, view_name, view_options, self)
|
||||
CollectionProxy.new(database, design_doc, view_name, view_options, self)
|
||||
end
|
||||
|
||||
def parse_view_options(options)
|
||||
|
|
|
@ -1,24 +1,6 @@
|
|||
require 'time'
|
||||
require File.join(File.dirname(__FILE__), '..', 'more', 'property')
|
||||
|
||||
class Time
|
||||
# returns a local time value much faster than Time.parse
|
||||
def self.mktime_with_offset(string)
|
||||
string =~ /(\d{4})[\-|\/](\d{2})[\-|\/](\d{2})[T|\s](\d{2}):(\d{2}):(\d{2})([\+|\s|\-])*(\d{2}):?(\d{2})/
|
||||
# $1 = year
|
||||
# $2 = month
|
||||
# $3 = day
|
||||
# $4 = hours
|
||||
# $5 = minutes
|
||||
# $6 = seconds
|
||||
# $7 = time zone direction
|
||||
# $8 = tz difference
|
||||
# utc time with wrong TZ info:
|
||||
time = mktime($1, RFC2822_MONTH_NAME[$2.to_i - 1], $3, $4, $5, $6, $7)
|
||||
tz_difference = ("#{$7 == '-' ? '+' : '-'}#{$8}".to_i * 3600)
|
||||
time + tz_difference + zone_offset(time.zone)
|
||||
end
|
||||
end
|
||||
require File.join(File.dirname(__FILE__), '..', 'more', 'typecast')
|
||||
|
||||
module CouchRest
|
||||
module Mixins
|
||||
|
@ -26,6 +8,8 @@ module CouchRest
|
|||
|
||||
class IncludeError < StandardError; end
|
||||
|
||||
include ::CouchRest::More::Typecast
|
||||
|
||||
def self.included(base)
|
||||
base.class_eval <<-EOS, __FILE__, __LINE__ + 1
|
||||
extlib_inheritable_accessor(:properties) unless self.respond_to?(:properties)
|
||||
|
@ -67,28 +51,24 @@ module CouchRest
|
|||
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
|
||||
self[key] = [self[key]] unless self[key].is_a?(Array)
|
||||
arr = self[key].collect do |value|
|
||||
value = typecast_value(value, klass, property.init_method)
|
||||
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'
|
||||
if property.type.downcase == '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)
|
||||
self[key] = typecast_value(self[key], klass, property.init_method)
|
||||
associate_casted_to_parent(self[key], assigned)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def associate_casted_to_parent(casted, assigned)
|
||||
|
@ -96,21 +76,6 @@ module CouchRest
|
|||
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}
|
||||
|
@ -118,14 +83,7 @@ module CouchRest
|
|||
cast_property(property, true)
|
||||
end
|
||||
|
||||
def cast_float(value)
|
||||
begin
|
||||
Float(value)
|
||||
rescue
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
module ClassMethods
|
||||
|
||||
def property(name, options={})
|
||||
|
@ -141,7 +99,7 @@ module CouchRest
|
|||
# make sure to use a mutex.
|
||||
def define_property(name, options={})
|
||||
# check if this property is going to casted
|
||||
options[:casted] = options[:cast_as] ? options[:cast_as] : false
|
||||
options[:casted] = !!(options[:cast_as] || options[:type])
|
||||
property = CouchRest::Property.new(name, (options.delete(:cast_as) || options.delete(:type)), options)
|
||||
create_property_getter(property)
|
||||
create_property_setter(property) unless property.read_only == true
|
||||
|
|
|
@ -14,7 +14,7 @@ module CouchRest
|
|||
include CouchRest::Mixins::ExtendedAttachments
|
||||
include CouchRest::Mixins::ClassProxy
|
||||
include CouchRest::Mixins::Collection
|
||||
include CouchRest::Mixins::AttributeProtection
|
||||
include CouchRest::Mixins::AttributeProtection
|
||||
|
||||
def self.subclasses
|
||||
@subclasses ||= []
|
||||
|
@ -58,6 +58,7 @@ module CouchRest
|
|||
unless self['_id'] && self['_rev']
|
||||
self['couchrest-type'] = self.class.to_s
|
||||
end
|
||||
after_initialize if respond_to?(:after_initialize)
|
||||
end
|
||||
|
||||
# Defines an instance and save it directly to the database
|
||||
|
@ -84,9 +85,9 @@ 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__ + 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)
|
||||
class_eval <<-EOS, __FILE__, __LINE__
|
||||
property(:updated_at, :read_only => true, :type => 'Time', :auto_validation => false)
|
||||
property(:created_at, :read_only => true, :type => 'Time', :auto_validation => false)
|
||||
|
||||
set_callback :save, :before do |object|
|
||||
object['updated_at'] = Time.now
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
module CouchRest
|
||||
|
||||
|
||||
# Basic attribute support for adding getter/setter + validation
|
||||
class Property
|
||||
attr_reader :name, :type, :read_only, :alias, :default, :casted, :init_method, :options
|
||||
|
||||
|
||||
# attribute to define
|
||||
def initialize(name, type = nil, options = {})
|
||||
@name = name.to_s
|
||||
|
@ -11,20 +11,19 @@ module CouchRest
|
|||
parse_options(options)
|
||||
self
|
||||
end
|
||||
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def parse_type(type)
|
||||
if type.nil?
|
||||
@type = 'String'
|
||||
elsif type.is_a?(Array) && type.empty?
|
||||
@type = 'Array'
|
||||
@type = ['Object']
|
||||
else
|
||||
@type = type.is_a?(Array) ? [type.first.to_s] : type.to_s
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def parse_options(options)
|
||||
return if options.empty?
|
||||
@validation_format = options.delete(:format) if options[:format]
|
||||
|
@ -35,7 +34,7 @@ module CouchRest
|
|||
@init_method = options[:init_method] ? options.delete(:init_method) : 'new'
|
||||
@options = options
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -56,4 +55,4 @@ class CastedArray < Array
|
|||
obj.casted_by = self.casted_by if obj.respond_to?(:casted_by)
|
||||
super(index, obj)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
180
lib/couchrest/more/typecast.rb
Normal file
180
lib/couchrest/more/typecast.rb
Normal file
|
@ -0,0 +1,180 @@
|
|||
require 'time'
|
||||
require 'bigdecimal'
|
||||
require 'bigdecimal/util'
|
||||
require File.join(File.dirname(__FILE__), '..', 'more', 'property')
|
||||
|
||||
class Time
|
||||
# returns a local time value much faster than Time.parse
|
||||
def self.mktime_with_offset(string)
|
||||
string =~ /(\d{4})[\-|\/](\d{2})[\-|\/](\d{2})[T|\s](\d{2}):(\d{2}):(\d{2})([\+|\s|\-])*(\d{2}):?(\d{2})/
|
||||
# $1 = year
|
||||
# $2 = month
|
||||
# $3 = day
|
||||
# $4 = hours
|
||||
# $5 = minutes
|
||||
# $6 = seconds
|
||||
# $7 = time zone direction
|
||||
# $8 = tz difference
|
||||
# utc time with wrong TZ info:
|
||||
time = mktime($1, RFC2822_MONTH_NAME[$2.to_i - 1], $3, $4, $5, $6, $7)
|
||||
tz_difference = ("#{$7 == '-' ? '+' : '-'}#{$8}".to_i * 3600)
|
||||
time + tz_difference + zone_offset(time.zone)
|
||||
end
|
||||
end
|
||||
|
||||
module CouchRest
|
||||
module More
|
||||
module Typecast
|
||||
|
||||
def typecast_value(value, klass, init_method)
|
||||
return nil if value.nil?
|
||||
|
||||
if value.instance_of?(klass) || klass.to_s == 'Object'
|
||||
value
|
||||
elsif ['String', 'TrueClass', 'Integer', 'Float', 'BigDecimal', 'DateTime', 'Time', 'Date', 'Class'].include?(klass.to_s)
|
||||
send('typecast_to_'+klass.to_s.downcase, value)
|
||||
else
|
||||
# Allow the init_method to be defined as a Proc for advanced conversion
|
||||
init_method.is_a?(Proc) ? init_method.call(value) : klass.send(init_method, value)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Typecast a value to an Integer
|
||||
def typecast_to_integer(value)
|
||||
value.kind_of?(Integer) ? value : typecast_to_numeric(value, :to_i)
|
||||
end
|
||||
|
||||
# Typecast a value to a String
|
||||
def typecast_to_string(value)
|
||||
value.to_s
|
||||
end
|
||||
|
||||
# Typecast a value to a true or false
|
||||
def typecast_to_trueclass(value)
|
||||
if value.kind_of?(Integer)
|
||||
return true if value == 1
|
||||
return false if value == 0
|
||||
elsif value.respond_to?(:to_s)
|
||||
return true if %w[ true 1 t ].include?(value.to_s.downcase)
|
||||
return false if %w[ false 0 f ].include?(value.to_s.downcase)
|
||||
end
|
||||
value
|
||||
end
|
||||
|
||||
# Typecast a value to a BigDecimal
|
||||
def typecast_to_bigdecimal(value)
|
||||
return value if value.kind_of?(BigDecimal)
|
||||
|
||||
if value.kind_of?(Integer)
|
||||
value.to_s.to_d
|
||||
else
|
||||
typecast_to_numeric(value, :to_d)
|
||||
end
|
||||
end
|
||||
|
||||
# Typecast a value to a Float
|
||||
def typecast_to_float(value)
|
||||
return value if value.kind_of?(Float)
|
||||
typecast_to_numeric(value, :to_f)
|
||||
end
|
||||
|
||||
# Match numeric string
|
||||
def typecast_to_numeric(value, method)
|
||||
if value.respond_to?(:to_str)
|
||||
if value.to_str =~ /\A(-?(?:0|[1-9]\d*)(?:\.\d+)?|(?:\.\d+))\z/
|
||||
$1.send(method)
|
||||
else
|
||||
value
|
||||
end
|
||||
elsif value.respond_to?(method)
|
||||
value.send(method)
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
# Typecasts an arbitrary value to a DateTime.
|
||||
# Handles both Hashes and DateTime instances.
|
||||
def typecast_to_datetime(value)
|
||||
return value if value.kind_of?(DateTime)
|
||||
|
||||
if value.is_a?(Hash)
|
||||
typecast_hash_to_datetime(value)
|
||||
else
|
||||
DateTime.parse(value.to_s)
|
||||
end
|
||||
rescue ArgumentError
|
||||
value
|
||||
end
|
||||
|
||||
# Typecasts an arbitrary value to a Date
|
||||
# Handles both Hashes and Date instances.
|
||||
def typecast_to_date(value)
|
||||
return value if value.kind_of?(Date)
|
||||
|
||||
if value.is_a?(Hash)
|
||||
typecast_hash_to_date(value)
|
||||
else
|
||||
Date.parse(value.to_s)
|
||||
end
|
||||
rescue ArgumentError
|
||||
value
|
||||
end
|
||||
|
||||
# Typecasts an arbitrary value to a Time
|
||||
# Handles both Hashes and Time instances.
|
||||
def typecast_to_time(value)
|
||||
return value if value.kind_of?(Time)
|
||||
|
||||
if value.is_a?(Hash)
|
||||
typecast_hash_to_time(value)
|
||||
else
|
||||
Time.mktime_with_offset(value.to_s)
|
||||
end
|
||||
rescue ArgumentError
|
||||
value
|
||||
rescue TypeError
|
||||
value
|
||||
end
|
||||
|
||||
# Creates a DateTime instance from a Hash with keys :year, :month, :day,
|
||||
# :hour, :min, :sec
|
||||
def typecast_hash_to_datetime(value)
|
||||
DateTime.new(*extract_time(value))
|
||||
end
|
||||
|
||||
# Creates a Date instance from a Hash with keys :year, :month, :day
|
||||
def typecast_hash_to_date(value)
|
||||
Date.new(*extract_time(value)[0, 3])
|
||||
end
|
||||
|
||||
# Creates a Time instance from a Hash with keys :year, :month, :day,
|
||||
# :hour, :min, :sec
|
||||
def typecast_hash_to_time(value)
|
||||
Time.local(*extract_time(value))
|
||||
end
|
||||
|
||||
# Extracts the given args from the hash. If a value does not exist, it
|
||||
# uses the value of Time.now.
|
||||
def extract_time(value)
|
||||
now = Time.now
|
||||
|
||||
[:year, :month, :day, :hour, :min, :sec].map do |segment|
|
||||
typecast_to_numeric(value.fetch(segment, now.send(segment)), :to_i)
|
||||
end
|
||||
end
|
||||
|
||||
# Typecast a value to a Class
|
||||
def typecast_to_class(value)
|
||||
return value if value.kind_of?(Class)
|
||||
::CouchRest.constantize(value.to_s)
|
||||
rescue NameError
|
||||
value
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -37,7 +37,7 @@ module CouchRest
|
|||
|
||||
def call(target)
|
||||
value = target.validation_property_value(field_name)
|
||||
property = target.validation_property(field_name)
|
||||
property = target.validation_property(field_name.to_s)
|
||||
return true if present?(value, property)
|
||||
|
||||
error_message = @options[:message] || default_error(property)
|
||||
|
@ -66,7 +66,7 @@ module CouchRest
|
|||
# Returns false for other property types.
|
||||
# Returns false for non-properties.
|
||||
def boolean_type?(property)
|
||||
property ? property.type == TrueClass : false
|
||||
property ? property.type == 'Boolean' : false
|
||||
end
|
||||
|
||||
end # class RequiredFieldValidator
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue