interface - <img src=“https://secure.travis-ci.org/shuber/interface.png”/> <img src=“https://codeclimate.com/github/shuber/interface/badges/gpa.svg” /> <img src=“https://codeclimate.com/github/shuber/interface/badges/coverage.svg” />¶ ↑
Experimental interfaces in ruby
Installation¶ ↑
gem install shuber-interface
Requirements¶ ↑
Ruby 1.9+
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
Testing¶ ↑
bundle exec rake
Contributing¶ ↑
-
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.