2014-11-14 2 views
2

У меня есть следующие классы:Рубин Метапрограммирование Q: Вызов метода внешнего класса на after_save

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

Пост является ActiveRecord и награда имеет can_award? класса, который принимает объект post и проверяет, соответствует ли он некоторым критериям. Если да, он обновляет post.owner.awards.

Я знаю, что могу сделать это с использованием шаблона Observer (я тестировал его, и код отлично работает). Однако для этого требуется добавить дополнительный код модели. Я бы не хотел прикоснуться к модели вообще, если это возможно. То, что я хотел бы сделать, это запустить проверку премии, как это (триггер будет вызываться при загрузке класса):

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-to-a- rails-model? rq = 1 – Anand

+0

, но все равно вы должны включить его в свою модель! Я не уверен, хотя – argentum47

ответ

0

Поскольку вы добавляете award_callback как class способ. Держу пари, что он будет зарегистрирован, если вы используете методы класса grep.

Так измените свой код, как показано ниже. Он должен работать нормально.

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

Вы должны подумать, почему вы хотите сделать это таким образом. Я бы сказал, что это даже хуже, чем использование шаблона наблюдателя. Вы нарушаете принцип наименьшего удивления (также называемый принципом наименьшего удивления).

Представьте, что это более крупный проект, и я пришел в качестве нового разработчика для этого проекта. Я отлаживаю проблему, когда сообщение не сохраняет правильно. Естественно, я сначала рассмотрю код модели. Я мог бы даже пройти через код контроллера сообщений. При этом не будет никаких указаний на то, что в сохранении Почты есть второй класс. Мне было бы намного сложнее понять, в чем проблема, так как я не знал, что код из AwardOne даже задействован. В этом случае было бы наиболее предпочтительным сделать это в контроллере. Это место, которое легче всего отлаживать и понимать (поскольку модели уже имеют достаточные обязанности и обычно больше).

Это распространенная проблема с метапрограммированием. В большинстве случаев лучше избегать его именно из-за принципа наименьшего удивления. Вы будете рады, что не использовали его год спустя, когда вернетесь к этому коду из-за некоторой проблемы, которую вам нужно отлаживать. Вы забудете, какую «умную» вещь вы сделали. Если у вас нет добрая причина, то просто придерживайтесь установленных конвенций, они есть не просто так.

Если ничто иное, по крайней мере, не выяснит способ сделать это элегантно, объявив что-то в модели Post. Например, зарегистрировав метод класса awardable на ActiveRecord::Base. Но лучший подход, вероятно, будет делать это в контроллере или через объект службы. Это не Ответственность AwardOne для обработки, как Post должен быть сохранен!

Смежные вопросы