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-02-09 20:20:23 +01:00
return unless self . respond_to? ( :new_document? ) && new_document?
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
# let's make sure we have a default and we can assign the value
if property . default && ( self . respond_to? ( " #{ key } = " ) || self . key? ( key ) )
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 |
next unless property . casted
key = self . has_key? ( property . name ) ? property . name : property . name . to_sym
target = property . type
if target . is_a? ( Array )
2009-02-13 05:28:07 +01:00
next unless self [ key ]
2009-02-09 20:20:23 +01:00
klass = :: CouchRest . constantize ( target [ 0 ] )
self [ property . name ] = self [ key ] . collect do | value |
2009-02-10 00:12:22 +01:00
# Auto parse Time objects
obj = ( ( property . init_method == 'new' ) && klass == Time ) ? Time . parse ( value ) : klass . send ( property . init_method , value )
2009-02-09 20:20:23 +01:00
obj . casted_by = self if obj . respond_to? ( :casted_by )
obj
end
else
2009-02-10 00:12:22 +01:00
# Auto parse Time objects
self [ property . name ] = if ( ( property . init_method == 'new' ) && target == 'Time' )
self [ key ] . is_a? ( String ) ? Time . parse ( self [ key ] . dup ) : self [ key ]
2009-09-03 04:54:25 +02:00
elsif
( ( property . init_method == 'new' ) && target == 'Date' )
self [ key ] . is_a? ( String ) ? Date . parse ( self [ key ] . dup ) : self [ key ]
2009-02-09 20:20:23 +01:00
else
2009-02-10 00:12:22 +01:00
# Let people use :send as a Time parse arg
2009-02-09 20:20:23 +01:00
klass = :: CouchRest . constantize ( target )
2009-03-03 06:15:02 +01:00
# I'm not convince we should or should not create a new instance if we are casting a doc/extended doc without default value and nothing was passed
# unless (property.casted &&
# (klass.superclass == CouchRest::ExtendedDocument || klass.superclass == CouchRest::Document) &&
# (self[key].nil? || property.default.nil?))
2009-02-10 00:12:22 +01:00
klass . send ( property . init_method , self [ key ] )
2009-03-03 06:15:02 +01:00
#end
2009-02-09 20:20:23 +01:00
end
2009-03-03 06:15:02 +01:00
self [ property . name ] . casted_by = self if self [ property . name ] . respond_to? ( :casted_by )
2009-02-09 20:20:23 +01:00
end
2009-01-30 03:45:01 +01:00
end
2009-02-09 20:20:23 +01:00
end
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 )
meth = property . name
class_eval <<-EOS
def #{meth}=(value)
self [ '#{meth}' ] = value
end
EOS
if property . alias
class_eval <<-EOS
alias #{property.alias.to_sym}= #{meth.to_sym}=
EOS
end
end
end # module ClassMethods
end
end
end