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

Let us know how we can contact you.

Thank you!

We'll respond shortly.

LABS
Service Oriented Foreman

I’m a big fan of David Dollar’s foreman for starting up everything you need to run an app. These days were seeing a number of projects that rely on multiple ruby apps with seperate gemfiles to have a usuable development environment.

On one project recently we had three external services which we did not want to depend on for development. We wrote a tiny rails app for each, which had json endpoints, models for fake data, and used Rails Admin almost exclusively for the UI. This was great, but starting each one by hand was a chore, and foreman didn’t want to respect the individual gemfiles.

The first thing that makes this dificult is bundler, which attempts to be helpful and make sure your gemset is still in effect when you shell out. How it does this is an ugly or beautiful hack, depending on your point of view.

    1.9.3p194 :001 > ENV['RUBYOPT']
    => nil
    1.9.3p194 :002 > require 'bundler'
    => true
    1.9.3p194 :003 > Bundler.require
    => [...]
    1.9.3p194 :004 > ENV['RUBYOPT']
    => "-I/Users/mkocher/.rvm/gems/ruby-1.9.3-p194@soloist/gems/bundler-1.2.0.rc.2/lib -rbundler/setup"

As you can see, Bundler has set RUBYOPT to load itself every time you shell out. We can prevent this from happening by using Bundler.with_clean_env.

    1.9.3p194 :005 > Bundler.with_clean_env { ENV['RUBYOPT'] }
    => nil

Better! But we’re not out of the woods yet.

RVM has recently started including rubygems-bundler in the global gemset. I with they hadn’t, but I also don’t want to tell people to uninstall it every time they get a new workstation.

To prevent rubygems-bundler from trying to keep you in your gemset, you’ll want to add a NOEXEC=skip to your environment.

With ruby, the cleanest way to do this is to include the env hash in your call to system:

  system({'NOEXEC'=>'skip'}, "rake -T")

Putting this all together, we made a script called run_app which looks like

    #!/usr/bin/env ruby
    require 'bundler'

    cmd = %Q{bash -lc "cd ../#{ARGV[0]} && source #{ENV['rvm_path']}/scripts/rvm && exec rackup config.ru -p #{ARGV[1]}"}

    Bundler.with_clean_env do
      system({'NOEXEC'=>'skip'}, cmd)
    end

We call this from our main project, and can pass it a directory and the port we want it to run on. foreman start now gives us an entire development environment nicely logging to one terminal.

Comments
  1. This is fantastic, thank you! I wonder if you’ve had issues with generating data on the remote services for your app’s test suite. We’re facing a similar challenge and solved it by mounting the services as engines but we’re trying to move away from that (dependency collisions and other issues). Thanks!

  2. Matthew Kocher says:

    I generally try and avoid testing across service boundaries. Every situation is unique, but if you can’t isolate one app from the other with a simple collection of fake objects or webmock stubs, you might be deceiving yourself about the things being separate services.

    Developers often crave a suite of integration tests that test everything soup-to-nuts, and you can write one with services as long as you test from the outside (and perhaps add some test hooks that are disabled in prod). Be weary when adding to this suite though, as these tests are much less useful than the fine grained tests written while in a tight TDD feedback loop. I try to avoid writing this suite altogether until there’s pain from not having it.

  3. Hurray for Matt! After 8 failed attempts to make Travis run `bundle` within an external project being tested I am so happy that the following patch works! https://github.com/drnic/daily_cloudfoundry_releases/commit/771ce5525f8068d0038db5060c8d40c89cfa8a71

  4. Chamnap says:

    It works well. Thanks alot.

Post a Comment

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

* Copy This Password *

* Type Or Paste Password Here *