2013-03-01 52 views
142

隨着Observers正式removed from Rails 4.0,我很好奇其他開發人員正在使用他們的地方。 (除了使用提取的寶石之外)雖然觀察者當然受到了濫用,並且有時很容易變得笨拙,但除了緩存清理之外,還有許多用例是有益的。Rails Observer替代品4.0

以一個需要跟蹤模型更改的應用程序爲例。觀察者可以很容易地觀察模型A的變化,並在數據庫中用模型B記錄這些變化。如果你想觀察幾個模型的變化,那麼一個觀察者就可以處理這個變化。

在Rails 4,我很好奇,其他開發人員使用的地方觀察什麼樣的戰略來重建該功能。

就個人而言,我傾向於一種「脂肪控制器」的實施,在這些變化中的每個模型控制器的創建/更新/刪除方法跟蹤。雖然它稍微擴大了每個控制器的行爲,但它確實有助於可讀性和理解,因爲所有代碼都位於同一位置。缺點是現在有幾個控制器中的代碼非常相似。將代碼提取到輔助方法中是一種選擇,但您仍然會對那些遍地亂放的方法進行調用。不是世界的盡頭,但也不完全符合「瘦身控制器」的精神。

ActiveRecord的回調是另一種可能的選擇,雖然一個我不喜歡的個人,因爲它在我看來,往往夫妻兩個不同的模型太緊密地聯繫在一起。

在鋼軌4

所以,沒有觀察世界,如果你要創建一個新的紀錄再創歷史新高後創建/更新/銷燬,你會用什麼設計模式?脂肪控制器,ActiveRecord回調,或其他完全嗎?

謝謝。

+3

我真的很驚訝有沒有張貼這個問題更多的答案。有點令人不安。 – courtsimas 2013-05-07 20:05:21

+18

https://github.com/krisleech/wisper – Kris 2013-05-17 14:36:26

回答

13

使用活動記錄回調只是簡單地翻轉耦合的依賴關係。例如,如果您有modelACacheObserver觀察modelA rails 3樣式,您可以刪除CacheObserver而不會出現問題。現在,A必須在保存後手動調用CacheObserver,這將是rails 4.您只需移動依賴項,以便可以安全地刪除A而不是CacheObserver

現在,從我的象牙塔我喜歡觀察者依賴於它觀察的模型。我是否在乎足夠混亂我的控制器?對我而言,答案是否定的。

想必你已經花了心思,爲什麼你想/需要的觀測,並因此形成取決於其觀測模型是不是一個可怕的悲劇。

我也有一個(合理的接地,我認爲)厭惡任何形式的觀察員依賴於一個控制器的動作。突然之間,你必須將觀察者注入任何控制器動作(或其他模型)中,以便更新要觀察的模型。如果您可以保證您的應用只會通過創建/更新控制器操作修改實例,那麼您將獲得更多的權力,但這不是我會對Rails應用做出的假設(考慮嵌套表單,模型業務邏輯更新關聯等)

+1

感謝您的評論@agmin。如果有更好的設計模式,我很樂意使用Observer。我最感興趣的是其他人如何構建他們的代碼和依賴以提供類似的功能(不包括緩存)。就我而言,我希望在更新屬性時隨時記錄對模型的更改。我曾經使用Observer來做到這一點。現在我試圖在胖控制器,AR回調或其他我沒有想到的東西之間做出決定。目前看起來並不高雅。 – kennyc 2013-03-02 01:43:32

32

現在他們在plugin

可我還建議an alternative這將給你喜歡的控制器:

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 
+0

ActiveSupport :: Notifications怎麼樣? – svoop 2014-03-03 20:30:45

+0

@svoop'ActiveSupport :: Notifications'適用於檢測,而不是通用的子/ pub。 – Kris 2014-03-05 09:58:24

+0

