My Ruby Journey: Hooking Things Up 
Tags: ruby, programming, rails
Published: 2018-11-03 09:52:36

---
title: My Ruby Journey: Hooking Things Up 
published: true
description: How Ruby Module's hooks work
tags: ruby, programming, rails
cover_image: https://thepracticaldev.s3.amazonaws.com/i/ipb1enanrg7eyxi33ctx.png
canonical_url: https://medium.com/@edisonywh/my-ruby-journey-hooking-things-up-91d757e1c59c
---

# My Ruby Journey: Hooking Things Up

I’ve only recently just begun my Ruby/Rails journey (worked for little less than a year), and one day I came across something like this.

```ruby
class Product
  acts_as_paranoid
  acts_as_taggable
  has_paper_trail
  monetize :price_cents
end
```

I knew they came from gems, but I thought it looked super cool and was really curious to how they work, so I took a step back, broke down the problem and tackle it one by one.

## Stop 1: Initial Findings

Couple of things I figured out here:
- **They are just normal class methods**
- When you put a method name inside the class, the method gets invoked every time you make a new instance of it.

e.g:

```ruby
class Example
  puts "This gets called whenever you initiate an instance of #{self}"
end

Example.new #=> "This gets called whenever you initiate an instance of Example
```

So, now that I know that they’re just class methods, I decided that my next step should be to write something similar; Reusable class methods! (I didn’t want to get into how Rails autoload it just yet)

And thus I began my journey, and then voilà, next obstacle arose!

## Stop 2: I can’t share class methods with Modules?
*(Spoiler: Yes you can, I just didn’t know at first!*)😛

At first, I thought it was going to be a simple `include Module` because that’s how I was told reusable methods work, so here I am, naively trying to define a class method on module then include-ing in the class — but it doesn’t work!

e.g:

```ruby
module Reusable
  def self.awesome?
    puts "awesome!"
  end
end

class AwesomeObject
  include Reusable
end

AwesomeObject.awesome? #=> undefined method `awesome?' for AwesomeObject:Class (NoMethodError)
```

The reason for this is because Ruby's `include` does not include `class methods`. 

There's a solution through `extend`, `include`'s lesser known sibling (the other one being `prepend`).

The full explanation is a little bit out of scope for this post, so I wont dive into it here, but if you want to learn more, it's due to something called `Singleton Class` in Ruby. This will also be the topic for my next blog post, so stay tuned!

## Stop 3: But I want everyone to feel `included` and hooked!

Since `include` is a much more popular sibling to `extend` and `prepend`, I was quite determined to make sure that the end users only have to remember to `include` a module, and not having to think -- 

'Do I `extend` this, `include` this or `prepend` this?'

Also hoping to avoid situation where you include a module, then spending an entire day to figure out why class method doesn't work.

*Actually, in hindsight telling developers to just blindly `extend` a method is not such a bad idea. But then again, if I gave up then there'd be no blog-post now* 😉

## Stop 4: Wish granted!

After much research, I figured out that Ruby has a `included`, `extended` and `prepended` hooks available for us to (you guessed it) *hook into*.

> source: https://ruby-doc.org/core-2.2.0/Module.html

These hooks allow us to execute code when a Module is being `included`, `extended` or `prepended`, and guess what're we going to do?

We're going to `extend` a module when it's being `included` (this sounded funny to me at first)

Let's take a look at a small example on how hooks work:

e.g: 

```ruby
module HookedModule
  def self.included(base)
    puts "#{self} is being included in #{base}"
  end
end

class BaseObject
  include HookedModule
end

BaseObject.new #=> HookedModule is being included in BaseObject
```

Couple things to note here:

- `included` needs to be a class method, because it's a method on `Module` class. If you don't declare it a class method, the hook doesn't work.
- The `base` argument being passed into `included` is the class that you're including the module from, which is BaseObject in our case.

## Final Stop: Extend? Include? Why not both?

Now that I have a better understanding on how hooks work, let's read back what my goal was:

> `Extend` a module when it's being `included`

Awesome! Let's try to extend class methods there! (Don't you just love it when the requirement tells you exactly what you need to do?)

```ruby
module HookedModule
  def self.included(base)
    base.extend ClassMethods
  end

  module ClassMethods
    def awesome?
      puts "Yes, #{self} is awesome, stop asking."
    end
  end
end

class BaseObject
  include HookedModule
end

BaseObject.awesome? #=> "Yes, BaseObject is awesome, stop asking.
```

The reason this work is because when we first hook into `included`, we gained access to our BaseObject through `base`. We can then call `extend` (a class method) on our `BaseObject` class, which works exactly the same as if we had just done it like so:

```ruby
class BaseObject
  extend HookedModule
end
```

This seems like a lot of work at first, but if you want to reuse instance methods and also class methods, it makes no sense to have to both include and extend YourModule.

## Recap

So, I started off the article trying to figure out how these gems provide magic methods — I still do not fully understand how the methods are being loaded without explicitly including or excluding a module (perhaps a topic for another blog post!), but I found out that they are all just normal class methods, and I could use this along with Module to make my class methods reusable.

## My Learnings:
- You can run methods on initialization of classes.
including a module does not include class methods.

- Module has hooks which we can hook into to simplify our API.

## Author’s Note
This is the first blog post I’ve ever written! Might not have been exactly the most advanced nor the most well-written post, but I’m learning still, so please do drop a comment if you have any feedback, I’d really appreciate them!

I also posted my article on Medium! Check it out here: https://medium.com/@edisonywh/my-ruby-journey-hooking-things-up-91d757e1c59c

So that’s it! My first Ruby Journey coming to a close. I love exploring through Ruby’s little magic here and there, so I’ll hopefully be writing a few more blog posts down the line. Until then, have a safe journey through your own Ruby land!