Le générateur de projet Rails 4 par défaut crée maintenant le répertoire "concerne" sous les contrôleurs et les modèles. J'ai trouvé quelques explications sur la façon d'utiliser les problèmes de routage, mais rien sur les contrôleurs ou les modèles.
Je suis à peu près sûr que cela a à voir avec la "tendance DCI" actuelle dans la communauté et je voudrais l'essayer.
La question est de savoir comment je suis censé utiliser cette fonctionnalité. Existe-t-il une convention sur la manière de définir la hiérarchie des noms/classes afin de la faire fonctionner? Comment puis-je inclure une préoccupation dans un modèle ou un contrôleur?
Alors je l'ai découvert par moi-même. C'est en fait un concept assez simple mais puissant. Cela concerne la réutilisation du code comme dans l'exemple ci-dessous. L'idée est essentiellement d'extraire des fragments de code communs et/ou spécifiques au contexte afin de nettoyer les modèles et d'éviter qu'ils ne deviennent trop gros et trop désordonnés.
Par exemple, je vais mettre un motif bien connu, le motif étiquetable:
# 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
En suivant l'exemple de produit, vous pouvez ajouter Taggable à la classe de votre choix et partager ses fonctionnalités.
Ceci s'explique assez bien par DHH :
Dans Rails 4, nous allons inviter les programmeurs à utiliser les problèmes rencontrés avec les répertoires par défaut app/models/inquiétants et app/controllers/concern qui font automatiquement partie du chemin de chargement. Associé au wrapper ActiveSupport :: Concern, ce support est juste suffisant pour faire briller ce mécanisme de factorisation léger.
Je lisais des informations sur l'utilisation de préoccupations relatives au modèle pour personnaliser les gros modèles ainsi que DRY vos codes de modèle. Voici une explication avec des exemples:
Prenons un modèle d'article, un modèle d'événement et un modèle de commentaire. Un article ou un événement a beaucoup de commentaires. Un commentaire appartient à l'article ou à l'événement.
Traditionnellement, les modèles peuvent ressembler à ceci:
Commentaire modèle:
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
end
Modèle d'article:
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
Modèle d'événement
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
Comme nous pouvons le constater, il existe un élément de code important commun à Event et Article. En utilisant des préoccupations, nous pouvons extraire ce code commun dans un module séparé Commentable.
Pour cela, créez un fichier commentable.rb dans 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
Et maintenant, vos modèles ressemblent à ceci:
Commentaire modèle:
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
end
Modèle d'article:
class Article < ActiveRecord::Base
include Commentable
end
Modèle d'événement:
class Event < ActiveRecord::Base
include Commentable
end
Considérons un modèle d'événement. Un événement a de nombreux participants et commentaires.
En règle générale, le modèle d'événement peut ressembler à ceci
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
Les modèles comportant de nombreuses associations et ayant par ailleurs tendance à accumuler de plus en plus de code et à devenir ingérables. Les préoccupations fournissent un moyen de personnaliser les modules adipeux en les rendant plus modulaires et plus faciles à comprendre.
Le modèle ci-dessus peut être restructuré en utilisant les problèmes décrits ci-dessous: Créez un fichier attendable.rb
et commentable.rb
dans le dossier app/models/concern/event.
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
Et maintenant, en utilisant Concerns, votre modèle d’événement se réduit à
class Event < ActiveRecord::Base
include Commentable
include Attendable
end
* Tout en utilisant des problèmes, il est conseillé de choisir un groupe basé sur un domaine plutôt qu'un groupe technique. Le groupe basé sur un domaine ressemble à "Commentable", "Photoable", "Attendable". Un groupe technique signifie "Méthodes de validation", 'FinderMethods' etc
Il vaut la peine de mentionner que l'utilisation de préoccupations est considérée comme une mauvaise idée par beaucoup.
Certaines raisons:
include
, il existe un système complet de gestion des dépendances - beaucoup trop complexe pour quelque chose qui est trivial, bon vieux Ruby, modèle de mixage.Les préoccupations sont un moyen facile de se tirer dans la jambe, soyez prudent avec elles.
Ce post m'a aidé à comprendre mes préoccupations.
# app/models/trader.rb
class Trader
include Shared::Schedule
end
# app/models/concerns/shared/schedule.rb
module Shared::Schedule
extend ActiveSupport::Concern
...
end
J'ai eu le sentiment que la plupart des exemples ici démontraient la puissance de module
plutôt que de la façon dont ActiveSupport::Concern
ajoute de la valeur à module
.
Exemple 1: Des modules plus lisibles.
Donc, sans souci, voici comment un module
typique sera.
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
Après refactorisation avec 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
Vous voyez que les méthodes d'instance, les méthodes de classe et le bloc inclus sont moins compliqués. Les préoccupations les injecteront de manière appropriée pour vous. C'est un avantage d'utiliser ActiveSupport::Concern
.
Exemple 2: Traitez les dépendances de modules avec élégance.
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
Dans cet exemple, Bar
est le module dont Host
a réellement besoin. Mais puisque Bar
a une dépendance avec Foo
la classe Host
doit include Foo
(mais attendez pourquoi Host
veut savoir à propos de Foo
? être évité?).
Donc, Bar
ajoute une dépendance partout où il va. Et ** ordre d'inclusion est également important ici. ** Cela ajoute beaucoup de complexité/dépendance à une base de code énorme.
Après refactorisation avec 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
Maintenant, cela semble simple.
Si vous pensez pourquoi ne pouvons-nous pas ajouter Foo
dépendance dans le module Bar
lui-même? Cela ne fonctionnera pas car method_injected_by_foo_to_Host_klass
doit être injecté dans une classe incluant Bar
pas sur le module Bar
lui-même.
Source: Rails ActiveSupport :: Concern
Dans les préoccupations, faites le fichier filename.rb
Par exemple, je veux dans mon application où l'attribut create_by existe met à jour sa valeur par 1, et 0 pour 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
Si vous voulez passer des arguments en action
included do
before_action only: [:create] do
blaablaa(options)
end
end
après cela, incluez dans votre modèle comme ceci:
class Role < ActiveRecord::Base
include TestConcern
end