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