Rails 4에서 우려 사항을 사용하는 방법


628

기본 Rails 4 프로젝트 생성기는 이제 컨트롤러 및 모델 아래에 "우려"디렉토리를 만듭니다. 라우팅 문제를 사용하는 방법에 대한 설명을 찾았지만 컨트롤러 나 모델에 대해서는 설명하지 않았습니다.

커뮤니티의 현재 "DCI 트렌드"와 관련이 있으며 시도해보고 싶습니다.

문제는이 기능을 어떻게 사용해야합니까? 네이밍 / 클래스 계층 구조를 정의하여 작동하게하는 방법에 대한 규칙이 있습니까? 모델이나 컨트롤러에 관심을 어떻게 포함시킬 수 있습니까?

답변:


617

그래서 나는 그것을 스스로 알아 냈습니다. 실제로는 매우 단순하지만 강력한 개념입니다. 아래 예제와 같이 코드 재사용과 관련이 있습니다. 기본적으로 아이디어는 모델을 정리하고 너무 뚱뚱하고 지저분 해지는 것을 피하기 위해 공통 및 / 또는 컨텍스트 특정 코드 덩어리를 추출하는 것입니다.

예를 들어, 잘 알려진 하나의 태그 가능한 패턴을 넣겠습니다.

# 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

따라서 제품 샘플에 따라 원하는 모든 클래스에 Taggable을 추가하고 기능을 공유 할 수 있습니다.

이것은 DHH에 의해 잘 설명되어 있습니다 .

Rails 4에서는 자동으로로드 경로의 일부인 기본 app / models / concerns 및 app / controllers / concerns 디렉토리에 관심을 갖도록 프로그래머를 초대합니다. ActiveSupport :: Concern 래퍼와 함께이 경량 팩토링 메커니즘을 빛나게하는 데 충분한 지원입니다.


11
DCI는 컨텍스트를 처리하고, 역할을 식별자로 사용하여 정신 모델 / 사용 사례를 코드에 맵핑하며 래퍼를 사용할 필요가 없으며 (방법은 런타임에 오브젝트에 직접 바인드 됨) DCI와는 전혀 관련이 없습니다.
ciscoheat

2
@yagooar는 런타임에 포함하더라도 DCI가되지 않습니다. 루비 DCI 예제 구현을 보려면 하나 살펴보세요 fulloo.info 또는 예 github.com/runefs/Moby 또는 루비 DCI을 할 적갈색을 사용하는 방법에 대한 어떤 DCI는 runefs.com을 (무엇 DCI입니다. 포스트 I의 한 일련의 방금 최근에 시작)
Rune FS

1
@ RuneFS && ciscoheat 둘 다 맞았습니다. 방금 기사와 사실을 다시 분석했습니다. 그리고 지난 주말에 루비 회의에 갔는데 여기서 DCI에 관한 이야기를했고 마침내 그 철학에 대해 조금 더 이해했습니다. DCI를 전혀 언급하지 않도록 텍스트를 변경했습니다.
yagooar

9
클래스 메소드는 특별히 명명 된 모듈 ClassMethods에 정의되어야하며이 모듈은 기본 클래스 인 ActiveSupport :: Concern에 의해 확장된다는 것을 언급 할 가치가 있습니다 (그리고 아마도 예제를 포함하여).
febeling

1
이 예를 들어 주셔서 감사합니다, 주로 B / C 내가 바보 인과 ClassMethods의 내부에 내 클래스 수준의 방법을 정의하는 것은 여전히 self.whatever와 모듈되었고, 그 일 = P하지 않습니다
라이언 대원

379

나는 모델 코드를 건조 할뿐만 아니라 뚱뚱한 모델을 피부 에 맞게 만들기 위해 모델 문제 를 사용하는 것에 대해 읽었 습니다. 다음은 예제를 사용한 설명입니다.

1) 모델 코드 건조

기사 모델, 이벤트 모델 및 설명 모델을 고려하십시오. 기사 나 이벤트에는 많은 의견이 있습니다. 의견은 기사 또는 이벤트에 속합니다.

전통적으로 모델은 다음과 같습니다.

댓글 모델 :

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

알다시피, 이벤트와 기사 모두에 공통적 인 중요한 코드가 있습니다. 우려 사항을 사용하여이 공통 코드를 별도의 모듈 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

