Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Implementable interfaces in ruby

branch: master

Fetching latest commit…

Octocat-spinner-32-eaf2f5

Cannot retrieve the latest commit at this time

Octocat-spinner-32 lib
Octocat-spinner-32 test
Octocat-spinner-32 .gitignore
Octocat-spinner-32 MIT-LICENSE
Octocat-spinner-32 README.rdoc
Octocat-spinner-32 Rakefile
Octocat-spinner-32 interface.gemspec
README.rdoc

interface

Implementable interfaces in ruby

Installation

gem install shuber-interface

require 'interface'

Usage

Simply create a module with any methods that you'd like its implementing objects to define

module RemoteControl
  # turns the device on
  def on
  end

  # turns the device off
  def off
  end
end

Then use the implements method in your classes (also aliased as implement to conform with include and extend naming conventions)

class BrokenDevice
  implements RemoteControl
end

BrokenDevice.new.on # NotImplementedError: BrokenDevice needs to implement 'on' for interface RemoteControl

class WorkingDevice < BrokenDevice
  def on
    @power = true
  end

  def method_missing(method, *args)
    method == :off ? @power = false : super
  end

  # Use this whenever you have custom method_missing logic
  # See http://www.ruby-doc.org/core/classes/Object.html#M001006
  def respond_to_missing?(method, include_private)
    method == :off || super
  end
end

WorkingDevice.new.on # true
WorkingDevice.new.off # false

WorkingDevice.interfaces # [RemoteControl]

Testing interface implementations

Include Interface::TestHelper in your test framework

Test::Unit::TestCase.send(:include, Interface::TestHelper)

Then you can use assert_implements_interfaces (aliased as assert_implements_interface) in your tests

class BrokenDeviceTest < Test::Unit::TestCase
  def test_should_implement_interfaces
    assert_implements_interfaces BrokenDevice.new # Failure: unimplemented interface methods for BrokenDevice: {Remote=>["off", "on"]}
  end
end

You can also explicitly list interfaces to test

module MockInterface end

class BrokenDevice
  implements Remote, MockInterface
end

class BrokenDeviceTest < Test::Unit::TestCase
  def test_should_implement_mock_interface
    assert_implements_interface BrokenDevice.new, MockInterface # passes
  end
end

Why would you ever want to use this?

It's useful for when you're working with libraries that have extensible APIs, like writing custom ActiveModel compliant models

Imagine if we defined ActiveModel with something like

module ActiveModel
  # checks if this object has been saved
  def new_record?
  end

  # checks if this object is valid
  def valid?
  end

  # and the rest of the methods...
end

We'd have a nice clear view of exactly which methods our custom implementations need to define AND one centralized place for documentation

Now we can define our custom implementation

class CompliantModel
  implements ActiveModel

  def valid?
    true
  end
end

Then we can easily test if we've completely implemented ActiveModel (or are missing any new methods because ActiveModel was updated)

require 'test/unit'

Test::Unit::TestCase.send(:include, Interface::TestHelper)

class CompliantModelTest < Test::Unit::TestCase
  def test_should_implement_active_model
    assert_implements_interface CompliantModel.new, ActiveModel # Failure: unimplemented interface methods for CompliantModel: {ActiveModel=>["new_record?"]}
  end
end

ActiveModel actually already has a great solution for this problem (providing a module called ActiveModel::Lint::Tests that you can include into your test cases to test compliance with the API) but it's still a good example for demonstrating this gem's usefulness

You can see this gem used in shuber/nestable which is actually why I created it

Patches and pull requests

  • Fork the project.

  • Make your feature addition or bug fix.

  • Add tests for it. This is important so I don't break it in a future version unintentionally.

  • Commit, do not mess with Rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)

  • Send me a pull request. Bonus points for topic branches.

Something went wrong with that request. Please try again.