Tuesday, February 02, 2016

Using inject to remove mutated state in Ruby

This article has a great pattern to allow filtering of ActiveRecord results based on query parameters, but it also has a snippet of code that really bugs me:

module Filterable
extend ActiveSupport::Concern
module ClassMethods
def filter(filtering_params)
results = self.where(nil)
filtering_params.each do |key, value|
results = results.public_send(key, value) if value.present?
end
results
end
end
end
view raw Filterable.rb hosted with ❤ by GitHub
The thing that bugs is using a .each block to mutate some state outside the block and return it. The power of blocks and enumerated methods like .each is one of Ruby's strong points, but it also gets abused. In this case, there is no need to mutate the results object and the code can be condensed and cleaned up as follows:

module Filterable
extend ActiveSupport::Concern
module ClassMethods
def filter(filtering_params)
# Create an anonymous scope and then reduce it base on key / value
filtering_params.inject(self.where(nil)) do |results, (key, value)|
results.public_send(key, value) if value.present?
end
end
end
end
Now we don't have any state being mutated. In a function like this the risk is minimal and it might be a moot point, but I think it's a good habit to get into. Whenever you reach for .each, it's worth thinking: could I use .map or .reduce / .inject instead and this way avoid mutating state.

Rant over.

No comments: