The advantage is that you can use variables in the creation of a method and create less code when there are several methods with minimal variations.
For example, imagine that I have a model User
with an attribute status
that defines if that user is in active or inactive state. I also need a couple of methods to help me:
- Check if a user is in a specific state.
- Check in the bd all users who are in a specific state.
Then the model that I describe to you would look something like this:
class User < ApplicationRecord
# Con esto podría hacer user.active? y retornaría true/false dependiendo si el usuario está activo o no
def active?
status == 'active'
end
def inactive?
status == 'inactive'
end
# Acá al ejecutar User.active retornaría todos los usuarios activos
def self.active
where(status: 'active')
end
def self.inactive
where(status: 'inactive')
end
end
Now imagine that there are new requirements and I need to add 5 more states for the user, for which the methods that I explained should also be defined. I could then create the methods, but it would be something repetitive, where the only thing that would change is the name of the state. In this case, some metaprogramming may be necessary and create the methods in the following way:
class User < ApplicationRecord
# Creo un arreglo con los nombres de todos los estados posibles, incluidos los 5 nuevos
STATUS = ['active', 'inactive', 'locked', 'deceased', 'admin', 'whatever', 'asdf']
# Y luego itero en ese arreglo para crear los métodos necesarios para cada estado
STATUS.each do |st|
# define_singleton_method sirve para definir métodos de clase
define_singleton_method st do
where(status: st)
end
# define_method sirve para definir métodos de instancia
define_method "#{st}?" do
status == st
end
end
end
So in this way I saved myself from writing 14 methods that practically did the same thing, besides that there is a much cleaner code.
Another clear example is what Rails does internally with the variable names that one defines. For example for create route helpers as users_path
or new_user_url
when you define resources :users
in the routes file.
Do not worry if in the first instance you feel that this has no greater utility or you do not find where to apply it. Its use is something that perhaps can not be visualized first, but as your code grows, you may feel the need to refactor your code and take up some metaprogramming, where it may become useful.