lib/moosex/attribute.rb
require 'moosex/types'
require 'moosex/attribute/modifiers'
module MooseX
class Attribute
include MooseX::Types
attr_reader :attr_symbol, :methods, :attribute_map
def is ; @attribute_map[:is] ; end
def writter ; @attribute_map[:writter] ; end
def reader ; @attribute_map[:reader] ; end
def override ; @attribute_map[:override] ; end
def doc ; @attribute_map[:doc] ; end
def default ; @attribute_map[:default] ; end
@@LIST_OF_PARAMETERS = [
:is, #MooseX::AttributeModifiers::Is ],
:isa, #MooseX::AttributeModifiers::Isa ],
:default, #MooseX::AttributeModifiers::Default ],
:required, #MooseX::AttributeModifiers::Required ],
:predicate, #MooseX::AttributeModifiers::Predicate],
:clearer, #MooseX::AttributeModifiers::Clearer ],
:traits, #MooseX::AttributeModifiers::Traits ],
:handles, #MooseX::AttributeModifiers::Handles ],
:lazy, #MooseX::AttributeModifiers::Lazy ],
:reader, #MooseX::AttributeModifiers::Reader ],
:writter, #MooseX::AttributeModifiers::Writter ],
:builder, #MooseX::AttributeModifiers::Builder ],
:init_arg, #MooseX::AttributeModifiers::Init_arg ],
:trigger, #MooseX::AttributeModifiers::Trigger ],
:coerce, #MooseX::AttributeModifiers::Coerce ],
:weak, #MooseX::AttributeModifiers::Weak ],
:doc, #MooseX::AttributeModifiers::Doc ],
:override, #MooseX::AttributeModifiers::Override ],
]
def initialize(attr_symbol, options ,klass)
@attr_symbol = attr_symbol
@attribute_map = {}
init_internal_modifiers(options.clone, klass.__moosex__meta.plugins, klass)
end
def init_internal_modifiers(options, plugins, klass)
list = @@LIST_OF_PARAMETERS.map do |parameter|
MooseX::AttributeModifiers::const_get(parameter.capitalize).new(self)
end
list.each do |plugin|
plugin.prepare(options)
end
plugins.sort.uniq.each do |plugin_klass|
begin
plugin_klass.new(self).prepare(options)
rescue => e
raise "Unexpected Error in #{klass} #{plugin_klass} #{@attr_symbol}: #{e}"
end
end
list.each do |plugin|
plugin.process(options)
end
generate_all_methods
plugins.sort.uniq.each do |plugin_klass|
begin
plugin_klass.new(self).process(options)
rescue NameError => e
next
rescue => e
raise "Unexpected Error in #{klass} #{plugin_klass} #{@attr_symbol}: #{e}"
end
end
MooseX.warn "Unused attributes #{options} for attribute #{@attr_symbol} @ #{klass} #{klass.class}",caller() if ! options.empty?
end
def generate_all_methods
@methods = {}
if @attribute_map[:reader]
@methods[@attribute_map[:reader]] = generate_reader
end
if @attribute_map[:writter]
@methods[@attribute_map[:writter]] = generate_writter
end
inst_variable_name = "@#{@attr_symbol}".to_sym
if @attribute_map[:predicate]
@methods[@attribute_map[:predicate]] = ->(this) do
this.instance_variable_defined? inst_variable_name
end
end
if @attribute_map[:clearer]
@methods[@attribute_map[:clearer]] = ->(this) do
if this.instance_variable_defined? inst_variable_name
this.remove_instance_variable inst_variable_name
end
end
end
generate_handles @attr_symbol
end
def generate_handles(attr_symbol)
delegator = ->(this) { this.__send__(attr_symbol) }
@attribute_map[:handles].each_pair do | method, target_method |
if target_method.is_a? Array
original_method, currying = target_method
@methods[method] = generate_handles_with_currying(delegator, original_method, currying)
else
@methods[method] = Proc.new do |this, *args, &proc|
delegator.call(this).__send__(target_method, *args, &proc)
end
end
end
end
def generate_handles_with_currying(delegator, original_method, currying)
Proc.new do |this, *args, &proc|
a1 = [ currying ]
if currying.is_a?Proc
a1 = currying[]
elsif currying.is_a? Array
a1 = currying.map{|c| (c.is_a?(Proc)) ? c[] : c }
end
delegator.call(this).__send__(original_method, *a1, *args, &proc)
end
end
def init(object, args)
value = nil
value_from_default = false
if args.has_key? @attribute_map[:init_arg]
value = args.delete(@attribute_map[:init_arg])
elsif @attribute_map[:default]
value = @attribute_map[:default].call
value_from_default = true
elsif @attribute_map[:required]
raise InvalidAttributeError, "attr \"#{@attr_symbol}\" is required"
else
return
end
value = @attribute_map[:coerce].call(value)
begin
@attribute_map[:isa].call( value )
rescue MooseX::Types::TypeCheckError => e
raise MooseX::Types::TypeCheckError, "isa check for field #{attr_symbol}: #{e}"
end
unless value_from_default
@attribute_map[:trigger].call(object, value)
end
value = @attribute_map[:traits].call(value)
inst_variable_name = "@#{@attr_symbol}".to_sym
object.instance_variable_set inst_variable_name, value
end
def generate_reader
inst_variable_name = "@#{@attr_symbol}".to_sym
builder = @attribute_map[:builder]
before_get = ->(object) { }
if @attribute_map[:lazy]
type_check = protect_isa(@attribute_map[:isa], "isa check for #{inst_variable_name} from builder")
coerce = @attribute_map[:coerce]
trigger = @attribute_map[:trigger]
traits = @attribute_map[:traits]
before_get = ->(object) do
return if object.instance_variable_defined? inst_variable_name
value = builder.call(object)
value = coerce.call(value)
type_check.call( value )
trigger.call(object, value)
value = traits.call(value)
object.instance_variable_set(inst_variable_name, value)
end
end
->(this) do
before_get.call(this)
this.instance_variable_get inst_variable_name
end
end
def protect_isa(type_check, message)
->(value) do
begin
type_check.call( value )
rescue MooseX::Types::TypeCheckError => e
raise MooseX::Types::TypeCheckError, "#{message}: #{e}"
end
end
end
def generate_writter
writter_name = @attribute_map[:writter]
inst_variable_name = "@#{@attr_symbol}".to_sym
coerce = @attribute_map[:coerce]
type_check = protect_isa(@attribute_map[:isa], "isa check for #{writter_name}")
trigger = @attribute_map[:trigger]
traits = @attribute_map[:traits]
->(this, value) do
value = coerce.call(value)
type_check.call( value )
trigger.call(this,value)
value = traits.call(value)
this.instance_variable_set inst_variable_name, value
end
end
end
end