Buggy Rails Code: los 10 errores más comunes que cometen los desarrolladores de Rails

Publicado: 2022-03-11

Ruby on Rails ("Rails") es un marco de código abierto popular, basado en el lenguaje de programación Ruby que se esfuerza por simplificar y agilizar el proceso de desarrollo de aplicaciones web.

Rails se basa en el principio de la convención sobre la configuración. En pocas palabras, esto significa que, de manera predeterminada, Rails asume que sus desarrolladores expertos seguirán las convenciones de mejores prácticas "estándar" (para cosas como nombres, estructura de código, etc.) y, si lo hace, las cosas funcionarán para usted "automáticamente". -mágicamente” sin necesidad de especificar estos detalles. Si bien este paradigma tiene sus ventajas, también tiene sus inconvenientes. En particular, la "magia" que sucede detrás de escena en el marco a veces puede conducir a falsificaciones, confusión y "¿qué diablos está pasando?" tipos de problemas También puede tener ramificaciones indeseables con respecto a la seguridad y el rendimiento.

En consecuencia, si bien Rails es fácil de usar, tampoco es difícil hacer un mal uso. Este tutorial analiza 10 problemas comunes de Rails, incluido cómo evitarlos y los problemas que causan.

Error común n.º 1: poner demasiada lógica en el controlador

Rails se basa en una arquitectura MVC. En la comunidad de Rails, hemos estado hablando de modelo gordo, controlador delgado por un tiempo, sin embargo, varias aplicaciones Rails recientes que he heredado violaron este principio. Es demasiado fácil mover la lógica de vista (que se aloja mejor en un asistente), o la lógica de dominio/modelo, al controlador.

El problema es que el objeto del controlador comenzará a violar el principio de responsabilidad única, lo que hará que los cambios futuros en el código base sean difíciles y propensos a errores. Generalmente, los únicos tipos de lógica que debe tener en su controlador son:

  • Manejo de sesiones y cookies. Esto también podría incluir autenticación/autorización o cualquier procesamiento de cookies adicional que necesite hacer.
  • Selección de modelo. Lógica para encontrar el objeto de modelo correcto dados los parámetros pasados ​​desde la solicitud. Idealmente, esto debería ser una llamada a un único método de búsqueda que establezca una variable de instancia que se usará más adelante para representar la respuesta.
  • Solicitud de gestión de parámetros. Recopilación de parámetros de solicitud y llamada a un método de modelo apropiado para conservarlos.
  • Representación/redireccionamiento. Renderizando el resultado (html, xml, json, etc.) o redireccionando, según corresponda.

Si bien esto aún supera los límites del principio de responsabilidad única, es algo así como lo mínimo que el marco de Rails requiere que tengamos en el controlador.

Error común n.º 2: poner demasiada lógica en la vista

El motor de plantillas de Rails listo para usar, ERB, es una excelente manera de crear páginas con contenido variable. Sin embargo, si no tiene cuidado, pronto puede terminar con un archivo grande que es una mezcla de código HTML y Ruby que puede ser difícil de administrar y mantener. Esta es también un área que puede dar lugar a muchas repeticiones, lo que lleva a violaciones de los principios DRY (no te repitas).

Esto puede manifestarse de varias maneras. Uno es el uso excesivo de la lógica condicional en las vistas. Como ejemplo simple, considere un caso en el que tenemos un método current_user disponible que devuelve el usuario conectado actualmente. A menudo, habrá estructuras lógicas condicionales como esta en los archivos de vista:

 <h3> Welcome, <% if current_user %> <%= current_user.name %> <% else %> Guest <% end %> </h3>

Una mejor manera de manejar algo como esto es asegurarse de que el objeto devuelto por current_user siempre esté configurado, ya sea que alguien haya iniciado sesión o no, y que responda a los métodos utilizados en la vista de una manera razonable (a veces denominado nulo). objeto). Por ejemplo, puede definir el ayudante current_user en app/controllers/application_controller de esta manera:

 require 'ostruct' helper_method :current_user def current_user @current_user ||= User.find session[:user_id] if session[:user_id] if @current_user @current_user else OpenStruct.new(name: 'Guest') end end

Esto le permitiría reemplazar el ejemplo de código de vista anterior con esta línea de código simple:

 <h3>Welcome, <%= current_user.name -%></h3>

