Tag: ruby

CallbackWorker

At Recurrency, we use Sidekiq for background processing. We have several cases where we need to create a model object, save it, and then have some method on that object execute in the background. Instead of creating individual worker classes for each of these use cases, our CTO – Jonathan George – came up with a simple reusable worker class to make this extremely easy.  He came up with CallbackWorker, which I have iterated on, simplified, and ended up getting down to 9 lines with the use of splat arguments, Global ID, and general Ruby awesomeness.

To use this generic worker, just call perform_async with your model object’s Global ID, the method to call as a symbol, and any parameters that need to be passed to the method:

This obviously won’t work for every situation, but it makes some of the simpler background processing tasks quicker to implement and reduces the amount of code we have to write and maintain.

If you port this to another background processing library, please share it in the comments.

Postmortem on bug found in i18n-extra_translations

I spent a significant portion of the second half of last week struggling with an issue in a Ruby on Rails project I work on regularly. This post is a postmortem on the issue.

Sometime in the middle of last week I started seeing rspec failures that looked like this:

ActionView::Template::Error:
 undefined method `[]=' for :used:Symbol
#./app/controllers/application_controller.rb:300:in `some_method'
#./app/controllers/application_controller.rb:350:in `second_method'
#./spec/controllers/some_random_controller_spec.rb:220:in ...

I modified the stack trace because it came from various different  specs, and the exact details don’t matter right now. The important thing to know is that the failures appeared to be random (different number of failures every time), the stack trace always terminated in the same method in the ApplicationController, and the method in the ApplicationController rendered a partial that lives inside a gem included in the Gemfile. We’ll refer to this external gem as common_components from here on.

The main reason that I struggled with this issue for so long is that I didn’t know about the -b option for rpsec. If you run rspec with the -b option, it will show you detailed stack traces. By default it only shows you the part of the stack trace relevant to your current project. Once I tried this, the detailed stack trace gave me a much better idea of where the problem was. I’ll spare you the massive stack trace that was generated and just say that the error was coming from common_components/_error.html.haml. The line the error was coming from looked like this:

%p= t('error.default_message')

Looking even further along in the stack trace I could see that the error was coming from gems/i18n-extra_translations-0.0.5/lib/i18n/extra_translations/store.rb:17:in `add_key’. I didn’t understand why this code was throwing an error, so I dug into the source code. The error came from line 17 of store.rb, which looks like this:
https://gist.github.com/MaxSchmeling/10646958.js
This store class inherits from Hash and is used as a container of all translation keys used by the application. The gem has an extender which replaced the I18n.translate method and will call store.miss or store.use. These two methods call the protected method add_key and tell it to add the key to the store with either a :used or a :miss result. The use of keys.inject on line 16 is a crafty way of building a nested Hash structure based on the dot separated translation keys. For instance, if these were the only two translations used in the application:

en.error.default_message
en.error.default_header

Then the store would end up looking like this:

{
  en: {
    error: {
      default_message: :used,
      default_header: :used
    }
  }
}

That code took a little bit to parse in my head, but once I figured out exactly how it was working, I still wasn’t quite sure why the error was happening. Clearly, line 16 sets h to a Hash, so why is it saying h is :used:Symbol on line 17? It didn’t make sense.

After a few minutes of head scratching, I realized that we had conflicting translation keys. It would make sense that h is set to :used in the case that add_keys has already been called with a less specific key then we’re currently adding.

Take the following translation files for example:
https://gist.github.com/MaxSchmeling/10648014.js
Now let’s step through, at a relatively high level, what happens in i18n-extra_translations when these keys are translated:

  1. I18n.translate(‘en.error’) is called.
  2. The store now looks like:
    {
      en: {
        error: :used
      }
    }
  3. I18n.translate(‘en.error.default_header’) is called.
  4. store.use is called for this key which in turn calls add_keys.
  5. On line 16 of store.rb, h is set to :used instead of an empty hash because it’s already been initialized in the previous I18n.translate call.
  6. On line 17, []= is undefined for :used:Symbol because it’s expecting h to be a Hash, not a symbol.

The problem became extremely clear once I realized what was happening. If you have “conflicting” translations, and a less specific translation is used after a more specific translation, the i18n-extra_translations gem can’t handle it. This also explains why the failures were random… since rspec runs tests in a random order, which tests failed would depend on which order the translations were called.

I’ve logged an issue on the project in GitHub. For now, we can fix the issue by “namespacing” translations in the common_components gem under a root key. For example, en.common_components.error.default_message instead of en.error.default_message.

tl;dr

The i18n-extra_translations gem (source) has a bug – or unsupported use case – that doesn’t allow it to function correctly in the case that you have conflicting translation files loaded. For instance, if a gem you’ve included in your project has the key en.error.default_message=”Internal Server Error” and your application has the key en.error=”Error” the i18n-extra_translations gem will fail internally with the error undefined method `[]=’ for :used:Symbol.

Humongous: A useful MongoDB browser

I just discovered Humongous (on GitHub) by Pankaj Bagwan while listening to one of the most recent Ruby5 podcast episodes. I gave it a try and it worked as promised.

To get it working it was just a matter of installing the gem and running the humongous command:

> sudo gem install humongous
> humongous
 

It is supposed to launch the browser automatically when you run the command, but it failed for me. When I ran the command again it told me that it was already running on http://0.0.0.0:9000/ so I just navigated to that URL manually and it worked like a charm.

I’m sure there are lots of advanced features of MongoDB that are not supported in the UI but for quick and easy MongoDB browsing and editing, I think this is pretty slick. Kudos to the author.

I also discovered a minor issue with Humongous that I logged in the project’s GitHub issue tracker. I plan to fix this issue and submit a pull request unless the author gets to it first.