lib/squares/base.rb in squares-0.2.9 vs lib/squares/base.rb in squares-0.3.0
- old
+ new
@@ -2,21 +2,54 @@
class Base
attr_accessor :id
def initialize *args
apply *args
+ trigger :after_initialize
end
def save
- store[@id] = Marshal.dump self
+ trigger :before_save
+ @_changed = false
+ store[@id] = Marshal.dump self.dup
+ trigger :after_save
nil
end
+ def delete
+ self.class.delete self.id
+ end
+
+ def destroy
+ trigger :before_destroy
+ delete
+ end
+
def == other
@id == other.id && properties_equal(other)
end
+ def [](key)
+ get_property key
+ end
+
+ def []=(key, value)
+ set_property key, value
+ end
+
+ def update_properties new_properties
+ new_properties.each do |key, value|
+ self[key] = value if valid_property?(key)
+ end
+ save
+ end
+ alias_method :update_attributes, :update_properties
+
+ def changed?
+ @_changed
+ end
+
def properties
self.class.properties
end
def to_h(key_name = :id)
@@ -25,42 +58,96 @@
h[property] = self.send(property)
end
h
end
+ def valid_property? property
+ self.class.valid_property? property
+ end
+
+ def defaults
+ self.class.defaults
+ end
+
private
+ def trigger hook_name
+ return if @hook_callback_in_progress
+ hooks = self.class.hooks
+ if hooks && hooks[hook_name]
+ hooks[hook_name].each do |hook|
+ @hook_callback_in_progress = true
+ self.instance_eval &hook
+ @hook_callback_in_progress = false
+ end
+ end
+ end
+
def properties_equal other
! properties.detect do |property|
self.send(property) != other.send(property)
end
end
def apply *args
@id, values = *args
- value_hash = values.to_h
- self.class.properties.each do |property|
- value = value_hash.has_key?(property) ? value_hash[property] : default_for(property)
- self.send "#{property.to_s.gsub(/\?$/,'')}=".to_sym, value
+ values_hash = values.to_h
+ properties_sorted_by_defaults.each do |property|
+ value = values_hash.has_key?(property) ? values_hash[property] : default_for(property)
+ set_property property, value
end
end
+ def properties_sorted_by_defaults
+ (properties || []).sort do |a,b|
+ a_val = defaults[a].respond_to?(:call) ? 1 : 0
+ b_val = defaults[b].respond_to?(:call) ? 1 : 0
+ a_val <=> b_val
+ end
+ end
+
def default_for property
- self.class.defaults[property]
+ defaults[property].respond_to?(:call) ?
+ defaults[property].call(self) :
+ defaults[property]
end
+ def get_property property
+ instance_variable_get "@#{instance_var_string_for property}"
+ end
+
+ def set_property property, value
+ unless valid_property?(property)
+ raise ArgumentError.new("\"#{property}\" is not a valid property of #{self.class}")
+ end
+ instance_variable_set("@#{instance_var_string_for property}", value).tap do |value|
+ @_changed = true
+ end
+ end
+
+ def instance_var_string_for property
+ property = self.class.normalize_property property
+ if property.to_s.match(/\?$/)
+ "#{property.to_s.gsub(/\?$/,'')}__question__".to_sym
+ else
+ property
+ end
+ end
+
def store
self.class.store
end
#############
class << self
include Enumerable
def [] id
if item = store[id]
- Marshal.restore item
+ Marshal.restore(item).tap do |item|
+ item.instance_eval 'trigger :after_find'
+ end
end
end
alias_method :find, :[]
def []= *args
@@ -73,11 +160,15 @@
raise ArgumentError.new(<<-ERR)
You must provide an instance of #{self.name} or at least
something which responds to #to_h"
ERR
end
- instance.tap { |i| i.save }
+ instance.tap do |i|
+ i.instance_eval 'trigger :before_create'
+ i.save
+ i.instance_eval 'trigger :after_create'
+ end
end
alias_method :create, :[]=
def has_key? key
store.has_key? key
@@ -92,36 +183,69 @@
def values
store.values.map{ |i| Marshal.restore i }
end
+ def valid_property? property
+ !!normalize_property(property)
+ end
+
+ def normalize_property property
+ unless properties.include?(property)
+ property = "#{property}?".to_sym if properties.include?("#{property}?".to_sym)
+ end
+ properties.include?(property) && property
+ end
+
def delete id
store.delete id
end
def each &block
values.each &block
end
- alias_method :where, :select
- def property prop, opts
+ def where(*args, &block)
+ result_set = []
+ if block
+ result_set = values.select(&block)
+ return result_set if result_set.empty?
+ end
+
+ args.each do |arg|
+ if arg.kind_of?(Hash)
+ result_set = (!result_set.empty? ? result_set : values).reject do |i|
+ failed_matches = 0
+ arg.each do |k,v|
+ raise ArgumentError.new("\"#{k}\" is not a valid property of #{self}") unless valid_property?(k)
+ failed_matches += 1 unless i[k] == v
+ end
+ failed_matches > 0
+ end
+ elsif arg.kind_of?(Symbol)
+ raise ArgumentError.new("\"#{arg}\" is not a valid property of #{self}") unless valid_property?(arg)
+ result_set = (result_set.empty? ? values : result_set).select do |i|
+ i[arg]
+ end
+ end
+ end
+ result_set
+ end
+
+ def property prop, opts={}
@_properties ||= []
@_properties << prop
uniquify_properties
- if prop.to_s.match(/\?$/)
- define_method prop do
- instance_variable_get "@#{self.class.instance_var_for prop}"
- end
- define_method prop.to_s.gsub(/\?$/, '=') do |v|
- instance_variable_set "@#{self.class.instance_var_for prop}", v
- end
- else
- attr_accessor prop
+ define_method prop do
+ get_property prop
end
- if default = opts[:default]
- defaults[prop] = default
+ define_method "#{prop.to_s.gsub(/\?$/, '')}=" do |v|
+ set_property prop, v
end
+ if opts.has_key?(:default)
+ defaults[prop] = opts[:default]
+ end
end
def properties *props
props.each do |prop|
property prop, {}
@@ -146,22 +270,55 @@
def store
@store ||= {}
end
- def instance_var_for property
- if property.to_s.match(/\?$/)
- "#{property.to_s.gsub(/\?$/,'')}__question__".to_sym
- else
- property
- end
- end
-
def models
@_models.uniq.sort { |a,b| a.to_s <=> b.to_s }
end
+ ### hooks
+ def hooks
+ @_hooks
+ end
+
+ def before_create *args, &block
+ add_hook :before_create, args, block
+ end
+
+ def after_create *args, &block
+ add_hook :after_create, args, block
+ end
+
+ def after_initialize *args, &block
+ add_hook :after_initialize, args, block
+ end
+
+ def after_find *args, &block
+ add_hook :after_find, args, block
+ end
+
+ def before_save *args, &block
+ add_hook :before_save, args, block
+ end
+
+ def after_save *args, &block
+ add_hook :after_save, args, block
+ end
+
+ def before_destroy *args, &block
+ add_hook :before_destroy, args, block
+ end
+
+ ### /hooks
+
private
+
+ def add_hook hook_id, args, block
+ @_hooks ||= {}
+ @_hooks[hook_id] ||= []
+ @_hooks[hook_id] << block
+ end
def uniquify_properties
@_properties = @_properties.uniq.compact
end