Un par de prácticas recomendadas adicionales de Rails:

  • Use diseños de vista y parciales de manera adecuada para encapsular cosas que se repiten en sus páginas.
  • Use presentadores/decoradores como la gema Draper para encapsular la lógica de creación de vistas en un objeto Ruby. A continuación, puede agregar métodos a este objeto para realizar operaciones lógicas que, de otro modo, podría haber incluido en su código de vista.

Error común #3: Poner demasiada lógica en el modelo

Dada la guía para minimizar la lógica en las vistas y los controladores, el único lugar que queda en una arquitectura MVC para poner toda esa lógica sería en los modelos, ¿verdad?

Bueno, no del todo.

Muchos desarrolladores de Rails realmente cometen este error y terminan metiendo todo en sus clases de modelo ActiveRecord , lo que lleva a archivos mongo que no solo violan el principio de responsabilidad única, sino que también son una pesadilla de mantenimiento.

Funcionalidades como la generación de notificaciones por correo electrónico, la interfaz con servicios externos, la conversión a otros formatos de datos y similares no tienen mucho que ver con la responsabilidad central de un modelo ActiveRecord que debería hacer poco más que encontrar y conservar datos en una base de datos.

Entonces, si la lógica no debería ir en las vistas, y no debería ir en los controladores, y no debería ir en los modelos, entonces, ¿dónde debería ir?

Ingrese objetos Ruby simples y antiguos (PORO). Con un marco completo como Rails, los desarrolladores más nuevos suelen ser reacios a crear sus propias clases fuera del marco. Sin embargo, trasladar la lógica del modelo a los PORO suele ser justo lo que recetó el médico para evitar modelos demasiado complejos. Con PORO, puede encapsular cosas como notificaciones de correo electrónico o interacciones de API en sus propias clases en lugar de incluirlas en un modelo ActiveRecord .

Entonces, con eso en mente, en términos generales, la única lógica que debe permanecer en su modelo es:

  • Configuración de ActiveRecord (es decir, relaciones y validaciones)
  • Métodos de mutación simples para encapsular la actualización de un puñado de atributos y guardarlos en la base de datos
  • Acceda a contenedores para ocultar la información del modelo interno (por ejemplo, un método de full_name que combina los first_name de nombre y last_name en la base de datos)
  • Consultas sofisticadas (es decir, que son más complejas que una simple find ); en términos generales, nunca debe usar el método where , ni ningún otro método de creación de consultas similar, fuera de la clase del modelo en sí.
Relacionado: 8 preguntas esenciales de la entrevista de Ruby on Rails

Error común n.º 4: utilizar clases auxiliares genéricas como vertedero

Este error es realmente una especie de corolario del error #3 anterior. Como se mencionó, el marco Rails pone énfasis en los componentes nombrados (es decir, modelo, vista y controlador) de un marco MVC. Hay definiciones bastante buenas de los tipos de cosas que pertenecen a las clases de cada uno de estos componentes, pero a veces podemos necesitar métodos que no parecen encajar en ninguno de los tres.

Los generadores de Rails construyen convenientemente un directorio de ayuda y una nueva clase de ayuda para acompañar cada nuevo recurso que creamos. Sin embargo, se vuelve demasiado tentador comenzar a incluir cualquier funcionalidad que no encaje formalmente en el modelo, la vista o el controlador en estas clases auxiliares.

Si bien Rails ciertamente está centrado en MVC, nada le impide crear sus propios tipos de clases y agregar directorios apropiados para contener el código de esas clases. Cuando tenga funcionalidad adicional, piense en qué métodos se agrupan y encuentre buenos nombres para las clases que contienen esos métodos. El uso de un marco completo como Rails no es una excusa para dejar de lado las buenas prácticas de diseño orientado a objetos.

Error común #5: Usar demasiadas gemas

Ruby y Rails cuentan con el respaldo de un rico ecosistema de gemas que, en conjunto, brindan casi cualquier capacidad que se le ocurra a un desarrollador. Esto es excelente para desarrollar rápidamente una aplicación compleja, pero también he visto muchas aplicaciones infladas en las que la cantidad de gemas en el Gemfile de la aplicación es desproporcionadamente grande en comparación con la funcionalidad provista.