@Kris - 你是對的。它主要用於檢測,但我不知道是什麼阻止它被用作pub/sub的通用方法?它確實提供了基本的構建模塊,對吧?換句話說,與'ActiveSupport :: Notifications'相比,wisper的優缺點是什麼? – gingerlime 2014-08-05 06:37:23

4

我的替代到Rails 3個觀察員是一個手工執行,它利用模型中定義的回調還設法(如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 callba中正。例如,我的用戶模型公開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)有他的名字的方法模型註冊這些事件的任何觀察員匹配事件。正如人們所預料的,多個觀察者可以爲同一個事件註冊,並(在參考了原來的問題的第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 

一個需要注意的是,在所有模型中公開的所有事件的名稱必須是唯一的。

-2

我有同樣的probjem!我找到了解決方案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

18

我的建議是閱讀詹姆斯Golick的博客文章在http://jamesgolick.com/2010/3/14/crazy-heretical-and-awesome-the-way-i-write-rails-apps.html(試圖忽略標題怎麼聽起來不莊重)。

早在一天,這一切都是「胖模型,瘦控制器」。然後,脂肪模型成爲一個巨大的頭痛,特別是在測試過程中。最近,推一直瘦車型 - 想法是,每個類應處理一個責任和模特的工作就是你的數據保存到數據庫中。那麼,我所有複雜的商業邏輯究竟在哪裏結束呢?在業務邏輯類中 - 代表事務的類。

當邏輯開始變得複雜時,這種方法可能會變成泥濘(giggity)。然而,這個概念是合理的,而不是用難以測試和調試的回調或觀察者隱式觸發事物,而是在類中將觸發事件顯式觸發,該類將邏輯置於模型之上。

+3

在過去的幾個月裏,我一直在做這樣的項目。你最終會得到很多小服務,但測試和維護的方便性絕對超過了這些缺點。我在這個中型系統上的相當全面的規格仍然只需要5秒鐘就可以運行:) – 2014-02-03 08:43:45

+0

也被稱爲PORO(普通老式紅寶石對象)或服務對象 – 2017-03-26 13:38:44

71

看看Concerns

創建一個名爲擔憂在你的模型目錄中的文件夾。添加有一個模塊:

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 

取決於你在做什麼,這可能讓你關閉而不觀察員。

+18

這種方法存在問題。值得注意的是,它不會清理你的模型; _include_將模塊中的方法複製回您的班級。將類方法提取到模塊中可能需要關注它們,但該類仍然臃腫。 – 2014-03-13 16:18:50

+12

標題是'Rails Observer Alternatives for 4.0'而不是'我如何最小化膨脹'。 擔心的事情不是Steven的工作嗎?不,建議'膨脹'是爲什麼這不起作用的原因,因爲觀察者的替代品不夠好。你將不得不提出一個更好的建議來幫助社區或解釋爲什麼擔心不能替代觀察員。希望你會陳述兩個= D – UncleAdam 2014-03-13 20:19:33

+10

膨脹始終是一個問題。一個更好的選擇是[wisper](https://github.com/krisleech/wisper),如果正確實施,它可以讓你把問題提取出來,分離出與模型沒有緊密耦合的類。這也使得它更容易單獨測試 – 2014-03-28 02:03:41

13

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 
8

在某些情況下,我只是用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 
3

我認爲觀察員的問題被棄用是不是觀察員和他們自己的,但他們是被濫用的藥物爲不良。

我會提醒您不要在回調中添加太多的邏輯,或者只是簡單地移動代碼來模擬觀察者的行爲,此時觀察者模式已經有了完善的解決方案。

如果使用觀察者有意義,那麼通過一切手段使用觀察者。只要理解你需要確保你的觀察者邏輯遵循聲音編碼實踐,例如SOLID。如果你想重新添加到您的項目 https://github.com/rails/rails-observers

看到這個簡短的線程

觀察員寶石可以用RubyGems的,而不是充分和全面的討論,我覺得基本論點是有效的。 https://github.com/rails/rails-observers/issues/2

1

如何使用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