ruby on rails - ejemplos - Cómo usar las preocupaciones en Rails 4
ruby on rails tutorial (6)
Así que lo descubrí por mi cuenta. En realidad es un concepto bastante simple pero poderoso. Tiene que ver con la reutilización de código como en el siguiente ejemplo. Básicamente, la idea es extraer fragmentos de código comunes y / o específicos al contexto para limpiar los modelos y evitar que se vuelvan demasiado gruesos 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 el ejemplo del producto, puede agregar Taggable a cualquier clase que desee y compartir su funcionalidad.
Esto está bastante bien explicado por DHH :
En Rails 4, invitaremos a los programadores a usar las preocupaciones con los directorios predeterminados de aplicación / modelos / inquietudes y aplicación / controladores / inquietudes que son automáticamente parte de la ruta de carga. Junto con el envoltorio ActiveSupport :: Concern, es suficiente apoyo para hacer que brille este ligero mecanismo de factorización.
El generador de proyecto de Rails 4 predeterminado ahora crea el "problema" del directorio en los 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 "tendencia actual de DCI" en la comunidad y me gustaría intentarlo.
La pregunta es, ¿cómo se supone que debo utilizar esta función, hay alguna convención sobre cómo definir la jerarquía de nombres / clases para que funcione? ¿Cómo puedo incluir una preocupación en un modelo o controlador?
En lo concerniente a hacer archivo file.rb
Por ejemplo, quiero en mi aplicación donde el atributo create_by existe actualice el valor 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 incluye en tu modelo así:
class Role < ActiveRecord::Base
include TestConcern
end
He estado leyendo sobre el uso de las preocupaciones de los modelos para personalizar los modelos de grasa, así como también SECAR los códigos de sus modelos. Aquí hay una explicación con ejemplos:
1) Secado de códigos modelo
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 al artículo o al 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 importante fragmento de código común tanto para el evento como para el artículo. En cuanto a las preocupaciones, podemos extraer este código común en un módulo independiente que se puede comentar.
Para esto, cree un archivo commentable.rb en app / models / concern.
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) Modelos de grasa para la piel.
Considere un modelo de evento. Un evento tiene muchos asistentes y comentarios.
Típicamente, 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 por lo demás tienen tendencia a acumular cada vez más código y se vuelven inmanejables. Las inquietudes brindan una forma de personalizar los módulos de grasa para que sean más modulares y fáciles de entender.
El modelo anterior se puede refactorizar utilizando las preocupaciones de la siguiente manera: attendable.rb
un archivo attendable.rb
y commentable.rb
en la carpeta de aplicaciones / modelos / preocupaciones / 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, su modelo de evento se reduce a
class Event < ActiveRecord::Base
include Commentable
include Attendable
end
* Si bien su uso se refiere, es aconsejable ir a la agrupación basada en el "dominio" en lugar de la agrupación "técnica". La agrupación basada en el dominio es como ''Comentable'', ''Fotogenerable'', ''Atendible''. La agrupación técnica significará ''ValidationMethods'', ''FinderMethods'', etc.
Sentí que la mayoría de los ejemplos aquí demuestran la potencia del module
lugar de cómo ActiveSupport::Concern
agrega valor al module
.
Ejemplo 1: Módulos más legibles.
Así que sin preocuparnos de cómo será un module
típico.
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ás que los métodos de instancia, los métodos de clase y el bloque incluido son menos complicados. Las preocupaciones los inyectarán apropiadamente para usted. Esa es una ventaja de usar ActiveSupport::Concern
.
Ejemplo 2: Manejar 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 como Bar
tiene dependencia con Foo
la clase Host
tiene que include Foo
(pero espera, ¿por qué Host
quiere saber acerca de Foo
? ¿Se puede evitar?).
Así que Bar
agrega dependencia donde quiera que vaya. Y el orden de inclusión también importa aquí. Esto agrega mucha complejidad / dependencia a una base de código enorme.
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 la dependencia de Foo
en el módulo Bar
? Eso no funcionará, ya que method_injected_by_foo_to_host_klass
se debe inyectar en la clase que incluye Bar
no en el módulo Bar
sí.
Vale la pena mencionar que el uso de las preocupaciones es considerado una mala idea por muchos.
Algunas razones:
- Hay algo de magia oscura detrás de las escenas: la preocupación es parchear el método de
include
, hay todo un sistema de manejo de dependencias: demasiada complejidad para algo que es trivial y un viejo patrón de mezcla Ruby. - Tus clases no son menos secas. Si metes 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 escondes el olor del código, de alguna forma metes la basura en los cajones.
- Codebase es realmente más difícil de navegar con todas esas preocupaciones alrededor.
- ¿Está seguro de que todos los miembros de su equipo tienen el mismo entendimiento de lo que realmente debería sustituir a la preocupación?
Las preocupaciones son una manera fácil de dispararte en la pierna, ten cuidado con ellas.
Este post 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