All Articles

Add Many monad to Active Support and Active Record Relation in Rails

Disclaimer: Many monad is still work in progress and still haven’t yet being added to Rails 6.

DHH proposed to bring the Many monad to Active Support and Active Record Relation. Here is the link to the issue where you track all the conversation: #37875

The basic idea is to reduce the deep chaining happening on the Active Record Relation collection. Let me try to explain this with an example

class Blog < ApplicationRecord
  has_many :categories
end

class Category < ApplicationRecord
  has_many :posts
end

class Post < ApplicationRecord
  has_many :comments
end

Let say, we want to retrieve all the comments for all the blogs.

  # Before addition of Many Monad
  blogs = Blog.where(author: "DHH")
  blogs.flat_map(:categories).flat_map(:posts).flat_map(:comments)

  # After addition of Many Monad
  blogs.categories.posts.comments

This issue got me curious to look into What is Monads? and as DHH has added in the issue description, a talk on monads in Ruby by Tom Stuart is the best way to start on learning Monads. After learning about Monad, I raised a PR: #38788 to add Many Monad. Let’s see if the PR get merged and Monad become the part of Rails 6.1

Let discuss a bit about what I learned in the entire process about Monads.

What is Monad?

Monads are abstract data types, which means that on certain values(eg: collection or object) the kind of operations we can perform with certain rules applied to it.

Handle nil

Monad should be able to handle nil because if any of the association in the chain return nil all the future chain will start to break. Handling nil without monad on long chaining could be difficult. It involves a lot of conditional checks on each operation.

Rails already have a try method which does the similar things conditional logic for us. As Tom has mentioned in his talk, using monkey patching on each object is a code smell.

Monad handles nils very gracefully by adding a try or and_then method to each monad. This try operation could be different for different monads but has a couple of rules. The first rule is try should always return a Monad.

Method missing

Each monad can be of any type. In the context of Many monad, it could be Array, Hash or an ActiveRecord::Relation collection. The set of methods, that can be performed on each type of collection values can be different. That is why method_missing handles the invoked property on each monad.

method_missing uses try to handle nils. This is where the second rule comes into the picture. try should always call the block.

The third rule of the monads is that they do not mutate the value. In our scenario, method_missing or try both do not mutate the value but just iterates over each value and calls the block passed.

In conclusion, Monads helps in making our code simpler and more reusable. I fell in love with Monad. I would love to use such simple monad in my day to day work.

Happy Coding!!