Blog Posts tagged with: rails

Jul 25 2011

Comments

Rails 3.1 HackFest Wrap-up

Thanks to everyone who participated in the Rails 3.1 HackFest! I hope you enjoyed sharing your love of Rails, plus a few beers, as much as we did. It was great getting to know some of our neighbors.

In addition to upgrading a few Rails apps to the latest 3.1 release candidate, we also submitted a few RailsGuides patches, found a bug and submitted a failing test. We didn't even have a chance to fix it before grzuy picked it up and submitted the patch.

We ran into a few bumps moving to the 3.1 asset pipeline, but overall the upgrade from Rails 3.0 to 3.1 has been smooth. As for the 2.3 to 3.0 upgrade, Rails provides deprecation warnings in the previous point release, 3.0.9 as I write this, so the upgrade goes a little something like this:

  1. Upgrade to Rails 3.0.9
  2. Upgrade other gems as necessary (e.g., haml 3.0 to haml 3.1 + sass 3.1)
  3. Run tests, fix tests, rinse and repeat
  4. Walk through the application and fix any deprecation warnings that crop up in the rails log
  5. Upgrade to Rails 3.1

And now...your moment of zen.

Jul 20 2011

Comments

Rails 3.1 HackFest comes to Bull City, July 22-23

Join us at Relevance Inc. this Friday and Saturday as we participate in the Rails 3.1 HackFest.

Come on down for two days of upgradin', pipelinein', bug fixin' good times! This is a great opportunity to meet some local Rails hackers, work through the 3.1 upgrade process, and give back to the larger Rails community. If you're in town this Friday or Saturday, please drop by any time.

UPDATE: The Relevance office will be open Friday and Saturday from 8:30am until 6:00pm or after. Grab your laptop and swing by for an hour or camp out for both days; we'll be here...with pizza.

Find us at:
514 South Duke Street
Durham, NC 27701

For more information, please contact us via email at info@thinkrelevance.com or by phone at 919-283-2748.

Jun 15 2010

Comments

Rethinking PDF Creation in Ruby

Every now and then, a requirement will come up in a project, that will make me second guess my career choice as a programmer. It usually involves making me go through tedious exercises, never knowing if I'll end up where I want to be along the way.

This happened a couple of weeks ago when one of our projects called for generating PDF reports. The reports needed many stylized elements, layouts, and dynamic graphs. If you've ever generated PDFs in Ruby before, you know that it can be both tedious and difficult using the standard go-to PDF libraries out there. Let's face it, we're web developers. Coming from HTML+CSS-based layouts, writing Ruby code for that stuff is a major pain.

To give you an idea of how heavy it can get, here's an example taken from Prawn. The example was ironically called simple_table.rb.

  Prawn::Document.generate("simple_table.pdf") do 

  table([["foo", "bar " * 15, "baz"], 
         ["baz", "bar", "foo " * 15]], :cell_style => {:padding => 12}) do
    cells.borders = []

    # Use the row() and style() methods to select and style a row.
    style row(0), :border_width => 2, :borders => [:bottom]

    # The style method can take a block, allowing you to customize 
    # properties per-cell.
    style(columns(0..1)) { |cell| cell.borders |= [:right] }
  end

  move_down 12

  table([%w[foo bar bazbaz], %w[baz bar foofoo]], 
        :cell_style => { :padding => 12 }, :width => bounds.width)

end

If you're scratching your head at this point, the code above generates a PDF with two simply styled tables. That's it. If you asked me to implement this in an app, I might have something half-way presentable in an hour. But, I could get a monkey, who just drank a whole bottle of scotch—don't ask about his drinking problem—to write two tables using HTML in less than 5 minutes.

A New Hope

Now some of you may be familiar with PrinceXML, which is a command line utility that will take HTML+CSS and give you back a beautiful PDF. It's even CSS2 compatible and passes the ACID2 test. Awesome. The only problem is that a single server license will set you back $3,800—which is prohibitively not awesome.

Being the open source zealots we are here at Relevance, we set out to find another solution. Tucked away in the internets, we stumbled across wkhtmltopdf. I know what you're thinking; awesome name, huh? wkhtmltopdf uses a WebKit rendering engine to make pretty PDFs out of HTML+CSS. Since it's leveraging WebKit, you get all the tasty CSS3 properties it supports. Ugly PDFs are suddenly a thing of the past.

