Build Dumb, Refactor Smart: Cómo eliminar los problemas del código de Ruby on Rails

Publicado: 2022-03-11
Daniel Lewis ha sido un desarrollador profesional de Ruby on Rails durante más de 4 años, trabajando en una docena de aplicaciones web de alto tráfico, muchas de ellas a través de Toptal.

A veces, los clientes nos solicitan funciones que realmente no nos gustan. No es que no nos gusten nuestros clientes, amamos a nuestros clientes. No es que no nos guste la función: la mayoría de las funciones solicitadas por los clientes se alinean perfectamente con sus objetivos comerciales e ingresos.

A veces, no nos gusta una solicitud de función porque la forma más fácil de resolverla es escribir un código incorrecto y no tenemos una solución elegante en la cabeza. Esto hará que muchos de nosotros, los desarrolladores de Rails, realicemos búsquedas infructuosas a través de RubyToolbox, GitHub, blogs de desarrolladores y StackOverflow en busca de una joya, un complemento o un código de ejemplo que nos haga sentir mejor con nosotros mismos.

A veces, no nos gusta una solicitud de función porque la forma más fácil de resolverla es escribir un código incorrecto.
Pío

Refactorizar el código de Ruby on Rails

Bueno, estoy aquí para decirles: está bien escribir mal código. A veces, el código incorrecto de Rails es más fácil de refactorizar en un código hermoso que una solución mal pensada implementada en una crisis de tiempo.

Este es el proceso de refactorización de Rails que me gusta seguir cuando soluciono los problemas de mis horribles soluciones de curitas:

Este proceso de refactorización de Ruby on Rails es el que sigo para abordar los problemas de Rails.

Para una perspectiva alternativa, aquí está el registro de confirmación de Git para una función que se ha refactorizado paso a paso:

Este registro muestra un refactor de Rails que se ha realizado paso a paso.

Y aquí hay otro artículo interesante sobre refactorización a gran escala de un colega en la red Toptal.

Veamos cómo se hace.

Las vistas

Paso 1. Comienza en las Vistas

Digamos que estamos comenzando un boleto para una nueva característica. El cliente nos dice: “Los visitantes deberían poder ver una lista de Proyectos activos en la página de bienvenida”.

Este ticket requiere un cambio visible, por lo que un lugar razonable para comenzar a trabajar sería en las Vistas. El problema es sencillo y todos hemos sido entrenados para resolver varias veces. Voy a resolverlo de la manera incorrecta y demostraré cómo refactorizar mi solución en sus áreas apropiadas. Resolver un problema de la manera incorrecta puede ayudarnos a superar el escollo de no conocer la solución correcta.

Para comenzar, supongamos que tenemos un modelo llamado Project con un atributo booleano llamado active . Queremos obtener una lista de todos los Projects donde active es igual a true , por lo que podemos usar Project.where(active: true) y recorrerlo con each bloque.

 app/views/pages/welcome.haml: %ul.projects - Project.where(active: true).each do |project| %li.project= link_to project_path(project), project.name

Sé lo que está diciendo: "Eso nunca pasaría una revisión de código" o "Mi cliente seguramente me despediría por esto". Sí, esta solución rompe la separación de preocupaciones Modelo-Vista-Controlador, puede resultar en llamadas de base de datos perdidas que son difíciles de rastrear y puede volverse difícil de mantener en el futuro. Pero considere el valor de hacerlo de la manera incorrecta :

  1. Puede obtener este cambio en la puesta en escena en menos de 15 minutos.
  2. Si se deja, este bloque es fácil de almacenar en caché.
  3. Solucionar este problema de Rails es sencillo (se le puede dar a un desarrollador junior).

Paso 2. Parciales

Después de hacerlo de la manera incorrecta , me siento mal conmigo mismo y quiero aislar mi código incorrecto. Si este cambio fuera claramente solo una preocupación de la capa Vista, podría refactorizar mi vergüenza en un parcial.

 app/views/pages/welcome.haml: = render :partial => 'shared/projects_list' app/views/shared/projects_list.haml: %ul.projects - Project.where(active: true).each do |project| %li.project= link_to project_path(project), project.name

Eso es un poco mejor. Claramente, todavía estamos cometiendo el error de una consulta de modelo en una vista, pero al menos cuando un mantenedor venga más tarde y vea mi horrible parcial, tendrá una forma sencilla de abordar ese problema de código de Rails en particular. Arreglar algo obviamente tonto siempre es más fácil que arreglar una abstracción con errores mal implementada.

Arreglar algo obviamente tonto siempre es más fácil que arreglar una abstracción con errores mal implementada.
Pío

Paso 3. Ayudantes

Los ayudantes en Rails son una forma de crear un DSL (lenguaje específico del dominio) para una sección de sus vistas. Tienes que reescribir tu código usando content_tag en lugar de haml o HTML, pero obtienes el beneficio de poder manipular estructuras de datos sin que otros desarrolladores te miren con ira durante 15 líneas de código View que no se imprime.

Si tuviera que usar ayudantes aquí, probablemente refactorizaría la etiqueta li .

 app/views/shared/projects_list.haml: %ul.projects - Project.where(active: true).each do |project| = project_list_item(project) app/helpers/projects_helper.rb: def project_list_item(project) content_tag(:li, :class => 'project') do link_to project_path(project), project.name end end

Los Controladores

Paso 4. Muévelo a los controladores