Esto causa varios problemas de Rails. El uso excesivo de gemas hace que el tamaño de un proceso de Rails sea mayor de lo necesario. Esto puede ralentizar el rendimiento en producción. Además de la frustración del usuario, esto también puede generar la necesidad de configuraciones de memoria de servidor más grandes y mayores costos operativos. También lleva más tiempo iniciar aplicaciones Rails más grandes, lo que hace que el desarrollo sea más lento y que las pruebas automatizadas tomen más tiempo (y, por regla general, las pruebas lentas simplemente no se ejecutan con tanta frecuencia).

Tenga en cuenta que cada gema que traiga a su aplicación puede, a su vez, tener dependencias con otras gemas, y éstas a su vez pueden tener dependencias con otras gemas, y así sucesivamente. Agregar otras gemas puede tener un efecto combinado. Por ejemplo, agregar la gema rails_admin generará 11 gemas más en total, un aumento de más del 10 % con respecto a la instalación básica de Rails.

En el momento de escribir este artículo, una nueva instalación de Rails 4.1.0 incluye 43 gemas en el archivo Gemfile.lock . Obviamente, esto es más de lo que se incluye en Gemfile y representa todas las gemas que el puñado de gemas estándar de Rails trae como dependencias.

Considere cuidadosamente si los gastos generales adicionales valen la pena a medida que agrega cada gema. Como ejemplo, los desarrolladores a menudo agregan casualmente la gema rails_admin porque esencialmente proporciona una buena interfaz web para la estructura del modelo, pero en realidad no es mucho más que una elegante herramienta de exploración de bases de datos. Incluso si su aplicación requiere usuarios administradores con privilegios adicionales, probablemente no quiera darles acceso a la base de datos sin formato y sería mejor que desarrollara su propia función de administración más simplificada que agregar esta gema.

Error común #6: Ignorar sus archivos de registro

Si bien la mayoría de los desarrolladores de Rails conocen los archivos de registro predeterminados disponibles durante el desarrollo y la producción, a menudo no prestan suficiente atención a la información de esos archivos. Si bien muchas aplicaciones dependen de herramientas de monitoreo de registro como Honeybadger o New Relic en producción, también es importante vigilar sus archivos de registro durante todo el proceso de desarrollo y prueba de su aplicación.

Como se mencionó anteriormente en este tutorial, el marco de Rails hace mucha "magia" por usted, especialmente en los modelos. Definir asociaciones en sus modelos hace que sea muy fácil obtener relaciones y tener todo disponible para sus vistas. Todo el SQL necesario para completar los objetos de su modelo se genera para usted. Eso es genial. Pero, ¿cómo sabe que el SQL que se genera es eficiente?

Un ejemplo con el que se encontrará a menudo se denomina problema de consulta N+1. Si bien el problema se comprende bien, la única forma real de observarlo es revisar las consultas SQL en sus archivos de registro.

Digamos, por ejemplo, que tiene la siguiente consulta en una aplicación de blog típica donde mostrará todos los comentarios para un conjunto seleccionado de publicaciones:

 def comments_for_top_three_posts posts = Post.limit(3) posts.flat_map do |post| post.comments.to_a end end

Cuando miramos el archivo de registro de una solicitud que llama a este método, veremos algo como lo siguiente, donde se realiza una sola consulta para obtener los tres objetos de publicación y luego se realizan tres consultas más para obtener cada uno de los comentarios de esos objetos:

 Started GET "/posts/some_comments" for 127.0.0.1 at 2014-05-20 20:05:13 -0700 Processing by PostsController#some_comments as HTML Post Load (0.4ms) SELECT "posts".* FROM "posts" LIMIT 3 Comment Load (5.6ms) ELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ? [["post_id", 1]] Comment Load (0.4ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ? [["post_id", 2]] Comment Load (1.5ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ? [["post_id", 3]] Rendered posts/some_comments.html.erb within layouts/application (12.5ms) Completed 200 OK in 581ms (Views: 225.8ms | ActiveRecord: 10.0ms)

