How to create a Rails gem with conditional dependency installation

Viewed 299

I'm trying to write my first gem, and want to create one that provides functionality to dry-up serialization. I'm using one gem at the moment and don't foresee any issues with it, but I'd like to be able to add support for multiple serialization gems. The only downside I see is that it would require adding them to the dependancy list and installing them into the project that uses the gem I am writing.

Is there a way, using an initializer, to set the preferred gem that a user might want to use and only install that one and not any of the others?

I'm going to write it without this functionality for now, but in the future, I'd like to make it more robust with the support for multiple gems.


I updated with an install generator that creates an initializer with a default gem listed, along with what I thought would work for dynamically adding the dependency. My test passes, and I am able to bundle install locally without any issue and my gem seems to be working as intended, but when I try to push to gemfury I am now getting an error:

remote: Initializing build: done.        
remote: ----→ Building package...        
remote:        RubyGem build detected        
remote:        Invalid gemspec in [/build/app/my_gem.gemspec]: cannot load such file -- blueprinter        
remote:        ERROR:  Error loading gemspec. Aborting.

And in my gemspec

spec.add_dependency MyGem.configuration.serializer || 'blueprinter'

Here is the code relevant to the current configuration attempt:

# lib/my_gem

require "my_gem/version"
require 'my_gem/configuration'
require "my_gem/blueprinter"

module MyGem

  class << self
    attr_accessor :configuration
  end

  def self.configuration(&block)
    @configuration ||= Configuration.new
  end

  def self.configure
    yield(configuration)
  end
end

# lib/my_gem/configuration

module MyGem
  class Configuration
    attr_accessor :serializer

    def initialize
      @serializer = nil
    end
  end
end

1 Answers

I wasn't able to really figure out the dependency injection for now. So I tried to take what @engineersmky suggested and created an install for the gems. So for right now I have this as my install generator

require 'generators/base_generator'

module MyGem
  module Blueprinter
    class InstallGenerator < BaseGenerator
      source_root File.expand_path("../../../templates", __FILE__)

      # Add blueprinter gem to gemfile after my_gem declaration and bundles the newly declared gem
      def install_blueprinter
        remove_other_supported_gems('ActiveModelSerializers', 'FastJsonapi')
        puts 'Installing Blueprinter...'
        insert_into_file('Gemfile',
                         "\ngem 'blueprinter'",
                         after: "gem 'dry_serialization', source: 'https://gem.fury.io/my_private_gems/'")
        run 'bundle install'
      end


      def helper_include
        copy_api_controller
        gsub_file(API_CONTROLLER_PATH, /^\t*(include MyGem::.*)\n/, '')
        puts 'Adding include statement to ApiController'
        insert_into_file(API_CONTROLLER_PATH,
                         "\n\tinclude MyGem::Blueprinter",
                         after: 'class ApiController < ActionController::API'
        )
      end

    end
  end
end

Gets called with rails g my_gem::blueprinter::install This creates an ApiController that inherits from ActionController::Base unless one already exists, inserts gem 'blueprinter' into the gemfile after my_gem's declaration, removes references to other serializer gems, then bundles to install the gem.

I like to separate my controllers into api and application to separate the concerns if I decide on a monolith app :D

Then you can use the dryed up methods that I'm creating in each file for each respective gem :D

Related