Most of the built-in object methods aren't actually used in my code and probably aren't needed by the players, so I didn't want to waste time overriding them all. But some of the basic ones were needed, like
is_a?is used by the
eql?methods defined by the classes being wrapped).
My initial plan for
ImmutableProxywas that the code that's using one doesn't have to know or care that it's using one except that it needs to not call any of the mutating functions. However, to avoid having to override every built-in
Objectmethod I think I'm going to change it so that the code does need to know if it wants to call one of the non-overriden
Objectmethods. I doubt I'll need it anywhere in my code, but it was easy enough to add the capabilities to
I overrode a few of the more used Object methods, and then added a
send_to_targetmethod that works just like send, but passed it directly to the target object (after making sure it's an allowed method).
Here's what I've got so far:
# Proxies any object but doesn't allow any mutating method (methods ending in # !) from being called. class ImmutableProxy def initialize(target) @target = target end # Use this to call any built-in object methods that are masked by # ImmutableProxy's built-in object methods. def send_to_target(name, *args, &block) raise "Mutable methods not allowed" unless allowed?(name) @target.__send__(name, *args, &block) end def method_missing(name, *args, &block) send_to_target(name, *args, &block) end def respond_to?(name, include_private=false) return false unless allowed?(name) @target.respond_to?(name, include_private) end def eql?(o) @target.eql?(o) end def ==(o) @target == o end def dup ImmutableProxy.new @target end def hash @target.hash end def is_a?(c) @target.is_a?(c) end def nil? @target.nil? end private def allowed?(name) ! name.to_s.end_with?('!') || !@target.respond_to?(name) end end
More work on ImmutableProxy to make it more flexible