Goodbye Prawn, Hello PDFKit

We were surprised that none of us had ever heard of wkhtmltopdf, considering how useful it is. When we looked for a Ruby library that leveraged it, we realized it didn't exist. Apparently not a whole lot of other people had heard of it either. That couldn't stand. A couple of open-source Fridays and several gallons of Mountain Dew later, we're excited to announce PDFKit, an open source library that makes working with wkhtmltopdf a snap.

Usage

Inline HTML+CSS => PDF

  kit = PDFKit.new("<h1>Oh Hai!</h1>")
  kit.stylesheets << '/path/to/pdf.css'
  kit.to_pdf # inline PDF
  kit.to_file('/path/to/save/pdf')

HTML file => PDF

  html_file = File.new('/path/to/html')
  kit = PDFKit.new(html_file)
  kit.to_pdf # inline PDF

Remote HTML => PDF

  kit = PDFKit.new("http://google.com")
  kit.to_pdf # inline PDF

What's the big deal?

If this hasn't sunk in yet, let's go over a quick list of wins this buys us:

  1. HTML+CSS - Assuming you're a web developer, there's a good chance that you already know HTML and can work with it efficiently.
  2. CSS3 - We get WebKit's CSS3 support for free. This means effects like drop shadows, rounded borders, transformations and others are super-easy. (Note: effects requiring blur radius do not work.)
  3. Testing - We have tools built into our normal workflow for testing HTML. You can even use Cucumber to drive the development of a PDF with PDFKit.

To give you an idea of how well this fits into our normal workflow here at Relevance, this is how we built out our PDF reports:

  1. Our designer mocked up a sample PDF and converted it to HTML+CSS.
  2. Using Cucumber to drive development, we created a controller action to generate this HTML view of the PDF. (It was just another URL in our app.)
  3. We added a screen-only stylesheet to the HTML that mimics the look of a PDF reader. This allowed us to get a feel of how it would look as a PDF.
  4. Using a bit of Rack Middleware that ships with PDFKit, we can get the PDF version of that web page by simply appending '.pdf' to the url.
  5. We're done. No crazy extra class to handle PDF rendering. No need to spend all day reading through docs to learn the obscure code and magic incantations required to generate your PDF.

Samples

  • PDF of google.com - PDF rendered from http://google.com
  • CSS3 Examples - Sample rendering of common CSS3 effects including border-radius, text-shadow, box-shadow, and border-image. Notice the lack of a blur radius on text-shadow and box-shadow.
  • Sample HTML page with PDF viewer CSS - Example of using a single HTML source to render both a screen version and a PDF version. Uses a media="screen" and media="all" to mark relevant CSS.
  • PDF generated from PDF viewer HTML - PDF generated from sample HTML above. You must tell PDFKit to only use print stylesheets in order to achieve this effect (PDFKit.new(html, :print_media_type => true)).

Go Forth and PDF

I encourage you to take PDFKit for a spin, let us know what you think, and even submit some patches.

If you are pumped about the possibility of using PDFKit on a future project, then I've achieved my goal. If not, I'd ask you to think about what is missing, find out if it's already out there, and let us know how to make PDFKit even better.

May 10 2010

Comments

Relevance Open-door Bug Mash Friday May 14

This Friday, May 14, Relevance Inc. is hosting a local bug mash in preparation for the Rails 3 BugMash May 15 & 16 organized by RailsBridge. We tip our hats to this worthy cause and hope to set a modest bar for the participants over the weekend.

Dan Pickett, who is coordinating the Rails 3 BugMash, recently challenged users to give back to the platform that has provided so much win for so many. At Relevance we certainly hear that message. Rails supports a significant portion of our work. It has been and continues to be one of the most progressive and dynamic platforms for programmers today. On top of that, it is fundamentally open source and thrives in a community of mutual collaboration.

In this spirit, we at Relevance pledge to donate our entire Friday to kick-starting the mashing festivities. Those bugs we target will be crushed not for prizes or glory, but out of gratitude to the platform and community. We are eager to see the magic that Rails 3 will produce and we hope to do our part to make that release the best it can be. If you will be in the area this Friday please feel free to stop by our office where there will be food, fun, and lots and lots of coffee.

You can find us at:
200 North Mangum St., Suite 204
Durham, NC, 27701

For more information, please contact us via email at info@thinkrelevance.com or by phone at 919-442-3030.

Sep 30 2009

