2014-11-14 12 views
2

我有以下類別:紅寶石元編程問:調用外部類的方法after_save的

class AwardBase 
class AwardOne < AwardBase 
class Post < ActiveRecord::Base 

崗位是一個ActiveRecord,而獎有can_award?類方法需要一個post對象並檢查它是否符合某些條件。如果是,它會更新post.owner.awards。

我知道我可以使用觀察者模式來做到這一點(我測試了它,代碼工作正常)。但是,這需要我爲該模型添加額外的代碼。如果可能的話,我不想觸摸模型。我希望做的是運行大獎支票像這樣(觸發器將在類加載時被調用):

class AwardOne < AwardBase 
    trigger :post, :after_save 

    def self.can_award?(post) 
    ... 
    end 
end 

與上面的代碼的目的是,它會自動添加AwardOne.can_award?到郵政after_save的方法

所以基本上我想要做的是讓trigger呼叫相當於:

class Post < ActiveRecord::Base 
    after_save AwardOne.can_award?(self) 
    ... 
end 

這基本上是:

class Post < ActiveRecord::Base 
    after_save :check_award 

    def check_award 
    AwardOne.can_award?(self) 
    end 
end 

哪有我這樣做而不修改Post類?


這裏是我做了(這似乎不工作):

class AwardBase 

    def self.trigger (klass, active_record_event) 
    model_class = klass.to_class 

    this = self 
    model_class.instance_eval do 
     def award_callback 
     this.can_award?(self) 
     end 
    end 

    model_class.class_eval do 
     self.send(active_record_event, :award_callback) 
    end 
    end 

    def self.can_award? (model) 
    raise NotImplementedError 
    end 
end 

上面的代碼失敗,出現錯誤:

NameError (undefined local variable or method `award_callback' for #<Post:0x002b57c04d52e0>): 
+0

請提供此錯誤的堆棧跟蹤。 –

+0

您可以使用ActiveSupport :: Concerns,與此處接受的答案類似:http://stackoverflow.com/questions/12084234/how-do-i-use-ruby-metaprogramming-to-add-callbacks-toa-a- rails-model?rq = 1 – Anand

+0

但你仍然需要將它包含在你的模型中!我不太確定 – argentum47

回答

0

因爲要添加award_callbackclass方法。我敢打賭,如果你使用類方法,它將被註冊。

所以改變你的代碼如下。它應該工作正常。

model_class.class_eval do ## Changed to class_eval 
    def award_callback 
    this.can_award?(self) 
    end 
end 

讓我舉一個具體的例子,如果這聽起來令人困惑。

class Test 
end 

Test.instance_eval do 
    def class_fun 
    p "from class method " 
    end 
end 

Test.class_eval do 
    def instance_fun 
    p "from instance method " 
    end 
end 


Test.methods.grep /class_fun/ 
# => [:class_fun] 

Test.instance_methods.grep /instance_fun/ 
# => [:instance_fun] 

Test.class_fun 
# => "from class method " 

Test.new.instance_fun 
# => "from instance method " 
1

你應該考慮你爲什麼要這樣做。我認爲這比使用觀察者模式更糟糕。你違反了最小驚喜原則(也叫做最小驚訝原則)。

想象一下,這是一個更大的項目,我來作爲這個項目的新開發者。我正在調試Post沒有正確保存的問題。當然,我會先通過模型的代碼。我甚至可能會通過帖子控制器的代碼。這樣做不會有任何跡象表明有第二類保存郵政。我很難弄清楚問題是什麼,因爲我不知道AwardOne的代碼甚至參與其中。 在這種情況下,實際上最好在控制器中這樣做。這是最容易調試和理解的地方(因爲模型已經有足夠的職責,而且通常更大)。

這是元編程的一個常見問題。大多數情況下,最好是避免出現這種情況,正是出於最少驚喜的原則。由於某些問題需要調試,因此在您回到此代碼之後的一年內,您會很高興您沒有使用它。你會忘記你做了什麼「聰明」的事情。如果你沒有一個好的理由,那就堅持既定的慣例,他們在那裏是有原因的。

如果沒有別的,那麼至少找出一種方法來優雅地通過在Post模型中聲明某些東西來做到這一點。例如,通過在ActiveRecord::Base上註冊awardable類方法。但最好的方法可能是在控制器中或通過服務對象來完成。這是不是責任AwardOne來處理如何保存Post