web-dev-qa-db-fra.com

Comment utiliser les problèmes dans Rails 4

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?

615
yagooar

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.

606
yagooar

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:

1) Séchage des codes de modèle

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

2) modèles de graisse peau-nizing.

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

371
Aaditi Jain

Il vaut la peine de mentionner que l'utilisation de préoccupations est considérée comme une mauvaise idée par beaucoup.

  1. comme ce gars
  2. et celui-ci

Certaines raisons:

  1. Il y a de la magie noire dans les coulisses - Concern est en train de corriger la méthode 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.
  2. Vos cours ne sont pas moins secs. Si vous insérez 50 méthodes publiques dans différents modules et que vous les incluez, votre classe en a toujours 50, mais vous cachez simplement cette odeur de code et vous mettez en quelque sorte vos ordures dans les tiroirs.
  3. Codebase est en réalité plus difficile à gérer avec toutes ces préoccupations.
  4. Etes-vous sûr que tous les membres de votre équipe ont la même compréhension de ce qui devrait réellement remplacer la préoccupation?

Les préoccupations sont un moyen facile de se tirer dans la jambe, soyez prudent avec elles.

94
Dr.Strangelove

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
54
aminhotob

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

38
shiva

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
6
Sajjad Murtaza