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?
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.
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.
Vale la pena mencionar que el uso de las preocupaciones es considerado una mala idea por muchos.
- como este chico
- y éste
Algunas razones:
- 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.
- 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.
- Codebase es realmente más difícil de navegar con todas esas preocupaciones.
- ¿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.
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
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
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