Есть ли крючок, похожий на класС# унаследованный, который запускается только после определения класса Ruby?
#inherited
вызывается сразу после оператора class Foo
. Я хочу что-то, что будет работать только после инструкции end
, которая закрывает объявление класса.
Вот какой код, чтобы показать, что мне нужно:
class Class
def inherited m
puts "In #inherited for #{m}"
end
end
class Foo
puts "In Foo"
end
puts "I really wanted to have #inherited tiggered here."
### Output:
# In #inherited for Foo
# In Foo
# I really wanted to have #inherited tiggered here.
Есть ли что-нибудь подобное? Может ли быть создано? Мне совершенно не повезло?
Ответы
Ответ 1
Вам может быть не повезло. Но это только предупреждение, а не окончательный ответ.
Ruby перехватывает начало определения класса, а не конец, для Class#inherited
b/c определения класса ruby не имеют реального конца. Они могут
быть вновь открыта в любое время.
несколько раз говорили о добавлении триггера const_added, но он еще не прошел. Из Matz:
Я не буду внедрять все возможные крючки. Поэтому, когда кто-то приходит с более конкретным использованием, я буду рассматривать это снова. Это be const_added
, а не class_added
.
Таким образом, это может справиться с вашим делом - но я не уверен (он может запускаться и в начале, когда он в конечном итоге реализован).
Что вы пытаетесь сделать с этим триггером? Это может быть другой способ сделать это.
Ответ 2
Я опаздываю, но я думаю, что у меня есть ответ (любому, кто здесь посещает).
Вы можете проследить до тех пор, пока не найдете окончание определения класса. Я сделал это в методе, который я назвал after_inherited
:
class Class
def after_inherited child = nil, &blk
line_class = nil
set_trace_func(lambda do |event, file, line, id, binding, classname|
unless line_class
# save the line of the inherited class entry
line_class = line if event == 'class'
else
# check the end of inherited class
if line == line_class && event == 'end'
# if so, turn off the trace and call the block
set_trace_func nil
blk.call child
end
end
end)
end
end
# testing...
class A
def self.inherited(child)
after_inherited do
puts "XXX"
end
end
end
class B < A
puts "YYY"
# .... code here can include class << self, etc.
end
Вывод:
YYY
XXX
Ответ 3
Посмотрите defined
gem. Вы можете сделать следующее:
require "defined"
Defined.enable!
class A
def self.after_inherited(child)
puts "A was inherited by #{child}"
end
def self.defined(*args)
superclass.after_inherited(self) if superclass.respond_to?(:after_inherited)
end
end
class B < A
puts "B was defined"
end
Вывод:
B was defined
A was inherited by B
Однако self.defined
будет запущен после каждого определения класса. Поэтому, если вы добавите следующий код
class B < A
puts "B was redefined"
end
Вы увидите
B was defined
A was inherited by B
B was redefined
A was inherited by B
Есть способы избежать того, что я могу вам объяснить, если вы хотите.
Однако, как сказано, вероятно, есть лучшие способы решения вашей проблемы.
Ответ 4
Используйте TracePoint
чтобы отслеживать, когда ваш класс отправляет событие :end
.
Этот модуль позволит вам создать self.finalize
вызов self.finalize
в любом классе.
module Finalize
def self.extended(obj)
TracePoint.trace(:end) do |t|
if obj == t.self
obj.finalize
t.disable
end
end
end
end
Теперь вы можете расширить свой класс и определить self.finalize
, который запустится, как только закончится определение класса:
class Foo
puts "Top of class"
extend Finalize
def self.finalize
puts "Finalizing #{self}"
end
puts "Bottom of class"
end
puts "Outside class"
# output:
# Top of class
# Bottom of class
# Finalizing Foo
# Outside class
Ответ 5
Если вы готовы предположить, что Ruby реализует ObjectSpaces, вы могли бы просмотреть все экземпляры модели после факта, а затем соответствующим образом изменить их. Google предлагает http://phrogz.net/ProgrammingRuby/ospace.html
Ответ 6
Rails имеет метод subclasses
, возможно, стоит посмотреть на реализацию:
class Fruit; end
class Lemon < Fruit; end
Fruit.subclasses # => [Lemon]
Ответ 7
У меня был такой же вопрос, когда мы пытались автоматически добавлять общие проверки ко всем моделям. Проблема заключалась в том, что если модель использовала имя_set_table_name, тогда мой код, добавивший добавленные проверки на основе типов данных БД, взорвался бы, потому что он угадывал имя таблицы на основе имени модели (потому что #inherited получил вызов ПЕРЕД именем #set_table_name).
Так же, как и вы, я действительно искал способ заставить #inherited стрелять ПОСЛЕ того, что все в модели уже загружено. Но мне не нужно было так далеко, все, что мне было нужно, это то, что срабатывало ПОСЛЕ #set_table_name. Таким образом, это оказалось простым, как наложение метода. Вы можете увидеть пример того, что я сделал здесь:
https://gist.github.com/1019294
В приведенном выше комментарии вы сказали: "Я пытаюсь добавить поведение к моделям activerecord, но мне нужно, чтобы все настройки модели проходили, прежде чем я начну с ними". Таким образом, мой вопрос к вам, если есть конкретные настройки модели, о которых вы заботитесь, если да, то, возможно, вы могли бы использовать подход сглаживания для достижения желаемого результата.
Ответ 8
Нет, насколько я знаю, такого зацепа нет, но хорошо, что вы можете сделать это сами. Вот возможная реализация:
Не супер чистый, но он работает:
puts RUBY_VERSION # 2.4.1
class Father
def self.engage_super_setup(sub)
puts "self:#{self} sub:#{sub}"
sub.class_eval do
puts "toy:#{@toy}"
end
end
def self.super_setup
if self.superclass.singleton_methods.include?(:engage_super_setup)
superclass.engage_super_setup(self)
end
end
end
Son = Class.new(Father) do
@toy = 'ball'
end.tap { |new_class| new_class.super_setup } # this is needed to:
# 1. call the super_setup method in the new class.
# 2. we use tap to return the new Class, so this class is assigned to the Son constant.
puts Son.name # Son
Выход:
self:Father sub:#<Class:0x0055d5ab44c038> #here the subclass is still anonymous since it was not yet assigned to the constant "Son"
toy:ball # here we can see we have acess to the @toy instance variable in Son but from the :engage_super_setup in the Father class
Son # the of the class has been assigned after the constant, since ruby does this automatically when a class is assigned to a constant
Так что это, очевидно, не так чисто, как крючок, но я думаю, что в итоге мы получили довольно хороший результат.
Если бы мы попытались сделать то же самое с: унаследованным, к сожалению, невозможно, потому что: унаследованный вызывается еще до того, как исполнитель выполнения в теле класса:
puts RUBY_VERSION # 2.4.1
class Father
def self.inherited(sub)
puts "self:#{self} sub:#{sub}"
sub.class_eval do
puts "toy:#{@toy.inspect}"
end
end
end
class Son < Father
puts "we are in the body of Son"
@toy = 'ball'
end
puts Son.name # Son
Выход:
self:Father sub:Son # as you can see here the hook is executed before the body of the declaration Son class runs
toy:nil # we dont have access yet to the instance variables
we are in the body of Son # the body of the class declaration begins to run after the :inherited hook.
Son