views:

1569

answers:

4

I'm really new to Ruby. And by new - less than 16 hours, but my boss gave me some Ruby code to add to. However, I found it was one giant file and not modular at all, so I decided to clean it up. Now that I've broken it up into several files/classes (generally speaking, 1 class per file,) I'm having problems piecing it together for it to work again. Originally everything was part of the same class, so the calls worked, but it looked ugly and it took an entire work day just to figure it out. I want to avoid that for the future as this code will grow much larger before it is done.

My main issue looks like the following (simplified, obviously):

class TestDevice   
   def initialize  
    @loghash = { }  
    ....  
   end  
end   

class Log  
    def self.msg(identifier, level, section, message)  
     ...  
     @loghash[identifier] = { level => { section => message }}  
     ...  
    end  
end  

device = TestDevice.new

After that, it calls out to other class methods, and those class methods reference back to the class Log for their logging needs. Of course, Log needs to access "device.loghash" somehow to log the information in that hash. But I can't figure out how to make that happen outside of passing the contents of "loghash" to every method, so that they, in turn, can pass it, and then return the value back to the origination point and then logging it at the end, but that seems really clumsy and awkward.

I'm hoping I am really just missing something.

+3  A: 

To create accessors for instance variables the simple way, use attr_accessor.

class TestDevice   
  attr_accessor :loghash

  def initialize  
    @loghash = { }  
    ....  
  end  
end

You can also manually define an accessor.

class TestDevice
  def loghash
    @loghash
  end
  def loghash=(val)
    @loghash = val
  end
end

This is effectively what attr_accessor does behind the scenes.

Chuck
Should be "This is effectively what attr_accessor does behind the scenes."
Antti Tarvainen
Right you are. Thanks for catching that.
Chuck
A: 

The original question was mine, but couldn't figure out how to add to it after the other response. That said...

I knew someone would mention an accessor after I posted it, but accessors only seem to be good outside of the class. In other words, if I did:

Class Somethingelse
  def self.something
   device.loghash='something here'
  end
end

That wouldn't work, or it doesn't seem to, or I'm doing it wrong. Either way, I do now that if I declare it as a global variable, I can certainly use it in another class, but years of C has me weary of going that route. A simple pointer or two would be glorious right about now.

Aedorn Varanis
If you want to add to the question, you can edit it. Using answers this way is confusing because answers aren't sorted chronologically on SO. Anyway, this should work if "device" is a valid variable referring to a TestObject device. Are you sure this is supposed to be a class (not instance) method?
Chuck
+2  A: 

Hi, how about passing the device object as a parameter to the msg function? (I'm assuming that there can be many devices in your program, otherwise you can use singleton pattern).

class TestDevice   
   attr_accessor :loghash

   def initialize  
    @loghash = { }  
    ....  
   end  
end   

class Log  
    def self.msg(device, identifier, level, section, message)  
     ...  
     device.loghash[identifier] = { level => { section => message }}  
     ...  
    end  
end
kyku
A: 

So you need to learn the rules of ruby scoping.

Ruby variables have different scope, depending on their prefix:

  • $global_variables start with a $, and are available to everyone.
  • @instance_variables start with a single @, and are stored with the current value of self. If two scopes share the same value of self (they're both instance methods, for example), then both share the same instance variables
  • @@class_variable start with @@, and are stored with the class. They're shared between all instances of a class - and all instances of subclasses of that class.
  • Constants start with a capital letter, and may be all caps. Like class variables, they're stored with the current self.class, but they also trickle up the hierarchy - so if you have a class defined in a module, the instances of the class can access the module's constants as well. Constants defined outside of a class have global scope. Note that a constant variable means that which object is bound to the constant won't change, not that the object itself won't change internal state.
  • local_variables start with a lowercase letter

You can read more about scope here.

Local variables scoping rules are mainly standard - they're available in all subscopes of the one in which they are defined except when we move into a module, class, or method definition. So if we look at your code from your answer

class TestDevice   
   attr_accessor :loghash
   def initialize  
    @loghash = { }  
   end  
end   

device = TestDevice.new

class Somethingelse
  def self.something
   device.loghash='something here' # doesn't work
  end
end

The scope of the device local variable defined at the toplevel does not include the Somethingelse.something method definition. So the device local variable used in the Somethingelse.something method definition is a different (empty) variable. If you want the scoping to work that way, you should use a constant or a global variable.

class TestDevice   
   attr_accessor :loghash
   def initialize  
    @loghash = { }  
   end  
end   

DEVICE = TestDevice.new
$has_logged = false

class Somethingelse
  def self.something
   DEVICE.loghash='something here'
   $has_logged = true
  end
end

p DEVICE.loghash  # prints `{}`
p $has_logged     # prints `false`
Somethingelse.something
p DEVICE.loghash  # prints `"something here"`
p $has_logged     # prints `true`
rampion