module ProxyMethod::ClassMethods

Constants

DEFAULT_PREFIX
DEFAULT_PROXY_MESSAGE

Public Instance Methods

proxied() click to toggle source

Return a proxied version of this class.

If the class has previously been “unproxied”, this returns a copy where all proxies are re-enabled.

# File lib/proxy_method.rb, line 243
def proxied
  self.dup.send(:reproxy!)
end
proxy_class_method(*original_method_names, &proxy_block) click to toggle source

Proxy one or more inherited class methods, so that they are not used directly. Given this base class:

class Animal
  def self.create
    'created'
  end

  def destroy
    'destroyed'
  end
end

The simplest implementation is to pass just a single method name:

class Dog < Animal
  proxy_class_method :create
end

Dog.create  
# => RuntimeError: Disabled by proxy_method

Dog.destroy  
# => 'destroyed'

Or multiple method names:

class Dog < Animal
  proxy_class_method :create, :destroy
end

Dog.create  
# => RuntimeError: Disabled by proxy_method

Dog.destroy  
# => RuntimeError: Disabled by proxy_method

With a custom error message:

class Dog < Animal
  proxy_class_method :create, raise: 'Disabled!'
end

Dog.create  
# => RuntimeError: Disabled!

You can still access the unproxied version by prefixing 'unproxied_' to the method name:

Dog.unproxied_create  
# => 'created'

And you can change the prefix for unproxied versions:

class Dog < Animal
  proxy_class_method :create, prefix: 'original_'
end

Dog.original_create  
# => 'created'

Finally, you can actually proxy the method, by providing an alternative block of code to run:

class Dog < Animal
  proxy_class_method(:create) do |object, method_name, *args, &block|
    "indirectly #{object.send(method_name)}"
  end
end

Dog.create  
# => 'indirectly created'
# File lib/proxy_method.rb, line 80
def proxy_class_method(*original_method_names, &proxy_block)
  options = if original_method_names.last.is_a?(Hash)
    original_method_names.pop
  else
    {}
  end

  original_method_names = Array(original_method_names).flatten

  error_message = options[:raise] || DEFAULT_PROXY_MESSAGE
  prefix = options[:prefix] || DEFAULT_PREFIX

  original_method_names.each do |original_method_name|
    proxied_class_methods.merge!(original_method_name => prefix)
    new_method_name = :"#{prefix}#{original_method_name}"

    self.singleton_class.send(:alias_method, new_method_name, original_method_name)
    define_singleton_method(original_method_name) do |*args, &block|
      if proxy_class_methods_enabled?
        if proxy_block
          proxy_block.call(self.unproxied, original_method_name, *args, &block)
        else
          raise error_message
        end
      else
        send(new_method_name, *args, &block)
      end
    end
  end
end
proxy_instance_method(*original_method_names, &proxy_block) click to toggle source

Proxy one or more inherited instance methods, so that they are not used directly. Given this base class:

class Animal
  def save
    'saved'
  end

  def update
    'updated'
  end
end

The simplest implementation is to pass just a single method name:

class Dog < Animal
  proxy_instance_method :save
end

Dog.new.save  
# => RuntimeError: Disabled by proxy_method

Dog.new.upate  
# => 'updated'

Or use the shorthand form:

class Dog < Animal
  proxy_method :save
end

Or multiple method names:

class Dog < Animal
  proxy_method :save, :update
end

Dog.new.save  
# => RuntimeError: Disabled by proxy_method

Dog.new.update  
# => RuntimeError: Disabled by proxy_method

With a custom error message:

class Dog < Animal
  proxy_method :save, raise: 'Disabled!'
end

Dog.new.save  
# => RuntimeError: Disabled!

You can still access the unproxied version by prefixing 'unproxied_' to the method name:

Dog.new.unproxied_save  
# => 'saved'

And you can change the prefix for unproxied versions:

class Dog < Animal
  proxy_method :save, prefix: 'original_'
end

Dog.new.original_save  
# => 'saved'

Finally, you can actually proxy the method, by providing an alternative block of code to run:

class Dog < Animal
  proxy_method(:save) do |object, method_name, *args, &block|
    "indirectly #{object.send(method_name)}"
  end
end

Dog.new.save  
# => 'indirectly saved'
# File lib/proxy_method.rb, line 192
def proxy_instance_method(*original_method_names, &proxy_block)
  options = if original_method_names.last.is_a?(Hash)
    original_method_names.pop
  else
    {}
  end

  original_method_names = Array(original_method_names).flatten

  error_message = options[:raise] || DEFAULT_PROXY_MESSAGE
  prefix = options[:prefix] || DEFAULT_PREFIX

  original_method_names.each do |original_method_name|
    proxied_instance_methods.merge!(original_method_name => prefix)
    new_method_name = :"#{prefix}#{original_method_name}"

    alias_method new_method_name, original_method_name

    define_method(original_method_name) do |*args, &block|
      if proxy_instance_methods_enabled?
        if proxy_block
          proxy_block.call(self.unproxied, original_method_name, *args, &block)
        else
          raise error_message
        end
      else
        send(new_method_name, *args, &block)
      end
    end
  end
end
Also aliased as: proxy_method
proxy_method(*original_method_names, &proxy_block)
unproxied() click to toggle source

Return an unproxied version of this class.

This returns a copy of the class where all proxies are disabled. This is sometimes necessary when a proxied method is being called by a different method outside your control.

# File lib/proxy_method.rb, line 233
def unproxied
  self.dup.send(:unproxy!)
end

Private Instance Methods

proxied_class_methods() click to toggle source
# File lib/proxy_method.rb, line 258
def proxied_class_methods
  @_proxied_class_methods ||= {}
end
proxied_instance_methods() click to toggle source
# File lib/proxy_method.rb, line 254
def proxied_instance_methods
  @_proxied_instance_methods ||= {}
end
proxy_class_methods_enabled?() click to toggle source
# File lib/proxy_method.rb, line 249
def proxy_class_methods_enabled?
  return @_proxy_class_methods_enabled if defined?(@_proxy_class_methods_enabled)
  @_proxy_class_methods_enabled = true
end
reproxy!() click to toggle source
# File lib/proxy_method.rb, line 267
def reproxy!
  @_proxy_class_methods_enabled = true
  self
end
unproxy!() click to toggle source
# File lib/proxy_method.rb, line 262
def unproxy!
  @_proxy_class_methods_enabled = false
  self
end