Running a Ruby program during development - ruby

I'm writing a program in Ruby for the first time (I'm using RubyMine as my IDE), having previously coded mostly in Java.
When programming in Java, I used to regularly 'run' my code after each bit of functionality I had added to it, just to check that it worked properly.
I'm trying to do that with Ruby, but I'm having a little bit of difficulty.
I have the following class, which I will be using as the menu for my program, so it is where I want the user to start off when they run the program:
class Application
# To change this template use File | Settings | File Templates.
def initialize
mainMenu
end
def navigateTo(what)
what.new.display
mainMenu
end
def mainMenu
puts "What would you like to do?
1: Add module to a scheme
2: Remove module from a scheme
3: Query modules
4: Modify module
5: Register a student on a scheme
6: Remove a student from a scheme
7: Register a student on a module
8: Remove a student from a module"
case gets.strip
when "1"
navigateTo Module
addModule
when "2"
navigateTo Module
when "3"
navigateTo Module
when "4"
navigateTo Module
when "5"
navigateTo Student
when "6"
navigateTo Student
when "7"
navigateTo Student
end
end
end
However, when I run the class, the console displays a line stating that it's running it, but then the next line is "Process finished with exit code 0"
I can't work out why this is? In Java, there was a main method, which was where the program would always go for instruction on what to do when it was run... but as far as I can tell with Ruby, there's no need for a main method? If this is the case, how can I run what I've written so far to check that I've written it correctly?
*UPDATED**
Ok, I've added in the line
Application.new
as suggested, and that's brilliant- the menu now prints out. I selected option 1 from the menu, and this line was printed out in the console:
#<Module:0x1bd2c90>What would you like to do?
followed by a printout of the menu again.
Option 1 should navigate to my Module class, which looks like this:
class Module
# To change this template use File | Settings | File Templates.
##moduleScheme = nil
##moduleYear = nil
##moduleTitle = ""
def self.moduleYear
##moduleYear
end
def initialize(v)
#val = v
end
# Set and get the #val object value
def set (v)
#val = v
end
def get
return #val
end
def addModule
moduleName = Module.new(30)
moduleRefNo = Random(100)
#moduleTitle = #moduleTitle
moduleYear(4)
print "What is the name of the module you would like to add?"
moduleName = gets
moduleRefNo
printf "Which year does the module belong to?"
##moduleYear = gets
puts "#{moduleName}, belonging to #{##moduleYear} has been added to the system, with reference number #{moduleRefNo}."
navigateTo Application
end
def addModuleToScheme
moduleName.moduleScheme = schemeName
end
def removeModuleFromScheme
moduleName.moduleScheme = nil
end
def queryModule
end
end
Once the the user has selected option 1 from the main menu, and the program has navigated to the Module class, I expected it to run that class fully, i.e. display the prompts to the user, and read in whatever they type on the keyboard, then navigate back to the menu, as indicated by the line
navigateTo Application
at the end of my 'addModule' function. However, for some reason, it seems to either not be navigating to the Module class, or just skipping straight to the end of it. Can anyone point out what I'm doing wrong here?

When you run a ruby file, it will run the file from beginning to end. In your file, you are just defining a class, so it will create that class in ruby, then do nothing, since you are not telling it to instantiate a instance of that class. At the end of your file, add Application.new, which will create an instance of that class, and, looking at your code, will print and receive input

Your code might look like this:
class Application
def run
puts "What would you like to do?
1: Add module to a scheme
...."
end
...
end
Application.new.run
# here the user will interact with you application

Related

Testing gets in rspec (user input)

