Search code examples
ruby-on-railsrubyruby-on-rails-3ruby-on-rails-pluginsmixins

A Rails 3 Engine-Gem which is Also an Application wants to share a DRY configuration via Mixin


I have a number of engines which are also gems and also applications (Rails3). They are gems so they can be easily installed and the dependencies managed via bundler in more than one application (its a whole stack upon which multiple applications are built). They are engines to take advantage of Rails resources - models and such. They are applications for two reasons: 1) to provide a full testing environment which is isolated from their including applications so you can do 'rails c' for example and 2) in order to run things like 'rake db:migrate' and seed and more.

I want both my engine and my application to inject some mixins into lower level dependencies. Here is the solution I came up with. It works fine - I am just wondering if anyone has any criticisms of the approach OR a best practice to share regarding the sharing issue OR the overall idea of engine-gem-applications:

The engine:

#my_engine/lib/my_engine.rb
require 'my_engine/config.rb'
module MyEngine
  class Engine < Rails::Engine
    config.to_prepare do
      MyEngine.inject_mixins
    end
  end
end

The application:

#my_engine/config/application.rb
require 'my_engine/config'
module MyEngine
  class Application < Rails::Application
    config.to_prepare do
      MyEngine.inject_mixins
    end
  end
end

The mixin:

#my_engine/lib/my_engine/config.rb
module MyEngine
  module CLASSMETHODS
    def inject_mixins
      ::ApplicationHelper.send(:include, MyEngine)
      ::SomeDependency::SomeClass.send(:include, MyEngine::SomeClassMixin)
    end
    #root should be defined as the root of this engine, ie relative to this file 
    def root
      File.join(File.dirname(__FILE__), '..','..')
    end
  end
extend CLASS_METHODS
end

(Update: I edited the above to wrap the module in my_engine module, otherwise more than one engine using this pattern simultaneously could have unpredictable effects, like MyEngine.root == SomeOtherEngine.root)


Solution

  • There's no rhyme or rule to this, but you have a couple different options.

    Your gem's tests can contain a dummy application for testing. Devise does this, for example. This is accepted practice for gems that are heavily Rails-dependent.

    You can also keep it separate. In the past I've set up a testing application with a Gemfile that points to the gem via path (gem 'mygem', :path => 'some/path'), which makes testing relatively easy. This can double as a sample application that you can provide in a separate repository (keep in mind when tagging the gem you should change the sample application's :path parameter to a specific version). The benefit here is that your sample application is always kept up to date.

    If you're simply talking about unit testing models, you can skip the above and just add a testing dependency on Active Record and SQLite. Keep fixture data with the gem.

    Since you have several of these engines and they will be mixed and matched in different applications, my suggestion is to set up an application that uses all of these gems and serves as your functional testbed. Keep unit tests with the individual gems, of course. This has the added benefit of integration testing between all engines, to ensure there are no conflicts.