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
2010-09-18 15:19:15 +02:00
extend ActiveSupport :: Concern
2010-08-12 05:27:53 +02:00
2010-09-18 15:19:15 +02:00
included do
extlib_inheritable_accessor ( :properties ) unless self . respond_to? ( :properties )
self . properties || = [ ]
raise " 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 ( method_defined? ( :[] ) && method_defined? ( :[]= ) )
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
2010-09-18 15:19:15 +02:00
# Read the casted value of an attribute defined with a property.
#
# ==== Returns
# Object:: the casted attibutes value.
2010-06-16 21:04:53 +02:00
def read_attribute ( property )
2010-09-18 15:19:15 +02:00
self [ find_property! ( property ) . to_s ]
2010-06-16 21:04:53 +02:00
end
2010-09-18 15:19:15 +02:00
# Store a casted value in the current instance of an attribute defined
# with a property.
2010-06-16 21:04:53 +02:00
def write_attribute ( property , value )
2010-08-12 05:27:53 +02:00
prop = find_property! ( property )
2010-09-17 23:25:56 +02:00
self [ prop . to_s ] = prop . is_a? ( String ) ? value : prop . cast ( self , value )
2010-06-16 21:04:53 +02:00
end
2010-09-18 15:19:15 +02:00
# Takes a hash as argument, and applies the values by using writer methods
# for each key. It doesn't save the document at the end. Raises a NoMethodError if the corresponding methods are
# missing. In case of error, no attributes are changed.
def update_attributes_without_saving ( hash )
# Remove any protected and update all the rest. Any attributes
# which do not have a property will simply be ignored.
attrs = remove_protected_attributes ( hash )
directly_set_attributes ( attrs )
end
alias :attributes = :update_attributes_without_saving
private
# The following methods should be accessable by the Model::Base Class, but not by anything else!
2010-06-16 21:04:53 +02:00
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-08-12 05:27:53 +02:00
2010-09-18 15:19:15 +02:00
def prepare_all_attributes ( doc = { } , options = { } )
apply_all_property_defaults
if options [ :directly_set_attributes ]
directly_set_read_only_attributes ( doc )
else
2010-10-22 15:39:12 +02:00
doc = remove_protected_attributes ( doc )
2010-09-18 15:19:15 +02:00
end
directly_set_attributes ( doc ) unless doc . nil?
end
2010-09-17 23:25:56 +02:00
2010-08-12 05:27:53 +02:00
def find_property! ( property )
prop = property . is_a? ( Property ) ? property : self . class . properties . detect { | p | p . to_s == property . to_s }
2010-09-18 15:19:15 +02:00
raise ArgumentError , " Missing property definition for #{ property . to_s } " if prop . nil?
prop
2010-08-12 05:27:53 +02:00
end
2010-10-22 15:39:12 +02:00
# Set all the attributes and return a hash with the attributes
# that have not been accepted.
2010-09-18 15:19:15 +02:00
def directly_set_attributes ( hash )
2010-10-22 15:39:12 +02:00
hash . reject do | attribute_name , attribute_value |
2010-09-18 15:19:15 +02:00
if self . respond_to? ( " #{ attribute_name } = " )
2010-10-22 15:39:12 +02:00
self . send ( " #{ attribute_name } = " , attribute_value )
true
2010-09-18 15:19:15 +02:00
elsif mass_assign_any_attribute # config option
self [ attribute_name ] = attribute_value
2010-10-22 15:39:12 +02:00
true
else
false
2010-09-18 15:19:15 +02:00
end
end
end
def directly_set_read_only_attributes ( hash )
property_list = self . properties . map { | p | p . name }
hash . each do | attribute_name , attribute_value |
next if self . respond_to? ( " #{ attribute_name } = " )
if property_list . include? ( attribute_name )
write_attribute ( attribute_name , hash . delete ( attribute_name ) )
end
end
end
def set_attributes ( hash )
attrs = remove_protected_attributes ( hash )
directly_set_attributes ( attrs )
end
2009-02-09 20:20:23 +01:00
module ClassMethods
2010-08-12 05:27:53 +02: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 )
2010-08-12 05:27:53 +02:00
2010-06-20 22:01:11 +02:00
set_callback :save , :before do | object |
write_attribute ( 'updated_at' , Time . now )
write_attribute ( 'created_at' , Time . now ) if object . new?
end
EOS
end
2010-08-12 05:27:53 +02:00
2009-02-06 02:06:12 +01:00
protected
2010-08-12 05:27:53 +02:00
2009-02-06 02:06:12 +01:00
# 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-08-12 05:27:53 +02: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
2010-08-12 05:27:53 +02: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-08-12 05:27:53 +02:00
2009-01-30 03:45:01 +01:00
end # module ClassMethods
2010-08-12 05:27:53 +02:00
2009-01-30 03:45:01 +01:00
end
end
2009-09-22 15:19:08 +02:00
end