Forum: Ruby Automatic ClassLoader (to eliminate 'require')

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
62711fa2787e85b5f0c88e245ef69f54?d=identicon&s=25 Alexey Petrushin (axyd80)
on 2008-10-11 09:30
Hello!

QUESTION: Is there any way to attach a listener that will fires when the
interpreter encounters an unknown Class and going to raise the
NameError?

For some reason all my classes are arranged the same way as files are
(i.e. A::B::C <=> A/B/C.rb, one to one)
So, theoretically i don't really need any usage of the 'require'
keyword.

My proposal is:
When the interpreter encounters unknown Class it raises the 'NameError'.
I'm wondering is there any way to attach a listener right before it
happens (like method_missing)?

Another way:
begin
  ...
rescue NameError
  # load needed class
end
But, it's not a solution, because in this case we loose the Point of
execution.

Thanks! :)
Ff9e18f0699bf079f1fc91c8d4506438?d=identicon&s=25 James Britt (Guest)
on 2008-10-11 09:52
(Received via mailing list)
Alexey Petrushin wrote:
> Hello!
>
> My proposal is:
> When the interpreter encounters unknown Class it raises the 'NameError'.
> I'm wondering is there any way to attach a listener right before it
> happens (like method_missing)?


http://www.ruby-doc.org/core/classes/Module.html#M001716



--
James Britt

www.happycamperstudios.com   - Wicked Cool Coding
www.jamesbritt.com           - Playing with Better Toys
www.ruby-doc.org             - Ruby Help & Documentation
www.rubystuff.com            - The Ruby Store for Ruby Stuff
6b0967f63d03e99b6c07a3f5ed224c77?d=identicon&s=25 Erik Veenstra (Guest)
on 2008-10-11 10:21
(Received via mailing list)
module AutoRequire
  def const_missing(const)
    puts File.join(to_s.split(/::/), const.to_s) + ".rb"
  end
end

# In A.rb:

module A
  extend AutoRequire
end

A::B

# In A/B.rb:

module A
  module B
    extend AutoRequire
  end
end

A::B::C

gegroet,
Erik V.
62711fa2787e85b5f0c88e245ef69f54?d=identicon&s=25 Alexey Petrushin (axyd80)
on 2008-10-11 13:21
Thanks! It works! :)

Implementation:

  GLOBAL_BINDING = binding
  class Module
    def const_missing(const)
      full_class_name = (self == Object or self == Module) ? const.to_s
: "#{self.name}::#{const}"
      file = full_class_name.gsub('::', '/')
      if File.directory?(file)
        eval "module #{full_class_name} end", GLOBAL_BINDING
      elsif File.exist?(file+".rb")
        script = File.read(file+".rb")
        unless self == Object or self == Module
          script = "\
  module #{self.name}
  #{script}
  end
  "
        end
        eval script, GLOBAL_BINDING
      else
        raise "Class not found: #{full_class_name}"
      end

      result = eval "#{full_class_name}", GLOBAL_BINDING
      return result if result
      raise "Class not found: #{full_class_name}"
    end
  end

  # A/B/C.rb
  #
  # class C
  # end
  #
  A::B::C.new

Notes:
- in the script for the class C, we write just C, instead of all A::B::C
- there are no really A and B modules, they are generated on the fly.
Da33a4ac652c1c8900392a8599206640?d=identicon&s=25 Thomas B. (tpreal)
on 2008-10-11 13:36
Alexey Petrushin wrote:
> Thanks! It works! :)

Hi there. That's a nice solution, I think. Thanks for sharing!

There might be some drawbacks in this approach, like now you cannot
require these files manually because it wouldn't create the correct
module nesting, but for some applications I think your code is great.

Things that you probably could improve:
- There's a constant named TOPLEVEL_BINDING, you can use it instead of
your own constant.
- You could backup the original const_missing and call it if the const
is really missing:

class Module
  alias const_missing_orig const_missing
  def const_missing(const)
    ...
    else
      const_missing_orig(const)
    end
  end
end

- Also I think there might be a more elegant way to create the class
from the file in the correct nesting. There might be a way to do it
using some class_eval or something, but I'll have to test it.

TPR.
62711fa2787e85b5f0c88e245ef69f54?d=identicon&s=25 Alexey Petrushin (axyd80)
on 2008-10-11 14:21
I'm hurry up, there are some bugs in current implementation. Do not use
it as it is.
Right now i'm using it in my project, so, stable solution should be
available, probably in one week :).
62711fa2787e85b5f0c88e245ef69f54?d=identicon&s=25 Alexey Petrushin (axyd80)
on 2009-06-15 21:18
I released it as gem, Quick Start here
http://bos-tec.com/ui/Portal/Site/Lab/RubyExt/ClassLoader

Hope it will be useful for some projects :).
62711fa2787e85b5f0c88e245ef69f54?d=identicon&s=25 Alexey Petrushin (axyd80)
on 2009-06-15 21:30
forgot, gem name is RubyExt
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (Guest)
on 2009-06-16 08:16
(Received via mailing list)
On 15.06.2009 21:18, Alexey Petrushin wrote:
> I released it as gem, Quick Start here
> http://bos-tec.com/ui/Portal/Site/Lab/RubyExt/ClassLoader
>
> Hope it will be useful for some projects :).

