Glad You're Ready. Let's Get Started!

Let us know how we can contact you.

Thank you!

We'll respond shortly.

Unbuilt Rails Dependencies: How to design for loosely-coupled, highly-cohesive components within a Rails application

I’m sure a few of you have encountered this scenario…

You’re a year in and your Rails application is growing in size as well as complexity. It’s becoming increasingly difficult to navigate through a sea of models, not to mention that your test suite is grinding to a screeching halt.

You have a handful of 3rd-party services you integrate with so you start thinking about extracting Gems. Although you’re nervous, as you’ll now need to manage and version multiple Gems. You’re looking at a mountain of work with little feature development.

Here’s a solution that has worked on several projects that might help to avoid this scenario: move loosely-coupled, highly-cohesive components to unbuilt gems within your lib directory while maintaining a single Git repository.

Sounds strange? Here are a few important facts that might help clarify the solution…

There’s only one Git repository and dependent gems are never “built”, their gemspec is simply referenced via your Rails Gemfile. This defers the need for a Gem versioning strategy as you Rails application moves forward with multiple dependent components all under the same umbrella.

From within your Gemfile…

gem 'component_1', path: "lib/component_1"

All your source is in one place. This allows you to continue to develop within a single environment with cross-component refactorings, which is now supported by tools like RubyMine.

Components have their own Gemfile, test_helper, test suite, and continuous integration environment. The unique Gemfile and test helper allows you to remove any unnecessary dependencies (Rails for example). The test suite and continuous integration environment helps to ensure downward dependencies and avoid circular dependencies.

As a result, you tend to end up with loosely-coupled, highly-cohesive components and avoid accidental design. In addition, test suites tend to run faster as they’re loading far fewer Gems and you’ve honored the timeless way of building large applications.

Here’s an example that describes the project structure…


Probably the most compelling argument is prematurely trying to identify and extract components. 3rd-party service integrations seem like a reasonable place to start, however, more recently we’ve been teasing out a variety of components very early within the development lifecycle with much success.

  1. Rami says:

    Nice solution.

    Does it mean also that I don’t need to restart the main app on each change to the engine?

  2. […] you want to start gemifying your libraries, I recommend reading this post from Pivotal Labs about working on gems locally from within your project. IMHO its a great way to migrate your […]

  3. I just got off a project where we did this, and I hated it. If something is packaged as a gem, that means it doesn’t belong in the main application—or, IMHO, in its code repository. The idea in this post basically puts stuff in the main repository that doesn’t belong there. If something is worth making a into a gem, I believe it’s worth moving it out of the main repo altogether.

    • Stephan Hagemann says:

      I believe you are describing only one of the use cases of unbuilt gems in a Rails app. If you are indeed packaging a part of the application that should really be considered its own, then you are still getting the benefits of not having to publish gems all the time on updates and of not having to manage multiple source code repositories if you go “unbuilt.” You can still do the extraction step as soon as you do in fact need the gem in a second application (and it will be very simple).

      There are many uses for gems as a code organization within one application though, and there your comment does not apply. In these cases, extraction is not intended and the code does belong to the application. I recently described a couple options for one type of application in Options for Admin Engines in Component-based Rails Applications.

      • > “There are many uses for gems as a code organization within one application though, and there your comment does not apply. In these cases, extraction is not intended and the code does belong to the application”

        I agree that there are many uses for gems with only one application — I like making parts of my application into gems quite a lot. But I think that if you have a gem, you should extract it and build it. If something is telling you that it wants to be a gem, then I think it’s telling you that it should be a separate project (or at least a separate repository).

        I get the feeling from another comment that you thought I didn’t like the components-based approach. In fact, I like that approach very much — but I prefer to go all the way with it and build my gems as soon as I create them. Unbuilt gems seems like the worst of both worlds: the code’s not in the main application, but I can’t manage it completely separately.

  4. I will say, though, that in the early stages of extraction, this may be useful for the sort of cross-component refactoring you’re talking about. But then what benefit does a Gemfile get you?

    • Stephan Hagemann says:

      The way I write component-based Rails, I like to have tests as low in the hierarchy as possible. I.e., I want to be able to navigate into a gem’s folder and run tests there. For that we need the Gemfile so that `bundle` in that directory can correctly refer to the needed gems.

  5. Michael Barinek says:

    I’m sorry to hear that you didn’t enjoy the component based style. We’ve had tremendous success over the past 4 years on multiple projects using the described approach.

    The Gem provides all the same benefits of any library; loose coupling, high cohesion, and confidence that you’re testing only the intended library. The approach provides you more control over your codebase and better describes component interaction and ensures north-south dependencies. The main benefit is confidence to move forward fast for large project teams without dealing with Gem versioning.

    Worth mentioning, the approach is intended for multiple applications (3+) that all share a similar set of domain specific libraries (6+) within a given team or company.

    Here are a few links that might help –


  6. Jeff Dickey says:

    What’s the recommended way to hook the component’s RSpec coverage into that of the parent project? Following your example’s naming convention, I have a `rails_app/spec` directory tree which includes a current total of 664 examples not including those in `lib/component_1`. If I then `cd lib/component_1 && bundle exec rspec`, I get a successful run with `14 examples, 0 failures`. However, those 14 examples are not included in the 664 reported as having been run from the application root.

  7. mazharoddin mohammed says:

    Thanks for the nice articles on Engines, I am new to Rails but started writing code and able to mount different engines, everything is working as expected, But I am facing problem while deploying the engine.
    I am using capistrano to deploy the app, and deployment was successful if I deploy app alone, but I am facing issue when I try to deploy app along with Engine.

    My app structure is like this.


    I checked-in blog engine code as a private repository using bitbucket and I am able to pull the code using bundle install, but changes are not getting deployed if I do some modifications to engine and check-in the code.
    And if i try to re package vendor/cache directory using command ‘bundle package –all’ am getting below error whenever i try to deploy.Later all attempts to deploy app along with engine’s are unsuccessful. Can you please let me know how to deploy engines along with main apps using capistrano and how to use local path to develop the engine and mount it on main app during development.

    ** [out ::] Git error: command `git reset –hard 523eb08393945438fd42602fa97a14bdfe48f166`
    ** [out ::]
    ** [out ::] in directory
    ** [out ::]
    ** [out ::] /home/deployer/apps/****/shared/bundle/ruby/2.2.0/bundler/gems/****-523eb0839394
    ** [out ::]
    ** [out ::] has failed.

Post a Comment

Your Information (Name required. Email address will not be displayed with comment.)

* Copy This Password *

* Type Or Paste Password Here *