2009-03-19 21:22:47 +01:00
require 'time'
2009-02-09 20:20:23 +01:00
require File . join ( File . dirname ( __FILE__ ) , '..' , 'more' , 'property' )
2009-07-08 08:55:20 +02:00
class Time
# returns a local time value much faster than Time.parse
def self . mktime_with_offset ( string )
2009-09-22 15:19:08 +02:00
string =~ / ( \ d{4})[ \ -| \/ ]( \ d{2})[ \ -| \/ ]( \ d{2})[T| \ s]( \ d{2}):( \ d{2}):( \ d{2})([ \ +| \ s| \ -])*( \ d{2}):?( \ d{2}) /
2009-07-08 08:55:20 +02:00
# $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
2009-01-30 03:45:01 +01:00
module CouchRest
module Mixins
2009-02-09 20:20:23 +01:00
module Properties
2009-01-30 03:45:01 +01:00
2009-02-06 03:57:11 +01:00
class IncludeError < StandardError ; end
2009-01-30 03:45:01 +01:00
def self . included ( base )
2009-07-19 10:23:51 +02:00
base . class_eval <<-EOS, __FILE__, __LINE__ + 1
2009-03-27 19:11:49 +01:00
extlib_inheritable_accessor ( :properties ) unless self . respond_to? ( :properties )
2009-03-20 02:53:17 +01:00
self . properties || = [ ]
EOS
2009-01-30 03:45:01 +01:00
base . extend ( ClassMethods )
2009-02-09 20:20:23 +01:00
raise CouchRest :: Mixins :: Properties :: IncludeError , " You can only mixin Properties in a class responding to [] and []=, if you tried to mixin CastedModel, make sure your class inherits from Hash or responds to the proper methods " unless ( base . new . respond_to? ( :[] ) && base . new . respond_to? ( :[]= ) )
2009-02-06 03:57:11 +01:00
end
def apply_defaults
2009-06-05 05:44:44 +02:00
return if self . respond_to? ( :new? ) && ( new ? == false )
2009-02-09 20:20:23 +01:00
return unless self . class . respond_to? ( :properties )
2009-02-06 03:57:11 +01:00
return if self . class . properties . empty?
# TODO: cache the default object
self . class . properties . each do | property |
key = property . name . to_s
2009-05-15 21:44:41 +02:00
# let's make sure we have a default
2009-06-16 00:31:50 +02:00
unless property . default . nil?
2009-02-06 03:57:11 +01:00
if property . default . class == Proc
2009-02-09 21:08:55 +01:00
self [ key ] = property . default . call
2009-02-06 03:57:11 +01:00
else
self [ key ] = Marshal . load ( Marshal . dump ( property . default ) )
end
end
end
2009-01-30 03:45:01 +01:00
end
2009-02-09 20:20:23 +01:00
def cast_keys
return unless self . class . properties
self . class . properties . each do | property |
2009-06-10 06:02:04 +02:00
cast_property ( property )
end
end
2009-06-12 21:22:58 +02:00
def cast_property ( property , assigned = false )
2009-06-10 06:02:04 +02:00
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 ]
2009-08-13 01:48:13 +02:00
if property . type . is_a? ( Array )
klass = :: CouchRest . constantize ( property . type [ 0 ] )
2009-06-10 06:02:04 +02:00
arr = self [ key ] . dup . collect do | value |
unless value . instance_of? ( klass )
value = convert_property_value ( property , klass , value )
2009-02-09 20:20:23 +01:00
end
2009-06-12 21:22:58 +02:00
associate_casted_to_parent ( value , assigned )
2009-06-10 06:02:04 +02:00
value
2009-02-09 20:20:23 +01:00
end
2009-06-12 21:22:58 +02:00
self [ key ] = klass != String ? CastedArray . new ( arr ) : arr
self [ key ] . casted_by = self if self [ key ] . respond_to? ( :casted_by )
2009-06-10 06:02:04 +02:00
else
2009-08-13 01:48:13 +02:00
if property . type == 'boolean'
klass = TrueClass
2009-02-09 20:20:23 +01:00
else
2009-08-13 01:48:13 +02:00
klass = :: CouchRest . constantize ( property . type )
end
2009-07-08 08:55:20 +02:00
2009-06-10 06:02:04 +02:00
unless self [ key ] . instance_of? ( klass )
self [ key ] = convert_property_value ( property , klass , self [ property . name ] )
end
2009-06-12 21:22:58 +02:00
associate_casted_to_parent ( self [ property . name ] , assigned )
2009-01-30 03:45:01 +01:00
end
2009-07-17 04:52:53 +02:00
2009-02-09 20:20:23 +01:00
end
2009-06-12 21:22:58 +02:00
def associate_casted_to_parent ( casted , assigned )
2009-06-10 06:02:04 +02:00
casted . casted_by = self if casted . respond_to? ( :casted_by )
2009-06-12 21:22:58 +02:00
casted . document_saved = true if ! assigned && casted . respond_to? ( :document_saved )
2009-06-10 06:02:04 +02:00
end
def convert_property_value ( property , klass , value )
2009-08-13 01:48:13 +02:00
if ( ( property . init_method == 'new' ) && klass == Time )
2009-07-19 09:01:07 +02:00
# 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
2009-08-13 01:48:13 +02:00
elsif ( ( property . init_method == 'new' ) && klass == Float )
2009-07-19 09:01:07 +02:00
cast_float ( value )
2009-08-13 01:48:13 +02:00
# 'boolean' type is simply used to generate a property? accessor method
elsif ( ( property . init_method == 'new' ) && klass == TrueClass )
value
2009-06-10 06:02:04 +02:00
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
2009-06-12 21:22:58 +02:00
cast_property ( property , true )
2009-06-10 06:02:04 +02:00
end
2009-07-19 09:01:07 +02:00
def cast_float ( value )
begin
Float ( value )
rescue
value
end
end
2009-02-09 20:20:23 +01:00
module ClassMethods
2009-02-06 02:06:12 +01:00
2009-01-30 03:45:01 +01:00
def property ( name , options = { } )
2009-03-20 02:53:17 +01:00
existing_property = self . properties . find { | p | p . name == name . to_s }
if existing_property . nil? || ( existing_property . default != options [ :default ] )
define_property ( name , options )
end
2009-02-06 02:06:12 +01:00
end
protected
# This is not a thread safe operation, if you have to set new properties at runtime
# make sure to use a mutex.
def define_property ( name , options = { } )
2009-02-09 20:20:23 +01:00
# check if this property is going to casted
2009-02-10 00:12:22 +01:00
options [ :casted ] = options [ :cast_as ] ? options [ :cast_as ] : false
2009-02-09 20:20:23 +01:00
property = CouchRest :: Property . new ( name , ( options . delete ( :cast_as ) || options . delete ( :type ) ) , options )
2009-01-30 03:45:01 +01:00
create_property_getter ( property )
create_property_setter ( property ) unless property . read_only == true
2009-02-06 02:06:12 +01:00
properties << property
2009-01-30 03:45:01 +01:00
end
2009-02-06 02:06:12 +01:00
2009-02-09 20:20:23 +01:00
# defines the getter for the property (and optional aliases)
2009-01-30 03:45:01 +01:00
def create_property_getter ( property )
2009-02-09 20:20:23 +01:00
# meth = property.name
2009-07-19 10:23:51 +02:00
class_eval <<-EOS, __FILE__, __LINE__ + 1
2009-02-09 20:20:23 +01:00
def #{property.name}
self [ '#{property.name}' ]
2009-01-30 03:45:01 +01:00
end
EOS
2009-07-25 05:12:03 +02:00
if property . type == 'boolean'
class_eval <<-EOS, __FILE__, __LINE__
def #{property.name}?
if self [ '#{property.name}' ] . nil? || self [ '#{property.name}' ] == false || self [ '#{property.name}' ] . to_s . downcase == 'false'
false
else
true
end
end
EOS
end
2009-01-30 03:45:01 +01:00
if property . alias
2009-07-19 10:23:51 +02:00
class_eval <<-EOS, __FILE__, __LINE__ + 1
2009-02-09 20:20:23 +01:00
alias #{property.alias.to_sym} #{property.name.to_sym}
2009-01-30 03:45:01 +01:00
EOS
end
end
2009-02-09 20:20:23 +01:00
# defines the setter for the property (and optional aliases)
2009-01-30 03:45:01 +01:00
def create_property_setter ( property )
2009-06-10 06:02:04 +02:00
property_name = property . name
2009-01-30 03:45:01 +01:00
class_eval <<-EOS
2009-06-10 06:02:04 +02:00
def #{property_name}=(value)
self [ '#{property_name}' ] = value
cast_property_by_name ( '#{property_name}' )
2009-01-30 03:45:01 +01:00
end
EOS
if property . alias
class_eval <<-EOS
2009-06-10 06:02:04 +02:00
alias #{property.alias.to_sym}= #{property_name.to_sym}=
2009-01-30 03:45:01 +01:00
EOS
end
end
end # module ClassMethods
end
end
2009-09-22 15:19:08 +02:00
end