My class has this #run method that so far is just this, to test the testing:
def run
puts "Enter 'class' to create a new class."
input = $stdin.gets.chomp
binding.pry
And in the tests so far I've got
allow($stdin).to receive(:gets).and_return 'class'
cli.run
Doing it this way I am able to see, in the pry session, that input has been set to 'class', as intended.
Is there a way to do with without adding $stdin to my call to gets in my method itself? i.e., input = gets.chomp
I've tried allow(cli.run).to receive(:gets).and_return 'class'
But then in the pry session, input is equal to the first line of the spec file!
You can avoid this as such:
def run
puts "Enter 'class' to create a new class."
input = gets.chomp
end
describe 'gets' do
it 'belongs to Kernel' do
allow_any_instance_of(Kernel).to receive(:gets).and_return('class')
expect(run).to eq('class')
end
end
The method gets actually belongs to the Kernel module. (method(:gets).owner == Kernel). Since Kernel is included in Object and almost all ruby objects inherit from Object this will work.
Now if run is an instance method scoped in a Class I would recommend scoping the stubbing a bit more such that:
class Test
def run
puts "Enter 'class' to create a new class."
input = gets.chomp
end
end
describe 'gets' do
it 'can be stubbed lower than that' do
allow_any_instance_of(Test).to receive(:gets).and_return('class')
expect(Test.new.run).to eq('class')
end
# or even
it 'or even lower than that' do
cli = Test.new
allow(cli).to receive(:gets).and_return('class')
expect(cli.run).to eq('class')
end
end
Example

Why the program pass all tests if I use regular if statement in the method but says `stack level too deep` when using a ternary operator instead?