Maybe you add a little abstract how this is better than "autoload".
Otherwise people might be left wondering why they should bother to add a
gem to their repository if they seem to get the same functionality out
of the box.

Kind regards

  robert
62711fa2787e85b5f0c88e245ef69f54?d=identicon&s=25 Alexey Petrushin (axyd80)
on 2009-06-16 09:02
Not exactly understood what you mean, as i know 'autoload' works almost
the same as require - you should explicitly specify connection between
your classes and corresponding files.
So, instead of bunch of 'require xxx' there will be bunch of even longer
'autoload xxx, xxx' lines.

Class loader allows you to forget about it, just place your class file
wherever you want and it find it. It uses conventions to find your
classes and resources.

There is no much sense to make one or two files to be loaded by
ClassLoader, the point is - make the whole project loaded by it (and
forget about require) or don't use it at all.

Or i misunderstood you and missing something?
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (Guest)
on 2009-06-16 09:12
(Received via mailing list)
2009/6/16 Alexey Petrushin <axyd80@gmail.com>:
> Not exactly understood what you mean, as i know 'autoload' works almost
> the same as require - you should explicitly specify connection between
> your classes and corresponding files.
> So, instead of bunch of 'require xxx' there will be bunch of even longer
> 'autoload xxx, xxx' lines.

Not necessarily.  Basically you need this only for the first constant
of a library.  In the autoloaded file you can have more 'autoload'
declarations.  Or you require a single file which contains all the
initial autoload declarations and every subsequent file contains
further autoload declarations.  If you provide this as a library then
usability for a user of that library is practically identical to your
approach - minus, you do not have to require another gem.

> Class loader allows you to forget about it, just place your class file
> wherever you want and it find it. It uses conventions to find your
> classes and resources.

I know that approach from Java - and also all the issues that come
with automatic loading.  You may end up loading other classes that you
intended to...

> There is no much sense to make one or two files to be loaded by
> ClassLoader, the point is - make the whole project loaded by it (and
> forget about require) or don't use it at all.

If I were to distribute a gem I would try to limit dependencies to
other gems.  If I can achieve the same effect for my users with built
in features I would stick to them.  I may be missing something here
but that's the reason why I suggested you provide a short summary of
using your gem vs. built in features.  If you want your gem to be used
you should probably do /some/ advertising. :-)

Kind regards

robert
62711fa2787e85b5f0c88e245ef69f54?d=identicon&s=25 Alexey Petrushin (axyd80)
on 2009-06-16 09:42
Now i understood, you are absolutely right, explicit 'require' gives you
more control and less dependencies, and there can be some class conflict
problems with auto-reloading.

I think what approach to choose depends on what kind of projects you are
working on.

Thanks for your suggestion :), I'll add comparison with standard
approach a bit later.
62711fa2787e85b5f0c88e245ef69f54?d=identicon&s=25 Alexey Petrushin (axyd80)
on 2009-06-16 09:43
> I know that approach from Java
Yes, it inspired by Java ClassLoader :)
62711fa2787e85b5f0c88e245ef69f54?d=identicon&s=25 Alexey Petrushin (axyd80)
on 2010-10-10 04:59
For some time I used it in my projects and yesterday release it as
standalone gem, docs are here
http://github.com/alexeypetrushin/class-loader

Let's say you have some applicaton

  /your_app
      /lib
          /animals
              /dog.rb
          /zoo.rb

instead of

  require 'animals/dog'
  require 'app'

you just write

  require 'class_loader'
  autoload_dir '/your_app/lib'

  Zoo.add Animals::Dog.new # <= all classes loaded automatically
Ff97ca87af59ee68ceff5877a8365788?d=identicon&s=25 Jarmo Pertman (juuser)
on 2010-10-13 22:54
(Received via mailing list)
There is also a require_all gem with similar approaches
http://github.com/jarmo/require_all

Jarmo Pertman
-----
IT does really matter - http://www.itreallymatters.net
C2c9a9a76a9e859e4bd00cfda181c660?d=identicon&s=25 Tony Arcieri (Guest)
on 2010-10-13 23:13
(Received via mailing list)
Also MagicLoader, my almost-complete code loading counterpart to
Bundler,
which is based on require_all:

http://github.com/tarcieri/magicloader

There are still a few edge cases it's broken for, but it's almost ready
for
production use.
62711fa2787e85b5f0c88e245ef69f54?d=identicon&s=25 Alexey Petrushin (axyd80)
on 2010-10-22 02:49
Wow, nice to see that I'm not alone :)

I accidentally mistyped the "Class Loader" gem url, sorry, here's the
right one:

'class_loader' gem - http://github.com/alexeypetrushin/class_loader


And one more thought - I also found it's handy to use kind of IoC
container, so instead of thinking about file/class initialization and
dependency you just require it when you need it, here's very basic
sample

  class Logger
      register_as :logger
  end

  class Application
      inject :logger => :logger

      def do_business
          # now we can use injected component
          logger.info 'done'
      end
  end

Details are here http://github.com/alexeypetrushin/micon, it's also
available as the 'micon' gem
This topic is locked and can not be replied to.