Simple Functional Strong Parameters In Ruby

Jun 02, 2017

Before Rails 3, the way to protect our Rails app against mass-assignment was to use the attr_accessible class method on our ActiveRecord models.

This was a less than ideal solution since our models, which represent a database table, had some knowledge of what kind of data our web servers were receiving.

The solution that the Rails community has found was the strong_parameters gem. The goal of this gem is to filter params at the controller level. This is a better solution, but I find it hard to use to filter complex data structures.

Even this example found in the documentation is pretty hard to understand.

params.permit(:name,
              {:emails => []},
              :friends => [ :name,
                            { :family => [ :name ],
                              :hobbies => [] }])

The Functional Approach

Partial application

To understand the solution that I propose, you need to understand partial application of functions, which is one of the key features of some functional languages such Haskell, OCaml, Elm etc. Note that in this document when I talk about functions, I am talking about lambdas or procs.

Here is the definition of partial application taken from Wikipedia:

In computer science, partial application (or partial function application) refers to the process of fixing a number of arguments to a function, producing another function of smaller arity.

To better understand what is partial application, let’s start with an example.

Let say that you have the add function that you define using lambda.

add = -> a, b { a + b }

You can call this function this way:

add.(1, 2)
#=> 3

But you can write it using partial application

add = -> a { -> b { a + b } }

Now you can call the function this way:

add_1 = add.(1)
# => #<Proc:0x007fcf57a58a60@(irb):12 (lambda)>
add_1.(2)
# => 3

This means that we can partially apply 1 to the add function then apply 2. Note that when we pass the last param, the result gets computed.

So you can call the same function this way:

add.(1).(2)
# => 3

This process of converting a function with multiple parameters to a function that recursively returns a function is called currying. Currying can be painful if done manually. Ruby has a solution for that: it’s the curry method defined on Proc objects.

So you can take a function with multiple params and convert it to a function with one param that returns a function with one param up until there are no parameters left.

Here’s an example:

add_3_numbers = -> a, b ,c { a + b + c }.curry

Then your function will be curried. You can then apply parameters one after the other.

add3_numbers.(1).(2).(3)
# => 6

This gives us an easy way to apply the parameters on that function in different contexts. This is a very powerful concept that allows you to “initialize” functions before running them.

Functional Strong Params

Hash

Let’s say that we define a function filter_hash, which takes a list of keys to keep from the params hash.

filter_hash = -> keys, params {
  keys.map { |key| [key, params[key]] }.to_h
}.curry

We can use this function to filter a params hash like this one:

params = {name: "Joe", age: "23", pwd: "hacked_password"}

filter_hash.([:name, :age]).(params)
# => {name: "Joe", age: "23"}

This is great, but if we have a more complicated params hash:

user_params = {name: "Joe", age: "23", pwd: "hacked_password",
               contact: { address: "2342 St-Denis",
                          to_filter: ""}}

We might want to apply a filter to the contact hash. But the filter_hash function does not allow us to apply a filter to the values of the params hash.

filter_hash.([:name, :age, :contact]).(params)
# => {name: "Joe", age: "23",
#     contact: { address: "2342 St-Denis",
#                to_filter: ""}}

Instead of using the filter_hash function, we can define the hash_of function, which takes two params. The first one is the fields param, which is a hash that maps each key you want to keep to a function that will be applied to the corresponding value in the params hash. The second one is the hash param, which corresponds to the hash be filtered.

Here is the definition:

hash_of = -> (fields, hash) {
  hash ||= {} # The hash can be nil in the case of nested hash_of
  fields.map { |(key, fn)| [key, fn.(hash[key])] }.to_h
}.curry

It’s easier to understand using an example.

user = hash_of.(name: -> a {a},
                age: -> a {a},
                contact: hash_of.(address: -> a {a}))
user.(user_params)
# => {name: "Joe", age: "23",
#     contact: { address: "2342 St-Denis" }

