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
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
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
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_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.
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.