Rails likes to own your application. It gets pride of place in the project’s root directory, setting up it’s
db. If Rails is just a delivery mechanism, why does it get the root directory?
I’ve got an application which ended up being more backend logic than CRUD. I’m starting to wonder if Rails is even an appropriate vehicle. In any case, I’ve broken out Rails-independent pieces into
vendor/modules. This has worked fabulously, expect I’m getting really tired to typing
vendor/modules, even with auto-complete.
Vendor is a piece of rails convention that says it’s something alien and outside the application, tacked on. In fact Rails is feeling more and more alien. Getting it out of the way was a multiple step process.
- Extract the core functionality into a Rails engine, hopefully flushing out some dependencies.
- Lift the Rails host application into
- Chop out
vendor/modulesso that the app is a directory full of gems.
The first step is fairly standard gem extraction – filling out a gemspec, shuffling Gemfile dependencies, adjusting some paths. There was one little hiccup with Bundler that I want to write about separately, but it comes down to making sure that the new engine
requires all it’s dependencies from
lib/mygem.rb. The rest is paths, paths, paths.
Once the main Rails app is at the same level as the modules, the autoload path has to change from
As a precursor to the refactoring, I changed from a block form of
path, to an imperative form, giving it the same effect as
source. This allowed gemspecs to search the right places for my custom gems, without having to list each one in the Gemfile. Once the modules got dropped a few levels, the master Gemfile had to change from specifying a path of
My master Gemfile has lot more direct references than I would like, but gemspecs don’t allow for the kind of grouping that the standard Rails bundler configuration thrives on.
rails terminal command looks for
script/rails to determine whether it’s in a rails project. Without that, you do have to type out
railshost/script/rails, but from there everything is normal.
I found that the default
.gitignore wasn’t covering some files in the moved rails directory, so I copied the file and adjusted both copies appropriately for their new positions.
A lot of changes relate to running on Heroku, because it’s designed as a generalized hosting system and can’t know about my custom directory structure. Fortunately between Rails, Rack, and Heroku there are enough hooks to make everything work out. (Note: I’m running on the cedar stack.)
I already mentioned Gemfile a little, but remember that Heroku is only going to install a Gemfile at the project root.
I knew from some previous research on running Rails in a subdirectory on Heroku that it was possible. Essentially, Heroku looks for a
config.ru “rackup file”, which Rails includes by default. Heroku just needs a rackup file at the root level, pointing at the appropriate place.
Many tasks happen by way of the base level rake file. In particular it’s how background workers and other processes are often launched. A copy of the Rails Rakefile with appropriately adjusted paths should do the job. (Myself, I’m using a modified copy for tasks that aren’t part of the rails app.)
I started using Foreman a little while ago, both to identify my own worker command, and to start up a set of support processes for local development. A well configured rakefile will obviate some needs, but I had to adjust the path to the
rails command for running the server, as well as some configuration paths for Postgres and Redis in my development environment.
Under normal circumstances, Heroku can detect that you’ve got a Rails app and update the
database.yml file to parse out their
DATABASE_URL. Move the file from it’s standard location, however, and they just aren’t that clever. You can see what Heroku’s standard
database.yml looks like by running a heroku console on a standard rails app and simply printing it out.
>> puts File.read('config/database.yml')
I ended up with dash of Sequel on top of ActiveRecord, so I had already developed a
DatabaseSpecification gem to translate between schemes, which made for a less verbose database config file. I was able to copy the dynamic development configuration to the production block and carry on with little trouble.
Files to watch
- calls to