Rails 4で懸念事項を使用する方法


628

デフォルトのRails 4プロジェクトジェネレーターは、コントローラーとモデルの下に「懸念事項」ディレクトリを作成するようになりました。ルーティングの問題を使用する方法についていくつかの説明を見つけましたが、コントローラーやモデルについては何もありません。

コミュニティの現在の「DCIトレンド」に関係していると確信しているので、試してみたいと思います。

問題は、私がこの機能をどのように使用することになっているのか、それを機能させるために命名/クラス階層を定義する方法に慣習があるのですか?モデルまたはコントローラーに懸念事項を含めるにはどうすればよいですか?

回答:


617

だから私はそれを自分で見つけました。それは実際にはかなりシンプルですが強力なコンセプトです。以下の例のように、コードの再利用に関係しています。基本的に、アイデアは、モデルをクリーンアップして、モデルが太りすぎて乱雑にならないようにするために、一般的なコードやコンテキスト固有のコードのチャンクを抽出することです。

例として、よく知られたパターン、タグ付け可能なパターンを1つ配置します。

# 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

したがって、Productサンプルに従って、Taggableを任意のクラスに追加し、その機能を共有できます。

これはDHHによってかなりよく説明されています:

Rails 4では、デフォルトのapp / models / concernsとapp / controllers / concernsディレクトリを使用して、自動的にロードパスに含まれる懸念事項を使用するようにプログラマーを招待します。ActiveSupport :: Concernラッパーと組み合わせることで、この軽量のファクタリングメカニズムを際立たせるのに十分なサポートが得られます。


11
DCIはコンテキストを扱い、ロールを識別子として使用してメンタルモデル/ユースケースをコードにマッピングします。ラッパーを使用する必要はなく(メソッドは実行時にオブジェクトに直接バインドされます)、これは実際にはDCIとは関係ありません。
ciscoheat 2013

2
@yagooarを実行時に含めても、DCIにはなりません。ルビDCIの実装例を見たい場合。いずれかを見てみましょうfulloo.infoかの例github.com/runefs/MobyまたはRubyでDCIを行うために栗色の使用方法については、何DCIがあるrunefs.com(何DCIがある。ポストIのきのシリーズです最近開始されました)
Rune FS

1
@RuneFS && ciscoheatどちらも正解でした。記事と事実をもう一度分析しました。そして、先週末、Rubyカンファレンスに行きました。そこでは、DCIについての1つの話があり、最後にその哲学についてもう少し理解しました。DCIについてまったく触れないようにテキストを変更しました。
yagooar 2013年

9
クラスメソッドは特別に名前が付けられたモジュールClassMethodsで定義されることになっていること、およびこのモジュールはActiveSupport :: Concernである基本クラスによって拡張されていることにも言及する価値があります。
2013

1
この例をお寄せいただきありがとうございます。主にb / c私は馬鹿げており、ClassMethodsモジュール内で自分のクラスレベルのメソッドをself.whateverで定義していますが、これは機能しません= P
Ryan Crews

379

モデルの懸念事項を使用して脂肪モデルをスキンナイズすること、およびモデルコードをDRYすることについて読んでいます。以下に例を挙げて説明します。

1)モデルコードの乾燥

Articleモデル、Eventモデル、Commentモデルを考えてみましょう。記事やイベントには多くのコメントがあります。コメントは記事またはイベントのいずれかに属しています。

従来、モデルは次のようになります。

コメントモデル:

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

記事モデル:

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

イベントモデル

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

お気づきのように、EventとArticleの両方に共通する重要なコードがあります。懸念事項を使用して、この共通コードを別のモジュールCommentableに抽出できます。

このため、app / models / concernsにcommentable.rbファイルを作成します。

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

これで、モデルは次のようになります。

コメントモデル:

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

記事モデル:

class Article < ActiveRecord::Base
  include Commentable
end

イベントモデル:

class Event < ActiveRecord::Base
  include Commentable
end

2)スキンナイジング脂肪モデル。

イベントモデルについて考えます。イベントには多くの参加者とコメントがあります。

通常、イベントモデルは次のようになります。

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

多くの関連付けがあるモデルや、そうでなければモデルがますます多くのコードを蓄積し、管理不能になる傾向があります。懸念事項は、脂肪モジュールをスキン化してモジュール化し、理解を容易にする方法を提供します。

上記のモデルは以下のように懸念を使用してリファクタリングすることができます作成attendable.rbし、commentable.rbアプリ/モデル/懸念/イベントフォルダ内のファイルを

ttendable.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

そして懸念を使用すると、イベントモデルは次のようになります。

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

*懸念事項を使用している間は、「技術」グループではなく「ドメイン」ベースのグループに進むことをお勧めします。ドメインベースのグループ化は、「コメント可能」、「写真可能」、「出席可能」のようなものです。技術グループは「ValidationMethods」、「FinderMethods」などを意味します


6
それで、懸念は継承、インターフェース、または多重継承を使用するための単なる方法ですか?共通の基本クラスを作成し、その共通の基本クラスからサブクラス化することの何が問題になっていますか?
Chloe

3
確かに、@ Chloe、私は赤で、「懸念」ディレクトリを持つRailsアプリは実際には「懸念」です...
Ziyan Junaideen

'included'ブロックを使用して、すべてのメソッドとインクルードを定義できます:クラスメソッド(を使用def self.my_class_method)、インスタンスメソッド、およびクラススコープ内のメソッド呼び出しとディレクティブ。必要なしmodule ClassMethods
A Fader Darkly

