labs

Keep gem configurations up-to-date with Rails generators

Many Ruby gems are packaged with a Rails generator that generates a configuration file. Keeping these gem configurations up-to-date can be much easier if you take advantage of these generators during the upgrade process.

Installing a gem that has configuration

One example is simple_form, a gem that makes it easy to create and maintain forms. Another is devise, a popular gem for handling user session management. (Both of these gems are maintained by the excellent developers at Plataformatec.)

The simple_form README says to run this command during installation:

$ rails generate simple_form:install
       exist  config
      create  config/initializers/simple_form.rb
      create  config/locales/simple_form.en.yml
      create  lib/templates/erb/scaffold/_form.html.erb

The file config/initializers/simple_form.rb looks like this:

# Use this setup block to configure all options available in SimpleForm.
SimpleForm.setup do |config|
  # Components used by the form builder to generate a complete input. You can remove
  # any of them, change the order, or even add your own components to the stack.
  # config.components = [ :placeholder, :label_input, :hint, :error ]

  # Default tag used on hints.
  # config.hint_tag = :span

  # CSS class to add to all hint tags.
  # config.hint_class = :hint

  # CSS class used on errors.
  # config.error_class = :error

and so on...

Also notice that you get a nice ERB template that extends the built-in Rails scaffold generator to automatically hook newly generated forms up to simple_form. And you get a default set of internationalization strings in config/locales.

Upgrading a gem that has configuration

The problem is that when you upgrade the simple_form gem from 1.5 to 2.0, the files that you have generated are stale. They might represent options that are no longer in the gem. Also, you might be missing out on new configurable features.

And the defaults might have changed, so any commented out lines showing the defaults will now be wrong. This recalls the programmer adage, “a comment is a lie waiting to happen.”

Well, there is an easy solution, of course. Just re-run the generator!

Assuming you’re using a source control system like Git, you can safely re-run the generator without breaking any of your code.

$ rails generate simple_form:install
SimpleForm 2 supports Twitter bootstrap. In case you want to generate bootstrap configuration, please re-run this generator passing --bootstrap as option.
    conflict  config/initializers/simple_form.rb
Overwrite /Users/grant/code/blog_posts/rerun_generators/config/initializers/simple_form.rb? (enter "h" for help) [Ynaqdh]

Oops, looks like we have a conflict! Not to worry. I always just say yes to everything and move on. We will address the conflicts later.

Afterwards, all three of the generated files show up as having changes.

$ git status
# On branch master
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   config/initializers/simple_form.rb
#   modified:   config/locales/simple_form.en.yml
#   modified:   lib/templates/erb/scaffold/_form.html.erb
#
no changes added to commit (use "git add" and/or "git commit -a")

I can use command-line tools like git diff or GUIs like GitX to figure out what changed, and more important, make an educated decision about which changes I want to keep and which ones I want to change back to the old version.

For example, there is the change to the scaffold:

$ git diff lib/templates/erb/scaffold/_form.html.erb
diff --git a/lib/templates/erb/scaffold/_form.html.erb b/lib/templates/erb/scaff
index 24a1768..201a069 100644
--- a/lib/templates/erb/scaffold/_form.html.erb
+++ b/lib/templates/erb/scaffold/_form.html.erb
@@ -1,13 +1,13 @@
 <%%= simple_form_for(@<%= singular_table_name %>) do |f| %>
   <%%= f.error_notification %>

-  <div class="inputs">
+  <div class="form-inputs">
   <%- attributes.each do |attribute| -%>
     <%%= f.<%= attribute.reference? ? :association : :input %> :<%= attribute.n
   <%- end -%>
   </div>

-  <div class="actions">
+  <div class="form-actions">
     <%%= f.button :submit %>
   </div>
 <%% end %>

Now I know that I should think about which class I want to use in my application for the inputs and actions div tags. I might stick to the old version, because perhaps I have a lot of CSS and JavaScript code built up that I don’t want to update right now. Or if my code is lean and easy to change, I’ll more likely adopt the new standard so that I can honor “convention over configuration”.

Now let’s look at config/initializers/simple_form.rb.Check out this diff; it’s got quite a lot of changes!

Changes to the name of a setting

Let’s focus on one small change:

-  # When false, do not use translations for labels, hints or placeholders.
-  # config.translate = true
+  # When false, do not use translations for labels.
+  # config.translate_labels = true

This configuration option changed! I imagine that using the old version would give a deprecation warning when your application boots. Most people would simple change translate to translate_labels and move on, but in our version, we pick up the change to the comment as well, helping out future developers (and our future selves) to figure out what’s going on more quickly.

Changes to settings that are not on by default

Here’s another interesting example. Note that the comment didn’t change. It turns out that the simple_form authors are doing something subtle here.

   # You can define the class to use on all labels. Default is nil.
-  # config.label_class = nil
+  config.label_class = 'control-label'

They really want new users that follow the installation instructions to set a config.label_class. This would make it easier for those users to style the form labels generated by simple_form while not affecting other labels that they might be manually generating. But the authors also don’t want to force that decision on other gem users who upgrade blindly and don’t even realize the configuration file exists.

So they have set a reasonable backwards-compatible default for the past, and have pushed out a good new opinionated default for new users. This is a balanced and thoughtful approach. And now that we are sitting in front of this diff output, we get to make a conscious decision about which direction to take.

Bigger changes

For my final example, I’ll ask you to spend some more time looking over the diff, which appears below.

Notice that several settings were bundled together and moved into a config.wrappers block. This change is pretty drastic, and I imagine there is some sort of backward-compatibility for users who use the old settings.

But by embracing the new block style, we learn that we can create additional groups of settings. The :default argument to config.wrappers implies that we can create additional named settings groups and mix and match where we use them in the application.

Indeed, we have now discovered the new Wrappers API as described in Plataformatec’s blog post announcing Simple Form 2.0.

So by re-running the generator, we find ourselves making a lot of interesting choices about our application right at the time that we upgrade a gem. This is great because we still have full context about why we chose to upgrade. Woe to the developer that is working on a bug several months from now who doesn’t understand why a setting is set in some strange old way.

Full diff of config/initializers/simple_form.rb

[gist id=5286543]