Repartiendo Responsabilidades En Rails Con composed_of
Published: 2023-10-21 02:02:51

El día de hoy me encuentro estudiando más acerca de técnicas para organizar código de una manera más adecuada con las bondades que brinda Ruby On Rails.
Por lo tanto, para dar inicio solo basta un simple, problema con su solución donde tenemos un modelo Student el cual estará compuesto de las siguientes columnas:

* name
* qualifications

Empezaremos generando nuestra migración con el siguiente comando:

```ruby
rails g migration CreateStudent
```

Esta debera de lucir como se muestra enseguida:
```ruby
class CreateStudent < ActiveRecord::Migration[7.0]
  def change
    create_table :students do |t|
      t.string  :name
      t.json :qualifications

      t.timestamps
    end
  end
end
```
Con lo cual podremos generar objetos del tipo Student de la siguiente manera:

```ruby
Student.create!(name: 'Daniel', qualifications: { subject_one: 10, subject_two: 9, subject_three: 8, subject_four: 10 })
```

Enseguida se nos pide que calculemos el promedio de las calificaciones y lo primero que hariamos es generar los modelos en nuestro modelo Student como se muestra enseguida:

```ruby
class Student < ApplicationRecord
  validates :name, presence: true
  validates :qualifications, presence: true

  def average_calculation
    sum_qualificiations / qualifications.size
  end
 
  private

  def sum_qualificiations
    qualifications.values.inject(0, :+)
  end
end
```
Con esto podremos obtener el promedio de un estudiante únicamente llamando al método **average_calculation** es decir:

```ruby
student = Student.first
student.average_calculation #=> 9
```
En este caso tenemos la clase Student y está debe ser capaz de únicamente tratar con temas relacionados con el estudiante, su nombre, nombre de tutor, teléfono de emergencias, etcétera.Si bien hasta aquí podemos indicar que hemos hecho lo solicitado, estamos olvidando una regla muy importante en la **POO** que es la responsabilidad única en clases.

## El problema

Este radica en que él hacer el calculo de calificaciones no es algo que una clase como Student debería de hacer y está logica la podemos pasar a un objeto compuesto que representara las calificaciones.

Todo esto lo podemos hacer gracias al método  **composed_of** que brinda Ruby On Rails el cual lo que hace es delegar la responsabilidad a una clase externa.

Lo anterior es llamado **composition** el cual podremos definirla como una relación entre dos o más clases donde podemos adquirir un comportamiento y utilizar el código que está en otra clase en nuestro objeto actual.

## La solucion

Lo que tendremos que hacer son dos cosas sencillas:
* Actualizar nuestro modelo Student
* Generar una nueva clase.

Nuestro modelo Student se actualizara de la siguiente manera:

```ruby
class Student < ApplicationRecord
  validates :name, presence: true
  validates :qualifications, presence: true

  composed_of :calculations,
              class_name: 'QualificationCalculation',
              mapping: [%w(qualifications qualifications)]
```
La sintaxis de **composed_of** cuenta con tres opciones, primero el nombre del método al cual haremos referencia (calculations), el nombre de su clase (la clase que crearemos) y el mapeo de la columna qualifications al atributo qualifications en la nueva clase.

Dicho esto procederemos a crear nuestra nueva clase (qualification_calculation.rb) justo en el directorio de modelos.

```ruby
class QualificationCalculation
  attr_accessor :qualifications

  def initialize(qualifications)
    @qualifications = qualifications
  end

  def average_calculation
    sum_qualifications / qualifications.size
  end

  private

  def sum_qualifications
    qualifications.values.inject(0, :+)
  end
end
```
Hecho esto podemos calcular el promedio de calificación de nuestro estudiante de la siguiente manera:

```ruby
student = Student.first
student.calculations.average_calculation #=> 9
```

De esta manera nuestro modelo original no cuenta con la responsabilidad de calcular el promedio y además si ocupamos otro calculo lo podemos agregar directamente en nuestra nueva clase **QualificationCalculation**.