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
2011-05-09 17:47:27 +02:00
class_attribute ( :properties ) unless self . respond_to? ( :properties )
class_attribute ( :properties_by_name ) unless self . respond_to? ( :properties_by_name )
2010-09-18 15:19:15 +02:00
self . properties || = [ ]
2011-04-20 16:44:49 +02:00
self . properties_by_name || = { }
2010-09-18 15:19:15 +02:00
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
2011-05-19 00:08:58 +02:00
def as_json ( options = nil )
Hash [ self ] . reject { | k , v | v . nil? } . as_json ( options )
end
2010-12-23 02:09:00 +01:00
# Returns the Class properties with their values
#
# ==== Returns
# Array:: the list of properties with their values
2010-11-11 01:33:46 +01:00
def properties_with_values
props = { }
properties . each { | property | props [ property . name ] = read_attribute ( property . name ) }
props
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
2011-04-20 12:31:46 +02:00
# with a property and update dirty status
2010-06-16 21:04:53 +02:00
def write_attribute ( property , value )
2011-02-28 16:00:41 +01:00
prop = find_property! ( property )
value = prop . is_a? ( String ) ? value : prop . cast ( self , value )
2011-04-20 16:44:49 +02:00
couchrest_attribute_will_change! ( prop . name ) if use_dirty? && self [ prop . name ] != value
2011-04-20 12:31:46 +02:00
self [ prop . name ] = value
2011-02-28 16:00:41 +01: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
2010-12-23 02:09:00 +01:00
# missing. In case of error, no attributes are changed.
2010-09-18 15:19:15 +02:00
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 )
2011-04-20 12:31:46 +02:00
directly_set_attributes ( attrs )
2010-09-18 15:19:15 +02:00
end
alias :attributes = :update_attributes_without_saving
2011-03-03 08:28:57 +01:00
# 'attributes' needed for Dirty
alias :attributes :properties_with_values
2011-02-28 16:00:41 +01:00
2011-04-20 12:31:46 +02:00
def set_attributes ( hash )
attrs = remove_protected_attributes ( hash )
directly_set_attributes ( attrs )
end
protected
2011-02-28 16:00:41 +01:00
def find_property ( property )
2011-04-20 16:44:49 +02:00
property . is_a? ( Property ) ? property : self . class . properties_by_name [ property . to_s ]
2011-02-28 16:00:41 +01:00
end
2010-09-18 15:19:15 +02:00
# 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
2011-04-20 12:31:46 +02:00
# Never mark default options as dirty!
dirty , self . disable_dirty = self . disable_dirty , true
2009-02-06 03:57:11 +01:00
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
2011-04-20 12:31:46 +02:00
self . disable_dirty = dirty
2009-06-10 06:02:04 +02:00
end
2010-08-12 05:27:53 +02:00
2011-06-08 18:22:35 +02:00
def prepare_all_attributes ( attrs = { } , options = { } )
2011-04-20 12:31:46 +02:00
self . disable_dirty = ! ! options [ :directly_set_attributes ]
2010-09-18 15:19:15 +02:00
apply_all_property_defaults
if options [ :directly_set_attributes ]
2011-06-08 18:22:35 +02:00
directly_set_read_only_attributes ( attrs )
directly_set_attributes ( attrs , true )
2010-09-18 15:19:15 +02:00
else
2011-06-08 18:22:35 +02:00
attrs = remove_protected_attributes ( attrs )
directly_set_attributes ( attrs )
2010-09-18 15:19:15 +02:00
end
2011-04-20 12:31:46 +02:00
self . disable_dirty = false
2011-06-08 18:22:35 +02:00
self
2010-09-18 15:19:15 +02:00
end
2010-09-17 23:25:56 +02:00
2010-08-12 05:27:53 +02:00
def find_property! ( property )
2011-02-28 16:00:41 +01:00
prop = find_property ( property )
2010-09-18 15:19:15 +02:00
raise ArgumentError , " Missing property definition for #{ property . to_s } " if prop . nil?
2010-08-12 05:27:53 +02:00
prop
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.
2011-06-08 18:22:35 +02:00
def directly_set_attributes ( hash , mass_assign = false )
return if hash . nil?
hash . reject do | key , value |
if self . respond_to? ( " #{ key } = " )
self . send ( " #{ key } = " , value )
elsif mass_assign || mass_assign_any_attribute
self [ key ] = value
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
2010-12-23 02:09:00 +01:00
2010-09-18 15:19:15 +02:00
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 )
2011-04-30 13:13:38 +02:00
raise " Invalid property definition, ' #{ name } ' already used for CouchRest Model type field " if name . to_s == model_type_key . to_s && CouchRest :: Model :: Base > = self
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
2011-04-01 19:45:13 +02:00
# on the document whenever saving occurs.
#
# These properties are casted as Time objects, so they should always
# be set to UTC.
2010-06-20 22:01:11 +02:00
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
2011-02-05 22:38:22 +01:00
if block . arity == 1 # Traditional, with options
type . class_eval { yield type }
else
type . instance_exec ( & block )
end
2010-06-16 21:04:53 +02:00
type = [ type ] # inject as an array
end
2011-03-06 03:41:37 +01: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
2011-04-20 16:44:49 +02:00
properties_by_name [ property . to_s ] = 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)
2011-04-20 12:31:46 +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
2010-11-11 01:33:46 +01:00