2009-07-01 12 views
234

Realmente estoy disfrutando 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.OO Design in Rails: Dónde poner cosas

Lib está bien, y he encontrado some solutions to get it to reload in a dev environment, 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?

Respuesta

371

Como Rails proporciona estructura en términos de MVC, es natural terminar usando solo el modelo, los contenedores de modelo, vista y controlador que se le proporcionan. La expresión típica para principiantes (e incluso algunos programadores 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 gordo, controlador delgado", y los desarrolladores intermedios eliminan rápidamente todo de sus controladores y lo lanzan al modelo, que comienza a convertirse en un nuevo bote de basura para lógica de 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, tiene 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.

Recuerde, cuando incluye un módulo en una clase, los métodos se convierten en métodos de instancia de la clase, por lo que aún termina con una clase que contiene ton de métodos, simplemente están organizados en múltiples archivos.

Esta solución puede funcionar bien en algunos casos; en otros casos, querrá pensar en usar 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 esos cuadros (persistencia, gestión de solicitud/respuesta), probablemente desee pensar en cómo sería modelar la idea en cuestión. Puede almacenar clases no modelo en app/clases, o en cualquier otro lugar, y añadir ese directorio a la ruta de carga haciendo:

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

Si está utilizando pasajero o JRuby, es probable que también desea agregar su camino a las rutas de carga ansiosos:

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

La línea de fondo es que una vez que se llega a un punto en Rails donde usted se encuentra haciendo esta pregunta, es el momento de reforzar sus chuletas de Ruby y comenzar las clases de modelado que aren No son solo las clases de MVC que Rails te da por defecto.

Actualización: Esta respuesta se aplica a Rails 2.xo superior.

+0

D'oh. No se me había ocurrido agregar un directorio separado para los que no eran Modelos. Puedo sentir que viene un tidy-up ... –

+0

Yehuda, gracias por eso. Gran respuesta.Eso es exactamente lo que estoy viendo en las aplicaciones que heredé (y las que hago): todo en los controladores, modelos, vistas y los ayudantes proporcionados automáticamente para los controladores y las vistas. Luego vienen los mixins de lib, pero nunca hay un intento de hacer modelos reales de OO. Sin embargo, tienes razón: en "aplicaciones/clases o en cualquier otro lugar". Solo quería comprobar si faltaba alguna respuesta estándar ... –

+33

Con versiones más recientes, config.autoload_paths se establece de manera predeterminada en todos los directorios en la aplicación. Por lo tanto, no necesita cambiar config.load_paths como se describe arriba. No estoy seguro acerca de eager_load_paths (yet) embargo, y necesito investigar eso. ¿Alguien ya lo sabe? –

58

Actualización: El uso de Preocupaciones ha sido confirmed as the new default in 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 

extensiones centrales de Ruby/Rails suelen tener lugar en inicializadores de configuración de modo que las bibliotecas sólo se cargan una vez sobre rieles boostrap.

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

Para los fragmentos de código reutilizable, que a menudo crean (micro) plugins para que pueda reutilizarlos en otros proyectos.

Los archivos auxiliares generalmente contienen métodos de ayuda y, a veces, clases cuando el objeto está destinado a ser utilizado por ayudantes (por ejemplo, constructores de formularios).

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

+0

Bizarre thing. No puedo obtener este require_dependency RAILS_ROOT + "/ lib/my_module" para trabajar con algo fuera del directorio lib. Definitivamente se ejecuta y se queja si el archivo no se encuentra, pero no lo vuelve a cargar. –

+0

Ruby requiere que solo cargue cosas una vez. Si quieres cargar algo incondicionalmente, usa load. – Chuck

+0

Además, me parece bastante inusual que desee cargar un archivo dos veces durante la vida útil de una instancia de una aplicación. ¿Estás generando código sobre la marcha? – Chuck

10

... la tendencia a hacer grandes subclases ActiveRecord y enormes controladores es muy natural ...

"enorme" es una palabra preocupante ... ;-)

¿Cómo se vuelven enormes sus 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 sus 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?

EDIT: Tratando de ampliar un poco, esperemos que no deformar nada demasiado mal ...

Ayudantes: viven en app/helpers y se utilizan sobre todo para hacer más simple vista.Son específicos del controlador (también están disponibles para todas las vistas para ese controlador) o generalmente están 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. Ver la Sección 6 en el ActionController Rails Guide Deja que la programación declarativa sea tu amiga.

La refabricación de modelos es un poco más una cuestión religiosa. Discípulos de Uncle Bob sugerirán, por ejemplo, que sigas los Cinco Mandamientos de SOLID. Joel & Jeff may recommend a más, er, enfoque "pragmático", aunque aparecieron ser un little more reconciled 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.

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

+0

Es cierto en todos los aspectos, Mike, y gracias por su preocupación ... he heredado un proyecto en el que había algunos métodos en los controladores que eran enormes. Los dividí en métodos más pequeños pero el controlador en sí mismo sigue siendo "gordo". Entonces, lo que estoy buscando son todas mis opciones para descargar cosas. Sus respuestas son, "funciones de ayuda", "filtros", "modelos", "mixins" y "clases de ayuda". Entonces, ¿dónde puedo poner estas cosas? ¿Puedo organizar una jerarquía de clases que se carga automáticamente en un env env? –