Ruby catch classes as they are defined

Building slot machines or simulations in Ruby Gosu always makes me end up with upp with a list of all available chunks stored by their class. For example [Pipe, PipeJunktion, Box, Pump]

, etc. Each class is defined in one of several separate files that I require from the main program. For now, I have to add a class to this list every time I add a new tile to the game. I was wondering if there is a way to catch all loading classes from a file.

Something line by line:

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

      

will be comfortable. Or could this be resolved with modules in some way?

+3


source to share


3 answers


Checking which classes have been added by a file is not something that is easy or usually done. Your best bet would be to put all tile classes in the same namespace. Since the classes can be reopened, they can be split into multiple files.

class Tiles
  class Pipe
    # ...
  end
end

class Tiles
  class Box
    # ...
  end
end

      

Then Tiles.constants

could return an array of characters: [:Pipe, :Box]

and could be used to get a list of class references with Tiles.constants.map { |const| Tiles.const_get const }

orTiles.constants.map &Tiles.method(:const_get)

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

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

      



If it tiles.rb

has class definitions for Pipe

and Box

, then it added_constants

will be [:Pipe, :Box]

.

The problem with this approach is that gem-added constants can be displayed, for example:

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

      

As I named it require 'mechanize'

, the list added_constants

will be quite long and will contain much more than just Foo

.

+2


source


You can do something like this:

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

      

Which collects all the files from a subfolder tiles

and requires it.

In the next step, load all classes by 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

      

+2


source


I suspect there are pitfalls with the following approach, but I'll post it and invite comments.

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

base_classes = ObjectSpace.each_object(Class).to_a

      

For my version of Ruby (2.4.0), in the IRB, base_classes.size #=> 490

. Now load the code with require

etc. Let's assume three classes are created:

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

      

Now let's compile a list of all existing classes and subtract base_classes

:

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

      

Returns an array of classes added by my code.

Of course, this does not show the classes in base_classes

that are overridden by my code, or show which classes are defined by the required gems.

0


source







All Articles