La capacidad de carga rápida de ActiveRecord en Rails hace posible reducir significativamente la cantidad de consultas al permitirle especificar de antemano todas las asociaciones que se van a cargar. Esto se hace llamando al método includes (o preload ) en el objeto Arel ( ActiveRecord::Relation ) que se está construyendo. Con includes , ActiveRecord garantiza que todas las asociaciones especificadas se carguen utilizando el mínimo número posible de consultas; p.ej:

 def comments_for_top_three_posts posts = Post.includes(:comments).limit(3) posts.flat_map do |post| post.comments.to_a end end

Cuando se ejecuta el código revisado anterior, vemos en el archivo de registro que todos los comentarios se recopilaron en una sola consulta en lugar de tres:

 Started GET "/posts/some_comments" for 127.0.0.1 at 2014-05-20 20:05:18 -0700 Processing by PostsController#some_comments as HTML Post Load (0.5ms) SELECT "posts".* FROM "posts" LIMIT 3 Comment Load (4.4ms) SELECT "comments".* FROM "comments" WHERE"comments "."post_id" IN (1, 2, 3) Rendered posts/some_comments.html.erb within layouts/application (12.2ms) Completed 200 OK in 560ms (Views: 219.3ms | ActiveRecord: 5.0ms)

Mucho más eficiente.

Esta solución al problema N+1 en realidad solo pretende ser un ejemplo del tipo de ineficiencias que pueden existir "debajo del capó" en su aplicación si no está prestando la atención adecuada. La conclusión aquí es que debe verificar sus archivos de registro de desarrollo y prueba durante el desarrollo para verificar (¡y abordar!) las ineficiencias en el código que genera sus respuestas.

Revisar los archivos de registro es una excelente manera de recibir alertas sobre las ineficiencias en su código y corregirlas antes de que su aplicación entre en producción. De lo contrario, es posible que no se dé cuenta de un problema de rendimiento de Rails resultante hasta que su sistema entre en funcionamiento, ya que es probable que el conjunto de datos con el que trabaja en desarrollo y prueba sea mucho más pequeño que en producción. Si está trabajando en una nueva aplicación, incluso su conjunto de datos de producción puede comenzar pequeño y su aplicación parecerá que funciona bien. Sin embargo, a medida que crece su conjunto de datos de producción, los problemas de Rails como este harán que su aplicación se ejecute cada vez más lentamente.

Si encuentra que sus archivos de registro están obstruidos con un montón de información que no necesita, aquí hay algunas cosas que puede hacer para limpiarlos (las técnicas allí funcionan tanto para el desarrollo como para los registros de producción).

Error común #7: Falta de pruebas automatizadas

Ruby y Rails brindan poderosas capacidades de prueba automatizadas de forma predeterminada. Muchos desarrolladores de Rails escriben pruebas muy sofisticadas utilizando estilos TDD y BDD y utilizan marcos de prueba aún más potentes con gemas como rspec y cucumber.

Sin embargo, a pesar de lo fácil que es agregar pruebas automatizadas a su aplicación Rails, me ha sorprendido muy desagradablemente la cantidad de proyectos que heredé o me uní donde literalmente no había pruebas escritas (o, en el mejor de los casos, muy pocas) por el anterior Equipo de desarrollo. Si bien hay mucho debate sobre cuán completas deben ser sus pruebas, está bastante claro que al menos deberían existir algunas pruebas automatizadas para cada aplicación.

Como regla general, debe haber al menos una prueba de integración de alto nivel escrita para cada acción en sus controladores. En algún momento en el futuro, es muy probable que otros desarrolladores de Rails deseen ampliar o modificar el código, o actualizar una versión de Ruby o Rails, y este marco de prueba les proporcionará una forma clara de verificar que la funcionalidad básica de la aplicación es trabajando. Un beneficio adicional de este enfoque es que brinda a los futuros desarrolladores una descripción clara de la colección completa de funciones proporcionadas por la aplicación.

Error común #8: Bloqueo de llamadas a servicios externos

Los proveedores externos de servicios de Rails suelen facilitar la integración de sus servicios en su aplicación a través de gemas que envuelven sus API. Pero, ¿qué sucede si su servicio externo se interrumpe o comienza a funcionar muy lentamente?

Para evitar el bloqueo de estas llamadas, en lugar de llamar a estos servicios directamente en su aplicación Rails durante el procesamiento normal de una solicitud, debe moverlos a algún tipo de servicio de cola de trabajos en segundo plano cuando sea posible. Algunas gemas populares utilizadas en las aplicaciones de Rails para este propósito incluyen:

  • trabajo retrasado
  • Resque
  • Sidekiq

