active_interface

ActiveInterface is a Ruby library for defining OOP interfaces in ruby

Getting Started

Add to your Gemfile

  gem 'active_interface'

Then Bundle install. Now create a directory like app/interfaces and put your first interface there. Any methods defined (or attributes specified in REQUIRED_ATTRIBUTES) will be enforced on the class the interface is appended to.

module ExampleInterface  
  extend ActiveInterface::Base

  REQUIRED_ATTRIBUTES = %i[size count count=].freeze

  def example
    super
  end
end

Make sure any methods defined either call super or interface_contract (more info below) in order for the underlying method to be executed.

for any class that you want to apply this interface to, append it after the definition. This ensures all the methods are defined by the class, and that when the Interface is appended that it will sit in front of the method calls and act as a pass through.

class MyClass
  ...
end
MyClass.append ExampleInterface

What's an OOP interface?

An interfaces allows developers to define an abstract collection of methods and attributes that must be implemented by a class. This allows developers to code against an abstract interface (that many classes could implement) instead of just a specific classes implementation.

Given the following class

class User

  attr_accessor :first_name, :last_name

  def full_name(seperator)
    first_name + seperator + last_name
  end
end

if we also want a Contact or Admin to have the same behavior, we might reach for creating a common Person class. But what if each of our Classes already inherit from another class? We could extact everything into a PersonModule. But if we also have a Product class, or a Car class with the same behavior but their own implementations, how can we allow them to be wildly different and yet similar enough to treat the same in certain circumstances?

Enter Active Interface!

module NameInterface

  extend ActiveInterface::Base

  REQUIRED_ATTRIBUTES = %i[first_name last_name].freeze

  def full_name(seperator)
    super 
  end

end

User.append NameInterface

Once we append User with NameInterface, we gain the following:

  • Will raise exception if any of the required attributes are not defined
  • Will raise exception if the expected methods are not defined or have different method signatures
  • the interface sits in front of every call to the methods
  • we can ask User.has_interface?(NameInterface) => true to develop our code against.

What's an interface contract?

Once appended, Active Interface will ensure that certain methods/signatures and attributes are present at initialization. However, as a ruby is a dynamically typed language, it can be ambiguious what the expected inputs and outputs are for a method or how flexible they are. As a developer that must rely on an interface created by another team, how can you be sure you'll get the expected return values, or that you know what the expected inputs are?

Enter Interface Contracts with Active Interface!

module NameInterface

  include ActiveInterface::Base

  REQUIRED_ATTRIBUTES = %i[first_name last_name].freeze

  def full_name(seperator)
    interface_contract(binding) do |c|
      c.enforce_input :seperator, kind_of: String, length: 1..4
      c.enforce_output kind_of: String
    end
  end
end

User.append NameInterface

With the above Interface, every call to #full_name will be enforced to adheres to the interface contract. This carries a number of benefits:

  • Documentation about what the expected parameters and return values are for the Interface
  • method level validations of input and output values abstracted from implementation
  • raises InterfaceError for any incorrect parameters per the contract
  • raises InterfaceError if the output does not conform to the contract
  • Ensures developers can implement interfaces and code against them reliably
  • the results of super are only called once all the inputs have been enforced, or at the end of the block.
  • If there are no exceptions raised, the block returns the result of super

Other Active Interface uses

Because of the runtime capabilities, Active Interface can be used for more than just API conformity. Some additional uses could include:

  • Logging request/response of method calls.
  • Transforming inputs or outputs (such as always calling .to_s)
  • Caching
  • Running history of previous inputs and outputs
  • And more!