2017-07-26 64 views
0

一些背景:活動記錄,使用實例變量在before_destroy塊定義

一個公司有很多用戶。誰是主人。 您可以銷燬除所有者以外的所有公司。 摧毀業主,就像摧毀公司。所以你必須做company.destroy 我防止銷燬用戶,如果他是所有者,除非你正在銷燬公司。

我的代碼:

class Company < ActiveRecord::Base 
    before_destroy { @bool_allow_owner_be_destroyed = true ; p object_id } 

    belongs_to :owner,  class_name: 'User', foreign_key: 'owner_id' 
    has_many :employees, class_name: 'User', dependent: :destroy 

    def can_owner_be_destroyed? 
    p @bool_allow_owner_be_destroyed 
    p object_id 
    [email protected]_allow_owner_be_destroyed 
    end 
end 

class User < ActiveRecord::Base 
    belongs_to  :company 
    before_destroy :cancel_if_owner 

    def cancel_if_owner 
    !self.is_owner? || self.company.can_owner_be_destroyed? 
    end 
end 

我的問題:

當我打電話company.destroy我看到我的調試。我通過before_destroy { @bool_allow_owner_be_destroyed = true ; p object_id },我看到了我的object_id。 當我通過can_owner_be_destroyed?方法時,我有相同的object_id,但我的@bool_allow_owner_be_destroyednil。這是我的代碼中唯一的地方,我觸摸了@bool_allow_owner_be_destroyed變量。

任何想法?

回答

0

根據聲明的順序,在爲父對象運行before_destroy回調之前,可以銷燬依賴對象。

您可以測試這個自己:

class Company < ActiveRecord::Base 
    belongs_to :owner,  class_name: 'User', foreign_key: 'owner_id' 
    has_many :employees, class_name: 'User', dependent: :destroy 

    before_destroy do 
    @test_variable = "hi" 
    puts @test_variable 
    puts "Company destroyed" 
    end 
end 

class User < ActiveRecord::Base 
    belongs_to  :company 

    before_destroy do 
    puts company.instance_variable_get(:@test_variable) 
    puts "User destroyed" 
    end 
end 

"Company destroyed"將所有的users都已經走了後,最後打印的東西,你會得到一個空字符串puts版代替「喜」爲每個用戶,因爲@test_variable尚未設置。但是,如果您在聲明關聯之前宣佈該回叫,則會首先打印"Company destroyed",並且您應該看到"hi"已打印,因爲每個User均爲destroy ed。

如果你問我,那是非常意外的。回調可能會使事情過於複雜,並且經常被濫用,我們如何保證我們確切知道在如此複雜的執行流程中發生了什麼?紅寶石有子對象內置回調系統,該系統更容易理解,並在其行爲很容易預測:super

class Company 
    def destroy(*args, &block) 
    @bool_allow_owner_be_destroyed = true 
    super 
    end 
end 

任何你做之前super呼叫將得到保證之前,任何的運行依賴對象被銷燬或由destroy觸發的任何其他事件。使用super時沒有歧義。

要取消destroyowner您還可以使用super以及早期return

class User < ActiveRecord::Base 
    belongs_to :company 

    def destroy(*args, &block) 
    return unless can_be_destroyed? 
    super 
    end 

    def can_be_destroyed? 
    !is_owner? || company.can_owner_be_destroyed? 
    end 
end 

總的來說,我會建議,試圖用隨身攜帶的實例變量一個ActiveRecord對象的狀態可能會惹上麻煩你在某些時候,尤其是當相關的對象將依賴於該國或你有多個實例您應用程序在同一時間運行。

您可以改爲將其設置爲db字段並堅持更改。

class AddAllowOwnerBeDestroyedColumnToCompanies < ActiveRecord::Migration 
    def change 
    add_column :companies, :allow_owner_be_destroyed, :boolean, default: false 
    end 
end 

class Company < ActiveRecord::Base  
    belongs_to :owner,  class_name: 'User', foreign_key: 'owner_id' 
    has_many :employees, class_name: 'User', dependent: :destroy 

    # You won't need this method b/c you now have `#allow_owner_be_destroyed?` for free 
    # def can_owner_be_destroyed? 
    # p allow_owner_be_destroyed 
    # p object_id 
    # allow_owner_be_destroyed 
    # end 

    def destroy(*args, &block) 
    update allow_owner_be_destroyed: true 
    super 
    end 
end 

class User < ActiveRecord::Base 
    belongs_to :company 

    def destroy(*args, &block) 
    return unless can_be_destroyed? 
    super 
    end 

    def can_be_destroyed? 
    !is_owner? || company.allow_owner_be_destroyed? 
    end 
end 
+0

感謝您的非常詳細的答案。 但我不同意你的看法。你說「before_destroy回調爲父對象運行」,但我認爲這取決於你的聲明的順序。 在你的例子中,你已經改變了聲明的順序 – djothefou

+0

我認爲執行順序對我們兩個人都不明顯的事實是足夠的理由,不要依賴回調來做這件事,不是嗎? :)回調功能強大,但對於可通過內置語言功能實現的功能也可能過於複雜。你也不能在回調中拋出像'destroy'這樣的動作。我認爲使用Ruby的內置回調系統(即'super')更適合你。你可以精確地控制它的執行時間,並且能夠早於你的'destroy'方法返回,與回調不同。 Ruby的強大,應該用它! –

+0

我也仍然不認爲你應該依賴一個實例變量來承載這個狀態。我不會假設我知道什麼時候一個新的對象將會或者不會像這樣在一個很長的執行流中被實例化,消除你的實例變量。不妨一次額外打一次數據庫以保證對象狀態的完整性 –