2010-06-20 22:01:11 +02:00
# encoding: utf-8
2009-01-30 03:45:01 +01:00
module CouchRest
2010-06-20 22:01:11 +02:00
module Model
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
2010-06-16 21:04:53 +02:00
# Returns the Class properties
#
# ==== Returns
# Array:: the list of properties for model's class
def properties
self . class . properties
end
def read_attribute ( property )
self [ property . to_s ]
end
def write_attribute ( property , value )
2010-06-20 22:01:11 +02:00
prop = property . is_a? ( Property ) ? property : self . class . properties . detect { | p | p . to_s == property . to_s }
2010-06-16 21:04:53 +02:00
raise " Missing property definition for #{ property . to_s } " unless prop
self [ prop . to_s ] = prop . cast ( self , value )
end
def apply_all_property_defaults
2009-06-05 05:44:44 +02:00
return if self . respond_to? ( :new? ) && ( new ? == false )
2009-02-06 03:57:11 +01:00
# TODO: cache the default object
self . class . properties . each do | property |
2010-06-16 21:04:53 +02:00
write_attribute ( property , property . default_value )
2009-06-10 06:02:04 +02:00
end
end
2010-03-03 03:18:32 +01:00
2009-02-09 20:20:23 +01:00
module ClassMethods
2009-02-06 02:06:12 +01:00
2010-06-16 21:04:53 +02:00
def property ( name , * options , & block )
2010-05-13 00:17:30 +02:00
opts = { }
type = options . shift
if type . class != Hash
opts [ :type ] = type
opts . merge! ( options . shift || { } )
else
opts . update ( type )
end
2009-03-20 02:53:17 +01:00
existing_property = self . properties . find { | p | p . name == name . to_s }
2010-05-13 00:17:30 +02:00
if existing_property . nil? || ( existing_property . default != opts [ :default ] )
2010-06-16 21:04:53 +02:00
define_property ( name , opts , & block )
2009-03-20 02:53:17 +01:00
end
2009-02-06 02:06:12 +01:00
end
2010-06-20 22:01:11 +02:00
# Automatically set <tt>updated_at</tt> and <tt>created_at</tt> fields
# on the document whenever saving occurs. CouchRest uses a pretty
# decent time format by default. See Time#to_json
def timestamps!
class_eval <<-EOS, __FILE__, __LINE__
property ( :updated_at , Time , :read_only = > true , :protected = > true , :auto_validation = > false )
property ( :created_at , Time , :read_only = > true , :protected = > true , :auto_validation = > false )
set_callback :save , :before do | object |
write_attribute ( 'updated_at' , Time . now )
write_attribute ( 'created_at' , Time . now ) if object . new?
end
EOS
end
2009-02-06 02:06:12 +01:00
protected
# This is not a thread safe operation, if you have to set new properties at runtime
2010-06-16 21:04:53 +02:00
# make sure a mutex is used.
def define_property ( name , options = { } , & block )
2009-02-09 20:20:23 +01:00
# check if this property is going to casted
2010-06-16 21:04:53 +02:00
type = options . delete ( :type ) || options . delete ( :cast_as )
if block_given?
type = Class . new ( Hash ) do
include CastedModel
end
type . class_eval { yield type }
type = [ type ] # inject as an array
end
2010-06-20 22:01:11 +02:00
property = Property . new ( name , type , options )
2010-03-03 03:18:32 +01:00
create_property_getter ( property )
2009-01-30 03:45:01 +01:00
create_property_setter ( property ) unless property . read_only == true
2010-06-20 22:01:11 +02:00
if property . type_class . respond_to? ( :validates_casted_model )
validates_casted_model property . name
end
2009-02-06 02:06:12 +01:00
properties << property
2010-06-16 21:04:53 +02:00
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}
2010-06-16 21:04:53 +02:00
read_attribute ( '#{property.name}' )
2009-01-30 03:45:01 +01:00
end
EOS
2010-05-21 23:00:19 +02:00
if [ 'boolean' , TrueClass . to_s . downcase ] . include? ( property . type . to_s . downcase )
2009-07-25 05:12:03 +02:00
class_eval <<-EOS, __FILE__, __LINE__
def #{property.name}?
2010-06-16 21:04:53 +02:00
value = read_attribute ( '#{property.name}' )
! ( value . nil? || value == false )
2009-07-25 05:12:03 +02:00
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)
2010-06-16 21:04:53 +02:00
write_attribute ( '#{property_name}' , value )
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
2010-03-03 03:18:32 +01:00
2009-01-30 03:45:01 +01:00
end # module ClassMethods
2010-03-03 03:18:32 +01:00
2009-01-30 03:45:01 +01:00
end
end
2009-09-22 15:19:08 +02:00
end