[ruby-on-rails] 4.0의 Rails Observer 대안

Observers 가 Rails 4.0에서 공식적으로 제거 되면서 다른 개발자들이 대신 사용하고있는 것이 궁금합니다. (추출 된 보석을 사용하는 것 이외) 관찰자들은 확실히 학대를 당하고 때로는 쉽게 다루기 어려워 질 수 있었지만, 캐시를 지우는 것 외에는 유익한 곳이 많았습니다.

예를 들어 모델의 변경 사항을 추적해야하는 응용 프로그램을 생각해보십시오. 관찰자는 모델 A에서 변경 사항을 쉽게 감시하고 데이터베이스에서 모델 B로 변경 사항을 기록 할 수 있습니다. 여러 모델의 변경 사항을보고 싶다면 단일 관찰자가이를 처리 할 수 ​​있습니다.

Rails 4에서는 다른 개발자들이 옵저버 대신 어떤 기능을 사용하여 그 기능을 재현하고 있는지 전략이 궁금합니다.

개인적으로 저는 각 모델 컨트롤러의 작성 / 업데이트 / 삭제 방법에서 이러한 변경 사항을 추적하는 일종의 “뚱뚱한 컨트롤러”구현에 기울고 있습니다. 각 컨트롤러의 동작을 약간 부 풀리지 만 모든 코드가 한 곳에 있기 때문에 가독성과 이해에 도움이됩니다. 단점은 이제 여러 컨트롤러에 매우 유사한 코드가 흩어져 있다는 것입니다. 해당 코드를 도우미 메서드로 추출하는 것은 선택 사항이지만 여전히 모든 곳에서 흩어져있는 메서드에 대한 호출이 남아 있습니다. 세계의 종말은 아니지만 “스키니 컨트롤러”라는 정신에는 있지 않습니다.

ActiveRecord 콜백은 또 다른 가능한 옵션이지만 개인적으로 싫어하는 것은 두 가지 다른 모델을 너무 밀접하게 결합시키는 경향이 있습니다.

따라서 Rails 4의 비 관찰자 세계에서 다른 레코드를 생성 / 업데이트 / 파기 한 후에 새 레코드를 만들어야한다면 어떤 디자인 패턴을 사용 하시겠습니까? 뚱뚱한 컨트롤러, ActiveRecord 콜백 또는 다른 것?

감사합니다.



답변

관심사 살펴보기

models 디렉토리에 우려라는 폴더를 작성하십시오. 거기에 모듈을 추가하십시오 :

module MyConcernModule
  extend ActiveSupport::Concern

  included do
    after_save :do_something
  end

  def do_something
     ...
  end
end

그런 다음 after_save를 실행하려는 모델에 다음을 포함하십시오.

class MyModel < ActiveRecord::Base
  include MyConcernModule
end

당신이하고있는 일에 따라, 이것은 관찰자없이 닫을 수 있습니다.


답변

그들은 지금 플러그인에 있습니다.

다음과 같은 컨트롤러를 제공 하는 대안 을 추천 할 수 있습니까?

class PostsController < ApplicationController
  def create
    @post = Post.new(params[:post])

    @post.subscribe(PusherListener.new)
    @post.subscribe(ActivityListener.new)
    @post.subscribe(StatisticsListener.new)

    @post.on(:create_post_successful) { |post| redirect_to post }
    @post.on(:create_post_failed)     { |post| render :action => :new }

    @post.create
  end
end


답변

내 제안에 제임스 Golick의 블로그 게시물을 읽는 것입니다 http://jamesgolick.com/2010/3/14/crazy-heretical-and-awesome-the-way-i-write-rails-apps.html (시도에서는 무시 제목이 잘못되었습니다).

당시에는 모두 “뚱뚱한 모델, 스키니 컨트롤러”였습니다. 그런 다음 지방 모델은 특히 테스트 중에 큰 두통이되었습니다. 보다 최근에는 스키니 모델에 대한 추진이 이루어졌습니다. 각 클래스는 하나의 책임을 처리해야하며 모델의 임무는 데이터를 데이터베이스에 유지하는 것입니다. 복잡한 비즈니스 로직은 어디에 있습니까? 비즈니스 로직 클래스-트랜잭션을 나타내는 클래스

이 접근법은 논리가 복잡해지기 시작할 때 혼란 (거대한)으로 바뀔 수 있습니다. 테스트와 디버깅이 어려운 콜백이나 옵저버로 암시 적으로 트리거하는 대신 모델 위에 로직을 계층화하는 클래스에서 명시 적으로 트리거합니다.


답변

활성 레코드 콜백을 사용하면 커플 링의 종속성이 반전됩니다. 당신이있는 경우 예를 들어, modelACacheObserver관찰은 modelA3 스타일 레일, 당신은 제거 할 수 CacheObserver없는 문제를. 이제 대신 사후 저장 (레일 4) A을 수동으로 호출해야 한다고 CacheObserver합니다. 종속성을 단순히 이동 A했지만 안전하게 제거 할 수는 있지만 제거 할 수는 없습니다 CacheObserver.

