Part of the Gem-based Development Process series
We started with a small application. A few controllers, a few models. Soon the company grew, the development team grew, and with that the application grew. Luckily, we namespaced applications early on, so it was mostly clear where one application lived from another, but the rails project became too big. As one coworker (Eddie) put it, "My TextMate file find is getting too slow!". It was time to split applications. But for a portal with a single look and feel, how do you share things across applications? Here is how we do it (this week).
We wanted to use gems, as described in Part I, but without gems being true 1st class citizens in rails, there was no way to make it happen. So what was important to share and more importantly version?
Thats obvious, rubygems already does (most of) this for us.
With a common header, or commonly shared assets, we'd like to leverage those.
III. Rake Tasks
We have lots of rake tasks to do all sorts of things, we need to share those too.
We'll cover this later. This gets complicated, and needs deployment input.
V. Plugems as Plugins
Using gems, as plugins, in your plugin dir, and the A/B/C problem.
We didnt do (much of) anything to get this. Thanks guys who wrote RubyGems!
We needed to load views from a few places, and it took some diving into rails internals to figure out how. There were a couple things we needed to figure out.
- Where to look for views.
- The order in which we look.
- Caching. Globbing for files is slow... especially across many directories. Cant do this a lot.
1. So we first overwrote the ActionView::Base's implementation of finding files. That was straightforward. We also fixed ActionController::Layout to allow layouts to live in gems as well. Once we provided ActionView and ActionController with a bigger list of partials, it could then load the partial from anywhere. Now you'll see the path to a file in a gem in certain stack traces, just to prove that where to find it.
2. So why is the order important and powerful? Lets say you decide to use our base_ui gem and its default layout.
<%= render :partial => "header"%>
<%= @content_for_layout -%>
<%= render :partial => "footer"%>
In our gem,
You've been told to build the FooBar application, whos pages dont have the same header. So in your application, add a
/app/views/layouts/_header.rhtml, and put whatever you want there. It just works.
So the dependency order is: app ==> plugins ==> gems. There is almost always true. I'll explain the exception later when "developing" things that are gems.
Note: The compiled template method names get kinda ugly, but who cares.. You only see those in some stack traces.
3. Caching was quickly important. Disclaimer: We only look for views in gems and plugins at startup. After that, bounce your app to pick up new files. Thanks to Zed, mongrel_rails restart is wicked fast.
III. Gem Rake Tasks
So the easiest way to include rake tasks from gems was to add a bootstrap.rake into /lib/tasks/bookstrap. We grab the manifest file for the application, and for each of the gems defined, 'load' the rake files. This was a quick fix and could probably be more elegant.
This conversation gets complicated quick. We'll cover this later.
V. Plugems ( Plugin + Gem = Plugem!)
So now you've taken all your favorite plugins and made them gems. In the next article, I'll cover developing gems as plugins, and the A --> B --> C dependency loading problem, where A and C are gems, and B is a plugin.