Conditionality is filtering. Don't filter control flow, filter data.

… and check why 5600+ Rails engineers read also this

Conditionality is filtering. Don’t filter control flow, filter data.

I am not that smart. I didn’t say it. Michael Feathers did. But it got me thinking and I know it inspired my colleagues as well.

A few weeks ago our client decided to change the URL structure of some of the most important pages. There were a few reasons to do it. They don’t matter so much right now. The business just decided to prioritize one kind of benefits over another kind. They turned out to be more important in the long term.

Instead of slugs provided by organizers such as /wrocloverb2016/ we now generate them ourselves based on the name of the event, its id, browser language, some translations so it looks like /e/wroc-love-rb-2016-tickets-111222333.

But, of course, for a few months, we need to support old URLs and continue to redirect them to new URLs. We implemented it as redirects in routing. Unfortunately, they need to query database to find matching events to know the new URL, the redirect should point to, but I think that’s acceptable.

Our platform also needs to support multiple languages and the URLs are a bit different in every language (the word “tickets” is translated). And we don’t need this redirection feature for new events which will only use the new URL structure.

This is the solution.

EventRedirectDate = Time.new(2017, 4, 28, 14)

EventRedirect = -> (path_params) {
  slugged = begin
    event = Event.where(
      Event.arel_table[:created_at].lt(EventRedirectDate)
    ).friendly.find(path_params.fetch(:id))
    I18n.with_locale(path_params[:locale] || event.default_locale) do
      event.slugged
    end
  rescue ActiveRecord::RecordNotFound
    raise ActionController::RoutingError.new('Not Found')
  end
  [path_params[:locale], "e", slugged].reject(&:blank?).join("/")
}
get '/:id', as: 'short_event', to: (redirect() do |path_params, _req|
  EventRedirect.(path_params)
end)

The line that I wanted to show you is:

[path_params[:locale], "e", slugged].reject(&:blank?).join("/")

We use routing-filter gem and when the URL path is /whatever/ then path_params[:locale] is nil and we use the default language of current country (which we know based on the domain). If it is /es/whatever then path_params[:locale] is es and we know that the user wants to see the page in Spanish. We normally recognize it and set I18n.locale in ApplicationController but it works in routing as well if you need it.

So there is nothing super unusual or fantastic in this line of code, except that I originally wanted to write it as:

if path_params[:locale]
  # ...
else
  # ...
end

But the tweet which stayed in my mind and the Don’t filter control flow, filter data phrase made me do it differently. I decided to filter data. All data. Even if "e" or slugged is never empty. A bit more in a functional style.

[path_params[:locale], "e", slugged].reject(&:blank?).join("/")

It is a trivial example but if you want to see how far you can go with it, I recommend watching Norbert’s talk. For bonus, you can learn more about scurvy ;) No wonder that Enumerable methods are often favorites of best Ruby developers. I think one of the main reasons is that they often allow us to avoid a bunch of if-statements. Instead, we can easily filter the data we work on.

There is a similar pattern in Redux reducers, especially when they are combined.

import { combineReducers } from 'redux'
import todos from './todos'
import counter from './counter'

let reducer = combineReducers({
  todos,
  counter
})

let store = createStore(reducer)
store.dispatch({
  type: 'ADD_TODO',
  text: 'Use Redux'
})

When an action is dispatched, the reducer does not think whether todos or counter should react to it. The action is passed to both of them and sometimes one of them decides to do nothing and keeps its current state. It filters out uninteresting actions.

BTW. Our book Fearless Refactoring: Rails controllers contains a chapter about Extract routing constraint technique (as well as many others) that you might be interested in, as well.

You might also like