ruby catch classes as they are defined - ruby

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?

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.

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

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.

Related

Get class/module name

Is there a better way to get class/module name viz, C from A::B::C, B from A::B::C, and A From A::B::C. The following code uses string and split to get "Stegosaurus" from Cowsay::Character::Stegosaurus, How to do away with string and split?
require 'cowsay'
x = Cowsay.random_character().class
x.name.split("::")[2]
require 'cowsay'
true
x = Cowsay.random_character().class
Cowsay::Character::Stegosaurus
x.name.split("::")[2]
"Stegosaurus"
I don't think there's anything for handling this in core/standard library.
As an alternative to custom written methods there is always activesupport:
require 'active_support/core_ext/string/inflections'
Cowsay::Character::Stegosaurus.name.demodulize
#=> "Stegosaurus"
Cowsay::Character::Stegosaurus.name.deconstantize
#=> "Cowsay::Character"
These methods are implemented as follows:
def demodulize(path)
path = path.to_s
if i = path.rindex('::')
path[(i+2)..-1]
else
path
end
end
def deconstantize(path)
path.to_s[0, path.rindex('::') || 0] # implementation based on the one in facets' Module#spacename
end
Take a look into docs if interested in more methods.
As to
A From A::B::C
If you'd require the whole activesupport, you'd get bunch of Module methods, including parent:
require 'active_support'
Cowsay::Character::Stegosaurus.parent
#=> Cowsay
If you're not going to use it extensively, I recommend to just grab the needed methods from activesupport and put it into some helper, because loading it whole might be an overkill.

How do I verify the number of elements and content of an array using ParameterMatchers?

I am learning Ruby with TDD using Mocha and MiniTest.
I have a class that has one public method and many private methods, so the only method my tests are going to tests are the public one.
This public method does some processing and creates an array which is sent to another object:
def generate_pairs()
# prepare things
pairs = calculate_pairs()
OutputGenerator.write_to_file(path, pairs)
end
Great. To test it, I would like to mock the OutputGenerator.write_to_file(path, pairs) method and verify the parameters. My first test I could sucessfully implement:
def test_find_pair_for_participant_empty_participant
available_participants = []
OutputGenerator.expects(:write_to_file).once.with('pairs.csv', [])
InputParser.stubs(:parse).once.returns(available_participants)
pair = #pairGenerator.generate_pairs
end
Now I would like to test with one pair of participants.
I am trying this
def test_find_pair_for_participant_only_one_pair
participant = Object.new
participant.stubs(:name).returns("Participant")
participant.stubs(:dept).returns("D1")
participant_one = Object.new
participant_one.stubs(:name).returns("P2")
participant_one.stubs(:dept).returns("D2")
available_participants = [participant_one]
OutputGenerator.expects(:write_to_file).once.with('pairs.csv', equals([Pair.new(participant, participant_one)])) # here it fails, of course
InputParser.stubs(:parse).once.returns(available_participants)
#obj.stubs(:get_random_participant).returns(participant)
pair = #obj.generate_pairs
end
The problem is that equals will only match the obj reference, not the content.
Is there any way I can verify the content of the array? Verifying the number of elements inside the array would also be extremely useful.
ps: I am sorry if the code doesn't follow ruby standards, I am doing this project to learn the language.
What you are testing here demonstrates a kind of hard coupling. That is your primary class is always dependent on OutputGenerator which makes testing your outputs tricky and can lead to a lot of pain if/when you have to refactor your designs.
A good pattern for this is dependency injection. With this you can just write a temporary ruby object you can use to evalute the output of your function however you want:
# in your main class...
class PairGenerator
def initialize(opts={})
#generator = opts[:generator] || OutputGenerator
end
def generate_pairs()
# prepare things
pairs = calculate_pairs()
#generator.write_to_file(path, pairs)
end
end
# in the test file...
# mock class to be used later, this can be at the bottom of the
# test file but here I'm putting it above so you are already
# aware of what it is doing
#
class MockGenerator
attr_reader :path, :pairs
def write_to_file(path, pairs)
#path = path
#pairs = pairs
end
end
def test_find_pair_for_participant_only_one_pair
participant = Object.new
participant.stubs(:name).returns("Participant")
participant.stubs(:dept).returns("D1")
participant_one = Object.new
participant_one.stubs(:name).returns("P2")
participant_one.stubs(:dept).returns("D2")
available_participants = [participant_one]
# set up a mock generator
mock_generator = MockGenerator.new
# feed the mock to a new PairGenerator object as a dependency
pair_generator = PairGenerator.new(generator: mock_generator)
# assuming this is needed from your example
pair_generator.stubs(:get_random_participant).returns(participant)
# execute the code
pair_generator.generator_pairs
# output state is now captured in the mock, you can evaluate for
# all the test cases you care about
assert_equal 2, mock_generator.pairs.length
assert mock_generator.pairs.include?(participant)
end
Hope this helps! Dependency Injection is not always appropriate but it is great for cases like this.
Some other posts about the use of dependency injection you might find helpful:
Dependency Injection in Ruby
A Ruby Refactor: Dependency Injection Options
Simple Dependency Injection in Ruby

