Ruby : to __callee__ or not __callee__
Tags: ruby, rails, programming
Published: 2023-09-26 15:07:44

Ever met those instructions :

- `__FILE__`
- `__method__`
- `__callee__`

?

I think we all met at least the first one while playing with Ruby within our early experimentations ... and maybe used the second one for debugging processes - if so I propose you to try a debugger like [pry](https://github.com/pry/pry), [byebug](https://github.com/deivid-rodriguez/byebug), [pry-byebug](https://github.com/deivid-rodriguez/pry-byebug) you'll get lots of the information right away (if you are using Ruby >3.1 you even get an [integrated one](https://dev.to/st0012/a-sneak-peek-of-ruby-s-new-debugger-5caa)).
But do you know what does the last one ?

## The difference with __method__

First of all, let's identify what `__method__` is doing :

```rb
def world
  p "hello #{__method__}"
end

world
```

Executing this file will produce:

```
$ > hello world
```

But why ?

Simply put : `__method__` is returning as a symbol the name of the method the instruction is in.
Here the method is **world**, so it **puts** the interpolation of the string and the method symbol which gives : _hello world_.

So what is the difference with `__callee__` ?
Well `__callee__` is returning the actual name of the **called method** dynamically as a symbol.

## Let's example ourselves

```rb
class Foo
  def root_method
    p "you did #{__method__}"
  end

  def root_callee
    p "you did #{__calle__}"
  end
end

Foo.new.tap do |this|
  this.root_method
  this.root_callee
end
```
File execution :
```
$ > you did root_method
  > you did root_callee
```

Here nothing big differentiate both behaviours : both return their method name.

Let's then put the `__callee__` specificities to the test :
```rb
class Foo
  alias_method :aliased_method, :root_method
  alias_method :aliased_callee, :root_callee
end

Foo.new.tap do |this|
  this.aliased_method
  this.aliased_callee
end
```
File execution :
```
$ > you did root_method
  > you did aliased_callee
```

So what happened ?

1. The aliased method that uses `__method__` interpolates the root method name.
2. The aliased method that uses `__callee__` interpolates with the aliased method name.

`__callee__` instead of `__method__` is looking up for the actual method name, not the one it originated from !

## Ok ... but how can I use this ?

That's a good question !

Let's imagine a situation where we send automatic mailing to update an asynchronous task for our users .

We will always send the same data structure, and the only thing that will change is the corpus of the mail.
We could go the naive way and produce a `Mailer` with every specific methods we need for every possible situations. But that's tedious and imply a lot of repetition - which we don't want !

So how can we use `__callee__` here to simplify our day to day life ?

## A one Mailer to rule them all

So we need a Mailer that will always send emails with 3 parameters : `email`, `user_name`, a `job_id`, to notify an asynchronous event to the users.
The only things that will change depending on the event is :

1. the subject of the mail
2. the corpus of the mail

So first we need something that will be able to structure the needed data as we need it :

```rb
class AsynchronUpdateMailer < ActionMailer::Base
  private

  def default_mail_action(email:, user_name:, jid:)
    @email = email
    @name = user_name
    @jid = jid

    mail(
      to: ["#{@name} <#{@email}>"],
      subject: "#{__callee__.to_s.gsub('_', ' ')}: #{jid}",
      &:html
    )
  end
end
```

We now have an `AsynchronUpdateMailer` that is able to receive parameters to structure a default email and uses `__callee__` and the job id to structure the subject. That's Good !
But we can't do anything with it yet.
_We turn the method private because this should never get called : no need to send a default mail, what we need is a simple first structure to be able to quickly extend its behaviour with the less possible code_

So let's extend it a bit to turn it a little bit more usefull :
```rb
class AsynchronUpdateMailer < ActionMailer::Base
  VALID_ACTIONS = %i[
    foo_success
    foo_failure
    bar_success
  ]

  VALID_ACTIONS.each do |valid_alias|
    alias_method valid_alias, :default_mail_action
  end

  private
  [...]
end
```

What did we do here ?

1. We defined a constant that identify which actions this mailer is able to produce.
2. when this Mailer is loaded by Rails, it will programmatically define an alias for every "valid action" that aliases `default_mail_action`, and each of those aliases will be public

Ok ... that's good and all. But what about the corpus ? What is the interest of this proposition if everyone of the sent emails does the same ?
Well, the answer is simple : we're going to use the amazing powers of Rails views system to resolve our situation !

## Rails views to the rescue

Every time a `ActionMailer::Base` uses the `mail` method it will rely on the called method name and the application file structure to obtain the view that matches :

1. the Mailer name to find the folder within the **app/views** folder
2. the given action name to find the corresponding **.haml** file

So let's add our files where they need to be !
Within the **app/views** folder we create a folder that is te machine name of our Mailer : `app/views/asynchronous_update_mailer`. We then create a `.haml` for every action we need - corresponding to the aliases we defined :

- `foo_success.haml`
- `foo_failure.haml`
- `bar_success.haml`

Each one of them is a common haml file and icing on the cake : we event get access to the state of the Mailer within this haml file.
So we can do things like :

```haml
%html
  %body
    %section
      %p
        = "Hello #{@user_name}!"
      %p
        = "You can use the number given within the subject of this mail to contact the support if need be !"
      ...
```

So ? Can you feel the maintenability and scalability of this setup tickling your brain ?
Obviously you can always go one step further, but here we potentially saved a lot of time and maintenance using the power of Rails !

## Conclusion

`__callee__` is one of those tool Ruby proposes that we don't always need ... but can still be of great use in the right context.
It always comes down to what we have and what we do with it. I'm sure you already made great use of it and can share with us in the comment some of your experiences with its usage !

## That's it for today !

Thank you for reading this article, and I hope it got you something interesting out of it. At least I had a blast typing it.

I'm sure you already made great use of the `__callee__` method, so don't hesitate to give your insights and personal examples in the comments : they will be greatly appreciated.

**Enjoy your skills and have a great day !**