Merge branch 'master' of git://github.com/sauy7/couchrest

This commit is contained in:
Sam Lown 2010-03-03 00:18:32 +00:00
commit 88f42d8fe0
12 changed files with 659 additions and 152 deletions

View file

@ -1,4 +1,3 @@
require 'time'
require File.join(File.dirname(__FILE__), '..', 'more', 'property')
class Time
@ -56,40 +55,36 @@ module CouchRest
def cast_keys
return unless self.class.properties
self.class.properties.each do |property|
cast_property(property)
key = self.has_key?(property.name) ? property.name : property.name.to_sym
# Don't cast the property unless it has a value
next if (value = self[key]).nil?
# Don't cast the property if it is not accessible
if self.class.respond_to? :accessible_properties
next if self.class.accessible_properties.index(key).nil?
end
write_property(property, value)
end
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
protected
def write_attribute(name, value)
unless (property = property(name)).nil?
write_property(property, value)
else
klass = ::CouchRest.constantize(property.type)
self[name] = value
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 write_property(property, value)
value = property.typecast(value)
value.casted_by = self if value.respond_to?(:casted_by)
self[property.name] = value
end
def property(name)
properties.find {|p| p.name == name.to_s}
end
def associate_casted_to_parent(casted, assigned)
casted.casted_by = self if casted.respond_to?(:casted_by)
@ -143,7 +138,7 @@ module CouchRest
# check if this property is going to casted
options[:casted] = options[:cast_as] ? options[:cast_as] : false
property = CouchRest::Property.new(name, (options.delete(:cast_as) || options.delete(:type)), options)
create_property_getter(property)
create_property_getter(property)
create_property_setter(property) unless property.read_only == true
properties << property
end
@ -181,8 +176,7 @@ module CouchRest
property_name = property.name
class_eval <<-EOS
def #{property_name}=(value)
self['#{property_name}'] = value
cast_property_by_name('#{property_name}')
write_attribute('#{property_name}', value)
end
EOS
@ -192,9 +186,7 @@ module CouchRest
EOS
end
end
end # module ClassMethods
end
end
end

View file

@ -84,9 +84,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

View file

@ -1,9 +1,13 @@
require 'time'
require 'bigdecimal'
require 'bigdecimal/util'
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 +15,184 @@ module CouchRest
parse_options(options)
self
end
def typecast(value)
do_typecast(value, type, init_method)
end
protected
def do_typecast(value, target, init_method)
return nil if value.nil?
if target == 'String' then typecast_to_string(value)
elsif target == 'Boolean' then typecast_to_boolean(value)
elsif target == 'Integer' then typecast_to_integer(value)
elsif target == 'Float' then typecast_to_float(value)
elsif target == 'BigDecimal' then typecast_to_bigdecimal(value)
elsif target == 'DateTime' then typecast_to_datetime(value)
elsif target == 'Time' then typecast_to_time(value)
elsif target == 'Date' then typecast_to_date(value)
elsif target == 'Class' then typecast_to_class(value)
elsif target.is_a?(Array) then typecast_array(value, target, init_method)
else
@klass ||= ::CouchRest.constantize(target)
value.kind_of?(@klass) ? value : @klass.send(init_method, value.dup)
end
end
def typecast_array(value, target, init_method)
value.map { |v| do_typecast(v, target[0], init_method) }
end
# 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_boolean(value)
return value if value == true || value == false
if value.kind_of?(Integer)
return true if value == 1
return false if value == 0
elsif value.respond_to?(:to_str)
return true if %w[ true 1 t ].include?(value.to_str.downcase)
return false if %w[ false 0 f ].include?(value.to_str.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
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 +203,7 @@ module CouchRest
@init_method = options[:init_method] ? options.delete(:init_method) : 'new'
@options = options
end
end
end

View file

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