ruby - Sharing a class across modules

I'm trying to mimic ActiveRecord with a simple set of ruby objects for running raw sql queries. Below is a spike I've been experimenting with:
module Runable
def run
return self::Results.new
end
end
module Query
class Results
def initialize
#results = Object.find_by_sql()
end
def to_a
#code
end
end
end
module Scored
extend Runable
include Query
QUERY = 'a raw sql query string'
end
module Unseen
extend Runable
include Query
QUERY = 'a different raw sql query string'
end
What I want to be able to do is create simple Modules for each type of raw sql query I'm going to run, put them into a file like Scored or Unseen above and call .run on them to get back a results object. So like this:
Scored.run #=> #<Scored::Results:0x0000000000>
Unseen.run #=> #<Unseen::Results:0x0000000000>
but instead I get this...
Scored.run #=> #<Query::Results:0x0000000000>
Unseen.run #=> #<Query::Results:0x0000000000>
I've been doing ruby and rails for over a year but I'm just beginning to get into more advanced ruby usage. This is my first big step into using modules and mixins.
The issue, as far as I can tell, is that module class methods have self scoped to the module they're defined in. So I get Query::Results because the initialize method for Results is defined in the Query module. That make sense?
Thank you for the help!
Update 5/30 16:45
Basically, I want to wrap a handful of raw SQL statements into modules like this:
module ScoredUsers
include Queryable
QUERY="SELECT * FROM users ..."
end
and interact with the queries like this:
r = ScoredUsers.run #=> ScoredUsers::Results
r.ids
r.load_objects
REDIS.zadd user:5:cache, r.to_a
I want to keep everything in modules and classes, the ruby way (I think?) so when I want to create a new query object I can simple use the boilerplate module like Scored above.
The reason why you are getting such a results is that class Results is created just once. When the module is included new constant is created within including class (Scored::Results), but it is pointing to same memory space as constant Query::Results.
What you need is that you have to create a new class for each class this module is being included in. This is perfect opportunity to use included method:
module Query
def self.included(mod)
results = Class.new do
def initialize
#results = Object.find_by_sql()
end
def to_a
#code
end
end
mod.const_set('Results', results)
end
end
Now of course we are left with the question - do we really need to do this? This depends on how you are planning to use those classes.

Adding Class Structure to Basic Ruby Code