Comments

10 Must-Have Rails Plugins and Gems (2009 Edition)

When Paul Graham wrote that the "list of n things" is a degenerate case of the essay, our first thought was "Wow! That's for us!" And we're going one step farther: we're recycling an old "list of n things" essay from last year.

Seriously, we've been thinking of revisiting 10 must-have Rails plugins for a while now. There is a place for lists like that, and the Rails plugin and add-on space has been moving quickly. We are always looking for better ways to do things, so we try out a lot of the plugins that come along. Our list of favorites---the ones that we use on almost every project---is almost completely different than last year's model.

There's one important change in focus: the plugins and gems that are solely related to testing are gone from this list. Of course, that doesn't mean we're down on testing. On the contrary, we built RunCodeRun because we think testing is so vital. We're saving the testing tools for the RunCodeRun blog; we'll be writing another degenerate essay there as a counterpart to this one.

There are numerous other plugins we use for special needs, such as PDF generation or attachment handling. But our favorites are the ones that we use on almost every project. So here they are, along with brief comments explaining why you want to check them out:

  • Inherited Resources: eliminates most of the boilerplate code from our controllers. (The new controller responder feature in Rails 3 is similar in intent.)
  • Formtastic: takes most of the pain out of writing the markup for HTML forms. (Together, Inherited Resources and Formtastic make a nice alternative to scaffolding frameworks like Streamlined and ActiveScaffold.)
  • CapGun: provides easy build notifications (see this previous post for more info).
  • Faker: helps us generate fake data. We use it for testing, but mostly for providing demo data for development and staging environments.
  • Clearance: feature-rich authentication and signup.
  • Safe ERB: helps ensure that our apps are not vulnerable to cross-site scripting attacks. (We look forward to similar functionality being baked into Rails 3.)
  • RedHill on Rails Core: we use this primarily to declare foreign key references in our database schemas. Telling the database about table relationships adds a small cost to our projects, but we've found that the benefits outweigh that cost. (It's unclear where this plugin lives at the moment, but there are numerous forks of it on GitHub.)
  • RPM: Rails Performance Management from New Relic; wonderful for discovering and diagnosing performance problems.
  • will_paginate: the nicest, easiest pagination plugin we've seen.
  • hoptoad: great, customer-friendly notifications about exceptions that happen in the app.

Don't reinvent the wheel! Use these plugins (or others like them), and definitely consider contributing to them if they fall short of what you need!

Sep 23 2009

Comments

Quick and Easy Logging with LogBuddy

Good logging is crucial for effective problem diagnosis in production. Plus, easy logging remains a terrific debugging technique during development. As helpful as "real debuggers" are, sometimes a debugging log statement is exactly what you need to find a problem quickly.

LogBuddy is a little gem that makes good logging easier. It helps even in Rails, which already has good logging support, and it's a bigger help when building gems and standalone Ruby apps, where you have to start from scratch with logging.

What does LogBuddy do for you that Rails doesn't already? There are numerous small features, but here are the big ones:

  1. It ensures that logger is available everywhere, not just in classes that extend parts of the framework.
  2. It makes it extremely easy to add informative debugging messages with annotated output and full exception info.

Installing and Adding LogBuddy to Your Project

To install the latest release of LogBuddy, just install the gem:

$ gem install relevance-log_buddy --source http://gems.github.com/

Then add a require 'log_buddy' statement to your app. If it's a Rails app, it's best to add this to environment.rb:

  config.gem 'relevance-log_buddy',
             :source => "http://gems.github.com/", 
             :lib => "log_buddy"

Finally, initialize LogBuddy. In Rails apps, we usually put this in config/initializers/logging.rb:

  LogBuddy.init