En los casos en los que no sea práctico o factible delegar el procesamiento a una cola de trabajo en segundo plano, deberá asegurarse de que su aplicación tenga suficientes disposiciones de manejo de errores y conmutación por error para esas situaciones inevitables cuando el servicio externo se cae o tiene problemas. . También debe probar su aplicación sin el servicio externo (quizás eliminando el servidor en el que se encuentra su aplicación de la red) para verificar que no tenga consecuencias imprevistas.

Error común n.º 9: casarse con migraciones de bases de datos existentes

El mecanismo de migración de bases de datos de Rails le permite crear instrucciones para agregar y eliminar automáticamente tablas y filas de bases de datos. Dado que los archivos que contienen estas migraciones se nombran de forma secuencial, puede reproducirlos desde el principio de los tiempos para llevar una base de datos vacía al mismo esquema que la producción. Por lo tanto, esta es una excelente manera de administrar cambios granulares en el esquema de la base de datos de su aplicación y evitar problemas con Rails.

Si bien esto ciertamente funciona bien al comienzo de su proyecto, a medida que pasa el tiempo, el proceso de creación de la base de datos puede llevar bastante tiempo y, a veces, las migraciones se extravían, se insertan fuera de servicio o se introducen desde otras aplicaciones Rails que utilizan el mismo servidor de base de datos.

Rails crea una representación de su esquema actual en un archivo llamado db/schema.rb (de manera predeterminada) que generalmente se actualiza cuando se ejecutan las migraciones de la base de datos. El archivo schema.rb puede incluso generarse cuando no hay migraciones presentes ejecutando la tarea rake db:schema:dump . Un error común de Rails es verificar una nueva migración en su repositorio de origen, pero no en el archivo schema.rb actualizado correspondiente.

Cuando las migraciones se han salido de control y tardan demasiado en ejecutarse, o ya no crean la base de datos correctamente, los desarrolladores no deben tener miedo de borrar el directorio de migraciones anterior, volcar un nuevo esquema y continuar desde allí. La configuración de un nuevo entorno de desarrollo requeriría un rake db:schema:load en lugar del rake db:migrate en el que confían la mayoría de los desarrolladores.

Algunos de estos problemas también se tratan en la Guía de Rails.

Error común n.° 10: verificar información confidencial en repositorios de código fuente

El marco Rails facilita la creación de aplicaciones seguras inmunes a muchos tipos de ataques. Parte de esto se logra mediante el uso de un token secreto para asegurar una sesión con un navegador. Aunque este token ahora se almacena en config/secrets.yml y ese archivo lee el token de una variable de entorno para servidores de producción, las versiones anteriores de Rails incluían el token en config/initializers/secret_token.rb . Este archivo a menudo se registra por error en el repositorio de código fuente con el resto de su aplicación y, cuando esto sucede, cualquier persona con acceso al repositorio ahora puede comprometer fácilmente a todos los usuarios de su aplicación .

Por lo tanto, debe asegurarse de que su archivo de configuración del repositorio (p. ej., .gitignore para usuarios de git) excluya el archivo con su token. Luego, sus servidores de producción pueden recoger su token de una variable de entorno o de un mecanismo como el que proporciona la gema dotenv.

Resumen del tutorial

Rails es un marco poderoso que oculta muchos de los feos detalles necesarios para construir una aplicación web robusta. Si bien esto hace que el desarrollo de aplicaciones web de Rails sea mucho más rápido, los desarrolladores deben prestar atención a los posibles errores de diseño y codificación, para asegurarse de que sus aplicaciones sean fácilmente extensibles y fáciles de mantener a medida que crecen.

Los desarrolladores también deben ser conscientes de los problemas que pueden hacer que sus aplicaciones sean más lentas, menos confiables y menos seguras. Es importante estudiar el marco y asegurarse de que comprende completamente las ventajas y desventajas de la arquitectura, el diseño y la codificación que está realizando a lo largo del proceso de desarrollo, para ayudar a garantizar una aplicación de alta calidad y alto rendimiento.

Relacionado: ¿Cuáles son los beneficios de Ruby on Rails? Después de dos décadas de programación, uso Rails