Refactoring mailer class

Image for post
Image for post

Refactoring Mailer class using metaprogramming

Refactoring is one of the important thing that you have to do to improve your code, it is the process of restructuring existing code, changing the factoring without changing its external behavior

The main purpose of refactoring is to fight technical debt. It transforms a mess, dirty, and smell code into clean code and simple design.

Refactoring improves non-functional attributes of the software.

The advantages:
- improve code readability
- reduced complexity
these can improve source-code maintainability and create a more expressive internal architecture or object model to improve extensibility

After a period of time i came across UserMailer class that already created months ago i found 5 methods doing almost same thing and sending mail for user with different content with `en` locale.
my task was to make those 5 methods able to send with ar locale as well so i decided to refactor the entire class and use define_method which is ruby metaprogramming method.

METAPROGRAMING: is a technique by which you can write code that writes code by itself dynamically at run time.
This means you can define methods and classes during run time. Crazy, right? In a nutshell, using metaprogramming you can reopen and modify classes, catch methods that don’t exist and create them on the fly, create code that is DRY by avoiding repetitions, and more

define_method(*args): Defines an instance method in the receiver. The method parameter can be a Proc, a Method or an UnboundMethod object. If a block is specified, it is used as the method body. This block is evaluated using instance_eval, a point that is tricky to demonstrate because define_method is private. (This is why we resort to the send hack in this example.)

The UserMailer class was looking like

class UserMailer < ApplicationMailer

Well!
Now we need to send the same above emails in different locales which are en and ar.
So we have to duplicate the 5 above methods, then our MailerUser class will be huge
instead of 5 methods we got 10 methods very huge class!

class UserMailer < ApplicationMailer

Now Let’s refactor the UserMailer class and replace all the methods with following ruby metaprograming code.

instead of more then 60 lines of code we got 12 lines of code so cool! right?

class UserMailer < ApplicationMailer

Let’s break down our class and explain little things

First we defined an array with ar and en locale so we can loop them and get the 5 methods in both ar and en then we got the names of the methods those we need to create them at the run time from I18n

I18n.t('mailer.user', locale: :en).keys
Image for post
Image for post

Now we have got the methods names in array we have to add ar and en in end of each method and defined the methods and pass 2 arguments user_id and subject which will be the mailer subject

define_method("#{method_name.to_s}_#{lang}") { |subject, user_id|

here we go!

now we have the same code that already had in each method which was finding the user id and send the mailer

@user = User.find_by(id: user_id)
return if @user.blank?
mail(to: @user.email, subject: subject)

Cool! now we got very dry code and in future if we needed other method to do same thing we can add the name of the method in I18n and call the method from anywhere

Calling any method from the model will be like

subject = I18n.t('mailer.user.registration_status_changeable.subject')
UserMailer.send("registration_status_changeable_#{I18n.locale}", subject, user.id).deliver_later

instead of

UserMailer.registration_status_changeable(user.id).deliver_later

Consultation

We have covered how to refactor big class using define_method(*args) and we reduced the number of code lines in the class, it’s good to look your code after period it will give you chance to refactor and improve your thinking of writing code

Self-Taught Back-End Developer || Aspiring Full-Stack Developer || Tech enthusiast || Big fans of FC. Barcelona.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store