Tal vez su horrible código no sea solo una preocupación de View. Si su código todavía huele mal, busque consultas que pueda hacer para pasar de las Vistas a los Controladores.

 app/views/shared/projects_list.haml: %ul.projects - @projects_list.each do |project| = project_list_item(project) app/controllers/pages_controller.rb: def welcome @projects = Project.where(active: true) end

Paso 5. Filtros del controlador

La razón más obvia para mover el código a un controlador before_filter o after_filter es el código que ha duplicado en varias acciones del controlador. También puede mover el código a un filtro de controlador si desea separar el propósito de la acción del controlador de los requisitos de sus vistas.

 app/controllers/pages_controller.rb: before_filter :projects_list def welcome end def projects_list @projects = Project.where(active:true) end

Paso 6. Controlador de aplicaciones

Suponga que necesita que su código aparezca en todas las páginas, o si desea que las funciones auxiliares del controlador estén disponibles para todos los controladores, puede mover su función al ApplicationController. Si los cambios son globales, es posible que también desee modificar el diseño de su aplicación.

 app/controllers/pages_controller.rb: def welcome end app/views/layouts/application.haml: %ul.projects - projects_list.each do |project| = project_list_item(project) app/controllers/application_controller.rb: before_filter :projects_list def projects_list @projects = Project.where(active: true) end

Los modelos

Paso 7. Refactorización al modelo

Como dice el lema de MVC: Fat Model, Skinny Controllers y Dumb Views . Se espera que refactoricemos todo lo que podamos en el Modelo, y es cierto que la funcionalidad más compleja eventualmente se convertirá en asociaciones de modelo y métodos de modelo. Siempre debemos evitar formatear/ver cosas en el modelo, pero se permite transformar datos en otros tipos de datos. En este caso, lo mejor para refactorizar en el Modelo sería la cláusula where(active: true) , que podemos convertir en un alcance. El uso de un alcance es valioso no solo porque hace que la llamada se vea más bonita, sino que si alguna vez decidimos agregar un atributo como delete o outdated , podemos modificar este alcance en lugar de buscar todas nuestras cláusulas where .

 app/controllers/application_controller.rb: before_filter :projects_list def projects_list @projects = Project.active end app/models/project.rb: scope :active, where(active: true)

Paso 8. Filtros modelo

No tenemos un uso particular para los after_save filters before_save o after_save de un modelo en este caso, pero el siguiente paso que suelo dar es mover la funcionalidad de los controladores y los métodos del modelo a los filtros del modelo.

Digamos que tenemos otro atributo, num_views . Si num_views > 50 , el proyecto se vuelve inactivo. Podríamos resolver este problema en la Vista, pero hacer cambios en la base de datos en una Vista no es apropiado. Podríamos resolverlo en el controlador, ¡pero nuestros controladores deberían ser lo más delgados posible! Podemos resolverlo fácilmente en el Modelo.

 app/models/project.rb: before_save :deactivate_if_over_num_views def deactivate_if_over_num_views if num_views > 50 self.active = false fi end

Nota: debe evitar llamar a self.save en un filtro de modelo, ya que esto provoca eventos de guardado recursivos, y la capa de manipulación de la base de datos de su aplicación debería ser el controlador de todos modos.

Paso 9. Bibliotecas

Ocasionalmente, su función es lo suficientemente grande como para justificar su propia biblioteca. Es posible que desee moverlo a un archivo de biblioteca porque se reutiliza en muchos lugares, o es lo suficientemente grande como para desarrollarlo por separado.

Está bien almacenar archivos de biblioteca en el directorio lib/ , pero a medida que crecen, ¡puedes transferirlos a un RubyGem real! Una gran ventaja de mover su código a una biblioteca es que puede probar la biblioteca por separado de su modelo.

De todos modos, en el caso de una Lista de Proyectos, podríamos justificar mover la llamada scope :active del modelo de Project a un archivo de biblioteca, y devolverlo a Ruby:

 app/models/project.rb: class Project < ActiveRecord::Base include Activeable before_filter :deactivate_if_over_num_views end lib/activeable.rb: module Activeable def self.included(k) k.scope :active, k.where(active: true) end def deactivate_if_over_num_views if num_views > 50 self.active = false end end end

Nota: se llama al método self.included cuando se carga una clase de Rails Model y pasa el alcance de la clase como la variable k .

Conclusión

En este tutorial de refactorización de Ruby on Rails, tomamos menos de 15 minutos e implementamos una solución y la pusimos en preparación para la prueba del usuario, lista para ser aceptada en el conjunto de funciones o eliminada. Al final del proceso de refactorización, tenemos un fragmento de código que establece un marco para implementar elementos que se pueden enumerar y activar en varios modelos que pasarían incluso el proceso de revisión más estricto.

En su propio proceso de refactorización de Rails, siéntase libre de omitir algunos pasos de la canalización si está seguro de hacerlo (por ejemplo, salte de Vista a Controlador o de Controlador a Modelo). Solo tenga en cuenta que el flujo de código es de Vista a Modelo.

No tengas miedo de parecer estúpido. Lo que separa a los lenguajes modernos de las antiguas aplicaciones de representación de plantillas CGI no es que hagamos todo de la manera correcta cada vez, es que nos tomamos el tiempo para refactorizar, reutilizar y compartir nuestros esfuerzos.

Relacionado: Truncamiento de marca de tiempo: un cuento de ActiveRecord de Ruby on Rails