Memoizando en Ruby (o qué es ese "||=" que usamos tanto)

Es una de las magias 🧙‍♀️ más antiguas en el monolito de Fintual.

La idea es evitar calcular un valor que vamos a usar en una clase más de una vez, junto con encapsular cualquier lógica innecesaria para que a la siguiente desarrolladora le sea más fácil entender el contexto.

Para memoizar en Ruby es necesario usar el operador "lazy initialization" ||=.  Además, el nombre de la nueva variable debe tener el mismo nombre que el método que la va a encapsular.

def perform
    return if find_user.blank?
    
    register_user_deposit(find_user, @capital)
    return unless deposit_status_service.verified_user?(find_user)
    
    @distribution = Goal::DepositDistribution.for(find_user)
end

private

def find_user
    Mx::MatchStatementLineToUser.for(line: @line, user_id: @user_id)
end
En cada llamada a find_user se va a llamar al comando Mx::MatchStatementLineToUser. Esto puede ser costoso.

def perform
    return if user.blank?
    
    register_user_deposit(user, @capital)
    return unless deposit_status_service.verified_user?(user)
    
    @distribution = Goal::DepositDistribution.for(user)
end

private

def user
    @user ||= Mx::MatchStatementLineToUser.for(line: @line, user_id: @user_id)
end
Memoizando la variable solo se va a ejecutar por primera vez en la linea 4. En los siguientes llamados se va a usar el valor cacheado.

Esta estrategia es muy estándar. Si intentamos definir el método con nombre distinto al de la variable, RuboCop, nuestra gema de análisis de código estático para Ruby, se va a quejar.

def find_user
    @user ||= Mx::MatchStatementLineToUser.for(line: @line, user_id: @user_id) 
end
Mal nombre del método.

La queja de RuboCop.

La mayor ventaja de esto es la evaluación lazy. La variable se va a inicializar la primera vez que se pida, se guarda y al siguiente llamado va a retornar inmediatamente el valor ya calculado.

Cada vez que se llama last_vacation sin memoizar el código le pregunta esto a la DB.

Al memorizar, solo le pregunta a la DB la primera vez.

Su gran desventaja es que, como todo método de caché, es susceptible a la invalidación de éste, por lo que hay que tener presente que el valor ya calculado no va a cambiar.