이제 상아탑에서 관찰자가 관찰하는 모델에 의존하는 것을 선호합니다. 컨트롤러를 어지럽 힐 정도로주의를 기울여야합니까? 나에게 대답은 ‘아니요’입니다.

아마도 관찰자가 왜 필요한지에 대한 생각을 했으므로 관찰자에 의존하는 모델을 만드는 것은 끔찍한 비극이 아닙니다.

또한 컨트롤러 동작에 의존하는 모든 종류의 관찰자에 대한 (합리적으로 접지 된 것 같아요) 싫증이 있습니다. 갑자기 관찰하려는 모델을 업데이트 할 수있는 컨트롤러 동작 (또는 다른 모델)에 관찰자를 주입해야합니다. 앱이 컨트롤러 생성 / 업데이트 작업을 통해서만 인스턴스를 수정하도록 보장 할 수 있다면 더 많은 힘을 얻을 수 있지만 이는 레일스 응용 프로그램 (중첩 양식, 모델 비즈니스 로직 업데이트 연결 등 고려)에 대한 가정이 아닙니다.


답변

Wisper 는 훌륭한 솔루션입니다. 콜백에 대한 개인적인 선호는 모델에 의해 호출되는 것이지만 요청이 들어올 때만 이벤트가 청취됩니다. 즉, 테스트 등에서 모델을 설정하는 동안 콜백이 발생하지 않기를 원하지만 원합니다. 컨트롤러가 관련 될 때마다 발생합니다. Wisper를 사용하면 블록 내부의 이벤트 만들을 수 있기 때문에 Wisper를 사용하여 쉽게 설정할 수 있습니다.

class ApplicationController < ActionController::Base
  around_filter :register_event_listeners

  def register_event_listeners(&around_listener_block)
    Wisper.with_listeners(UserListener.new) do
      around_listener_block.call
    end
  end
end

class User
  include Wisper::Publisher
  after_create{ |user| publish(:user_registered, user) }
end

class UserListener
  def user_registered(user)
    Analytics.track("user:registered", user.analytics)
  end
end


답변

어떤 경우에는 단순히 Active Support Instrumentation을 사용합니다

ActiveSupport::Notifications.instrument "my.custom.event", this: :data do
  # do your stuff here
end

ActiveSupport::Notifications.subscribe "my.custom.event" do |*args|
  data = args.extract_options! # {:this=>:data}
end


답변

Rails 3 Observers에 대한 나의 대안은 모델 내에 정의 된 콜백을 활용하면서 (위의 답변에서 agmin 상태로) “종속성 넘기기 … 커플 링”을 관리하는 수동 구현입니다.

내 객체는 관찰자를 등록하는 기본 클래스에서 상속됩니다.

class Party411BaseModel

  self.abstract_class = true
  class_attribute :observers

  def self.add_observer(observer)
    observers << observer
    logger.debug("Observer #{observer.name} added to #{self.name}")
  end

  def notify_observers(obj, event_name, *args)
    observers && observers.each do |observer|
    if observer.respond_to?(event_name)
        begin
          observer.public_send(event_name, obj, *args)
        rescue Exception => e
          logger.error("Error notifying observer #{observer.name}")
          logger.error e.message
          logger.error e.backtrace.join("\n")
        end
    end
  end

end

상속에 대한 구성의 정신에서 위의 코드를 모듈에 배치하고 각 모델에서 혼합 할 수 있습니다.

이니셜 라이저는 옵저버를 등록합니다.

User.add_observer(NotificationSender)
User.add_observer(ProfilePictureCreator)

그런 다음 각 모델은 기본 ActiveRecord 콜백 외에도 자체 관찰 가능한 이벤트를 정의 할 수 있습니다. 예를 들어, 내 사용자 모델은 2 개의 이벤트를 노출합니다.

class User < Party411BaseModel

  self.observers ||= []

  after_commit :notify_observers, :on => :create

  def signed_up_via_lunchwalla
    self.account_source == ACCOUNT_SOURCES['LunchWalla']
  end

  def notify_observers
    notify_observers(self, :new_user_created)
    notify_observers(self, :new_lunchwalla_user_created) if self.signed_up_via_lunchwalla
  end
end

해당 이벤트에 대한 알림을 수신하려는 관찰자는 (1) 이벤트를 노출하는 모델에 등록하고 (2) 이름이 이벤트와 일치하는 방법을 가지고 있으면됩니다. 예상 한대로 여러 명의 옵저버가 동일한 이벤트에 등록 할 수 있으며 (원래 질문의 두 번째 단락을 참조하여) 옵저버는 여러 모델의 이벤트를 감시 할 수 있습니다.

아래의 NotificationSender 및 ProfilePictureCreator 옵저버 클래스는 다양한 모델에 의해 노출되는 이벤트에 대한 메소드를 정의합니다.

NotificationSender
  def new_user_created(user_id)
    ...
  end

  def new_invitation_created(invitation_id)
    ...
  end

  def new_event_created(event_id)
    ...
  end
end

class ProfilePictureCreator
  def new_lunchwalla_user_created(user_id)
    ...
  end

  def new_twitter_user_created(user_id)
    ...
  end
end

한 가지주의 사항은 모든 모델에서 노출되는 모든 이벤트의 이름이 고유해야한다는 것입니다.