OO Design in Rails: dónde poner cosas

Realmente disfruto de Rails (aunque generalmente soy RESTless), y me gusta que Ruby sea muy OO. Aún así, la tendencia a crear grandes subclases de ActiveRecord y enormes controladores es bastante natural (incluso si usa un controlador por recurso). Si crearas mundos de objetos más profundos, ¿dónde colocarías las clases (y los módulos, supongo)? Estoy preguntando sobre los puntos de vista (¿en Helper ellos mismos?), Controladores y modelos.

Lib está bien, y he encontrado algunas soluciones para que vuelva a cargar en un entorno de desarrollo , pero me gustaría saber si hay una mejor manera de hacer esto. Realmente me preocupan las clases que crecen demasiado. Además, ¿qué pasa con los motores y cómo encajan?

Debido a que Rails proporciona estructura en términos de MVC, es natural terminar usando solo los contenedores de modelo, vista y controlador que se le proporcionan. La expresión típica para principiantes (e incluso algunos progtwigdores intermedios) es meter toda la lógica de la aplicación en el modelo (clase de base de datos), controlador o vista.

En algún momento, alguien señala el paradigma del “modelo de grasa, controlador delgado”, y los desarrolladores intermedios eliminan todo precipitadamente de sus controladores y lo lanzan al modelo, que comienza a convertirse en un nuevo bote de basura para la lógica de la aplicación.

Los controladores delgados son, de hecho, una buena idea, pero el corolario, poner todo en el modelo, no es realmente el mejor plan.

En Ruby, tienes un par de buenas opciones para hacer las cosas más modulares. Una respuesta bastante popular es simplemente usar módulos (generalmente escondidos en lib ) que contienen grupos de métodos, y luego incluir los módulos en las clases apropiadas. Esto ayuda en los casos en que tiene categorías de funcionalidad que desea reutilizar en múltiples clases, pero donde la funcionalidad todavía está vinculada a las clases.

Recuerda que cuando incluyes un módulo en una clase, los métodos se convierten en métodos de instancia de la clase, por lo que aún terminas con una clase que contiene una tonelada de métodos, simplemente están organizados muy bien en varios archivos.

Esta solución puede funcionar bien en algunos casos; en otros casos, querrá pensar en utilizar clases en su código que no sean modelos, vistas o controladores.

Una buena manera de pensar sobre esto es el “principio de responsabilidad única”, que dice que una clase debe ser responsable de un único (o pequeño número) de cosas. Sus modelos son responsables de la persistencia de los datos de su aplicación en la base de datos. Sus controladores son responsables de recibir una solicitud y devolver una respuesta viable.

Si tiene conceptos que no encajan perfectamente en esas casillas (persistencia, gestión de solicitudes / respuestas), probablemente quiera pensar cómo modelaría la idea en cuestión. Puede almacenar clases que no sean modelos en aplicaciones / clases, o en cualquier otro lugar, y agregar ese directorio a su ruta de carga haciendo:

 config.load_paths < < File.join(Rails.root, "app", "classes") 

Si está utilizando pasajero o JRuby, probablemente también quiera agregar su ruta a las rutas de carga ansiosas:

 config.eager_load_paths < < File.join(Rails.root, "app", "classes") 

El resultado final es que una vez que llegas a un punto en Rails donde te encuentras haciendo esta pregunta, es hora de reforzar tus chuletas de Ruby y comenzar a modelar clases que no son solo las clases de MVC que Rails te brinda por defecto.

Actualización: esta respuesta se aplica a Rails 2.xy superior.

Actualización : el uso de Preocupaciones ha sido confirmado como el nuevo predeterminado en Rails 4 .

Realmente depende de la naturaleza del módulo en sí. Normalmente ubico las extensiones de controlador / modelo en una carpeta / concerns dentro de la aplicación.

 # concerns/authentication.rb module Authentication ... end # controllers/application_controller.rb class ApplicationController include Authentication end # concerns/configurable.rb module Configurable ... end class Model include Indexable end # controllers/foo_controller.rb class FooController < ApplicationController include Indexable end # controllers/bar_controller.rb class BarController < ApplicationController include Indexable end 

