Ruby on Rails
Organizacion de la clase
Buscar..
Observaciones
Esto parece algo simple de hacer, pero cuando tus clases comienzan a aumentar de tamaño, estarás agradecido de que te hayas tomado el tiempo de organizarlas.
Clase de modelo
class Post < ActiveRecord::Base
belongs_to :user
has_many :comments
validates :user, presence: true
validates :title, presence: true, length: { in: 6..40 }
scope :topic, -> (topic) { joins(:topics).where(topic: topic) }
before_save :update_slug
after_create :send_welcome_email
def publish!
update(published_at: Time.now, published: true)
end
def self.find_by_slug(slug)
find_by(slug: slug)
end
private
def update_slug
self.slug = title.join('-')
end
def send_welcome_email
WelcomeMailer.welcome(self).deliver_now
end
end
Los modelos suelen ser responsables de:
- establecer relaciones
- validando datos
- Proporcionar acceso a datos a través de ámbitos y métodos.
- Realización de acciones en torno a la persistencia de datos.
En el nivel más alto, los modelos describen conceptos de dominio y gestionan su persistencia.
Clase de servicio
El controlador es un punto de entrada a nuestra aplicación. Sin embargo, no es el único punto de entrada posible. Me gustaría tener mi lógica accesible desde:
- Tareas de rastrillo
- trabajos de fondo
- consola
- pruebas
Si lanzo mi lógica en un controlador, no será accesible desde todos estos lugares. Así que probemos el enfoque de "controlador delgado, modelo gordo" y pasemos la lógica a un modelo. ¿Pero cual? Si una determinada pieza de lógica involucra modelos de User
, Cart
y Product
, ¿dónde debería vivir?
Una clase que hereda de ActiveRecord::Base
ya tiene muchas responsabilidades. Maneja interfaz de consulta, asociaciones y validaciones. Si agrega aún más código a su modelo, se convertirá rápidamente en un desorden inigualable con cientos de métodos públicos.
Un servicio es solo un objeto regular de Ruby. Su clase no tiene que heredar de ninguna clase específica. Su nombre es una frase verbal, por ejemplo, CreateUserAccount
lugar de UserCreation
o UserCreationService
. Vive en el directorio de aplicaciones / servicios. Tienes que crear este directorio por ti mismo, pero Rails cargará automáticamente las clases dentro de ti.
Un objeto de servicio hace una cosa.
Un objeto de servicio (también conocido como objeto de método) realiza una acción. Mantiene la lógica empresarial para realizar esa acción. Aquí hay un ejemplo:
# app/services/accept_invite.rb
class AcceptInvite
def self.call(invite, user)
invite.accept!(user)
UserMailer.invite_accepted(invite).deliver
end
end
Las tres convenciones que sigo son:
Los servicios van bajo el app/services directory
. Le animo a usar subdirectorios para los dominios pesados de la lógica de negocios. Por ejemplo:
- El archivo
app/services/invite/accept.rb
definiráInvite::Accept
mientras queapp/services/invite/create.rb
definiráInvite::Create
- Los servicios comienzan con un verbo (y no terminan con el servicio):
ApproveTransaction
,SendTestNewsletter
,ImportUsersFromCsv
- Los servicios responden al método de
call
. Descubrí que usar otro verbo lo hace un poco redundante:ApproveTransaction.approve()
no lee bien. Además, el método decall
es el método de facto paralambda
,procs
y métodos de objetos.
Beneficios
Los objetos de servicio muestran lo que hace mi aplicación
Solo puedo ver el directorio de servicios para ver qué hace mi aplicación: ApproveTransaction
, CancelTransaction
, BlockAccount
, SendTransactionApprovalReminder
...
Una mirada rápida a un objeto de servicio y sé en qué consiste la lógica de negocios. No tengo que pasar por los controladores, las devoluciones de llamada del modelo ActiveRecord
y los observadores para comprender lo que implica "aprobar una transacción".
Modelos de limpieza y controladores.
Los controladores convierten la solicitud (parámetros, sesión, cookies) en argumentos, los transmiten al servicio y los redirigen o representan según la respuesta del servicio.
class InviteController < ApplicationController
def accept
invite = Invite.find_by_token!(params[:token])
if AcceptInvite.call(invite, current_user)
redirect_to invite.item, notice: "Welcome!"
else
redirect_to '/', alert: "Oopsy!"
end
end
end
Los modelos solo tratan con asociaciones, ámbitos, validaciones y persistencia.
class Invite < ActiveRecord::Base
def accept!(user, time=Time.now)
update_attributes!(
accepted_by_user_id: user.id,
accepted_at: time
)
end
end
¡Esto hace que los modelos y los controladores sean mucho más fáciles de probar y mantener!
Cuándo usar la clase de servicio
Alcance los objetos de servicio cuando una acción cumpla con uno o más de estos criterios:
- La acción es compleja (p. Ej., Cerrar los libros al final de un período contable)
- La acción se extiende a través de múltiples modelos (por ejemplo, una compra de comercio electrónico utilizando los objetos Order, CreditCard y Customer)
- La acción interactúa con un servicio externo (p. Ej., Publicación en redes sociales)
- La acción no es una preocupación central del modelo subyacente (por ejemplo, barriendo los datos desactualizados después de un cierto período de tiempo).
- Hay varias formas de realizar la acción (por ejemplo, autenticarse con un token de acceso o contraseña).
Fuentes