1

Let's say I have an Application that requires different files. Each file contains a single purpose "thing". I call it "thing" because I'm unsure if it will be a class or a module.

The First Thing

# things/one.rb
class One
  def self.do_it    
    "I'm the one"
  end
end

And the second

# things/two.rb
class Two
  def self.do_it    
    "I'm not the only one!"
  end
end

Now I wanna require all files in the thingsdirectory and execute the do_it method. But I wanna do this dynamically - for every thing in things folder without even knowing their Classname.

# app.rb
Dir["things/*.rb"].each {|file| require file }

Keep in Mind: I simply wanna execute the code in the things files - it's not necessary that they are Classes or Modules

PascalTurbo
  • 2,189
  • 3
  • 24
  • 41

1 Answers1

1

You can't reliably do this for a number of reasons, but the most important is that you're under no obligations to declare a single class in any given file, or a class at all. You've got the right idea with loading in all files in a particular directory, but if you need to operate on those there's two ways to crack that nut.

The easiest way is to declare these classes as a subclass of some already known parent. ActiveSupport has the descendents method to reflect on a particular class, that's part of Rails, but you can also do it the hard way if you have to.

The second easiest way is to correlate the names with the classes in them, and then convert filenames to class-names algorithmically. This is how the Rails autoloader works. cat.rb contains Cat, bad_dog.rb contains BadDog and so on.

If you look more closely at how things like Test::Unit work they take the first approach, any declared test subclasses are executed before the Ruby process terminates. It's a fairly simple system that puts no constraints on what the files are called or where and how the classes are declared.

Rails leans towards the second approach where the path communicates intent. This makes finding files more predictable but requires more discipline on the part of the developer to conform to that standard.

They both have merit. Pick the one that works for your use case.

Community
  • 1
  • 1
tadman
  • 208,517
  • 23
  • 234
  • 262
  • Very interesting! One way to do it would be : `classes_before = ObjectSpace.each_object(Class).to_a; Dir["things/*.rb"].each {|file| require_relative file }; classes_after = ObjectSpace.each_object(Class).to_a; do_it_classes = (classes_after - classes_before).select{|klass| klass.respond_to?(:do_it)}` – Eric Duminil Jan 25 '17 at 21:16
  • Do be careful with that approach as you're expecting those files to immediately instantiate any classes whereas that might not be the case if you're dealing with `autoload` declarations or other forms of lazy loading, plus dynamic programming that might factory produce classes on-demand. – tadman Jan 25 '17 at 21:40
  • True. The OP didn't mention `autoload` though, and said that there was one thing for each file. I realize my code doesn't take modules into accounts, though. – Eric Duminil Jan 25 '17 at 21:55
  • 1
    @EricDuminil I'm just adding caveats here because when it comes to Ruby, there's a *lot* of liberty given to the programmer. Without house-rules, standards like I've described, it's very hard to figure out exactly what to do. Your code's a good example of how to dig around *if* everything is correctly loaded. – tadman Jan 25 '17 at 21:57
  • Got it. Thanks for the explanation! – Eric Duminil Jan 25 '17 at 21:59