this is pretty crazy, and I feel really stupid asking this. But I have a basic code in Ruby and it's comprised of
user input assigned to value
input extracted to array by line
iterations over each line extracting specific text and specific numbers
if /else statements
if text includes specific words then you apply math
if text includes specific words you don't apply math
i also have a running total that i assign outside the loop and add to inside the loop
this all works fine, but the project requires class structure, how do i take a code basically
simple with if else statements basic loop (for each do |x|) and basic variable = true, or variable = input * 52/ 300
where do i start making this into class-based structure with OOP?
I'll give you a basic outline to get your started, but I won't do your work for you. :)
First: define a class to hold your input.
class MyClass
def initialize(data)
#data = data
...put your initialization code here, this gets run when you call MyClass.new()
end
attr_accessor :data
...
end
Second: define a "container" class to hold multiple instances of class.
class MyContainer
def initialize(record)
#records << record
end
...
end
Third: for each line received, create a new instance of your class
mydata = MyClass.new(input)
Forth: store the new instance in your container class.
MyContainer.new(mydata)
Now, you can do things like create an add method in MyContainer which will iterate though all the classes it's holding in #records and produce a sum.

ruby: how to load .rb file in the local context

How this simple task can be done in Ruby?
I have some simple config file
=== config.rb
config = { 'var' => 'val' }
I want to load config file from some method, defined in main.rb file so that the local variables from config.rb became local vars of that method.
Something like this:
=== main.rb
Class App
def loader
load('config.rb') # or smth like that
p config['var'] # => "val"
end
end
I know that i can use global vars in config.rb and then undefine them when done, but i hope there's a ruby way )
The config file.
{ 'var' => 'val' }
Loading the config file
class App
def loader
config = eval(File.open(File.expand_path('~/config.rb')).read)
p config['var']
end
end
As others said, for configuration it's better to use YAML or JSON. To eval a file
binding.eval(File.open(File.expand_path('~/config.rb')).read, "config.rb")
binding.eval(File.read(File.expand_path('~/config.rb')), "config.rb")
This syntax would allow you to see filename in backtraces which is important. See api docs [1].
Updated eval command to avoid FD (file descriptor) leaks. I must have been sleeping or maybe should have been sleeping at that time of the night instead of writing on stackoverflow..
[1] http://www.ruby-doc.org/core-1.9.3/Binding.html
You certainly could hack out a solution using eval and File.read, but the fact this is hard should give you a signal that this is not a ruby-like way to solve the problem you have. Two alternative designs would be using yaml for your config api, or defining a simple dsl.
The YAML case is the easiest, you'd simply have something like this in main.rb:
Class App
def loader
config = YAML.load('config.yml')
p config['var'] # => "val"
end
end
and your config file would look like:
---
var: val
I do NOT recommend doing this except in a controlled environment.
Save a module to a file with a predetermined name that defines an initialize and run_it methods. For this example I used test.rb as the filename:
module Test
##classvar = 'Hello'
def initialize
#who = 'me'
end
def get_who
#who
end
def run_it
print "#{##classvar} #{get_who()}"
end
end
Then write a simple app to load and execute it:
require 'test'
class Foo
include Test
end
END {
Foo.new.run_it
}
# >> Hello me
Just because you can do something doesn't mean you should. I cannot think of a reason I'd do it in production and only show it here as a curiosity and proof-of-concept. Making this available to unknown people would be a good way to get your machine hacked because the code could do anything the owning account could do.
I just had to do a similar thing as I wanted to be able to load a "Ruby DLL" where it returns an anonymous class ( a factory for instances of things ) I created this which keeps track of items already loaded and allows the loaded file to return a value which can be anything - a totally anonymous Class, Module, data etc. It could be a module which you could then "include" in an object after it is loaded and it could could supply a host of "attributes" or methods. you could also add an "unload" item to clear it from the loaded hash and dereference any object it loaded.
module LoadableModule
##loadedByFile_ = {};
def self.load(fileName)
fileName = File.expand_path(fileName);
mod = ##loadedByFile_[fileName];
return mod if mod;
begin
Thread.current[:loadReturn] = nil;
Kernel.load(fileName);
mod = Thread.current[:loadReturn];
##loadedByFile_[fileName] = mod if(mod);
rescue => e
puts(e);
puts(e.backtrace);
mod = nil;
end
Thread.current[:loadReturn] = nil;
mod
end
def self.onLoaded(retVal)
Thread.current[:loadReturn] = retVal;
end
end
inside the loaded file:
LoadableModule.onLoaded("a value to return from the loaded file");

Resources