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.
Conditionality is filtering. Don’t filter control flow, filter data.
— Michael Feathers (@mfeathers) March 18, 2017
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.