Search code examples
crystal-lang

Crystal - How to dynamically get global variables from modules in multiple files


I have multiple files in this format: car1.cr

module Cars
  module Car1
    BRAND = "TOYOTA"
  end
end

All of these files are getting required by a main file where I can do this:

puts Cars::Car1::BRAND #=> TOYOTA

What I'm trying to do is puts all brands of all files dynamically, allowing me to just create a new file (or delete a file) in the first format and automatically being able to get printed without adding it manually (or removing it) .

I tried following this answer https://stackoverflow.com/a/50531198/13508702 but couldn't manage to achieve my goal.

Any help would be apreaciated!


Solution

  • Do you really need the constants? Couldn't your files just build up a hash?

    # cars.cr
    module Cars
      BRANDS = {} of String, String
    end
    
    # car1.cr
    module Cars
      module Car1
        BRANDS["Car1"] = "TOYOTA"
    
        # Or if you really need the constant
        BRAND = "TOYOTA"
        BRANDS["Car1"] = BRAND
      end
    end
    
    # Or if it really just defines the data
    Cars::BRANDS["Car1"] = "TOYOTA"
    

    I suspect you collect more info than the brand, so you may just define a value type:

    module Cars
      record Car, name : String, brand : String, model : String
    
      CARS = [] of Car
    end
    
    Cars::CARS << Car.new("Car1", "TOYOTA", "AA")
    

    The general answer here is, try to think of a way to restructure your program so that you don't need this meta-programming ability. That usually leads to cleaner and easier to follow code.

    To answer the actual question:

    module Cars
      module Car1
        BRAND = "TOYOTA"
      end
    
      module Car2
        BRAND = "HONDA"
      end
    
      def self.collect_brands
        {{@type.constants.map {|car| "#{car}::BRAND".id }}}
      end
    end
    
    Cars.collect_brands # => ["TOYOTA", "HONDA"]