Last Updated: January 28, 2019
·
704
· wojtha

Smart params matcher for Rails

Today I needed to match params against set of nested controllers and I begin to feel that my simple params_match? helper is too simple for that job.

Note: here is a gist with the code. Coderwall code formatting kind of sucks.

# Usage:
#    params_match?(controller: 'comments', article_id: 23, id: 1)
def params_match?(pattern)
  return true if pattern.nil? || pattern.empty?

  pattern.collect do |(param, value)|
    if value.is_a? Array
      value.map(&:to_s).include?(params[param].to_s)
    else
      value.to_s == params[param].to_s
    end
  end.reduce(&:&)
end

This helper simple search for given pattern and ignore the rest of request parameters.

So if we have e.g. controller comments nested under articles we need to match against those actions like this:

params_match?(controller: 'articles', action: 'show', article_id: @article.id) || \
params_match?(controller: 'comments', action: 'index', article_id: @article.id) || \
params_match?(controller: 'comments', action: 'edit', article_id: @article.id, id: @comment.id) || \
params_match?(controller: 'comments', action: 'update', article_id: @article.id, id: @comment.id)
# etc

Very verbose and not comfortable. It will help if we can provide some kind of 'OR' condition to controllers, actions and parameters.

Lets make some more advanced helper:

# Usage:
#    smart_params_match?([:comments], [:edit, :update], {article_id: 23, id: 1}, {:comment_id: 1})
def smart_params_match?(controllers, actions, *params_or)
  return false unless controllers.nil? || controllers.empty? || [*controllers].include?(params[:controller].to_sym)
  return false unless actions.nil? || actions.empty? || [*actions].include?(params[:action].to_sym)
  params_or.nil? || params_or.empty? || [*params_or].map { |pars| params_match?(pars) }.reduce(&:|)
end

And now we try to match the same condition as in previous example:

smart_params_match?(:articles, :show, article_id: @article.id) || \
smart_params_match?(:comments, [:index, :edit, :update], {article_id: @article.id}, {article_id: @article.id, id: @comment.id})

Which can be simplified to this:

smart_params_match?(:articles, :show, article_id: @article.id) || \
smart_params_match?(:comments, [], article_id: @article.id)

Or even to this

smart_params_match?([:articles, :comments], nil, article_id: @article.id)

Note that you can use zero to many controllers, actions and parameters combinations. The nil or [] for controllers or actions act as a wildcard. Parameters are matched only against given keys and values (we are reusing params_match? here). If you need to match multiple parameter patterns you can just another hash of parameters at the end of the argument list. The matcher returns true if current request params includes on of the given controllers, actions and if one of the parameter patterns matches.

Let me know if you have some better solution for this or if you have any improvement to the code.

Happy coding!

<script src="https://gist.github.com/wojtha/2972034afdcf6796ce1a.js"></script>