5

This is a newbie question as I am attempting to learn Ruby by myself, so apologies if it sounds like a silly question!

I am reading through the examples of why's (poignant) guide to ruby and am in chapter 4. I typed the code_words Hash into a file called wordlist.rb

I opened another file and typed the first line as require 'wordlist.rb' and the rest of the code as below

#Get evil idea and swap in code
print "Enter your ideas "
idea = gets
code_words.each do |real, code|
    idea.gsub!(real, code)
end

#Save the gibberish to a new file
print "File encoded, please enter a name to save the file"
ideas_name = gets.strip
File::open( 'idea-' + ideas_name + '.txt', 'w' ) do |f|
    f << idea
end

When I execute this code, it fails with the following error message:

C:/MyCode/MyRubyCode/filecoder.rb:5: undefined local variable or method `code_words' for main:Object (NameError)

I use Windows XP and Ruby version ruby 1.8.6

I know I should be setting something like a ClassPath, but not sure where/how to do so!

Many thanks in advance!

Svante
  • 50,694
  • 11
  • 78
  • 122

5 Answers5

5

While the top-level of all files are executed in the same context, each file has its own script context for local variables. In other words, each file has its own set of local variables that can be accessed throughout that file, but not in other files.

On the other hand, constants (CodeWords), globals ($code_words) and methods (def code_words) would be accessible across files.

Some solutions:

CodeWords = {:real => "code"}

$code_words = {:real => "code"}

def code_words
  {:real => "code"}
end

An OO solution that is definitely too complex for this case:

# first file
class CodeWords
  DEFAULT = {:real => "code"}

  attr_reader :words
  def initialize(words = nil)
    @words = words || DEFAULT
  end
end

# second file
print "Enter your ideas "
idea = gets
code_words = CodeWords.new
code_words.words.each do |real, code|
  idea.gsub!(real, code)
end

#Save the gibberish to a new file
print "File encoded, please enter a name to save the file"
ideas_name = gets.strip
File::open( 'idea-' + ideas_name + '.txt', 'w' ) do |f|
  f << idea
end
Yehuda Katz
  • 28,535
  • 12
  • 89
  • 91
1

I think the problem might be that the require executes the code in another context, so the runtime variable is no longer available after the require.

What you could try is making it a constant:

CodeWords = { :real => 'code' }

That will be available everywhere.

Here is some background on variable scopes etc.

Arthur
  • 191
  • 1
  • 5
1

I was just looking at the same example and was having the same problem. What I did was change the variable name in both files from code_words to $code_words .

This would make it a global variable and thus accesible by both files right?

My question is: wouldn't this be a simpler solution than making it a constant and having to write CodeWords = { :real => 'code' } or is there a reason not to do it ?

raed
  • 61
  • 9
  • My God it worked!! I was on the same problem (for a couple hours now) and was wondering the same thing, just making code_words a global variable. Leave it to someone with a handful of points to supply the clearest, most concise, easiest to understand answer. None of this trying to decipher or interpret a given answer, just a straight, simple use of code brevity. Thank you! ...@raed do you have any warning signs for other landmines of Why's Poigniant exercises? – Padawan Jun 20 '15 at 13:37
  • In order to get it to work, I also had to change 'require' to 'require_relative'. Here is the difference between the two: http://stackoverflow.com/questions/3672586/what-is-the-difference-between-require-relative-and-require-in-ruby – Padawan Jun 20 '15 at 13:46
0

A simpler way would be to use the Marshal.dump feature to save the code words.

# Save to File
code_words = {

'starmonkeys' => 'Phil and Pete, those prickly chancellors of the New Reich', 'catapult' => 'chucky go-go', 'firebomb' => 'Heat-Assisted Living', 'Nigeria' => "Ny and Jerry's Dry Cleaning (with Donuts)", 'Put the kabosh on' => 'Put the cable box on' }

# Serialize
f = File.open('codewords','w')
  Marshal.dump(code_words, f)
f.close

Now at the beginning of your file you would put this:

# Load the Serialized Data
code_words = Marshal.load(File.open('codewords','r'))
Cuervo's Laugh
  • 206
  • 3
  • 8
0

Here's the easy way to make sure you can always include a file that's in the same directory as your app, put this before the require statement

$:.unshift File.dirname(__FILE__)

$: is the global variable representing the "CLASSPATH"

Ana Betts
  • 73,868
  • 16
  • 141
  • 209