3

I know that there are several useful hook methods that Ruby provides. However, I couldn't seem to find anything like a 'constant_added' hook. The reason I would like one is because I wish to override it so that whenever a constant is added, certain other actions are performed with regards to updating some variables without having to call some sort of update method myself.

More specifically, I am trying to keep a list of all existing constants that match a particular regex, but without looping over all the existing constants searching for matches at certain intervals or updating the list whenever the last constant added matches the regex; I believe this would require an explicit method call.

If a hook does not already exist, would it be possible to create one, and if not, how might I go about getting this behavior?

Daniel Brady
  • 934
  • 3
  • 11
  • 27
  • This seems like [a solution to a problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). What problem are you solving by storing all this as constants? Might a Hash-like object be more sensible? Then you can override `#[]=`. – Schwern Dec 21 '22 at 00:22
  • I don't remember honestly, this question is ten years old . But based on where I was in my life back then, I think I was simply eyes-deep in exploring the language: the summer of '13 was when I first encountered Ruby at an internship. – Daniel Brady Jan 18 '23 at 16:23

3 Answers3

2

I once did it in Ruby 1.9 using Kernel.set_trace_func. You can keep checking for "line" events. Whenever such event occurs, take the difference of the constants from the previous time using the constants method. If you detect a difference, then that is the moment a constant was added/removed.

Kernel.set_trace_func ->event, _, _, _, _, _{
  case event
  when "line"
    some_routine
  end
}

Ruby 2.0 may have more powerful API, which allows for a more straightforward way to do it, but I am not sure.

sawa
  • 165,429
  • 45
  • 277
  • 381
  • Ruby 2.0 provides an object-oriented API of `Kernel::set_trace_func` called `TracePoint`. Both of these things accomplish my goal, but I wonder at the performance hit? When I tested it out in IRB, there was a very noticeable lag while typing and evaluating as soon as I activated the trace function. I would say that this would be a great solution if I needed to monitor a particular block of code for constant creation, but I don't think it would be a practical replacement for a hook method if it impacts the performance so much, unfortunately :\ – Daniel Brady Jul 01 '13 at 16:08
  • `Kernel::set_trace_func` (or `TracePoint`) is pretty cool though! I certainly may not use it every day, but it's useful to know that it exists if I need it. – Daniel Brady Jul 01 '13 at 16:11
  • 1
    If you want to track class names, you can hook into `Class#inherited` which will be called every time a class is defined. You can also hook into `Module#extended` and `Module#included` to track when a module is being used. Otherwise, TracePoint is your friend. – Matt Jan 26 '14 at 01:01
  • Great advice! What do you mean by `take the difference of the constants from the previous time using the constants method` exactly? `Module.constants` or `Object.constants`? – Ehsan Apr 07 '22 at 21:26
1

Module#const_added is coming in Ruby 3.2. Looks like this:

Foo::BAR = 42 # call Foo.const_added(:BAR)
class Foo::Baz; end # call Foo.const_added(:Baz)
Foo.autoload(:Something, "path") # call Foo.const_added(:Something)
Chris Salzberg
  • 27,099
  • 4
  • 75
  • 82
0

So I think I've come up with an alternative:

class Object
  def typedef &block
    Module.new &block
  end
end

module Type
  @@types = []
  @@prev = Module.constants

  def self.existing_types
    diff = Module.constants - @@prev
    @@prev = Module.constants

    new_types = diff.select { |const| const.to_s.start_with? 'TYPE_' }

    @@types |= new_types
  end
end

Instead of monitoring when constants are created, I process the constant list when I access it. So now I can do something like this:

irb(main):002:0> Type.existing_types
=> []
irb(main):003:0> TYPE_rock = typedef do
irb(main):004:1*   def self.crumble
irb(main):005:2>     puts "I'm crumbling! D:"
irb(main):006:2>   end
irb(main):007:1> end
=> TYPE_rock
irb(main):008:0> Type.existing_types
=> [:TYPE_rock]
irb(main):009:0> FOO_CONST = 54
=> 54
irb(main):011:0> TYPE_water = typedef do
irb(main):012:1*   def self.splash
irb(main):013:2>     puts "Water! :3"
irb(main):014:2>   end
irb(main):015:1> end
=> TYPE_water
irb(main):016:0> Type.existing_types
=> [:TYPE_rock, :TYPE_water]
irb(main):017:0> TYPE_rock.crumble
I'm crumbling! D:
=> nil
irb(main):018:0> TYPE_water.splash
Water! :3
=> nil

What do you think? This accomplishes what I want, and it's fairly simple but I'm still new to Ruby and I'm not sure if there's some nice API that Ruby might already provide to do this.

Daniel Brady
  • 934
  • 3
  • 11
  • 27
  • It may accomplish what you want, but is not an answer to you original question. You changed the question. However, it might be a good idea. – sawa Jul 02 '13 at 08:53
  • @sawa Very true. And though I think I'm going to go with my alternative, I am still very interested if people have suggestions about my original question. I've just started learning and using Ruby, but I want to learn and use it well. – Daniel Brady Jul 02 '13 at 18:03