lib/moosex.rb in moosex-0.0.10 vs lib/moosex.rb in moosex-0.0.11

- old
+ new

@@ -4,13 +4,14 @@ # Author:: Tiago Peczenyj (mailto:tiago.peczenyj@gmail.com) # Copyright:: Copyright (c) 2014 Tiago Peczenyj # License:: MIT # require "moosex/version" +require "moosex/types" module MooseX - + def MooseX.included(c) c.extend(MooseX::Core) c.class_exec do @@ -36,35 +37,91 @@ def c.inherited(subclass) subclass.class_exec do old_meta = subclass.__meta - meta = MooseX::Meta.new(old_meta.attrs) + meta = MooseX::Meta.new(old_meta) define_singleton_method(:__meta) { meta } end end end class Meta - attr_reader :attrs - def initialize(attrs=[]) - @attrs = attrs.map{|att| att.clone } + attr_reader :attrs, :after, :before, :around + + def initialize(old_meta=nil) + @attrs = [] + @after = Hash.new {|hash, key| hash[key] = []} + @before= Hash.new {|hash, key| hash[key] = []} + @around= Hash.new {|hash, key| hash[key] = []} + + if old_meta + @attrs = old_meta.attrs.map{|att| att.clone } + @after.merge! old_meta.after.clone + @before.merge! old_meta.before.clone + @around.merge! old_meta.around.clone + end end def add(attr) @attrs << attr end + + def add_after(method, block) + @after[method] << MooseX::Hook::After.new(method, block) + end + def add_before(method, block) + @before[method] << MooseX::Hook::Before.new(method, block) + end + + def add_around(method, block) + @around[method] << MooseX::Hook::Around.new(method, block) + end + def init(object, args) @attrs.each{ |attr| attr.init(object, args) } end end module Core - + def after(method_name, &block) + method = instance_method method_name + + define_method method_name do |*args| + result = method.bind(self).call(*args) + block.call(self,*args) + result + end + + __meta.add_after(method_name, block) + end + + def before(method_name, &block) + method = instance_method method_name + + define_method method_name do |*args| + block.call(self,*args) + method.bind(self).call(*args) + end + + __meta.add_before(method_name, block) + end + + def around(method_name, &block) + method = instance_method method_name + + define_method method_name do |*args| + + block.call(method, self,*args) + + end + __meta.add_around(method, block) + end + def has(attr_name, attr_options = {}) if attr_name.is_a? Array attr_name.each do |attr| has(attr, attr_options) end @@ -90,46 +147,64 @@ __meta.add(attr) end end end + module Hook + class Base + attr_reader :method, :block + def initialize(method, block) + @method= method + @block = block + end + end + + class After < Base + end + + class Before < Base + end + + class Around < Base + end + end + + class InvalidAttributeError < TypeError + + end + class Attribute + include MooseX::Types attr_reader :attr_symbol, :is, :reader, :writter, :lazy, :builder, :methods DEFAULTS= { lazy: false, clearer: false, required: false, predicate: false, - isa: lambda { |value| true }, + isa: isAny, handles: {}, trigger: lambda {|object,value|}, # TODO: implement coerce: lambda {|object| object}, # TODO: implement } REQUIRED = [ :is ] VALIDATE = { is: lambda do |is, field_name| unless [:rw, :rwp, :ro, :lazy, :private].include?(is) - raise "invalid value for field '#{field_name}' is '#{is}', must be one of :private, :rw, :rwp, :ro or :lazy" + raise InvalidAttributeError, "invalid value for field '#{field_name}' is '#{is}', must be one of :private, :rw, :rwp, :ro or :lazy" end end, }; COERCE = { is: lambda do |is, field_name| is.to_sym end, isa: lambda do |isa, field_name| - return isa if isa.is_a? Proc - - return lambda do |new_value| - unless new_value.is_a?(isa) - raise "isa check for \"#{field_name}\" failed: is not instance of #{isa}!" - end - end + isType(isa) end, default: lambda do |default, field_name| return default if default.is_a? Proc return lambda { default } @@ -149,11 +224,11 @@ begin predicate.to_sym rescue => e # create a nested exception here - raise "cannot coerce field predicate to a symbol for #{field_name}: #{e}" + raise InvalidAttributeError, "cannot coerce field predicate to a symbol for #{field_name}: #{e}" end end, clearer: lambda do|clearer, field_name| if ! clearer return false @@ -163,11 +238,11 @@ begin clearer.to_sym rescue => e # create a nested exception here - raise "cannot coerce field clearer to a symbol for #{field_name}: #{e}" + raise InvalidAttributeError, "cannot coerce field clearer to a symbol for #{field_name}: #{e}" end end, handles: lambda do |handles, field_name| unless handles.is_a? Hash @@ -180,11 +255,11 @@ handles = array_of_handles.map do |handle| if handle == BasicObject - raise "ops, should not use BasicObject for handles in #{field_name}" + raise InvalidAttributeError, "ops, should not use BasicObject for handles in #{field_name}" elsif handle.is_a? Class handle = handle.public_instance_methods - handle.superclass.public_instance_methods @@ -258,11 +333,11 @@ init_arg: a, }).merge(o) REQUIRED.each { |field| unless o.has_key?(field) - raise "field #{field} is required for Attribute #{a}" + raise InvalidAttributeError, "field #{field} is required for Attribute #{a}" end } COERCE.each_pair do |field, coerce| if o.has_key? field o[field] = coerce.call(o[field], a) @@ -340,23 +415,24 @@ value = args[ @init_arg ] elsif @default value = @default.call value_from_default = true elsif @required - raise "attr \"#{@attr_symbol}\" is required" + raise InvalidAttributeError, "attr \"#{@attr_symbol}\" is required" else return end - - value = @coerce.call(value) - - @isa.call( value ) + value = @coerce.call(value) + begin + @isa.call( value ) + rescue MooseX::Types::TypeCheckError => e + raise MooseX::Types::TypeCheckError, "isa check for field #{attr_symbol}: #{e}" + end unless value_from_default @trigger.call(object, value) end - inst_variable_name = "@#{@attr_symbol}".to_sym object.instance_variable_set inst_variable_name, value end private @@ -373,11 +449,16 @@ before_get = lambda do |object| return if object.instance_variable_defined? inst_variable_name value = builder.call(object) value = coerce.call(value) - type_check.call(value) + begin + type_check.call( value ) + rescue MooseX::Types::TypeCheckError => e + raise MooseX::Types::TypeCheckError, "isa check for #{inst_variable_name} from builder: #{e}" + end + trigger.call(object, value) object.instance_variable_set(inst_variable_name, value) end end @@ -386,16 +467,21 @@ instance_variable_get inst_variable_name end end def generate_writter + writter_name = @writter inst_variable_name = "@#{@attr_symbol}".to_sym coerce = @coerce type_check = @isa trigger = @trigger Proc.new do |value| value = coerce.call(value) - type_check.call(value) + begin + type_check.call( value ) + rescue MooseX::Types::TypeCheckError => e + raise MooseX::Types::TypeCheckError, "isa check for #{writter_name}: #{e}" + end trigger.call(self,value) instance_variable_set inst_variable_name, value end end end \ No newline at end of file