(You can pass some options to LogBuddy.init; we'll get back to that in a bit.)

LogBuddy creates and initializes a logger for the app to use. In Rails apps, it simply uses RAILS_DEFAULT_LOGGER unless you tell it differently.

Using LogBuddy

LogBuddy mixes a couple of methods into every object in the system: logger and d. Here's how they work.

The logger method is no surprise at all. It simply returns the Logger instance, and you can log by calling debug, info, warn, error, or log methods on it. LogBuddy's logger doesn't usually do anything special; the benefit is that, since it's mixed into Object, it's available everywhere, automatically. (In a typical Rails app, there are numerous contexts where logger doesn't work, and you have to explicitly use RAILS_DEFAULT_LOGGER.)

My personal favorite LogBuddy feature is the d method. Like logger, it's available everywhere. But the d method is designed just for debugging messages. You can call it with an explicit string, or with some object you want to see the value of:

  d "FINISHED PARSING"
  d some_exception
  d result

Strings are logged the same way logger.debug would do it. Exceptions are logged with all of the information you might want: the message, exception class name, and backtrace. Finally, if you pass any other object, d calls that object's inspect method and logs the resulting string.

Where d really shines is when you want to log several values at once, with annotations to distinguish them. Just pass a single-line block to d, like this:

  d { first; current; last}

That produces these three log lines:

  first = "foo"
  current = "bar"
  last = "baz"

The values you log can be any Ruby expression:

  d { 3; name; @model; RAILS_ENV; options[:limit] }

and you'll get just what you want out of that:

  3 = "3"
  name = "primary"
  @model = #<Contact id: 14, name: "Joe">
  RAILS_ENV = "development"
  options[:limit] = 5

There are some restrictions if you use this feature. The entire call to d must fit on one line, and you must use the curly-brace style of block, rather than the do/end style. Finally, if you want to log multiple values, separate them with semicolons, not commas.

(You may be wondering how LogBuddy accomplishes that trick. The answer is left as an exercise for the reader ... especially since there are some hints in the restrictions just mentioned. Of course, you can always read the source.)

LogBuddy Initialization Options

The LogBuddy.init method takes an options hash. Here are the permissible options:

  • :logger -- you can supply a logger instance for LogBuddy to use. If you don't supply one, LogBuddy uses RAILS_DEFAULT_LOGGER if it's defined; otherwise, it creates a new logger that writes to standard output.
  • :log_to_stdout -- by default, messages from the d method are logged (using logger.debug) and also written to standard output. Set this option to false to only use the logger.
  • :disabled -- set this option to true to turn off the output from the d method. It's common to set it this way:
    LogBuddy.init :disabled => Rails.env.production?
  • :log_gems -- if you set this option to true, LogBuddy watches gem activation and logs information about each gem. This can be useful for tracking down gem activation errors

Try It Out!

LogBuddy is really easy to set up, and then it's there when you need it most: when you're focused on a problem and just need to get the details quickly. Please try it and let us know what you think!

Sep 16 2009

Comments

Easy Build Notifications with CapGun

Most of us get notified about lots of little events on our projects: Commits, build failures (and fixed builds), and runtime exceptions, for example. But deployment is where the rubber meets the road on web development projects. Deployment to staging means new functionality to try out and test, and of course deployment to production is even more important.

CapGun is a gem we use to send email notifications whenever we deploy one of our projects. It works with Capistrano and uses ActionMailer to let every interested party know when a deployment happens.

Here's how to work with CapGun in a Rails project. Add this to config/environment.rb:

  config.gem 'relevance-cap_gun', 
             :source => "http://gems.github.com/", 
             :lib => "cap_gun"

Then install and unpack the gem:

  $ rake gems:install
  $ rake gems:unpack

Finally, edit your deployment script (usually config/deploy.rb) and add this:

  require File.join(RAILS_ROOT,
                    Dir["vendor/gems/relevance-cap_gun-*/lib"].last,
                    'cap_gun')

  set :cap_gun_action_mailer_config, {
    :address => "smtp.gmail.com",
    :port => 587,
    :user_name => "grey-goo@example.com", # deploy bot email address
    :password => "outtacontrol",  # deploy bot email password
    :authentication => :plain 
  }

  set :cap_gun_email_envelope, {
    :recipients => %w[fooproj-devs@example.com fooproj-mgr@example.com], 
    :from => "Foo Project Deployment Bot <grey-goo@example.com>"
  }

  after "deploy:restart", "cap_gun:email"

We usually use GMail as our MTA for this purpose, and CapGun includes support for secure, authenticated email connections so that you can do the same. It's best to create a special-purpose, throwaway email account just for things like this; that way there's little risk in putting the email password in the project deployment script. (But there's also nothing stopping you from reading it from another file that's not checked into source control, just like you would do with your production database passwords. The deploy.rb file is just Ruby code, after all. And if the source is going to be in a public repository, you should definitely do that.)

The deployment email has a brief summary at the top (in fact, the subject often tells you all you need to know). But there are useful details in the body. Here's an example of what you might see from the configuration above:

From: Foo Project Deployment Bot <grey-goo@example.com>
To: fooproj-devs@example.com, fooproj-mgr@example.com
Date: Tue, 15 Sep 2009 17:27:57 -0400
Subject: [DEPLOY] fooproj deployed to production

fooproj was deployed to production by george at September 15th, 2009 5:27 PM EDT.

Nerd details
============
Release: /var/apps/fooproj/releases/20090915212721
Release Time: September 15th, 2009 5:27 PM EDT
Release Revision: 69246739f2a1cbcef5ca76f1842c21849db7778a

Previous Release: /var/apps/fooproj/releases/20090915194707
Previous Release Time: September 15th, 2009 3:47 PM EDT
Previous Release Revision: 9ceb19b1777470d1d5133f433fdd5d1873c7a4c0

Repository: git@github.com:foocorp/fooproj.git
Deploy path: /var/apps/fooproj
Domain: fooproj.example.com
Branch: main

Commits since last release
====================
d37a15c:who put a blink tag in here?
f1b603e:Added favicon

Setting up CapGun only takes a few minutes, and you'll love the increased visibility into your team's deployments. (And even if you're a team of one, you'll appreciate having the emails as a record of your deployments.) Try it out, and let us know what you think!

Sep 09 2009

Comments

Keep Models out of Your Views' Business! (Part 2)

Last week, I wrote about using Rails’ I18N facilities to break dependencies between your models and views, by changing how model and attribute names are displayed. But there’s another place where the implementation of models sometimes peeks through into views: validation error messages.

Depending on how you use them, Rails’ error message helpers may insert attribute names into messages, and the changes from last week will take care of that automatically. But it goes farther than that. Often, validation error messages express things in a way that is not appropriate for end users. The usual solution is to override the default messages with `:message => '...'` options on the validation declarations. But again, that means the model is actively involved in presentation issues, and we’d like to avoid that if possible.

Whole plugins have been written to try to solve this problem, but Rails internationalization support has made it much easier. When validation error messages have to refer to the name of a model class or attribute, they will use the locale definitions we’ve already supplied. And it turns out that they look in the locale for error message text, too.

Overriding Validation Error Messages

Last week, we used a typical Rails example: a blogging app with a model called `Post` with attributes `title` and `body`. (And we used I18N to relabel `Post` as `Article`, and `title` as `headline`.) Let’s continue with that example, assuming this declaration in `Post`:

  validates_presence_of :title

The default error message (with the re-labeling we’ve already done) is “Headline can’t be blank.” How would we change that to something friendlier, like “Headline should not be empty; please supply one”?

Let’s add a little more to `en.yml`—an “errors” section that includes customized validation error messages:

  en:
    activerecord:
      # models and attributes sections carried over from
      # previous article
      models:
        post: "Article"
      attributes:
        post:
          title: "Headline"
      # errors section is new
      errors:
        models:
          post:
            attributes:
              title:
                blank: "should not be empty; please supply one"

We’re now several levels deep in this YAML structure, but it should be fairly easy to follow. Under `activerecord.errors` there’s a section for `models`, within which we find our model (`post`). Of its `attributes` we’ve supplied a custom error message for the `title` attribute; in particular, we’ve customized the `blank` message.

With that change in place, the error messages will read as we’d like them to, with no change to the model or the views.

Note that we restricted this change to just the `title` attribute of `Post`. If we wanted to change all “can’t be blank” messages for all attributes of `Post`, we could do that as well:

  en:
  activerecord:
    errors:
      models:
        post:
          blank: "should not be empty; please supply one"

Finally, if we wanted to make the change across all the models, here’s how:

en:
  activerecord:
    messages:
      blank: "should not be empty; please supply one"

Which Messages Can I Override?

You can see the current default messages in the source code for the activerecord gem; just consult the file `lib/active_record/locale/en.yml`. But here’s the entire list, mapped to the validation methods that use them. (Some validations use different messages depending on the circumstances.)

validates_acceptance_of
`:accepted` (“must be accepted”)
validates_associated
`:invalid` (“is invalid”)
validates_confirmation_of
`:confirmation` (“doesn’t match confirmation”)
validates_exclusion_of
`:exclusion` (“is reserved”)
validates_format_of
`:invalid` (“is invalid”)
validates_inclusion_of
`:inclusion`(“is not included in the list”)
validates_length_of
`:too_short` (“is too short (minimum is {{count}} characters)”)
`:too_long` (“is too long (maximum is {{count}} characters)”)
validates_length_of (with :is option)
`:wrong_length` (“is the wrong length (should be {{count}} characters)”)
validates_numericality_of
`:not_a_number` (“is not a number”)
validates_numericality_of (with :odd option)
`:odd` (“must be odd”)
validates_numericality_of (with :even option)
`:even` (“must be even”)
validates_numericality_of (with :greater_than option)
`:greater_than` (“must be greater than {{count}}”)
validates_numericality_of (with :greater_than_or_equal_to option)
`:greater_than_or_equal_to` (“must be greater than or equal to {{count}}”)
validates_numericality_of (with :equal_to option)
`:equal_to` (“must be equal to {{count}}”)
validates_numericality_of (with :less_than option)
`:less_than` (“must be less than {{count}}”)
validates_numericality_of (with :less_than_or_equal_to option)
`:less_than_or_equal_to` (“must be less than or equal to {{count}}”)
validates_presence_of
`:blank` (“can’t be blank”)
validates_uniqueness_of
`:taken` (“has already been taken”)

Interpolating values

You probably noticed that some of the default messages contain the string `{{count}}`. Validations that use some threshold value pass that value into the I18n library as the option `count`, and I18n interpolates the count value into the message string.

In addition to `count`, all of the validations supply three other values that can be interpolated: `model` and `attribute` (the model and attribute names, already humanized as discussed in part 1) and `value` (the erronenous value of the attribute). You can write your error messages to make use of any of those values, using the same double-curly-brace syntax.

Writing custom validations

If you write your own validation methods, whether just for your project or in a plugin, you should make use of the same mechanisms.

When a validation detects an error, it reports that error by calling the `#add` method on the `errors` object. Here’s an example (taken from `validates_inclusion_of`, and slightly modified to make more sense out of context):

  record.errors.add(attr_name, :inclusion, 
                               :value => value, 
                               :default => options[:message]) 

The first parameter is the name of the attribute being validated. Second is the symbol that’s used to look up the default message text from the I18n message repositories. Finally there’s an options hash, which includes the current attribute value (for possible interpolation into messages) and any new message text that may have been supplied directly on the call to `validates_inclusion_of`.

(That last bit is somewhat confusing; the option to `add` is called `default`, but it’s actually a specific override. That bit of weirdness is there to maintain some internal compatibility for the benefit of plugins that were written for older versions of Rails. I wouldn’t be surprised to see that cleaned up in Rails 3, although for now it still works the same way.)

All you need to do in your custom validation is to use the `#add` method in the same way, and supply default error message text in a message repository, under the `en.activerecord.messages` key. You can do it directly in `config/locales/en.yml`, like this:

  en:
    activerecord:
      messages:
        bozo: "can't be ridiculous"

Plugins and gems can supply their own YAML file. They just need to tell Rails about it by pushing the file path onto `I18n.load_path`.

Conclusion

As I said last week, the fact that Rails uses model and attribute names in views isn’t a bad thing; it’s a useful convention that helps developers move quickly. The test of a framework like Rails is how easy it is to break those links when you need to. Rails’ internationalization support makes it easy to solve this particular problem, with the added benefit that it makes your application easier to localize, should you need to go down that path.

I’ve completely stopped overriding validation error messages directly in the model code. You should, too!

Sep 02 2009

Comments

Keep Models out of Your Views' Business!

One of the core goals of the MVC architecture is to separate the presentation of a model from its implementation and semantics. And yet Rails, out of the box, takes the representation of models---even the names of tables and columns in the database---right through to the user. The default behavior, for form labels and other places where models and attributes are referred to, is simply to use the name of the model or attribute.

Does this mean Rails' implementation of MVC is broken? Not at all! My favorite definition of "architecture" in software is "the set of decisions that will be hard to change later." And the best architectures are the ones that keep that set small, allowing developers to move quickly without fear of being boxed-in later in the project. Rails uses its default behavior to let you move quickly, but leaves your options open for changing that behavior later.

I think Rails' defaults work really well in most cases. After all, we should be working with a domain language that we share with customers and domain experts, so it's likely that our schema and models will use names that are appropriate for presentation. Nevertheless, it's common to reach a point where the user interface needs to use different terms for some of the domain concepts.

So let's see how Rails can help. Assume we've developed the "canonical" Rails application, a blogging engine. The primary model is called Post, and each post has a title and body. And then, the customer decides that the users will prefer writing "articles" rather than "posts", and "headlines" instead of "titles".

What can we do?

Breaking the Link: The Obvious Ways

The most obvious, brute-force solution is to avoid the defaults, instead supplying literal names in all of our views. While there are places where it will be appropriate to supply literal labels, this option should be reserved for specific places where the context requires a different label. Using this approach to change labels throughout the application would be way too much work, and create a maintenance nightmare.

You could actually change your model---rename the table, or column, along with every place that name appears in your Ruby code. But that approach would be tedious and error-prone if the name were central to your model. More importantly, it would be a mistake to change the domain model understood by those involved in the project just to satisfy presentation constraints. It's at this point that the MVC architecture needs to pull its weight by helping you deal with this problem.

Digging a little deeper, you realize that the places in ActionView that use model and attribute names (e.g., the label helper) transform them to "human" form first, removing underscores, adding spaces, and capitalizing appropriately. They do that by calling the human_name method (for model names) and human_attribute_name (for attributes). Every ActiveRecord model inherits those methods from ActiveRecord::Base. So one answer is to override those methods. That's much better than either of the other options, but it's still not very good. If you solve your problem this way, the model will be actively involved in presentation issues. We want to work with the MVC architecture, not against it.

Breaking the Link: The Right Way

The answer to our problem can be found in the documentation for ActiveRecord::Base#human_attribute_name:

This used to be depricated in favor of humanize, but is now preferred, because it automatically uses the I18n module now.

We can use Rails' internationalization support to supply the new names we want to present to users. Think about your current problem as specifying the English localization for your application.

If it's not already there, create the file config/locales/en.yml, with the following contents:

  en:
    activerecord:
      models:
        post: "Article"
      attributes:
        post:
          title: "Headline"

Under the en.activerecord key there are two sections that define presentation names for the locale: models contains names for models, and attributes contains names for ... well, I think you get the idea. So in our example, the new name for a post is "Article", and the new name for a post's title is "Headline". Note that you specify attribute names in the context of a model class, so if there were another title attribute in your system on a different model class, it would not be affected.

Suddenly, throughout your application, those changes will take effect. You can still supply different labels in particular contexts as needed, but the default is to use the names in the locale file.

Update: As commenter Tim Watson pointed out, it's not currently automatic everywhere. It does automatically happen in error messages, but not in labels (making labels handle this automatically is planned for an upcoming Rails point release). You can supply the proper label text on your own (using the human_attribute_name method). If you're using custom FormBuilders, you can easily encapsulate this change; otherwise, see commenter Priit Tamboom's solution for doing a quick monkeypatch of the label helper until Rails handles it properly.

As you may have gathered from the documentation excerpt above, you should use Post.human_attribute_name('title') instead of calling humanize on an attribute name when writing views. Likewise, you should use post.class.human_name rather than post.class.name.humanize or the literal 'Post' for model class names.

Also, associations are treated as attributes, rather than inheriting the new name of their target model. So if some model had an association to multiple posts, you would need to define an attribute renaming in the appropriate model; it would not automatically be called "Articles".

Finally, this approach doesn't deal with controller names in URLs; for that, you should modify the routes in config/routes.rb.

More To Come

There's more to this story, because there's another place where your models peek through into the user interface in undesirable ways. I'll address that in a follow-up post.

Aug 25 2009

Comments

Tarantula Supports Ruby 1.9

We're excited about Ruby 1.9. It's fast and stable, and brings nice improvements for programmers. But there are obstacles to adoption---mostly gems, plugins, and tools that don't support 1.9 yet.

So we're working on the things Relevance maintains, to get them on board. We're doing it by dog-fooding: We've got one customer project that we're actively testing on both 1.8 and 1.9 (although deployment is still on 1.8 for the moment). That's helping us see where the holes are, so we can fix them. We're already working on Ruby 1.9 support for RunCodeRun, and investigating the best way to interface rcov with 1.9.

Now one more piece of the puzzle is in place: on our last open-source Friday, Chad upgraded Tarantula to support Ruby 1.9. If you're trying out Rails on 1.9 (or even if you aren't), get the latest version (0.2.1) of tarantula from github:

gem install --source http://gems.github.com relevance-tarantula

Then add the plugin to your app and add some fuzz-testing goodness to it!

Popular Tags