lib/surrogate/endower.rb in surrogate-0.5.5 vs lib/surrogate/endower.rb in surrogate-0.6.0

- old
+ new

@@ -32,105 +32,109 @@ def endow_klass klass.extend ClassMethods add_hatchery_to klass enable_defining_methods klass - record_initialization_for_instances_of klass - remember_invocations_for_instances_of klass + klass.send :include, InstanceMethods invoke_hooks klass end def endow_singleton_class hatchery = add_hatchery_to singleton enable_defining_methods singleton singleton.module_eval &block if block klass.instance_variable_set :@hatchling, Hatchling.new(klass, hatchery) - remember_invocations_for_instances_of singleton invoke_hooks singleton klass end def invoke_hooks(klass) self.class.hooks.each { |hook| hook.call klass } end - # yeesh :( pretty sure there isn't a better way to do this - def record_initialization_for_instances_of(klass) - def klass.method_added(meth) - return if meth != :initialize || @hijacking_initialize - @hijacking_initialize = true - current_initialize = instance_method :initialize - - # `define' records the args while maintaining the old behaviour - # we have to do it stupidly like this because there is no to_proc on an unbound method - define :initialize do |*args, &block| - current_initialize.bind(self).call(*args, &block) - end - ensure - @hijacking_initialize = false - end - initialize = klass.instance_method :initialize - klass.__send__ :define_method, :initialize do |*args, &block| - initialize.bind(self).call(*args, &block) - end - end - def singleton klass.singleton_class end - def remember_invocations_for_instances_of(klass) - klass.__send__ :define_method, :invocations do |method_name| - @hatchling.invocations method_name - end - end - def add_hatchery_to(klass) klass.instance_variable_set :@hatchery, Surrogate::Hatchery.new(klass) end def enable_defining_methods(klass) def klass.define(method_name, options={}, &block) @hatchery.define method_name, options, &block end - - def klass.api_method_names - @hatchery.api_method_names - end end end # use a module so that the method is inherited (important for substitutability) module ClassMethods # Should this be dup? (dup seems to copy singleton methods) and may be able to use #initialize_copy to reset ivars # Can we just remove this feature an instead provide a reset feature which could be hooked into in before/after blocks (e.g. https://github.com/rspec/rspec-core/blob/622505d616d950ed53d12c6e82dbb953ba6241b4/lib/rspec/core/mocking/with_rspec.rb) def clone - hatchling, hatchery = @hatchling, @hatchery + hatchling, hatchery, parent_name = @hatchling, @hatchery, name Class.new self do + extend Module.new { define_method(:name) { parent_name && parent_name + '.clone' } } # inherit the name -- use module so that ApiComparison comes out correct (real classes inherit their name method) Surrogate.endow self do hatchling.api_methods.each { |name, options| define name, options.to_hash, &options.default_proc } end hatchery.api_methods.each { |name, options| define name, options.to_hash, &options.default_proc } end end # Custom new, because user can define initialize, and we need to record it - # Can we move this into the redefinition of initialize and have it explicitly record itself? def new(*args) instance = allocate self.last_instance = instance instance.instance_variable_set :@hatchling, Hatchling.new(instance, @hatchery) - instance.send :initialize, *args + instance.__send__ :initialize, *args instance end def last_instance Thread.current["surrogate_last_instance_#{self.object_id}"] end def last_instance=(instance) Thread.current["surrogate_last_instance_#{self.object_id}"] = instance + end + + + def inspect + return name if name + methods = SurrogateClassReflector.new(self).methods + method_inspections = [] + + # add class methods + if methods[:class][:api].any? + meth_names = methods[:class][:api].to_a.sort.take(4) + meth_names[-1] = '...' if meth_names.size == 4 + method_inspections << "Class: #{meth_names.join ' '}" + end + + # add instance methods + if methods[:instance][:api].any? + meth_names = methods[:instance][:api].to_a.sort.take(4) + meth_names[-1] = '...' if meth_names.size == 4 + method_inspections << "Instance: #{meth_names.join ' '}" + end + + # when no class or instance methods + method_inspections << "no api" if method_inspections.empty? + + "AnonymousSurrogate(#{method_inspections.join ', '})" + end + end + + # Use module so the method is inherited. This allows proper matching (e.g. other object will inherit inspect from Object) + module InstanceMethods + def inspect + methods = SurrogateClassReflector.new(self.class).methods[:instance][:api].sort.take(4) + methods[-1] = '...' if methods.size == 4 + methods << 'no api' if methods.empty? + class_name = self.class.name || 'AnonymousSurrogate' + "#<#{class_name}: #{methods.join ' '}>" end end end