I was working on coding challenge called Robot name. I also had tests for that. The program passed all the tests. The code is below..
class Robot
attr_accessor :name
##robots = []
def initialize
#name = self.random_name
##robots << self.name
end
def random_name
name = ''
2.times do
name << ('a'..'z').to_a.sample
end
3.times do
name << (1..9).to_a.sample.to_s
end
no_duplicate(name.upcase)
end
def reset
#name = self.random_name
end
def no_duplicate(name)
if ##robots.include? name
reset
else
name
end
end
end
If you need to see the tests file you can look it up here robot_name_tests.
Then I started to refactor and one of the first things was to refactor no_duplicate method. So after refactoring the code looked like this
class Robot
...
# the rest of code stayed the same
def no_duplicate(name)
##robots.include? name ? reset : name
end
end
With this version all tests showed SystemStackError: stack level too deep. Why does it give this error and what is going on behind the scenes in both cases considering the code provided? Thanks!
I like your poetry mode code but it has led you into trouble here.
One way to kinda keep it in poetry mode but fix your operator priority issue is to do this:
def no_duplicate(name)
(##robots.include? name) ? reset : name
end
Update: if you work in Big Corporation With Coding Standards you will need to make it a bit more boring. I thought this was obvious but the gallery is correctly noting the usual solution:
##robots.include?(name) ? reset : name

read json in Ruby and set variables for use in another class

The need here is to read a json file and to make the variables which is done from one class and use them with in another class. What I have so far is
helper.rb
class MAGEINSTALLER_Helper
#note nonrelated items removed
require 'fileutils'
#REFACTOR THIS LATER
def load_settings()
require 'json'
file = File.open("scripts/installer_settings.json", "rb")
contents = file.read
file.close
#note this should be changed for a better content check.. ie:valid json
#so it's a hack for now
if contents.length > 5
begin
parsed = JSON.parse(contents)
rescue SystemCallError
puts "must redo the settings file"
else
puts parsed['bs_mode']
parsed.each do |key, value|
puts "#{key}=>#{value}"
instance_variable_set("#" + key, value) #better way?
end
end
else
puts "must redo the settings file"
end
end
#a method to provide feedback simply
def download(from,to)
puts "completed download for #{from}\n"
end
end
Which is called in a file of Pre_start.rb
class Pre_start
#note nonrelated items removed
def initialize(params=nil)
puts 'World'
mi_h = MAGEINSTALLER_Helper.new
mi_h.load_settings()
bs_MAGEversion=instance_variable_get("#bs_MAGEversion") #doesn't seem to work
file="www/depo/newfile-#{bs_MAGEversion}.tar.gz"
if !File.exist?(file)
mi_h.download("http://www.dom.com/#{bs_MAGEversion}/file-#{bs_MAGEversion}.tar.gz",file)
else
puts "mage package exists"
end
end
end
the josn file is valid json and is a simple object (note there is more just showing the relevant)
{
"bs_mode":"lite",
"bs_MAGEversion":"1.8.0.0"
}
The reason I need to have a json settings file is that I will need to pull settings from a bash script and later a php script. This file is the common thread that is used to pass settings each share and need to match.
Right now I end up with an empty string for the value.
The instance_variable_setis creating the variable inside MAGEINSTALLER_Helper class. That's the reason why you can't access these variables.
You can refactor it into a module, like this:
require 'fileutils'
require 'json'
module MAGEINSTALLER_Helper
#note nonrelated items removed
#REFACTOR THIS LATER
def load_settings()
content = begin
JSON.load_file('scripts/installer_settings.json')
rescue
puts 'must redo the settings file'
{} # return an empty Hash object
end
parsed.each {|key, value| instance_variable_set("##{key}", value)}
end
#a method to provide feedback simply
def download(from,to)
puts "completed download for #{from}\n"
end
end
class PreStart
include MAGEINSTALLER_Helper
#note nonrelated items removed
def initialize(params=nil)
puts 'World'
load_settings # The method is available inside the class
file="www/depo/newfile-#{#bs_MAGEversion}.tar.gz"
if !File.exist?(file)
download("http://www.dom.com/#{#bs_MAGEversion}/file-#{#bs_MAGEversion}.tar.gz",file)
else
puts "mage package exists"
end
end
end
I refactored a little bit to more Rubish style.
On this line:
bs_MAGEversion=instance_variable_get("#bs_MAGEversion") #doesn't seem to work
instance_variable_get isn't retrieving from the mi_h Object, which is where your value is stored. The way you've used it, that line is equivalent to:
bs_MAGEversion=#bs_MAGEversion
Changing it to mi_h.instance_variable_get would work. It would also be painfully ugly ruby. But I sense that's not quite what you're after. If I read you correctly, you want this line:
mi_h.load_settings()
to populate #bs_MAGEversion and #bs_mode in your Pre_start object. Ruby doesn't quite work that way. The closest thing to what you're looking for here would probably be a mixin, as described here:
http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_modules.html
We do something similar to this all the time in code at work. The problem, and solution, is proper use of variables and scoping in the main level of your code. We use YAML, you're using JSON, but the idea is the same.
Typically we define a constant, like CONFIG, which we load the YAML into, in our main code, and which is then available in all the code we require. For you, using JSON instead:
require 'json'
require_relative 'helper'
CONFIG = JSON.load_file('path/to/json')
At this point CONFIG would be available to the top-level code and in "helper.rb" code.
As an alternate way of doing it, just load your JSON in either file. The load-time is negligible and it'll still be the same data.
Since the JSON data should be static for the run-time of the program, it's OK to use it in a CONSTANT. Storing it in an instance variable only makes sense if the data would vary from instance to instance of the code, which makes no sense when you're loading data from a JSON or YAML-type file.
Also, notice that I'm using a method from the JSON class. Don't go through the rigamarole you're using to try to copy the JSON into the instance variable.
Stripping your code down as an example:
require 'fileutils'
require 'json'
CONTENTS = JSON.load_file('scripts/installer_settings.json')
class MAGEINSTALLER_Helper
def download(from,to)
puts "completed download for #{from}\n"
end
end
class Pre_start
def initialize(params=nil)
file = "www/depo/newfile-#{ CONFIG['bs_MAGEversion'] }.tar.gz"
if !File.exist?(file)
mi_h.download("http://www.dom.com/#{ CONFIG['bs_MAGEversion'] }/file-#{ CONFIG['bs_MAGEversion'] }.tar.gz", file)
else
puts "mage package exists"
end
end
end
CONFIG can be initialized/loaded in either file, just do it from the top-level before you need to access the contents.
Remember, Ruby starts executing it at the top of the first file and reads downward. Code that is outside of def, class and module blocks gets executed as it's encountered, so the CONFIG initialization will happen as soon as Ruby sees that code. If that happens before you start calling your methods and creating instances of classes then your code will be happy.

Accessing/Dealing with Variables in Ruby

Let me preface by stating I'm a "new" programmer - an IT guy trying his hand at his first "real" problem after working through various tutorials.
So - here is what I'm trying to do. I'm watching a directory for a .csv file - it will be in this format: 999999_888_filename.csv
I want to return each part of the "_" filename as a variable to pass on to another program/script for some other task. I have come up w/ the following code:
require 'rubygems'
require 'fssm'
class Watcher
def start
monitor = FSSM::Monitor.new(:directories => true)
monitor.path('/data/testing/uploads') do |path|
path.update do |base, relative, ftype|
output(relative)
end
path.create do |base, relative, ftype|
output(relative)
end
path.delete { |base, relative, ftype| puts "DELETED #{relative} (#{ftype})" }
end
monitor.run
end
def output(relative)
puts "#{relative} added"
values = relative.split('_',)
sitenum = values[0]
numrecs = values[1]
filename = values[2]
puts sitenum
end
end
My first "puts" gives me the full filename (it's just there to show me the script is working), and the second puts returns the 'sitenum'. I want to be able to access this "outside" of this output method. I have this file (named watcher.rb) in a libs/ folder and I have a second file in the project root called 'monitor.rb' which contains simply:
require './lib/watcher'
watcher = Watcher.new
watcher.start
And I can't figure out how to access my 'sitenum', 'numrecs' and 'filename' from this file. I'm not sure if it needs to be a variable, instance variable or what. I've played around w/ attr_accessible and other things, and nothing works. I decided to ask here since I've been spinning my wheels for a couple of things, and I'm starting to confuse myself by searching on my own.
Thanks in advance for any help or advice you may have.
At the top of the Watcher class, you're going to want to define three attr_accessor declarations, which give the behavior you want. (attr_reader if you're only reading, attr_writer if you're only writing, attr_accessor if both.)
class Watcher
attr_accessor :sitenum, :numrecs, :filename
...
# later on, use # for class variables
...
#sitenum = 5
...
end
Now you should have no problem with watcher.sitenum etc. Here's an example.
EDIT: Some typos.
In addition to Jordan Scales' answer, these variable should initialized
class Watcher
attr_accessor :sitenum, :numrecs, :filename
def initialize
#sitenum = 'default value'
#numrecs = 'default value'
#filename = 'default value'
end
...
end
Otherwise you'll get uninformative value nil

Correct way to TDD methods that calls other methods

I need some help with some TDD concepts. Say I have the following code
def execute(command)
case command
when "c"
create_new_character
when "i"
display_inventory
end
end
def create_new_character
# do stuff to create new character
end
def display_inventory
# do stuff to display inventory
end
Now I'm not sure what to write my unit tests for. If I write unit tests for the execute method doesn't that pretty much cover my tests for create_new_character and display_inventory? Or am I testing the wrong stuff at that point? Should my test for the execute method only test that execution is passed off to the correct methods and stop there? Then should I write more unit tests that specifically test create_new_character and display_inventory?
I'm presuming since you mention TDD the code in question does not actually exist. If it does then you aren't doing true TDD but TAD (Test-After Development), which naturally leads to questions such as this. In TDD we start with the test. It appears that you are building some type of menu or command system, so I'll use that as an example.
describe GameMenu do
it "Allows you to navigate to character creation" do
# Assuming character creation would require capturing additional
# information it violates SRP (Single Responsibility Principle)
# and belongs in a separate class so we'll mock it out.
character_creation = mock("character creation")
character_creation.should_receive(:execute)
# Using constructor injection to tell the code about the mock
menu = GameMenu.new(character_creation)
menu.execute("c")
end
end
This test would lead to some code similar to the following (remember, just enough code to make the test pass, no more)
class GameMenu
def initialize(character_creation_command)
#character_creation_command = character_creation_command
end
def execute(command)
#character_creation_command.execute
end
end
Now we'll add the next test.
it "Allows you to display character inventory" do
inventory_command = mock("inventory")
inventory_command.should_receive(:execute)
menu = GameMenu.new(nil, inventory_command)
menu.execute("i")
end
Running this test will lead us to an implementation such as:
class GameMenu
def initialize(character_creation_command, inventory_command)
#inventory_command = inventory_command
end
def execute(command)
if command == "i"
#inventory_command.execute
else
#character_creation_command.execute
end
end
end
This implementation leads us to a question about our code. What should our code do when an invalid command is entered? Once we decide the answer to that question we could implement another test.
it "Raises an error when an invalid command is entered" do
menu = GameMenu.new(nil, nil)
lambda { menu.execute("invalid command") }.should raise_error(ArgumentError)
end
That drives out a quick change to the execute method
def execute(command)
unless ["c", "i"].include? command
raise ArgumentError("Invalid command '#{command}'")
end
if command == "i"
#inventory_command.execute
else
#character_creation_command.execute
end
end
Now that we have passing tests we can use the Extract Method refactoring to extract the validation of the command into an Intent Revealing Method.
def execute(command)
raise ArgumentError("Invalid command '#{command}'") if invalid? command
if command == "i"
#inventory_command.execute
else
#character_creation_command.execute
end
end
def invalid?(command)
!["c", "i"].include? command
end
Now we finally got to the point we can address your question. Since the invalid? method was driven out by refactoring existing code under test then there is no need to write a unit test for it, it's already covered and does not stand on it's own. Since the inventory and character commands are not tested by our existing test, they will need to be test driven independently.
Note that our code could be better still so, while the tests are passing, lets clean it up a bit more. The conditional statements are an indicator that we are violating the OCP (Open-Closed Principle) we can use the Replace Conditional With Polymorphism refactoring to remove the conditional logic.
# Refactored to comply to the OCP.
class GameMenu
def initialize(character_creation_command, inventory_command)
#commands = {
"c" => character_creation_command,
"i" => inventory_command
}
end
def execute(command)
raise ArgumentError("Invalid command '#{command}'") if invalid? command
#commands[command].execute
end
def invalid?(command)
!#commands.has_key? command
end
end
Now we've refactored the class such that an additional command simply requires us to add an additional entry to the commands hash rather than changing our conditional logic as well as the invalid? method.
All the tests should still pass and we have almost completed our work. Once we test drive the individual commands you can go back to the initialize method and add some defaults for the commands like so:
def initialize(character_creation_command = CharacterCreation.new,
inventory_command = Inventory.new)
#commands = {
"c" => character_creation_command,
"i" => inventory_command
}
end
The final test is:
describe GameMenu do
it "Allows you to navigate to character creation" do
character_creation = mock("character creation")
character_creation.should_receive(:execute)
menu = GameMenu.new(character_creation)
menu.execute("c")
end
it "Allows you to display character inventory" do
inventory_command = mock("inventory")
inventory_command.should_receive(:execute)
menu = GameMenu.new(nil, inventory_command)
menu.execute("i")
end
it "Raises an error when an invalid command is entered" do
menu = GameMenu.new(nil, nil)
lambda { menu.execute("invalid command") }.should raise_error(ArgumentError)
end
end
And the final GameMenu looks like:
class GameMenu
def initialize(character_creation_command = CharacterCreation.new,
inventory_command = Inventory.new)
#commands = {
"c" => character_creation_command,
"i" => inventory_command
}
end
def execute(command)
raise ArgumentError("Invalid command '#{command}'") if invalid? command
#commands[command].execute
end
def invalid?(command)
!#commands.has_key? command
end
end
Hope that helps!
Brandon
Consider refactoring so that the code that has responsibility for parsing commands (execute in your case) is independent of the code that implements the actions (i.e., create_new_character, display_inventory). That makes it easy to mock the actions out and test the command parsing independently. You want independent testing of the different pieces.
I would create normal tests for create_new_character and display_inventory, and finally to test execute, being just a wrapper function, set expectations to check that the apropriate command is called (and the result returned). Something like that:
def test_execute
commands = {
"c" => :create_new_character,
"i" => :display_inventory,
}
commands.each do |string, method|
instance.expects(method).with().returns(:mock_return)
assert_equal :mock_return, instance.execute(string)
end
end

Resources