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 que app/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 de call es el método de facto para lambda , 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

Blog de Adam Niedzielski

Blog de Brew House

Código Clima Blog



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow