Bullet Train is distributed as an application template in a Git repository that we grant you access to.

We're not the only ones in the world distributing a software framework this way, but it's not exactly common either, so let's talk through a bit of the how and why.

How does it work in practice?

When you create a new app with Bullet Train, you'll:

  1. clone our private repository,
  2. rename the origin remote to bullet-train,
  3. create your own private repository,
  4. set the new repository as your origin remote.

How do updates work?

After following the previously mentioned steps, we're now "upstream" for you, so when we merge new updates into bullet-train/master, you have the ability to simply merge those into your own project.

(We actually recommend you create a feature branch, and then git merge bullet-train/master into that branch. In that branch, take whatever steps the CHANGELOG.md says are required, run the test suite, and when you're sure everything is in good shape, merge that branch into master.)

The vast majority of the time, these merges happen cleanly. This is because, for the most part, folks building on Bullet Train don't need to make major modifications to the core functionality we're providing, so our updates and your code never directly conflict. However, if you need to, you absolutely can customize or modify the functionality we provide. Let's talk about that.

Can you customize Bullet Train? Is it safe to modify it's features?

Absolutely. This isn't discouraged at all. In fact, if you modify Bullet Train and then merge in updates from us, you'll only run into a merge conflict if we also update the same functionality you've modified.

In practice, these merge conflicts are not very painful at all. I've done tons of them on a number of projects, and they're all pretty brainless to resolve. The fact is, you were in that part of the code that we wrote, you read it, you understood it, you then modified it, and now you're being asked to reason through the difference between the thing you implemented and the new code we've implemented. This is not unlike what your experience would be if you have multiple developers working on the same project: Occasionally they will step on each others feet. Very manageable.

In fact, this is how we frame the relationship to customers: Pretend the Bullet Train team is like another team of developers that's in the row of cubicles one over from you, and we're also working on your product. If you run into a merge conflict and you're not 100% sure what we were trying to do, just stand up and ask us to look it over with you. (Worth noting: This has never actually been required.1)

Wrapping Up

Distributing Bullet Train as a repository really leverages the power of Git and sidesteps the bundle of pain that we would all be experiencing if we tried to distribute such a large set of functionality as a Rails engine.

I've gone the Rails engine route before with Koudoku, my Stripe integration for Rails, and there is a reason why it has (for just over 1,000 stars on GitHub) almost 200 forks. There are so many "last mile" issues developers run into where they need to fork and customize gems like Koudoku, Devise, et al.

I think Pete Keen, the author of the other really popular Stripe integration for Rails, said it best:

Questions?

If you've got any questions about this approach that I haven't already explained here, please don't hesitate to reach out on Twitter either publicly or via DM and I'll try to update this article accordingly.

1 I'll update this article the first time anyone asks for our help to resolve a merge conflict. ✌️