lib/moosex.rb in moosex-0.0.2 vs lib/moosex.rb in moosex-0.0.3

- old
+ new

@@ -10,18 +10,13 @@ def self.included(o) o.extend(MooseX::Core) o.class_exec do - meta = MooseX::Meta.new() - - #class_variable_set "@@meta".to_sym, meta + meta = MooseX::Meta.new - define_singleton_method :__meta do - meta - # class_variable_get "@@meta".to_sym - end + define_singleton_method(:__meta) { meta } end def initialize(args={}) self.class.__meta().init(self, args) @@ -30,60 +25,179 @@ end module Core - def has(attr_name, attr_options) - attr = MooseX::Attribute.new(attr_name, attr_options) + def has(attr_name, attr_options = {}) + if attr_name.is_a? Array + attr_name.each do |attr| + has(attr, attr_options) + end + elsif attr_name.is_a? Hash + attr_name.each_pair do |attr, options | + has(attr, options) + end + else - g = attr.generate_getter - - define_method attr_name, &g - - s = attr.generate_setter - - if attr_options[:is].eql? :rw - - define_method "#{attr_name}=", &s - - elsif attr_options[:is].eql? :rwp + attr = MooseX::Attribute.new(attr_name, attr_options) - define_method "#{attr_name}=", &s + g = attr.generate_getter - private "#{attr_name}=" + define_method attr.attr_symbol, &g + + s = attr.generate_setter + + case attr.is + when :rw + define_method "#{attr.attr_symbol}=", &s + + when :rwp + define_method "#{attr.attr_symbol}=", &s + + private "#{attr.attr_symbol}=" + end + __meta.add(attr) end - - __meta.add(attr) end - end class Attribute + attr_reader :attr_symbol, :is, :isa, :default, :required + + DEFAULTS= { + :clearer => false, + :required => false, + :predicate => false, + :isa => lambda { |x| true }, + } + + REQUIRED = [ :is ] + + VALIDATE = { + :is => lambda do |is, field_name| + unless [:rw, :rwp, :ro, :lazy].include?(is) + raise "invalid value for field '#{field_name}' is '#{is}', must be one of :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 + end, + :default => lambda do |default, field_name| + return default if default.is_a? Proc + + return lambda { default } + end, + :required => lambda do |required, field_name| + !!required + end, + :predicate => lambda do |predicate, field_name| + begin + if ! predicate + return false + elsif predicate.is_a? TrueClass + return "has_#{field_name}?".to_sym, + end + + return predicate.to_sym + rescue e + # create a nested exception here + raise "cannot coerce field predicate to a symbol for #{field_name}: #{e}" + end + end, + :clearer => lambda do |clearer, field_name| + begin + if ! clearer + return false + elsif clearer.is_a? TrueClass + return "reset_#{field_name}!".to_sym, + end + + return clearer.to_sym + rescue e + # create a nested exception here + raise "cannot coerce field clearer to a symbol for #{field_name}: #{e}" + end + end, + }; + def initialize(a, o) + # todo extract this to a framework, see issue #21 on facebook + o = DEFAULTS.merge(o) + + REQUIRED.each { |field| + unless o.has_key?(field) + raise "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) + end + end + VALIDATE.each_pair do |field, validate| + return if ! o.has_key? field + + validate.call(o[field], a) + end + @attr_symbol = a - @options = o + @is = o[:is] + @isa = o[:isa] + @default = o[:default] + @required = o[:required] + @predicate = o[:predicate] + @clearer = o[:clearer] end def init(object, args) + inst_variable_name = "@#{@attr_symbol}".to_sym + setter = @attr_symbol.to_s.concat("=").to_sym value = nil - + + if @predicate + object.define_singleton_method @predicate do + instance_variable_defined? inst_variable_name + end + end + + if @clearer + object.define_singleton_method @clearer do + if instance_variable_defined? inst_variable_name + remove_instance_variable inst_variable_name + end + end + end + if args.has_key? @attr_symbol value = args[ @attr_symbol ] - elsif @options[:required] + elsif @required raise "attr \"#{@attr_symbol}\" is required" + elsif @default + value = @default.call else - value = (@options[:default].is_a? Proc) ? @options[:default].call : @options[:default] + return end - if @options[:is].eql? :ro + if @is.eql? :ro # TODO: remove redundancy - inst_variable_name = "@#{@attr_symbol}".to_sym type_check = generate_type_check type_check.call(value) object.instance_variable_set inst_variable_name, value else @@ -106,20 +220,11 @@ instance_variable_set inst_variable_name, value end end def generate_type_check - if @options.has_key? :isa - isa = @options[:isa] - return isa if isa.is_a? Proc - - return lambda do |new_value| - raise "isa check for \"#{@attr_symbol}\" failed: lol is not #{isa}!" unless new_value.is_a? isa - end - end - - lambda { |new_value| } + return @isa end end class Meta def initialize \ No newline at end of file