Есть ли крючок, похожий на класС# унаследованный, который запускается только после определения класса 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