/ lib es mi elección preferida para bibliotecas de propósito general. Siempre tengo un espacio de nombres de proyecto en lib donde coloco todas las bibliotecas específicas de la aplicación.

 /lib/myapp.rb module MyApp VERSION = ... end /lib/myapp/CacheKey.rb /lib/myapp/somecustomlib.rb 

Las extensiones de Ruby / Rails core generalmente tienen lugar en los inicializadores de configuración, de modo que las bibliotecas solo se cargan una vez en Rails boostrap.

 /config/initializer/config.rb /config/initializer/core_ext/string.rb /config/initializer/core_ext/array.rb 

Para los fragmentos de código reutilizables, a menudo creo (micro) complementos para poder reutilizarlos en otros proyectos.

Los archivos auxiliares suelen contener métodos auxiliares y, a veces, clases cuando el objeto está destinado a ser utilizado por ayudantes (por ejemplo, creadores de formularios).

Esta es una descripción realmente general. Proporcione más detalles sobre ejemplos específicos si desea obtener sugerencias más personalizadas. 🙂

… la tendencia a hacer grandes subclases de ActiveRecord y enormes controladores es bastante natural …

“enorme” es una palabra preocupante … 😉

¿Cómo se vuelven enormes tus controladores? Eso es algo que debes tener en cuenta: idealmente, los controladores deberían ser delgados. Escogiendo una regla empírica de la nada, sugiero que si regularmente tienes más de, digamos, 5 o 6 líneas de código por método de controlador (acción), entonces tus controladores probablemente sean demasiado gordos. ¿Hay una duplicación que podría pasar a una función auxiliar o un filtro? ¿Hay lógica de negocios que pueda ser empujada hacia abajo en los modelos?

¿Cómo tus modelos llegan a ser enormes? ¿Debería buscar maneras de reducir el número de responsabilidades en cada clase? ¿Hay algún comportamiento común que puedas extraer en mixins? ¿O áreas de funcionalidad que puedes delegar en clases de ayuda?

EDITAR: Intenta expandir un poco, con suerte no distorsionar nada demasiado mal …

Ayudantes: viven en app/helpers y se utilizan principalmente para simplificar las vistas. Son específicos del controlador (también están disponibles para todas las vistas para ese controlador) o generalmente disponibles ( module ApplicationHelper en application_helper.rb).

Filtros: supongamos que tiene la misma línea de código en varias acciones (muy a menudo, la recuperación de un objeto usando params[:id] o similar). Esa duplicación se puede abstraer primero a un método separado y luego fuera de las acciones por completo al declarar un filtro en la definición de la clase, como before_filter :get_object . Consulte la Sección 6 en la Guía de Railes de ActionController. Permita que la progtwigción declarativa sea su amiga.

Refactorear modelos es un poco más de una cosa religiosa. Los discípulos de Tío Bob sugerirán, por ejemplo, que sigas los Cinco Mandamientos de SOLID . Joel y Jeff pueden recomendar un enfoque más, eh, “pragmático”, aunque parecían estar un poco más reconciliados posteriormente. Encontrar uno o más métodos dentro de una clase que operan en un subconjunto claramente definido de sus atributos es una manera de tratar de identificar las clases que pueden refactorizarse a partir de su modelo derivado de ActiveRecord.

Por cierto, los modelos de Rails no tienen que ser subclases de ActiveRecord :: Base. O para decirlo de otra manera, un modelo no tiene que ser un análogo de una tabla, o incluso relacionado con nada almacenado. Aún mejor, siempre y cuando nombre su archivo en la app/models acuerdo con las convenciones de Rails (llame a #nderscore en el nombre de la clase para averiguar qué buscará Rails), Rails lo encontrará sin necesidad de ningún require .

Aquí hay una excelente publicación en el blog sobre cómo refactorizar los modelos gordos que parecen surgir de la filosofía del “controlador delgado”:

http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/

El mensaje básico es “No extraigas Mixins de Fat Models”, utiliza clases de servicio en su lugar, el autor proporciona 7 patrones para hacerlo