Viktor Trón
Viktor Trón

Reputation: 8894

proper way to specify optional dependencies of a gem in ruby Gemfile

a gem intends to support gems a or b as alternatives for a functionality.

In code I check with defined?(A) if I fall back to b that's fine.

But as a gem developer how to specify these dependencies?

1) what do I put in the Gemfile.

group :development, :test do
  gem 'a', :require => false
  gem 'b', :require => false
end

This allows Bundle.require(:test) not to auto-require a,b?

2) How can explicitly require a and b separately to mimic (or mock) the scenario when we fall back to b in my tests?

3) Also how do I specify that either a or b is prerequisite for the gem.

thanks

Upvotes: 5

Views: 2934

Answers (2)

Paulo Henrique
Paulo Henrique

Reputation: 1025

I got this same question a while ago. My solution was to think that the developer should specify this behavior. I would not specify it on the gem, but on the wiki. I would recommend you to document it clearly that the developer need to define one of the dependencies.

To make it better, you can make a check on initialization of the gem, looking for the dependencies, if none can be found, just raise a runtime exception or if you prefer, your own exception. =)

Upvotes: 0

Matheus Moreira
Matheus Moreira

Reputation: 17030

Don't include the a gem in your dependencies, but require it anyway. If that fails, it will raise LoadError, from which you can rescue.

begin
  require 'a'
rescue LoadError
  # The 'a' gem is not installed
  require 'b'
end

I believe this is the best way to use and test this setup:

  1. Define an interface for your backend and allow a custom implementation to be easily plugged in.

    module YourGem
      class << self
        attr_accessor :backend
    
        def do_something_awesome
          backend.do_something_awesome
        end
      end
    end
    
  2. Implement the a and b backends.

    # your_gem/backends/a.rb
    require 'a'
    
    module YourGem::Backends::A
      def self.do_something_awesome
        # Do it
      end
    end
    
    # your_gem/backends/b.rb
    require 'b'
    
    module YourGem::Backends::B
      def self.do_something_awesome
        # Do it
      end
    end
    
  3. Set the one you want to use.

    begin
      require 'your_gem/backends/a'
      Gem.backend = YourGem::Backends::A
    rescue LoadError
      require 'your_gem/backends/b'
      Gem.backend = YourGem::Backends::B
    end
    

    This will use YourGem::Backend::A even if b is installed.

  4. Before testing, make sure both a and b gems are installed, require both backends in the test code, run the tests with one backend, then run the tests again with the other backend.

Upvotes: 4

Related Questions