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-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-03-20 02:53:17 +01:00
base . class_eval <<-EOS, __FILE__, __LINE__
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
if property . default
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
def cast_property ( property )
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 ]
target = property . type
if target . is_a? ( Array )
klass = :: CouchRest . constantize ( target [ 0 ] )
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-10 06:02:04 +02:00
associate_casted_to_parent ( value )
value
2009-02-09 20:20:23 +01:00
end
2009-06-10 06:02:04 +02:00
self [ key ] = target [ 0 ] != 'String' ? CastedArray . new ( arr ) : arr
else
klass = :: CouchRest . constantize ( target )
unless self [ key ] . instance_of? ( klass )
self [ key ] = convert_property_value ( property , klass , self [ property . name ] )
end
associate_casted_to_parent ( self [ property . name ] )
2009-01-30 03:45:01 +01:00
end
2009-02-09 20:20:23 +01:00
end
2009-06-10 06:02:04 +02:00
def associate_casted_to_parent ( casted )
casted . casted_by = self if casted . respond_to? ( :casted_by )
casted . document_saved = true if casted . respond_to? ( :document_saved )
end
def convert_property_value ( property , klass , value )
if ( ( property . init_method == 'new' ) && klass . to_s == 'Time' )
value . is_a? ( String ) ? Time . parse ( value . dup ) : 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 )
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
class_eval <<-EOS, __FILE__, __LINE__
def #{property.name}
self [ '#{property.name}' ]
2009-01-30 03:45:01 +01:00
end
EOS
if property . alias
2009-02-09 20:20:23 +01:00
class_eval <<-EOS, __FILE__, __LINE__
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-04-23 06:24:38 +02:00
end