Consistently Snake- and Camel-Casing

I am working on a couple of projects that use Ruby on the back end and JavaScript on the front end. The Ruby convention for variables is snake_case, while JavaScript variables are camelCased. This causes friction when we pass things between the front end and the back end.

An ad-hoc solution might result in the Ruby code handling some camel-cased variables when reading in JSON or when sending a response. Otherwise, the JavaScript code has underscores all over the place, which is also undesirable. It has the effect of cluttering up our front end code.

Overall, this discrepancy makes it harder to derive the right variable name each time on both the server and the client. Languages have conventions primarily to make it easier to remember what to call things. However, this breaks down when there are two or more languages in play that have different conventions.

A solution that I implemented that I’m pretty happy with so far is to consistently snake- and camel-case on the server. This can be done with two steps. First, we create a middleware that intercepts requests with a JSON body and converts the keys to snake-case. Then, whenever we send a JSON response, we convert the response to camel-case for the client to consume.

There are a few advantages to doing it this way. We will have consistent snake-casing on the back end and consistent camel-casing on the front end. Our linters will have fewer false positives. In addition, our tests are also generally easier to write because they can use the correct case (except for server-side controller tests, since these require camel-case input.)

For the specific project that I’m working on, I used a pair of gems written by the same author. The plissken gem turns camel-cased hash keys into their snake-case equivalent, and even works recursively for arrays of hashes. The awrence gem does the reverse, going from snake-case to camel-case. So if we were using Sinatra and ActiveSupport, a middleware might look like:

  ...
  use Rack::Parser, parsers: {
    'application/json' => -> (data) do
      JSON.parse(data).to_snake_keys.with_indifferent_access
    end
  }
  ...

Which will load the JSON body into the params hash, and we can access it with symbols or strings.

Our JSON responses can be automatically camel-cased with the following middleware:

class CamelizeJsonResponseMiddleware < Sinatra::Base
  after do
    pass unless content_type == 'application/json'
    if response.body.length > 0
      body response.body.to_camelback_keys.to_json
    end
  end
end

The nice thing about using middlewares is that we don’t have to remember to convert for each request. Our application is more consistent as a result.

One potential downside is if the gems don’t work as expected or if the input or output is particularly complicated and our expectations are violated. The other would be if someone new came onto the project and doesn’t understand the middlewares. They might be quite confused until they figured out what was going on. I think documentation and logging would help address most of the issues here.

I’m pretty happy with how this worked out, and think that it makes the code a lot cleaner. Hope this helps you on your projects!

Categories: main

« The Cross-Country RV Trip I Didn't Take Should I Work For Free? »

Comments