proxy_method

Prevent running an inherited method directly.

The purpose of this gem is to prevent directly running the inherited methods you choose to block, and instead raise a custom Error message. The original method can still be called under a different name.

This was created to help enforce the use of interactors over directly calling ActiveRecord methods like create, save, and update. One downside of interactors is that they require top-of-mind awareness in order to use them, and by default new/forgetful/overworked developers will create ActiveRecord instances all willy nilly until society crumbles.

While this was created for ActiveRecord models specifically, it works with any inherited methods. The method being proxied has to already exist, or it can't be overridden.

Usage

Given these basic classes:

class Animal
  def self.create
    'created'
  end

  def save
    'saved'
  end

  def update
    'updated'
  end
end

class Turtle < Animal
  include ProxyMethod

  proxy_class_method :create
  proxy_instance_method :save

  # for instance methods, you can also just call "proxy_method"
  proxy_method :update
end

You can do this:

Turtle.create
# => RuntimeError: Disabled by proxy_method

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

Turtle.unproxied_create
# => 'created'

Turtle.new.unproxied_save
# => 'saved'

Raise a custom error message:

class CustomCow < Animal
  include ProxyMethod

  proxy_method :save, raise: "Don't save here, use an interactor!"
end

CustomCow.new.save
# => RunTimeError: Don't save here, use an interactor!

Specify multiple methods at once:

class MultiMonkey < Animal
  include ProxyMethod

  proxy_method :save, :update, raise: "Use an interactor!"
end

MultiMonkey.new.save
# => RunTimeError: Use an interactor!

MultiMonkey.new.update
# => RunTimeError: Use an interactor!    

Supply your own prefix for the original, unproxied method:

class PrefixPelican < Animal
  include ProxyMethod

  proxy_method :save, prefix: 'pelican_'
end

PrefixPelican.new.pelican_save
# => 'saved'

Use an "unproxied" version of an instance:

class DefaultDuck < Animal
  include ProxyMethod

  proxy_method :save
end

duck = DefaultDuck.new
duck_unproxied = duck.unproxied

duck.save
# => RunTimeError: Disabled by proxy_method 

duck_unproxied.save
# => 'saved'

This is important, because if you proxy a method like #save in ActiveRecord, you're also preventing the natural use of #create and #update since these rely on #save under the hood. "Unproxying" the entire instance then frees an interactor to use #create, #update and #save in an intentional way.

You can also use an unproxied version of the entire class:

duck = DefaultDuck.new
duck_unproxied = DefaultDuck.unproxied.new

duck.save
# => RunTimeError: Disabled by proxy_method 

duck_unproxied.save
# => 'saved'

Run a custom block, instead of raising an exception at all. In other words, actually proxy the method! The object which is passed to the block is already unproxied, you don't have to do this yourself:

class MethodicalMeerkat < Animal
  include ProxyMethod

  proxy_method(:save) do |unproxied_object|
    "indirectly #{unproxied_object.save}!"
  end
end

MethodicalMeerkat.new.save
# => 'indirectly saved!'

The object which is passed to the block is already unproxied, so it's free to use as you'd expect.

From there, you can allow your proxied block it to be used for multiple methods:

class MethodicalMeerkat < Animal
  include ProxyMethod

  proxy_method(:save, :update) do |object, method_name|
    "indirectly #{object.send(method_name)}!"
  end
end

MethodicalMeerkat.new.save
# => 'indirectly saved!'

MethodicalMeerkat.new.update
# => 'indirectly updated!'    

Provide your proxied block with args and a block of its own. The block will be given the unproxied object itself, followed by the standard arguments to method_missing, so it works in much the same way.

class MethodicalMeerkat < Animal
  include ProxyMethod

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

MethodicalMeerkat.new.save
# => 'indirectly saved!'

MethodicalMeerkat.new.update
# => 'indirectly updated!'    

Installation

Add this line to your application's Gemfile:

gem 'proxy_method'

And then execute:

$ bundle

Or install it yourself as:

$ gem install proxy_method

Contributing

Feel free to fork and create a pull request.

License

The gem is available as open source under the terms of the MIT License.