4

I have a situation where I define a variable at the top of my script, and want to reference it in a method:

#############
# Variables #
#############
tmp_dir = '/path/to/tmp/dir'

###########
# Methods #
###########
def cache(page)
  begin
    %x[wget -q -O #{tmp_dir}/page #{page}]
  rescue => msg
    puts "error: #{msg}"
    exit
  end
 end

cache("http://somepage.com")

I am getting this error:

undefined local variable or method `tmp_dir' for main:Object

I'm guessing I need to make tmp_dir a global variable? I hate using global variables. Is there a Ruby-ish way to do this?

0

5 Answers 5

6

tmp_dir is defined as a variable in the class definition, but in the instance function you're looking for a tmp_dir defined on an instance of the class. That's why tmp_dir is undefined inside the function.

You can make it global or make it a class variable as quick fixes. I think there's a better alternative: wrap this up in its own class that knows how to cache, and then initialize tmp_dir without making it global OR a class variable:

class Cacher
    def initialize(tmp_dir)
        @tmp_dir = tmp_dir
    end

    def cache(page)
        wget "#{@tmp_dir}/page"
    end
end

# in your main file:
cacher = Cacher.new('/path/to/tmp/dir') # here's your configuration line, but with no global!

# later

cacher.cache("index.html")
Sign up to request clarification or add additional context in comments.

3 Comments

Adding a class make sense to me.
Eh. My issue with this, however, is I have a bunch of constants I want to use (but not make global) and I don't want to declare them all in the initialization. I also will use these across different methods.
If you want a value accessible across lots of functions, the options are instance variables, class variables, global variables, and parameters.
3

You're correct. You need to make the variable global by prefixing it with $. Ex:

$tmp_dir = '/path/to/tmp/dir'

Rather than do that, you could also make it an instance variable, or you could refactor to make it a class. I would recommend doing what Riley Lark said.

Comments

3

You are right. Global variables should be avoided. The natural choices are instance variables on the class, or constants.

In your case, it looks like you have something that does not change throughout the execution of the script. Then, a constant is most appropriate. You can define this constant within an appropriate module.

TmpDir = "/path/to/tmp/dir"

Also notice that Ruby has a built in way to refer to tmp dir.

require "tmpdir"
Dir.tmpdir # => "/tmp" (depending on the environment)

8 Comments

+1,You are on the right track. Use that standard lib with block would be a good solution too. Complete the answer please. :)
Although it’s more idiomatic to use ALL_CAPS for constants like this, and LeadingCaps for classes.
@matt Right. But note that classes are constants.
@sawa yes, but "/path/to/tmp/dir" isn’t a class.
I want to avoid declaring TmpDir = "/path/to/tmp/dir" inside each method / class I have. I'll have multiple methods and classes using this constant. I would like a solution where I set it once, I'm not sure that this is it.
|
1

In addition to other answers, to make local variable set in class definition scope work in method definition, you can use define_method which is a method that takes block. Blocks in Ruby are closures, so they go together with environment that they were set in:

define_method(:cache) do |page|
  begin
    %x[wget -q -O #{tmp_dir}/page #{page}]
  rescue => msg
    puts "error: #{msg}"
    exit
  end
end

3 Comments

+1 from me. I was thinking something like this. But I am having a thick brain... :(
Does this only work inside a class? Note: I'm not using any classes, it's just a straight up method.
@awojo Every Ruby code is written in the context of a class. If it isn't set explicitly, it referrs to Object class.
0

Short answer

It sounds like you're writing a short script. If that's the case, there's no reason you can't use a modifiable global variable like $tmp_dir. But if you don't need to modify it, you should use @sawa's solution of a global constant like TMP_DIR. In that case you should call .freeze on the string to avoid accidental modification.

Longer answer

If the script becomes longer or more complex, you should refactor into classes. The TMP_DIR solution will still work in this case. But if you need to modify the value, you can create a ConfigObject class to group these variables.

Example:

ConfigObject = Struct.new(:tmp_dir, :file_limit)
# it's a good idea to create this before everything else
$config = ConfigObject.new('/tmp/dir', 10)

class Foo
  def do_something
    $config.file_limit  # use this somehow
    $config.file_limit = 5  # change
  end
end

A similar technique uses class variables to accomplish the same thing:

class ConfigObject
  class << self
    attr_accessor :tmp_dir, :file_limit
  end
  @tmp_dir = '/tmp/dir'
  @file_limit = 10
end

class Foo
  def do_something
    ConfigObject.file_limit  # use this somehow
    ConfigObject.file_limit = 5  # change
  end
end

Think of ConfigObject as a "service" used by other classes. If your app gets complicated enough to need multiple interacting services, you might need want to set up a kind of service registry that hold references to the services (google "dependency injection" for more info).

Note: you shouldn't name your class Config because that's already a built-in class.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.