1
私が懸念している問題は、モデルに機能を直接追加することです。したがってadd_item、たとえば、2つの懸念事項の両方が実装されている場合、うんざりしています。一部のバリデーターが機能しなくなったときにRailsが壊れたと思いましたが、誰かがany?懸念を抱いて実装していました。私は別の解決策を提案します。別の言語のインターフェースのような懸念を使用します。機能を定義する代わりに、その機能を処理する個別のクラスインスタンスへの参照を定義します。次に、1つのことを行うより小さくてよりきちんとしたクラスがあります...
Aフェーダー・ダークリー

@aaditi_jain:誤解を避けるために、小さな変更を修正してください。つまり、「app / models / concerns / eventフォルダーにattendable.rdおよびcommentable.rbファイルを作成します」-> attableable.rdはattendable.rbでなければなりません
Rubyist

97

多くの人が懸念を使用することは悪い考えであると考えていることに言及する価値があります。

  1. この男のように
  2. そしてこれ

いくつかの理由:

  1. 舞台裏でいくつかの暗い魔法が起こっています-懸念はパッチinclude方法であり、依存関係処理システム全体があります-ささいな古き良きRubyミックスインパターンでは複雑すぎます。
  2. あなたのクラスも同様に乾燥しています。さまざまなモジュールに50のパブリックメソッドを詰め込み、それらを含めた場合、クラスには50のパブリックメソッドがまだあります。コードの臭いを隠し、ゴミを引き出しに入れるだけです。
  3. コードベースは、実際にこれらすべての懸念をナビゲートするのが困難です。
  4. チームのすべてのメンバーが、懸念を実際に置き換える必要があるものを同じように理解していますか?

懸念は脚で自分を撃つ簡単な方法です、それらに注意してください。


1
SOがこのディスカッションに最適な場所ではないことは知っていますが、クラスをドライに保つ他のタイプのRubyミックスインは何ですか?優れたOO設計、サービスレイヤー、または私が見逃している何かのために単に主張しない限り、あなたの議論の理由#1と#2は反論のようです。(私は反対しません-私は代替案を追加することをお勧めします!)
toobulkeh

2
github.com/AndyObtiva/super_moduleの使用は1つのオプションであり、古き良きClassMethodsパターンの使用は別のオプションです。そして、より多くのオブジェクト(サービスなど)を使用して問題を明確に分離することは、間違いなく進むべき道です。
Dr.Strangelove 2015

4
これは質問に対する回答ではないため、反対投票。それは意見です。それは確かにメリットがあると私は確信していますが、StackOverflowに関する質問への回答ではありません。
アダム

2
@アダムそれは独断的な答えです。誰かがレールでグローバル変数を使用する方法を尋ねると想像してください、確かに物事を行うためのより良い方法がある(つまり、Redis.current対$ redis)がトピックのスターターにとって有用な情報であるかもしれないと述べますか?ソフトウェア開発は本質的に独断的な規律であり、それを回避することはできません。実際に、私はstackoverflowの上のすべての時間最高であり、それは良いことで答え回答や議論などの意見を参照
Dr.Strangelove

2
確かに、質問への回答とともにそれを言及することは問題ないようです。ただし、実際にはOPの質問に答えるものはありません。懸念事項やグローバル変数を使用してはならない理由を誰かに警告するだけの場合は、質問に追加できる良いコメントになりますが、実際には良い答えにはなりません。
アダム


46

ここでの例のほとんどは、にmoduleどのようにActiveSupport::Concern付加価値を与えるかというよりも、の力を示していると感じましたmodule

例1:より読みやすいモジュール。

したがって、これを心配することなく、典型的なものmoduleはどうなりますか。

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

でリファクタリングし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

インスタンスメソッド、クラスメソッド、含まれているブロックはそれほど複雑ではありません。懸念はあなたのためにそれらを適切に注入します。これがを使用する利点の1つActiveSupport::Concernです。


例2:モジュールの依存関係を適切に処理します。

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

この例でBarは、Host本当に必要なモジュールです。しかし、クラスBarFooの依存関係があるHost必要がありますinclude Foo(しかし、なぜHost知りたいのFooですか?それを回避できますか?)。

したがってBar、依存関係がどこにでも追加されます。また、ここでは包含の順序も重要です。これは巨大なコードベースに多くの複雑さ/依存性を追加します。

でリファクタリングした後 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

今ではシンプルに見えます。

モジュール自体にFoo依存関係を追加できないのはなぜBarですか?モジュール自体にmethod_injected_by_foo_to_host_klass含まれてBarいないクラスに注入する必要があるため、これは機能しませんBar

ソース: Rails ActiveSupport :: Concern


それをありがとう。私は彼らの利点が何であるか疑問になり始めていました...
Hari Karam Singh

FWIWこれは、ドキュメントから大まかにコピーして貼り付けたものです。
デイブニュートン

7

懸念事項として、ファイルfilename.rbを作成します。

たとえば、属性create_byが存在するアプリケーションで、値を1ずつ更新し、updated_byを0にしたい

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

アクションで引数を渡したい場合

included do
   before_action only: [:create] do
     blaablaa(options)
   end
end

その後、次のようにモデルに含めます。

class Role < ActiveRecord::Base
  include TestConcern
end
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.