9

Possible Duplicate:
ruby: can I have something like Class#inherited that's triggered only after the class definition?

class A
  def self.inherited(child)
    puts "XXX"
  end
end

class B < A
  puts "YYY"
end

prints out

XXX
YYY

I'd prefer

YYY
XXX

if I could get it somehow.

Community
  • 1
  • 1
bradgonesurfing
  • 30,949
  • 17
  • 114
  • 217
  • 1
    Just answered the same question there: http://stackoverflow.com/questions/790626/ruby-can-i-have-something-like-classinherited-thats-triggered-only-after-the-c/7096259#7096259. – Simon Perepelitsa Aug 17 '11 at 16:30
  • Just looked at that now. Looks very interesting. I'll check in detail in the morning. My specific use case is to force subglasses to implement a specific method. If the method is not there then the check will cause the class definition to fail early. My above example doesn't detail this but the define gem might give me the hooks I need. Thanks. – bradgonesurfing Aug 17 '11 at 21:38
  • It's now possible to do this in Ruby using `TracePoint`. See my answer to the same question above. http://stackoverflow.com/questions/790626/is-there-a-hook-similar-to-classinherited-thats-triggered-only-after-a-ruby-cl/34559282 – user513951 Jan 01 '16 at 19:30
  • Actually, my answer to another similar question (http://stackoverflow.com/questions/32233860/how-can-i-set-a-hook-to-run-code-at-the-end-of-a-ruby-class-definition/34424483#34424483) is probably a better fit for your specific use case. – user513951 Jan 01 '16 at 20:16

3 Answers3

16

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
Sony Santos
  • 5,435
  • 30
  • 41
  • This solution no longer works in modern versions of Ruby. Use `TracePoint`. See http://stackoverflow.com/questions/790626/is-there-a-hook-similar-to-classinherited-thats-triggered-only-after-a-ruby-cl/34559282#34559282 – user513951 Jan 01 '16 at 19:31
1

This is not possible. Consider this: when should a class definition be considered "done" in Ruby?

Instead of self.inherited, I would personally make a class method called finalize! and put your post-class-creation routine in there.

# A silly, contrived example

class A
  def self.author(person)
    @@authors ||= Array.new
    @@authors << person
  end

  def self.finalize!
    @@authors.map! { |a| Author[a] }
  end
end

class B < A
  author "Jason Harris"
  author "George Duncan"

  finalize!
end

You can probably get away with making a wrapper function instead:

class A
  def self.define_authors(&blk)
    yield
    # (...finalize here)
  end
  ...
end

class B < A
  define_authors {
    author "Jason Harris"
    author "George Duncan"
  }
end

...Or do consider that there may be ways where that finalizing step may not be needed.

Rico Sta. Cruz
  • 526
  • 5
  • 14
  • See the comment above about the define gem. It looks like it might be possible. – bradgonesurfing Aug 17 '11 at 21:40
  • This can now be done without installing any gems, by using `TracePoint`. See http://stackoverflow.com/questions/790626/is-there-a-hook-similar-to-classinherited-thats-triggered-only-after-a-ruby-cl/34559282#34559282 – user513951 Jan 01 '16 at 19:32
0

There is no such hook AFAIK.

A workaround could be:

class A
  @@my_lambda = nil
  def self.inherited(child)
    @@my_lambda = lambda { puts "XXX" }
  end

  def self.after_inherited
    @@my_lambda.yield
  end
end

class B < A
  puts "YYY"
  # other definitions
  self.superclass.after_inherited
end

It looks a bit messy but its output is:

YYY
XXX

This way you guarantee that your code in B gets executed before the after_interited() of A. So it does what you want but not the way you want to it.

ayckoster
  • 6,707
  • 6
  • 32
  • 45
  • The question is specifically asking how to trigger an after_inherited callback. Calling a callback explicitly from the child class is not really a callback at all. – bradgonesurfing Aug 17 '11 at 14:43
  • You can't trigger a callback that does not exist however... – Mchl Aug 17 '11 at 14:45
  • @mchl "it's not possible" is a valid answer. If you create that then I'll mark it as the correct one if nobody can come up with a clever hack to solve the problem. The above solution is just a round about way of writing "puts 'YYY'; puts 'XXX'" in the class body of B. Not really that instructive. – bradgonesurfing Aug 17 '11 at 15:17
  • @bradgonesurfing: My solution is just a workaround. I thought you needed working code. Your question is misleading, you should have posted "Is there an inherited ...?" – ayckoster Aug 17 '11 at 15:46
  • 1
    No, it is not misleading. You've just concentrated your answer on matching the output with example, while it was.. well an example to show what comes after what. – Mchl Aug 17 '11 at 19:52
  • Creating a hook is now possible using `TracePoint`. See http://stackoverflow.com/questions/790626/is-there-a-hook-similar-to-classinherited-thats-triggered-only-after-a-ruby-cl/34559282#34559282 – user513951 Jan 01 '16 at 19:33