Feature #7548
openLoad and Require Callbacks
Description
=begin
Should #load and #require have callbacks? e.g.
def required(path)
...
end
def loaded(path, wrap)
...
end
On occasion I have wanted to do load monitoring to track down a bug. This would have made it easier.
Are there any other good use cases?
=end
Updated by Anonymous almost 12 years ago
Is there a particular benefit of this over the current setup of require?
require() calls are not async so I don't really see the benefit of
adding callbacks to them.
You mentioned you wanted to use it for monitoring, perhaps you can
further explain that to clarify what you're trying to get at.
Yorick
Updated by trans (Thomas Sawyer) almost 12 years ago
=begin
There are have been times when I wanted to see what libraries were being loaded and in what order. Usually because of some esoteric bug, and trying to isolate what libraries are involved. In such a case I've had to manually override load and require, something like:
class << Kernel
alias :_require, :require
def require(*args)
$stderr.puts "require: %s" % [args.inspect]
_require(*args)
end
alias :_load, :load
def load(*args)
$stderr.puts "load: %s" % [args.inspect]
_load(*args)
end
end
module Kernel
def require(*args)
Kernel.require(*args)
end
def load(*args)
Kernel.load(*args)
end
end
Notice we have to do both Kernel instance and singleton methods to be sure we catch all of them. These days we might also need to add (({#require_relative})) to that.
So the thought then is, if there were callbacks, it would be easier to do, simply:
def required(*args)
$stderr.puts "require: %s" % [args.inspect]
end
def loaded(*args)
$stderr.puts "load: %s" % [args.inspect]
end
Actually considering this a bit more, it would probably be even better just to have one callback method that passed options to designate which kind of loading took place. e.g.
def loaded(file, opts)
opts[:load] => true means #load was used/
false means #require was used
opts[:relative] => true means #require_relative was used
opts[:wrap] => the wrap argument for #load.
end
=end
Updated by trans (Thomas Sawyer) almost 12 years ago
=begin
Some additional thoughts on this. It occurs to me that #prepend would make this a ((little)) easier now. One could do:
module LoadCallbacks
def load(*args)
super(*args)
$stderr.puts "load: %s" % [args.inspect]
end
def require(*args)
super(*args)
$stderr.puts "require: %s" % [args.inspect]
end
def require_relative(*args)
super(*args)
$stderr.puts "require: %s" % [args.inspect]
end
end
module Kernel
prepend LoadCallbacks
class << self
prepend LoadCallbacks
end
end
That's a little more elegant, but still feels a bit clunky.
I think what would really make the difference, and what would make this callback feature request effectively pointless, is if Ruby had a single method through which all loading was routed. So (({#load})), (({#require})) and (({#require_relative})) would all call this one method passing in options (i.e. named parameters) to tell it which kind of loading is to happen. That would be nice b/c it would allow us a single target method to see what was going on, instead of the current ((six)) methods!
=end
Updated by Anonymous almost 12 years ago
It seems that you are shaping and changing your suggestion with each new post,
so it is a bit hard to react. But the way I see it, your suggestions belong
somewhere to Std-lib 'debug'. Perhaps it might be possible to change stdlib
'debug' to support cherrypicking like ActiveSupport, so you could call
require 'debug/kernel/load_callbacks' if you want those callbacks, but not the
DEBUGGER itself. I am against adding this directly to the core.
Updated by trans (Thomas Sawyer) almost 12 years ago
It seems that you are shaping and changing your suggestion with each new post,
so it is a bit hard to react.
Sorry. It's a process and discussion sort of request, which is why I presented it first with a question.
But the way I see it, your suggestions belong somewhere to Std-lib 'debug'.
Perhaps it might be possible to change stdlib 'debug' to support
cherrypicking like ActiveSupport,
You mean "cherrypicking like Facets" right? ;)
so you could call require 'debug/kernel/load_callbacks' if you want those callbacks,
but not the DEBUGGER itself. I am against adding this directly to the core.
I think that's very reasonable. Ultimately I like my last suggestion, the one about having a central method that handles loading. That strikes me as a more solid design for Ruby in general. So I think I will make a more concrete proposal for that in another ticket. But short of that, then I agree. Having a standard "cherrypickable" debugging library for this would be the nicest approach.
Updated by trans (Thomas Sawyer) almost 12 years ago
=begin
Ran into a little snag with implementation of this as a simple Ruby library. It is not possible to override (({#require_relative})) to add the callback b/c it is lexically scoped (is that the right term for this?). In other words, (({#require_relative})) uses the (({FILE})) in which it is called. But any override ends up with the (({FILE})) in which the override is defined instead.
Is there a way around this? If not, then the only way to do this properly/completely is via core code.
=end
Updated by Anonymous almost 12 years ago
=begin
You can use (({Kernel#caller})) to obtain the filename of the caller
=end
Updated by trans (Thomas Sawyer) almost 12 years ago
=begin
@charliesome That doesn't quite fix the problem.
foo/require_relative.rb¶
alias_method :require_relative_without_callback, :require_relative
def require_relative(feature)
result = require_relative_without_callback(feature)
Kernel.loaded(feature) if result
result
end
bar/use_it.rb¶
load 'foo/require_relative.rb'
require_relative "anything"
Produces
LoadError: cannot load such file -- foo/anything
But it should be (({bar/anything})).
Your suggestion would allow me to re-implement #relative_require. I know how to do that, I've done it before for Facets. But I've never felt it a very robust solution.
=end
Updated by Anonymous almost 12 years ago
trans (Thomas Sawyer) wrote:
You mean "cherrypicking like Facets" right? ;)
Sorry, I'm a noob, not knowing whom to credit :]
Updated by trans (Thomas Sawyer) almost 12 years ago
I just release a gem called backload that basically handles this. But in working through the problem set, it's become clear that there are some features of a good load callback that just can't be done in pure Ruby. I've already mentioned the #relative_require issue. Another issue, is that it would be nice to get the full path of the loaded file, e.g. as an additional option called :fullpath. There's no way to do that in pure Ruby b/c there is no way to lookup a load path (see issue #6376). Even if there were, it would be redundant b/c Ruby is just going to look it up again in the C code.
So putting it all together, it seems this really needs to be implemented in the C if it is to be done right. I do not know if that would mean it cannot be an optional standard library, or not.
I almost did not bother to release backload (and I still have half a mind to yank it) b/c it can't be as complete and solid an implementation as it really ought, but I suppose for some uses it would suffice.
Updated by ko1 (Koichi Sasada) almost 12 years ago
- Category set to core
- Assignee set to matz (Yukihiro Matsumoto)
I'm sorry I don't catch up this discussion.
I assigned this ticket to matz, but I'm not sure matz is good person to handle it.
Please correct me.
Basically, I agree with this proposal.¶
Updated by hsbt (Hiroshi SHIBATA) 8 months ago
- Status changed from Open to Assigned