¿Puede ActiveRecord::Relation recibir mensajes de métodos de clase? Sí, sí puede
Tags: rails, webdev, spanish, ruby
Published: 2019-11-22 15:52:48

Ruby on Rails es una criatura enorme. Ofrece muchas funcionalidades y elementos que hacen fácil la vida de quien usa el _framework_ bajo el nombre de magia. Magia que muchas veces no tenemos ni idea de cómo funciona.

Algunos podrán decir que esa magia es muy mala al ocultar cosas a quien desarrolla. Otros dirán que es buena justamente por lo mismo. En todo caso, siempre está la posibilidad de examinar el código fuente y ver cómo toda esa magia trabaja bajo cuerda.

Hace algunos días me topé con una de esas magias que sin querer sabía cómo funcionaba pero no había analizado tanto.

## Métodos de Clase y Scopes

Los `scopes` en Rails son una buena forma de crear métodos para hacer consultas sin necesidad de definir un método de clase. Su sintaxis es más sencilla y en muchas ocasiones corta.

Uno de los beneficios explícitos de usarlos es poder encadenarlos para hacer una cadena de invocación:

```ruby
class Algo < ActiveRecord::Base
  scope :default, -> { where(default: true }
  scope :available, -> { where(available: true }
end
```

Los _scopes_ `default` y `available` pueden encadenarse uno tras otro:

```ruby
Algo.default.available
```

Con métodos de clase no es esto posible.

## Entra `ActiveRecord::Relation`

Bueno, resulta que en un proyecto en el que trabajo estaba este método de clase que parecía no tener uso.

```ruby
class Taker < ActiveRecord::Base
  def self.to_csv
    # implementación
  end
end
```

Cuando en Sublime Text o Atom se hacía una búsqueda de `Taker.to_csv` no aparecía nada. Pensé que nada lo usaba. Permití que se borrara su test y esa implementación en el modelo.

Al par de días, un error aparece. ¿Qué será? Inicia el nuevo _sprint_, me asignan dicho bug y procedo a revisar qué ocurre. Como le habíamos hecho refactor a ese modelo, pensé que algo podría haberse movido a donde no correspondía así que empecé por lo obvio, comparar lo actual con un _commit_ viejo pero no, no era por ahí.

Luego de analizar por un momento la situación, pensé en traer de nuevo ese viejo método borrado y probar. Y esa era la solución. Por alguna razón, ese método de clase era lo que faltaba.

Pero, ¿por qué un método de clase podría llamarse en una colección? Resulta que lo que llamaba al método era el resultado de una consulta con un `#where`. Uno creería que para usar métodos de clase habría que siempre usar el nombre de la clase y pasar el mensaje, es decir, el método. Resulta que no. Esa magia "oculta" de Rails lo hace posible.

La explicación está dada en [Stack Overflow](https://stackoverflow.com/a/21681483/1407371):

> 1. By definition, model scopes return `ActiveRecord::Relation` objects
> 2. By definition, scopes have access to class methods
> 3. Therefore, `ActiveRecord::Relation` objects have access to class methods

Español:

> 1. Por definición, los scopes retornan instancias de `ActiveRecord::Relation`
> 2. Por definición, los scopes tienen acceso a los métodos de clase
> 3. Por lo tanto, instancias `ActiveRecord::Relation` tienen acceso a métodos de clase

Ta da! Estuvo siempre sobre mis narices solo que no eran tan obvio.

El código culpable tenía la línea:

```ruby
ts = filtered_ts.to_csv
```

En primera medida creíamos que ese `.to_csv` era algún método de Rails como tal y no el que estaba definido en el modelo. Cuando aparece el bug comprobamos que no era así y está dado por la explicación anterior.

Esta situación nos tomó por sorpresa. El bug no fue algo mayor y tuvimos la fortuna de aprender algo más de la mucha magia que hay en el _framework_.

***

_Este artículo fue publicado [primero en mi blog personal](https://otroespacioblog.wordpress.com/2018/01/25/puede-activerecordrelation-recibir-mensajes-de-metodos-de-clase-si/), Otro Espacio Blog. Ahí escribo sobre todo lo que aprendo al programar y también sobre temas no relacionados a tecnología._