많은 연관성이 있고 그렇지 않으면 더 많은 코드를 누적하고 관리 할 수없는 경향이있는 모델이 있습니다. 우려 사항은 지방 모듈을 피부에 맞게 처리하여보다 모듈화되고 이해하기 쉬운 방법을 제공합니다.

위의 모델은 다음과 같은 우려를 사용하여 리팩토링 할 수 있습니다 : a 작성 attendable.rbcommentable.rb app / models / concerns / 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

이제 관심사를 사용하면 이벤트 모델이

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

* 사용하는 동안 '기술적'그룹화보다는 '도메인'기반 그룹화를 사용하는 것이 좋습니다. 도메인 기반 그룹은 'Commentable', 'Photoable', 'Attendable'과 같습니다. 기술 그룹화는 'ValidationMethods', 'FinderMethods'등을 의미합니다.


6
그렇다면 우려는 상속이나 인터페이스 또는 다중 상속을 사용하는 방법일까요? 공통 기본 클래스를 작성하고 해당 공통 기본 클래스에서 서브 클래 싱을 사용하면 어떤 문제가 있습니까?
클로이

3
실제로 @Chloe, 나는 'concerns'디렉토리를 가진 Rails 앱이 실제로는 'concern'인
곳인 레드가 있습니다

'included'블록을 사용하여 모든 메소드를 정의 def self.my_class_method하고 클래스 범위의 클래스 메소드 (포함 ), 인스턴스 메소드 및 메소드 호출 및 지시문을 포함 할 수 있습니다. 불필요module ClassMethods
페이더 어둡게

1
내가 우려하는 문제는 모델에 직접 기능을 추가한다는 것입니다. add_item예를 들어 두 가지가 모두 구현 과 관련되어 있다면 문제 가 생길 수 있습니다. 일부 유효성 검사기가 작동을 멈췄을 때 Rails가 고장 났다고 생각하지만 누군가가 any?염려에 빠졌습니다. 다른 해결책을 제안합니다. 다른 언어로 된 인터페이스와 같은 관심사를 사용하십시오. 기능을 정의하는 대신 해당 기능을 처리하는 별도의 클래스 인스턴스에 대한 참조를 정의합니다. 그러면 한 가지 일을하는 더 작고 깔끔한 수업이 있습니다.
Fader Darkly

@aaditi_jain : 오해를 피하기 위해 작은 변화를 수정하십시오. 예 : "app / models / concerns / event 폴더에 attendable.rd 및 commentable.rb 파일 만들기"-> attendable.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하는 것은 또 하나 좋은 오래된 ClassMethods 패턴입니다 사용하여, 하나 개의 옵션입니다. 그리고 더 많은 객체 (서비스와 같은)를 사용하여 문제를 명확하게 분리하는 것이 분명합니다.
Dr.Strangelove

4
이것은 질문에 대한 답변이 아니기 때문에 하향 투표. 의견입니다. 그것은 장점이 있다고 확신하지만 StackOverflow에 대한 질문에 답해서는 안됩니다.
Adam

2
@Adam 그것은 의견이 많은 답변입니다. 누군가 레일에서 전역 변수를 사용하는 방법을 물을 것이라고 생각한다고 가정하십시오. 물론 더 나은 방법 (예 : Redis.current 대 $ redis)이 주제 시작에 유용한 정보가 될 수 있다고 언급합니까? 소프트웨어 개발은 ​​본질적으로 의견이 많은 학문입니다. 사실, 나는 의견을 답변과 토론으로 본다. 어떤 답변은 항상
스택 오버플

2
물론, 질문에 대한 답변 과 함께 언급하면 괜찮습니다. 답변에 실제로 OP의 질문에 대한 답변은 없습니다. 당신이하고 싶은 모든 사람들에게 왜 우려 또는 전역 변수를 사용해서는 안되는지 경고하는 것이라면 질문에 추가 할 수있는 좋은 의견을 제시하지만 실제로는 좋은 대답을하지는 못합니다.
Adam


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

인스턴스 메소드, 클래스 메소드 및 포함 된 블록이 덜 지저분하다는 것을 알 수 있습니다. 우려 사항이 귀하에게 적절하게 주입됩니다. 이것이을 사용하는 이점 중 하나입니다 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의 종속성이 있기 때문에Hostinclude 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 :: 문제


고마워 나는 그들의 장점이 무엇인지 궁금해하기 시작했다 ...
Hari Karam Singh

그러나 이것은 문서 에서 대략 복사하여 붙여 넣습니다 .
Dave Newton

7

우려 파일 이름을 만듭니다 .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.