Pregunta Cómo usar las preocupaciones en Rails 4


El generador de proyectos predeterminado Rails 4 ahora crea el directorio "preocupaciones" bajo controladores y modelos. He encontrado algunas explicaciones sobre cómo usar las preocupaciones de enrutamiento, pero nada sobre controladores o modelos.

Estoy bastante seguro de que tiene que ver con la actual "tendencia DCI" en la comunidad y me gustaría probarlo.

La pregunta es, ¿cómo se supone que debo usar esta característica ?, ¿hay alguna convención sobre cómo definir la jerarquía de denominación / clase para que funcione? ¿Cómo puedo incluir una preocupación en un modelo o controlador?


578
2018-01-26 21:36


origen


Respuestas:


Así que lo descubrí por mi cuenta. En realidad, es un concepto bastante simple pero poderoso. Tiene que ver con la reutilización del código como en el ejemplo a continuación. Básicamente, la idea es extraer trozos de código comunes y / o específicos del contexto para limpiar los modelos y evitar que se vuelvan demasiado gordos y desordenados.

Como ejemplo, pondré un patrón bien conocido, el patrón etiquetable:

# app/models/product.rb
class Product
  include Taggable

  ...
end

# app/models/concerns/taggable.rb
# notice that the file name has to match the module name 
# (applying Rails conventions for autoloading)
module Taggable
  extend ActiveSupport::Concern

  included do
    has_many :taggings, as: :taggable
    has_many :tags, through: :taggings

    class_attribute :tag_limit
  end

  def tags_string
    tags.map(&:name).join(', ')
  end

  def tags_string=(tag_string)
    tag_names = tag_string.to_s.split(', ')

    tag_names.each do |tag_name|
      tags.build(name: tag_name)
    end
  end

  # methods defined here are going to extend the class, not the instance of it
  module ClassMethods

    def tag_limit(value)
      self.tag_limit_value = value
    end

  end

end

Entonces, siguiendo la muestra del Producto, puede agregar Taggable a cualquier clase que desee y compartir su funcionalidad.

Esto está bastante bien explicado por DHH:

En Rails 4, vamos a invitar a los programadores a utilizar las preocupaciones con el   aplicación / modelos / preocupaciones predeterminadas y directorios de aplicaciones / controladores / inquietudes   que son automáticamente parte de la ruta de carga. Junto con el   ActiveSupport :: Concern wrapper, es suficiente soporte para hacer esto   ligero mecanismo de factorización brillar.


578
2018-02-25 22:50



He estado leyendo sobre el uso preocupaciones modelo para personalizar los modelos gordos y SECAR los códigos de modelo. Aquí hay una explicación con ejemplos:

1) CÓDIGOS modelo de secado

Considere un modelo de artículo, un modelo de evento y un modelo de comentario. Un artículo o un evento tiene muchos comentarios. Un comentario pertenece a Artículo o Evento.

Tradicionalmente, los modelos pueden verse así:

Modelo de comentario:

class Comment < ActiveRecord::Base
  belongs_to :commentable, polymorphic: true
end

Modelo del artículo:

class Article < ActiveRecord::Base
  has_many :comments, as: :commentable 

  def find_first_comment
    comments.first(created_at DESC)
  end

  def self.least_commented
   #return the article with least number of comments
  end
end

Modelo de evento

class Event < ActiveRecord::Base
  has_many :comments, as: :commentable 

  def find_first_comment
    comments.first(created_at DESC)
  end

  def self.least_commented
   #returns the event with least number of comments
  end
end

Como podemos observar, hay un fragmento significativo de código común tanto para el evento como para el artículo. Usando las preocupaciones, podemos extraer este código común en un módulo separado Commentable.

Para esto crea un archivo commentable.rb en la aplicación / modelos / inquietudes.

module Commentable
  extend ActiveSupport::Concern

  included do
    has_many :comments, as: :commentable
  end

  # for the given article/event returns the first comment
  def find_first_comment
    comments.first(created_at DESC)
  end

  module ClassMethods
    def least_commented
      #returns the article/event which has the least number of comments
    end
  end
end

Y ahora tus modelos se ven así:

Modelo de comentario:

class Comment < ActiveRecord::Base
  belongs_to :commentable, polymorphic: true
end

Modelo del artículo:

class Article < ActiveRecord::Base
  include Commentable
end

Modelo de evento:

class Event < ActiveRecord::Base
  include Commentable
end

2) Skin-nizing Fat Models.

Considera un modelo de evento. Un evento tiene muchos asistentes y comentarios.

Por lo general, el modelo de evento podría verse así

class Event < ActiveRecord::Base   
  has_many :comments
  has_many :attenders


  def find_first_comment
    # for the given article/event returns the first comment
  end

  def find_comments_with_word(word)
    # for the given event returns an array of comments which contain the given word
  end 

  def self.least_commented
    # finds the event which has the least number of comments
  end

  def self.most_attended
    # returns the event with most number of attendes
  end

  def has_attendee(attendee_id)
    # returns true if the event has the mentioned attendee
  end
end

Los modelos con muchas asociaciones y, de lo contrario, tienden a acumular más y más códigos y se vuelven inmanejables. Las preocupaciones proporcionan una forma de identificar los módulos de grasa haciéndolos más modularizados y fáciles de entender.

El modelo anterior se puede refactorizar utilizando las siguientes preocupaciones: Crear un attendable.rb y commentable.rb archivo en la aplicación / modelos / inquietudes / carpeta de eventos

attendable.rb

