Saturday, January 27, 2007

Welcome to the Revolution on Rails

Revolution Health Group is creating a health company where patients are put at the center of the health system, with more choices, more convenience, and more control over their health care.

It was only a few months ago that our organization decided to take the plunge into a mostly Rails architecture. We've just exited stealth startup mode, and we'd like to start becoming active members of the community. We aren't a traditional startup, and this is by no means a traditional rails application. We have a large team, many interconnected applications, and we've dealt with many of the issues small rails apps have been lucky enough to avoid.

We are discovering what we can say and what code we can release, but we realize the community has helped us get to where we are, and its about time to start giving back. A lot of people have been involved in making this portal a reality, and hopefully you'll hear from them all here.

Our passion is to end the rails enterprise argument.

RevolutionHealth.
Take a look.

Friday, January 26, 2007

Conditional block-level helper to DRY up views

We're at RailsEdge in Reston and Bruce Williams and Marcel did a little talk on cleaning up views with block helpers, etc.

I wrote a helper of this flavor a while back to DRY up an article rendition issue, so I figured I would throw it up here.

Basically, we have a generic article view but have the need to swap out 'chunks' of the Article in different situations without creating a crazy cartesian product of rendition views.

View now looks like this:

<div class="article_module_head">
<% unless_partial @article.rendition, 'headline' do %>
<h2><%= @headline_override || @article.headline %></h2>
<% end %>
<% unless_partial @article.rendition, 'subhead' do %>
<% if @section %>
<h3><%= @section.title %></h3>
<% else %>
<h3><%= @article.subheadline %></h3>
<% end %>
<% end %>
...
</div>
...


Helper looks like this:

#
# Allows you to render a block of default code, but instead render a
# partial from a bucket of partials if it's there. Makes it so overriding
# a block of code in a template is as simple as creating a partial -- convention,
# etc.
#
#
# e.g.
#
# @article.rendition #=> 'foo'
#
# <% unless_partial @article.rendition, 'author' do %>
# <h2 class="article_module_attribution"><%= @article.time_string %></h2>
# <h2 class="article_module_attribution"><%= @article.author %></h2>
# <% end %>
#
# This block will first check for the _foo partial, then fall back to the block.
#
# Inline version (empty if no partial):
#
# <%= unless_partial @article.rendition, 'aftercopyright' %>
#
#
module ConditionalPartialHelper
def unless_partial(rendition, partial, &block)
begin
output = render :partial => "#{controller.class.controller_path}/#{partial}/#{rendition}"
if block_given?
# block given, so they may not be using <%= on the helper call, so you can't just return it.
concat(output || '', block.binding)
else
# no block given, they used <%=
return output
end
rescue ActionView::ActionViewError # no overriding partial
yield if block_given?
end
# contingency for <%= on a with block--the block is doing the concatting
return ''
end
end


Hopefully the comment block explains it all.

Thursday, January 25, 2007

[PLUGIN RELEASE] - Browser Profiler


===================================
http://rubyforge.org/projects/browser-prof/
===================================


BrowserProfiler a quick way to do performance testing from within your application. While it's often easy to write automated tests that are wrapped with performance profiling, the test implementation of some of rails is totally different in test mode. Also, you might only do certain things in production mode (like caching!) so performance testing in production is really nice.

Simple usage: http://localhost:3000/some/action?browser_profile!

If you add the browser_profile! param to any URL, it will append the profiling output to the bottom of the html file.

What profiling you ask? Well, it needs ruby-prof, which is an awesome library here: http://ruby-prof.rubyforge.org/

We used the ruby convention of bang (!) parameter to imply its modifies itself, the html body.
Walk through your own app while profiling to find issues locally, or even after deployment to your production machines. Dont leave this stuff on in real production, obviously. :)

Advanced Usage:
Often in Rails on post operations, you redirect afterwards. So how do you see the profiling output of a post-redirect operation?

http://localhost:3000/some/post_redirect?file_profile!

Add the file_profile!, and it will output the results to file specified in RAILS_ROOT/log/profile_out.html .

Hint: If you are testing against a site you've deployed somewhere, use FireBug to modify the DOM before the post or Tamper Data to add to the post parameters, and you can add params at will.

The functionality appears to work fine in Rails 1.1, and 1.2, but the tests fail.. Needs more investigation.

License: Released under the MIT license.

Dependencies: ruby-prof (Last tested with ruby-prof 0.4.1)
> gem install ruby-prof

Plugems

Part of the Gem-based Development Process series

Implementations:

Runtime

Deployment

*****

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?

I. Code
Thats obvious, rubygems already does (most of) this for us.

II. Views
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.

IV. Assets

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.




I. Code
We didnt do (much of) anything to get this. Thanks guys who wrote RubyGems!

II. Views
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.

  1. Where to look for views.
  2. The order in which we look.
  3. 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,
/gems/base_ui/
/views/layouts/
default.rhtml
_header.rhtml
_footer.rhtml

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.

IV. Assets
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.

Wednesday, January 24, 2007

Gem-based Rails Dependency Loading: Part 1 - Justification

Part of the Gem-based Development Process series

*****

Quickly, how plugins work:

For each plugin, in alphabetical order:

  1. Paths are added to the load order (i.e. lib)
  2. Initialization routines are run (init.rb)

Quickly, how RubyGems work (in comparison):
  1. Addition of paths to the Ruby require path ($:.unshift)
  2. Autorequire of a file within this new require path for 'initialization'
  3. Activation of dependencies--Rubygems will recursively activate (i.e. perform steps 1 and 2) all dependencies (and sub-dependencies) before loading the gem proper. You don't have to worry dependencies failing to be in the load path (or failing to run their initialization routine)
Justification 1: Dependencies

