4.0의 Rails Observer 대안


154

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

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

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

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

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

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

감사합니다.


4
이 질문에 대한 답변이 더 이상 없다는 사실이 정말 놀랍습니다. 당황의 종류.
courtsimas

답변:


82

관심사 살펴보기

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

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


20
이 방법에는 문제가 있습니다. 특히 모델을 정리하지는 않습니다. include 모듈에서 메소드를 클래스로 다시 복사합니다. 클래스 메소드를 모듈로 추출하면 우려에 따라 그룹 메소드를 그룹화 할 수 있지만 클래스는 여전히 부풀어 있습니다.
Steven Soroka

15
제목은 'Rails Observer Alternatives for 4.0'이 아니라 '팽창을 최소화하는 방법'이 아닙니다. 문제가 스티븐 일을 어떻게하지 않습니까? 그리고 아니요, '부풀림'이 관찰자를 대체 할 수없는 이유는 충분하지 않다고 제안합니다. 커뮤니티를 돕거나 우려가 관찰자를 대신 할 수없는 이유를 설명하기 위해 더 나은 제안을해야합니다. 다행스럽게도 = D
UncleAdam

10
부풀림은 항상 관심사입니다. 더 좋은 대안은 wisper입니다 . 이는 올바르게 구현되면 문제를 추출하여 모델과 밀접하게 연결되지 않은 클래스를 분리하여 문제를 정리할 수 있습니다. 또한 독립 테스트를 훨씬 쉽게 수행 할 수 있습니다.
Steven Soroka

4
이 작업을 수행하기 위해 보석을 가져 와서 팽창 또는 전체 앱 팽창을 모델링하십시오. 개별 선호도에 따라 그대로 둘 수 있습니다. 추가 제안에 감사드립니다.
UncleAdam

IDE의 메소드 자동 완성 메뉴 만 부풀려서 많은 사람들에게 좋을 것입니다.
lulalala

33

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

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

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

ActiveSupport :: Notifications는 어떻습니까?
svoop

@svoop ActiveSupport::Notifications은 일반적인 서브 / 펍이 아닌 인스 트루먼 테이션 에 맞춰져 있습니다.
Kris

@ 크리스-당신이 맞아요. 주로 계측에 사용되지만 pub / sub의 일반적인 방법으로 사용되지 않는 이유는 무엇입니까? 기본 구성 요소를 제공합니까? 다시 말해, 위스퍼의 장점과 단점은 ActiveSupport::Notifications?
gingerlime

Notifications많이 사용 하지는 않았지만 Wisper더 좋은 API와 '전역 가입자', '접두사'및 '이벤트 매핑'과 같은 기능 Notifications이 없습니다. 향후 릴리스에서는 WisperSideKiq / Resque / Celluloid를 통한 비동기 게시도 허용됩니다. 또한 향후 Rails 릴리스에서는 잠재적 Notifications으로 더 많은 계측에 중점을 둔 API 가 변경 될 수 있습니다.
Kris

21

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

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

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


4
지난 몇 달 동안 프로젝트를 위해 이와 같은 일을 해왔습니다. 많은 작은 서비스가 필요하지만 테스트 및 유지 관리의 용이성은 단점보다 훨씬 큽니다. 이 중간 크기의 시스템 내 매우 광범위한 사양은 여전히 :) 실행 5 초 걸릴
루카 스 필러

PORO (Plain Old Ruby Objects) 또는 서비스 개체라고도합니다
Cyril Duchon-Doris

13

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

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

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

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


1
의견 @agmin에 감사드립니다. 더 나은 디자인 패턴이 있다면 옵저버를 사용하지 않는 것이 좋습니다. 다른 사람들이 코드와 종속성을 구조화하여 유사한 기능을 제공하는 방법에 가장 관심이 있습니다 (캐싱 제외). 필자의 경우 속성이 업데이트 될 때마다 모델 변경 사항을 기록하고 싶습니다. 나는 이것을하기 위해 관찰자를 사용했다. 이제 뚱뚱한 컨트롤러, AR 콜백 또는 내가 생각하지 못한 다른 것을 결정하려고합니다. 지금도 우아하지 않은 것 같습니다.
kennyc

13

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

9

어떤 경우에는 단순히 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

4

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

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


3

관찰자가 더 이상 사용되지 않는 문제는 관찰자가 스스로 나쁘다는 것이 아니라 남용되었다는 것입니다.

이 문제에 대한 올바른 해결책이 이미 옵저버 패턴 인 경우 콜백에 너무 많은 로직을 추가하거나 단순히 코드를 이동하여 관찰자의 동작을 시뮬레이션하지 않도록주의해야합니다.

관찰자를 사용하는 것이 의미가 있다면 반드시 관찰자를 사용하십시오. 관찰자 로직이 SOLID와 같은 사운드 코딩 방식을 따르는 지 확인해야합니다.

관찰자 gem은 프로젝트에 다시 추가하려는 경우 rubygems에서 사용할 수 있습니다 https://github.com/rails/rails-observers

이 간단한 스레드를 참조하십시오. 포괄적 인 토론은 아니지만 기본 논거가 유효하다고 생각합니다. https://github.com/rails/rails-observers/issues/2



2

대신 PORO를 사용하는 것은 어떻습니까?

이에 대한 논리는 '저장에 대한 추가 조치'가 비즈니스 논리 일 가능성이 높다는 것입니다. 이것은 AR 모델 (가능한 한 간단해야 함)과 컨트롤러 (올바로 테스트하기에 귀찮은)를 분리하고 싶습니다.

class LoggedUpdater

  def self.save!(record)
    record.save!
    #log the change here
  end

end

그리고 간단히 다음과 같이 호출하십시오.

LoggedUpdater.save!(user)

추가 저장 후 조치 오브젝트를 주입하여 확장 할 수도 있습니다.

LoggedUpdater.save(user, [EmailLogger.new, MongoLogger.new])

그리고 '엑스트라'의 예를 들기 위해. 그래도 조금 스파이 킹하고 싶을 수도 있습니다.

class EmailLogger
  def call(msg)
    #send email with msg
  end
end

이 방법이 마음에 들면 Bryan Helmkamps 7 Patterns 블로그 게시물을 읽는 것이 좋습니다 .

편집 : 또한 위의 솔루션을 사용하면 필요할 때 트랜잭션 논리를 추가 할 수 있다고 언급해야합니다. 예 : ActiveRecord 및 지원되는 데이터베이스 :

class LoggedUpdater

  def self.save!([records])
    ActiveRecord::Base.transaction do
      records.each(&:save!)
      #log the changes here
    end
  end

end


-2

나는 같은 문제가있다! 모델 변경 사항을 추적 할 수 있도록 ActiveModel :: Dirty 솔루션을 찾았습니다!

include ActiveModel::Dirty
before_save :notify_categories if :data_changed? 


def notify_categories
  self.categories.map!{|c| c.update_results(self.data)}
end

http://api.rubyonrails.org/classes/ActiveModel/Dirty.html

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.