lib/surrounded/context.rb in surrounded-0.1.0 vs lib/surrounded/context.rb in surrounded-0.2.0

- old
+ new

@@ -1,86 +1,181 @@ require 'set' +require 'surrounded/context/role_map' module Surrounded module Context def self.extended(base) base.send(:include, InstanceMethods) + base.singleton_class.send(:alias_method, :setup, :initialize) end def triggers @triggers.dup end + def policy + @policy ||= :trigger + end + private - def setup(*setup_args) - attr_reader(*setup_args) - private(*setup_args) + def apply_roles_on(which) + @policy = which + end - define_method(:initialize){ |*args| - Hash[setup_args.zip(args)].each{ |role, object| + def initialize(*setup_args) + private_attr_reader(*setup_args) - role_module_name = Context.classify_string(role) - klass = self.class + # I want this to work so I can set the arity on initialize: + # class_eval %Q< + # def initialize(#{*setup_args}) + # arguments = parameters.map{|arg| eval(arg[1].to_s) } + # map_roles(setup_args.zip(arguments)) + # apply_roles if policy == :initialize + # end + # > - if mod = klass.const_defined?(role_module_name) && !mod.is_a?(Class) - object = Context.modify(object, klass.const_get(role_module_name)) - end + define_method(:initialize){ |*args| + map_roles(setup_args.zip(args)) - roles[role.to_s] = object - instance_variable_set("@#{role}", object) - } + apply_roles if policy == :initialize } end + def private_attr_reader(*method_names) + attr_reader(*method_names) + private(*method_names) + end + def trigger(name, *args, &block) store_trigger(name) define_method(:"trigger_#{name}", *args, &block) private :"trigger_#{name}" define_method(name, *args){ begin - (Thread.current[:context] ||= []).unshift(self) + apply_roles if policy == :trigger + self.send("trigger_#{name}", *args) + ensure - (Thread.current[:context] ||= []).shift + remove_roles if policy == :trigger end } end def store_trigger(name) @triggers ||= Set.new @triggers << name end - def self.classify_string(string) - string.to_s.gsub(/(?:^|_)([a-z])/) { $1.upcase } - end - - def self.modify(obj, mod) - return obj if mod.is_a?(Class) - if obj.respond_to?(:cast_as) - obj.cast_as(mod) - else - obj.extend(mod) - end - end - module InstanceMethods def role?(name, &block) + return false unless role_map.role?(name) accessor = eval('self', block.binding) - roles.values.include?(accessor) && roles[name.to_s] + role_map.role_player?(accessor) && role_map.assigned_player(name) end def triggers self.class.triggers end private - def roles - @roles ||= {} + def role_map + @role_map ||= RoleMap.new + end + + def map_roles(role_object_array) + role_object_array.each do |role, object| + map_role(role, role_behavior_name(role), object) + end + end + + def map_role(role, mod_name, object) + instance_variable_set("@#{role}", object) + role_map.update(role, mod_name, object) + end + + def policy + @policy ||= self.class.policy + end + + def add_interface(role, behavior, object) + applicator = behavior.is_a?(Class) ? method(:add_class_interface) : method(:add_module_interface) + + role_player = applicator.call(object, behavior) + map_role(role, role_module_basename(behavior), role_player) + role_player.store_context(self) + role_player + end + + def add_module_interface(obj, mod) + adder_name = module_extension_methods.find{|meth| obj.respond_to?(meth) } + return obj if !adder_name + + obj.method(adder_name).call(mod) + obj + end + + def add_class_interface(obj, klass) + wrapper_name = wrap_methods.find{|meth| klass.respond_to?(meth) } + return obj if !wrapper_name + + klass.method(wrapper_name).call(obj) + end + + def remove_interface(role, behavior, object) + remover_name = (module_removal_methods + unwrap_methods).find{|meth| object.respond_to?(meth) } + return object if !remover_name + + object.remove_context + role_player = object.method(remover_name).call + + map_role(role, role_module_basename(behavior), role_player) + + role_player + end + + def apply_roles + traverse_map method(:add_interface) + end + + def remove_roles + traverse_map method(:remove_interface) + end + + def traverse_map(applicator) + role_map.each do |role, mod_name, object| + if self.class.const_defined?(mod_name) + applicator.call(role, self.class.const_get(mod_name), object) + end + end + end + + def module_extension_methods + [:cast_as, :extend] + end + + def wrap_methods + [:new] + end + + def module_removal_methods + [:uncast] + end + + def unwrap_methods + [:__getobj__] + end + + def role_behavior_name(role) + role.to_s.gsub(/(?:^|_)([a-z])/) { $1.upcase }.sub(/_\d+/,'') + end + + def role_module_basename(mod) + mod.to_s.split('::').last end end end end \ No newline at end of file