4

Building tile games or simulations in Ruby Gosu always makes me end upp with a list of all available tiles, saved by their class. For example [Pipe, PipeJunktion, Box, Pump] and so on. Each class is defined in one of a few separate files, which i required from the main program. For now i have to add the class myself to this list every time I add a new tile to the game. I was wondering if there was a way to catch all loading classes from a file.

Something along the lines of:

allTiles = []
require_relative 'tiles.rb'.each_class {|class| allTiles << class}

would be handy. Or can this be solved with modules in some way?

Leddy231
  • 63
  • 5
  • Do you need them to be in an array? You could also just load them all and iterate over the descendants of the superclass.. – Marcus Ilgner Apr 28 '17 at 18:35

3 Answers3

2

You can do something like this:

Dir['tiles/*.rb'].each { |file| require file }

What would collect all files from a tiles subfolder and requires it.

In a next step load all classes by their file names:

all_tiles = Dir['tiles/*.rb'].map do |file| 
  file_name = File.basename(x, '.*')
  camel_cased_name = file_name.split('_').collect(&:capitalize).join
  Object.const_get(camel_cased_name)
end

Btw the same can be done in Rails like this:

all_tiles = Dir['tiles/*.rb'].map do |file| 
  File.basename(x, '.*').camelize.constantize
end
spickermann
  • 100,941
  • 9
  • 101
  • 131
2

Checking which classes were added by a file is not something that's easily or commonly done. A better approach would be to put all the tile classes under a single namespace. Since classes can be re-opened, these can be split among multiple files.

class Tiles
  class Pipe
    # ...
  end
end

class Tiles
  class Box
    # ...
  end
end

Then Tiles.constants could would return an array of symbols: [:Pipe, :Box], and could be used to get a list of class references using Tiles.constants.map { |const| Tiles.const_get const } or Tiles.constants.map &Tiles.method(:const_get)

If for whatever reason it was really important to know which constants were added by a specific file, the following code shows an approach:

constants1 = Object.constants
require "./tiles.rb"
constants2 = Object.constants
added_constants = constants2 - constants1

If tiles.rb had class definitions for Pipe and Box, then added_constants would be [:Pipe, :Box].

The problem with this approach is that might show constants added by gems, for example:

constants1 = Object.constants
require 'mechanize'
class Foo
end
constants2 = Object.constants
added_constants = constants2 - constants1

Since I called require 'mechanize', the added_constants list will be quite long and include much more than just Foo.

max pleaner
  • 26,189
  • 9
  • 66
  • 118
0

I suspect there are pitfalls with the following approach, but I will put it out and invite comments.

First, use ObjectSpace::each_object to compile a list of all classes that exist before any custom classes have been created:

base_classes = ObjectSpace.each_object(Class).to_a

For my version of Ruby (2.4.0), within IRB, base_classes.size #=> 490. Now load the code with require's etc. Suppose that causes three classes to be created:

class A; end
class B; end
class C; end

Now compile a list of all classes that now exist and subtract base_classes:

ObjectSpace.each_object(Class).to_a - base_classes
  #=> [A, B, C]

This returns an array of classes that have been added by my code.

Of course this does not show classes in base_classes that are overridden by my code or show which classes are defined by required gems.

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100