module Attendable
  extend ActiveSupport::Concern

  included do 
    has_many :attenders
  end

  def has_attender(attender_id)
    # returns true if the event has the mentioned attendee
  end

  module ClassMethods
    def most_attended
      # returns the event with most number of attendes
    end
  end
end

commentable.rb

module Commentable
  extend ActiveSupport::Concern

  included do 
    has_many :comments
  end

  def find_first_comment
    # for the given article/event returns the first comment
  end

  def find_comments_with_word(word)
    # for the given event returns an array of comments which contain the given word
  end

  module ClassMethods
    def least_commented
      # finds the event which has the least number of comments
    end
  end
end

Y ahora, usando Concerns, tu modelo de Evento se reduce a

class Event < ActiveRecord::Base
  include Commentable
  include Attendable
end

* Al usar las preocupaciones, es aconsejable ir a la agrupación basada en 'dominio' en lugar de a la 'técnica'. La agrupación basada en dominio es como 'Commentable', 'Photoable', 'Accesible'. La agrupación técnica significará 'ValidationMethods', 'FinderMethods', etc.


349
2017-09-15 22:50



Vale la pena mencionar que el uso de las preocupaciones es considerado una mala idea por muchos.

  1. como este chico
  2. y éste

Algunas razones:

  1. Hay algo de magia oscura detrás de escena - La preocupación es parchear include método, hay un sistema completo de manejo de la dependencia, demasiada complejidad para algo que es trivial y bueno, el patrón de mixin de Ruby.
  2. Tus clases no son menos secas. Si agregas 50 métodos públicos en varios módulos y los incluyes, tu clase todavía tiene 50 métodos públicos, es solo que ocultas ese código y hueles, como que colocas tu basura en los cajones.
  3. Codebase es realmente más difícil de navegar con todas esas preocupaciones.
  4. ¿Estás seguro de que todos los miembros de tu equipo tienen el mismo entendimiento de lo que realmente debería sustituir la preocupación?

Las preocupaciones son una manera fácil de dispararse en la pierna, tenga cuidado con ellas.


86
2018-03-23 14:11



Esta publicación me ayudó a entender las preocupaciones

# app/models/trader.rb
class Trader
  include Shared::Schedule
end

# app/models/concerns/shared/schedule.rb
module Shared::Schedule
  extend ActiveSupport::Concern
  ...
end

55
2018-03-17 19:20



Sentí que la mayoría de los ejemplos aquí demuestran el poder de module en lugar de cómo ActiveSupport::Concern agrega valor a module.

Ejemplo 1: Módulos más legibles

Entonces, sin importar esto, ¿cómo un típico module estarán.

module M
  def self.included(base)
    base.extend ClassMethods
    base.class_eval do
      scope :disabled, -> { where(disabled: true) }
    end
  end

  def instance_method
    ...
  end

  module ClassMethods
    ...
  end
end

Después de refactorizar con ActiveSupport::Concern.

require 'active_support/concern'

module M
  extend ActiveSupport::Concern

  included do
    scope :disabled, -> { where(disabled: true) }
  end

  class_methods do
    ...
  end

  def instance_method
    ...
  end
end

Verá que los métodos de instancia, los métodos de clase y el bloque incluido son menos desordenados. Las preocupaciones se inyectarán apropiadamente para usted. Esa es una ventaja de usar ActiveSupport::Concern.


Ejemplo 2: Maneje las dependencias del módulo con gracia.

module Foo
  def self.included(base)
    base.class_eval do
      def self.method_injected_by_foo_to_host_klass
        ...
      end
    end
  end
end

module Bar
  def self.included(base)
    base.method_injected_by_foo_to_host_klass
  end
end

class Host
  include Foo # We need to include this dependency for Bar
  include Bar # Bar is the module that Host really needs
end

En este ejemplo Bar es el módulo que Host realmente necesita Pero desde Bar tiene dependencia con Foo el Host clase tiene que include Foo (pero espera por qué Host quiero saber sobre Foo ? se puede evitar?).

Asi que Bar agrega dependencia donde quiera que va. Y el orden de inclusión también importa aquí. Esto agrega mucha complejidad / dependencia a una gran base de código.

Después de refactorizar con ActiveSupport::Concern

require 'active_support/concern'

module Foo
  extend ActiveSupport::Concern
  included do
    def self.method_injected_by_foo_to_host_klass
      ...
    end
  end
end

module Bar
  extend ActiveSupport::Concern
  include Foo

  included do
    self.method_injected_by_foo_to_host_klass
  end
end

class Host
  include Bar # It works, now Bar takes care of its dependencies
end

Ahora parece simple.

Si estás pensando por qué no podemos agregar Foo dependencia en Bar módulo en sí? Eso no funcionará desde entonces method_injected_by_foo_to_host_klass tiene que ser inyectado en la clase que incluye Bar no en Bar módulo en sí

Fuente:  Rails ActiveSupport :: Preocupación


29
2017-12-03 09:11



En lo que respecta a las preocupaciones, haz un archivo filename.rb

Por ejemplo, quiero en mi aplicación donde el atributo create_by existe update there value en 1, y 0 para updated_by

module TestConcern 
  extend ActiveSupport::Concern

  def checkattributes   
    if self.has_attribute?(:created_by)
      self.update_attributes(created_by: 1)
    end
    if self.has_attribute?(:updated_by)
      self.update_attributes(updated_by: 0)
    end
  end

end

después de eso, incluya en su modelo de esta manera:

class Role < ActiveRecord::Base
  include TestConcern
end

6
2018-01-15 08:45