Есть ли хук, похожий на наследуемый Class #, который срабатывает только после определения класса Ruby? - PullRequest
17 голосов
/ 26 апреля 2009

#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.

Существует ли что-нибудь подобное? Это может быть создано? Мне не повезло?

Ответы [ 8 ]

7 голосов
/ 18 августа 2011

Я опаздываю, но думаю, что у меня есть ответ (для всех, кто посещает здесь).

Вы можете отслеживать, пока не найдете конец определения класса. Я сделал это методом, который я назвал 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
7 голосов
/ 26 апреля 2009

Возможно, вам не повезло. Но это только предупреждение, а не окончательный ответ.

Ruby перехватывает начало определения класса, а не его конец, для Class#inherited b / c определения класса ruby ​​не имеют реального конца. Они могут быть открыт в любое время.

Пару лет назад велись разговоры о добавлении триггера const_added , но он еще не прошел. Из маца :

Я не собираюсь реализовывать все возможные ловушки. Поэтому, когда кто-то приходит с более конкретным использованием, я рассмотрю это снова. Было бы быть const_added, а не class_added.

Так что это может справиться с вашим делом - но я не уверен (это может сработать и при запуске, когда он в конечном итоге будет реализован).

Что вы пытаетесь сделать с этим триггером? Может быть другой способ сделать это.

4 голосов
/ 17 августа 2011

Взгляните на defined драгоценный камень. Вы можете сделать так:

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

Есть способы избежать того, что я могу вам объяснить, если хотите.

Однако, как уже говорилось, возможно, есть более эффективные способы решения вашей проблемы.

3 голосов
/ 01 января 2016

Техника, использующая set_trace_func, больше не работает. Вместо этого используйте TracePoint и отслеживайте, когда ваш класс отправляет событие :end.

Этот модуль позволит вам создать обратный вызов 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
1 голос
/ 01 июня 2012

В Rails есть метод subclasses, возможно, стоит взглянуть на реализацию:

class Fruit; end

class Lemon < Fruit; end

Fruit.subclasses # => [Lemon]
1 голос
/ 26 апреля 2009

Если вы хотите предположить, что ваш Ruby реализует ObjectSpaces, вы можете посмотреть все экземпляры модели по факту, а затем соответствующим образом изменить их. Google предлагает http://phrogz.net/ProgrammingRuby/ospace.html

0 голосов
/ 09 мая 2019

Нет, такого знания нет, но хорошо, что вы можете сделать это самостоятельно . Вот возможная реализация:

Не супер чистый, но работает:

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
0 голосов
/ 10 июня 2011

У меня возник тот же вопрос, когда я пытался автоматически добавить общие проверки для всех моделей. Проблема заключалась в том, что если бы модель использовала #set_table_name, то мой код, который добавил добавленные проверки, основанные на типах данных БД, взорвался бы, потому что он угадывал имя таблицы на основе имени модели (потому что #inherited вызывался ДО #set_table_name).

Так же, как и вы, я действительно искал способ получить #inherited для стрельбы ПОСЛЕ того, как все в модели уже было загружено. Но мне не нужно было заходить так далеко, все, что мне нужно, это что-то, что запускается ПОСЛЕ #set_table_name. Таким образом, оказалось, что это просто, как псевдоним метода. Вы можете увидеть пример того, что я сделал здесь: https://gist.github.com/1019294

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

...