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.
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
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.
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.
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,
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.