Note that the -> a { a } function is very useful in functional programming. It is so useful that it has a name. It is called the id function. It takes a param and returns it as is. Since id is used in a different context in Rails, we will rename it to same.

same = -> a { a }
same.(2) # => 2

We can then rewrite the user function

user = hash_of.(name: same,
                age: same,
                contact: hash_of.(address: same))

This very small function makes the filter functions more DSLish.

Note that these functions are very composable. We can put the contact filter in a variable:

contact = hash_of.(address: same)
user = hash_of.(name: same, age: same,
                contact: contact)

You can then reuse this filter in another controller very easily.

Array

If we want to filter a field that contains an array. We can use the following function:

array_of = -> fn, value { value.kind_of?(Array) ?  value.map(&fn) : [] }.curry

Let say that we have a list of contacts:

contacts_params = [{address: "21 Jump Street", remove: "me" },
                   {address: "24 Sussex", remove: "me too" }]

We can use our array_of to filter out our contacts.

array_of.(contact).(contacts_params)
# => [{address: "21 Jump Street"},
#     {address: "24 Sussex"}]

Default Values

With strong_params gem, it can be hard to set a default value on blank data. But using these very simple functions, it gets very trivial. We can create a default function.

default = -> default, a { a.blank? ? default : a  }.curry
contact = hash_of.(address: default.("N/A"))
params = [{address: "21 Jump Street", remove: "me" },
          {address: ""}]
array_of.(contact).(params)
# => [{address: "21 Jump Street"},
#     {address: "N/A"}]

Scalar Values

Using the same function can cause some security issues. If you are expecting the value of a field to be a string and you get a hash, that function will return that hash. A solution to that problem is the scalar function

scalar = -> a { a.kind_of?(Array) || a.kind_of?(Hash) ? nil : a }

Here’s an example

hash_of.(name: scalar).(name: {hack: "coucou"})
# => { name: nil }

hash_of.(name: scalar).(name: "Martin")
# => { name: "Martin" }

Strong params example

Here’s a way to rewrite the example that we’ve extracted from the strong_parameters documentation.

The original one

params.permit(:name,
              {:emails => []},
              :friends => [ :name,
                            { :family => [ :name ],
                              :hobbies => [] }])

The functional one

friend = hash_of.(name: scalar,
          family: hash_of.(name: scalar),
          hobbies: array_of.(scalar))
hash_of.({ name: scalar,
           emails: array_of.(scalar),
           friends: array_of.(friend)})

Ok, it’s a bit more code, but it is a way simpler solution, easier to understand, easier to test, more extensible, more reusable, and more composable. The only drawback is that you need to be familiar with partial application. However, knowing partial application can improve your code in different ways.

Extensibility

It is straightforward to extend this solution by creating your own functions. Nothing forbids you to create validations, cast functions, etc. If you want to extend the strong_parameters gem you have to monkey patch it, which is pretty bad.

Other contexts

You can also use this pattern in other contexts, such as converting JSON structure coming from a remote API. Since you can set defaults or cast data like dates, it is a perfect solution to filter/transform complex JSON payloads structure.

Conclusion

With only a few very simple functions, you can do a good part of what the strong_parameters gem does.

Here’s a recap of the functions that we’ve talked about in this blog post.

same = -> a { a }
hash_of = -> fields , hash {
  hash ||= {}
  fields.map { |(key, fn)| [key, fn.(hash[key])] }.to_h
}.curry
array_of = -> fn, value { value.kind_of?(Array) ?  value.map(&fn) : [] }.curry
default = -> default, a { a.blank? ? default : a  }.curry
scalar = -> a { a.kind_of?(Array) || a.kind_of?(Hash) ? nil : a }

This solution is so simple that bugs are very less likely to exist. It covers most functionalities that the strong_params gem is offering, but I think that with minor changes, we would be able to support all of them.

In the next post I will show you how to integrate this solution in your Rails application.