Is there a hook similar to Class#inherited that's triggered only after a Ruby class definition?

前端 未结 8 2053
无人共我
无人共我 2020-12-03 13:35

#inherited is called right after the class Foo statement. I want something that\'ll run only after the end statement that closes the c

相关标签:
8条回答
  • 2020-12-03 14:08

    Rails has a subclasses method, it might be worth looking at the implementation:

    class Fruit; end
    
    class Lemon < Fruit; end
    
    Fruit.subclasses # => [Lemon]
    
    0 讨论(0)
  • 2020-12-03 14:09

    I had the same question when trying to automatically add generic validations to all models. The problem was that if the model used #set_table_name then my code that added added the validations based on the DB data-types would blow up because it was guessing at the table name based on the name of the model (because #inherited was getting called BEFORE #set_table_name).

    So just like you, I was really looking for a way to get #inherited to fire AFTER everything in the model had already been loaded. But I didn't really need to take it that far, all I needed was something that fired AFTER #set_table_name. So it turned out to be a simple as aliasing the method. You can see an example of what I did here: https://gist.github.com/1019294

    In a comment above you stated "I'm trying to add behavior to activerecord models, but I need all the model customizations to go through before I mess with it". So my question to you is if there are specific model customizations you care about, if so then maybe you could use an aliasing approach to achieve your desired result.

    0 讨论(0)
  • 2020-12-03 14:14

    Use TracePoint to track when your class sends up an :end event.

    This module will let you create a self.finalize callback in any class.

    module Finalize
      def self.extended(obj)
        TracePoint.trace(:end) do |t|
          if obj == t.self
            obj.finalize
            t.disable
          end
        end
      end
    end
    

    Now you can extend your class and define self.finalize, which will run as soon as the class definition ends:

    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
    
    0 讨论(0)
  • 2020-12-03 14:24

    You may be out of luck. But that's only a warning, not a definitive answer.

    Ruby hooks the beginning of the class definition, rather than the end, for Class#inherited b/c ruby class definitions don't have a real end. They can be reopened any time.

    There was some talk a couple years ago about adding a const_added trigger, but it hasn't gone through yet. From Matz:

    I'm not going to implement every possible hook. So when somebody comes with more concrete usage, I will consider this again. It would be const_added, not class_added.

    So this might handle your case - but I'm not sure (it may trigger on the start too, when it's eventually implemented).

    What are you trying to do with this trigger? There may be another way to do it.

    0 讨论(0)
  • 2020-12-03 14:26

    Take a look at defined gem. You can do like this:

    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
    

    Output:

    B was defined
    A was inherited by B
    

    However self.defined will be fired after each class definition. So if you add the following code

    class B < A
      puts "B was redefined"
    end
    

    You will see

    B was defined
    A was inherited by B
    B was redefined
    A was inherited by B
    

    There are ways to avoid that which I can explain to you if you want.

    However, as said there are probably better ways to solve your problem.

    0 讨论(0)
  • 2020-12-03 14:29

    I am late, but I think I have an answer (to anyone who visit here).

    You can trace until you find the end of the class definition. I did it in a method which I called 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
    

    Output:

    YYY
    XXX
    
    0 讨论(0)
提交回复
热议问题