So long as each gem's first order dependencies are properly 'recorded' in the gemspec, you can load your gems in any order you desire. This relieves the need to untangle webs of dependencies and do crazy hacks such as:

  1. Renaming plugins to effect load order
  2. Bulk-adding directories to the load path in order to 'cheat' the load order (which can work initially, but ultimately fails since some plugins can be dependent on the actual hooks installed by another plugin's init script)


Justification 2: Maintenance versioning

If you've got multiple applications sharing your plugin, it can be awkward to maintain under the standard plugin model. You're going to want to continue development on the plugin, but you don't want to force all clients to be on 'trunk' (or force all clients to move to trunk to pick up critical bugfixes.)

With this in mind, you're going to need the ability to backport bugfixes to maintenance branches (and provide a mechanism for clients to absorb these backported fixes without absorbing unwanted non-backwards-compatible changes.)

With RubyGems, client applications need only use the right optimistic version lock, and they'll pick up critical fixes on their minor (or major) revision without committing to a boatload of unwelcome API changes.

We're very wary about overengineering our solutions, so keep in mind that things begin their life as plugins (and continue to work as usual.) We just take care to gem-up things that are not well suited for plugins (e.g. highly cross-dependent or highly versioned.)

Next time: How we made it work

Tuesday, January 23, 2007

Gem-based Rails Dependency Loading: Part 0 - Introduction

Part of the Gem-based Development Process series

*****

Once you begin to build a lot of plugins (shared across numerous Rails applications), cross-plugin dependencies start to creep in. If you've ever renamed your plugins to effect load order, then you know what I'm talking about. Our idea, which has been extremely successful, is to package interesting dependencies as RubyGems. These dependencies include proprietary shared plugins as well as library dependencies.

Since our applications care to share a lot more than bundles of Ruby classes, we decided to beef up plugins as well.

  • Part I: Justification
  • Part II: Bundling a plugin as a Gem and getting it all to work with Rails
  • Part III: Extracting views, partials, tasks and layouts into plugins and gems
  • Part IV: Retaining developer convenience in a Gem-driven Rails application
Next time: Our justification behind this approach

A New RHG Developer's Illustrated Primer

Part of the Gem-based Development Process series

*****

Dan had just completed his last project and was looking for another job where he can apply his skills in Rails and AJAX, when he run into a blog discussing challenges of an enterprise RoR-based application. He never worked on a large-scale Rails project so he was eager to try. He applied to a position at Revolution Health Group and after going through series of interviews, received an offer. People he talked to on campus, and his own scrutinizing of the web-site on which he was supposed to work, made him decide to accept the offer. He started on Monday.

Now it's Wednesday. Dan has spent his first two days at RHG reading the development's wiki, talking to coworkers, and configuring his new development workstation, a MacBook Pro. He was just assigned a bug, so he is eager to prove that he was the right choice for a job.

First, he decides to install a copy of one of the applications (rop) locally. Armed with instructions from wiki, he installs the deployment support tools:

$ gem install rhg_deployment --remote --source http://gems.revolutionhealth.com:8808

He now has two new commands - rhg and rhgcontrol. He sets up a runtime environment for the application:
$ rhgcontrol setup
Setting up the runtime environment
mkdir -p /opt/rhg/applications/etc
mkdir -p /opt/rhg/applications/tmp
...
mkdir -p /opt/rhg/applications/log

He downloads the latest version of the application with all its dependencies to his workstation:
$ rhg update rop
Bulk updating Gem source index for: http://gems.revolutionhealth.com:8808
Installing [ actionwebservice, 1.1.6 ]
Installing [ activesupport, 1.3.1 ]
Installing [ rails, 1.1.6 ]
...
Installing [ rhg_ui, 1.5.40766 ]
Installing [ rhg_migrations, 1.0.37541 ]
Installing [ rop, 1.4.40837 ]

Dan has all he needs now so he deploys the application, converting it from a static gem to a live site:
$ rhg deploy rop
Deploying rop-1.4.40837
Loading /usr/lib/ruby/gems/1.8/gems/rop-1.4.40837/config/deployment/database.yml as database.yml
Executing: ln -nfs /usr/lib/ruby/gems/1.8/gems/rop-1.4.40837 /opt/rhg/applications/rop
...
Executing: ln -nfs /opt/rhg/applications/log/rop log

It is a rails app, so it needs a DB which he creates using mysqladmin. He then issues a command to add a structure and populate with data his development DB:
$ rhgcontrol migrate rop
Running migrations for rop
cd /opt/rhg/applications/rop
Executing: rake db:migrate
...

Dan plans to use both lighttpd and mongrel to host the application, but first he goes with the default one, lighttpd, using a config supplied with the deployment tools:
$ rhgcontrol add rop
Adding lighttpd configuration for rop_8001

$ rhgcontrol start rop
Executing on rop_8001
Executing: /usr/sbin/lighttpd -f /opt/rhg/applications/etc/rop_8001.conf

Dumping the runtime manifest

He points his browser to http://localhost:8001 and plays with the application. He feels like doing some coding. He checks out the latest application code from subversion and runs mongrel from the top of the source tree:
$ mongrel_rails start -d

He fixes some code and navigates his browser to http://localhost:3000 to see the changes. It works but he needs to do some more fixes, this time in a shared component rhg_ui. He checks out the latest component code, sym-links it to the vendor/plugin directory of the application making it temporarily a plugin, and changes some code to see the immediate result. The bug is resolved, and, after running unit tests, he checks in the modified code for both the application and the component.

The changes he made are in a latent state. They are in the source tree but no gems were built off them yet. Dan knows that QA usually builds application gems off the latest code but not component ones. He decides to build a gem for rhg_ui himself. He navigates to the top of the component source tree and runs a command to tag, build, package, and publish the component as a gem to our local gem server:
$ rhg publish
...
Committed revision 40967.
...
Checking out tag to /tmp/rhg_ui-1.5.40966
Changing directory to /tmp/rhg_ui-1.5.40966
Building gem from tag
...
Successfully installed rhg_ui, version 1.5.40966
Pushing gem to development gem server via rhg tool: rhg push rhg_ui
Bulk updating Gem source index for: http://gems.revolutionhealth.com:8808
Bulk updating Gem source index for: http://gems.revolutionhealth.com:8808/archive
SSH User: dsmith
SSH Password: XXXXXXX
Publishing gems...
...
Refreshing the gem server indexes at /opt/rhg/gems

His changes are now packaged and ready to be picked up by QA for testing and pushing to production. How that part is done is for another time...

Monday, January 22, 2007

Gem-based Development Process

The gem based development with multiple applications and shared components was our solution when we could not bear anymore the pain of working on a mammoth monolithic application. The code built to support that change makes it easier for developers, QA team and Operations to work on, deploy and deliver the whole application as well as individual components. Next installments, written by people who made it possible, will reveal the ideas and implementation details of how pushing gem usage to extreme let us survive and get the the site to where it is now - open for visitors, helping them to better manage their health and the health of their families.

Coming soon: