Preventing conflict with module-specific instance variables - ruby

I have a mixin that can show up in several different areas of our codebase. In a couple of cases there is, or was, an edge case where the module's use of instance variables conflicted with instance variables used by the classes.
Prepending variable names #__like_this was one solution, but the other we came up with was more sophisticated, having this general structure, in which the accessor methods are closures:
module HiddenValue
def initialize
hidden_value = nil
define_singleton_method :value, ->() { hidden_value }
define_singleton_method :value=, ->(v) { hidden_value = v }
super
end
end
class RealClass
include HiddenValue
# [...]
end
foo = RealClass.new
foo.value = 123
foo.value # => 123
Is this bad style? It means that all methods wanting direct access to the variable must be defined dynamically inside initialize, and that could get ugly. Is there a cleaner way? Or is this the closest we'll get to a module-level "lexical"-ish scoped instance variable?

Related

Declaring an instance variable outside of `initialize` method

I have been taught to declare my instance variables with def initialize. I have been under the impression that I could declare instance variables only within my initialize methods.
Nevertheless, I declared an instance variable #foo outside my initialize method, and made it work as I intended:
class FooBar
def initialize(bar)
#bar = bar
end
def foo_as_instance_var
#foo = #bar.split(' ')
#foo
end
end
x = "something wicked this way comes"
y = FooBar.new(x)
puts y.foo_as_instance_var
Why am I able to declare an instance variable outside of initialize method? Since I can declare instance variables in any method, is there a best practices rule I should follow, regarding where to declare instance variables (i.e., declare them within initialize) or does it not matter?
I have been taught to declare my instance variables with def initialize
Since initialize is the first instance method call in an object's life cycle, you typically declare your instance variables right there in order to ensure properly initialized variables. It's also the first place I'd expect instance variables to be defined when reading code.
I have been under the impression that I could declare instance variables only within my initialize methods.
There's no such restriction. You can declare instance variable anywhere within your instance.
A common use is memoization:
class FooBar
def foo
#foo ||= expensive_operation
end
end
On the first call, this would evaluate expensive_operation and assign the result to #foo. On subsequent calls, #foo is returned.
Another popular example is Rails which uses instance variables to pass data from the controller to its view:
class FooController < ApplicationController
def index
#foos = Foo.all
end
end
is there a best practices rule I should follow, regarding where to declare instance variables
It depends on their purpose (see above examples). As a general rule, declare them in a way that avoids undefined variables (nil errors) and structure your code so it is easy to read / follow.
Just to add to Stefan's excellent answer
I have been taught to declare my instance variables with def initialize
A common mistake that ruby newbies make is something like this:
class Person
#name = "John"
def introduce
puts "Hi, my name is #{#name}"
end
end
And then they wonder why their names are not printed. To make this work, one can set the variable #name in the initializer, just as the instruction says.
Lets start with the biggest misnomer - in Ruby there is no separate step of declaring variables - Variables are declared as you set them.
What the difference? Look at Java for example:
public class Bicycle {
private int cadence;
private int gear;
private int speed;
public Bicycle(int startCadence, int startSpeed, int startGear) {
gear = startGear;
cadence = startCadence;
speed = startSpeed;
}
}
We have to declare all the instance variables before we set them in the initializer (Bicycle). The same code in Ruby reads:
class Bicycle
def initialize(cadence, speed, gear)
#cadence = cadence
#speed = speed
#gear = gear
end
end
There is no declaration - only assignment. Ruby will even let you access instance variables which have not been set without error.
irb(main):003:0> #not_set
=> nil
You can't do that (generally) in languages where variables must be defined*.
I have been taught to declare my instance variables with def
initialize. I have been under the impression that I could declare
instance variables only within my initialize methods.
Nonsense. You can assign instance variables anywhere. Its commonly done in everything from setters and mutators (methods that alter an object) to factory methods (class methods that return an instance) or anywhere that you are altering the state of an object.
class Book
def initialize(title, author)
#title = title
self.author = author # calls the setter.
end
# A factory method
def create_from_csv(filename)
# ...
end
# A very contrived setter
def author=(author)
#author = "#{author.forename.upcase}. #{author.surname}"
end
# a mutator
def out_of_print!
#out_of_print = true
#last_printed = Date.today
end
end
However the initialize method is where you should handle initializing your objects (duuh) and is thus the obvious place to set initial values.

What is difference between class variable and variable defined on class level

What is the difference between class variable and variable defined on the class level?
say, bar is defined with ## which means it's a class variable and will be accessible within all methods in class.
class Foo
##bar = 'bar'
end
so does bar without ##, so what is the difference..?
class Foo
bar = 'bar'
end
Well, with your second option, bar is a local variable which gets out of scope when reaching the end. As such, it won't be accessible to any methods (class methods nor instance methods) of the class.
With that being said, in Ruby, there are class variables (##bar which are shared between all child classes and their instances and instance variables (#bar) Since classes are also just objects in Ruby, you can also define an instance variable on class level (or more correctly: on the singleton class of your class). This can work like this:
class Foo
def self.bar
#bar
end
def self.bar=(value)
#bar = value
end
end
Compared to class variables, these instance variables on the singleton class are not accessible on Foo instances nor on child classes of Foo.
The ##bar will return the same variable to all instances of the class, not just the same variable to all methods in the class.
The way I think of class variables (##variables) is as namespaced global variables. Their usage is just about as frowned upon as using global variables, but not quite since you are limiting the scope to within the code defined in your class.
Generally they would be used to track some kind of state across your application.
Let's say you had an object that needed to be able to identify its most recently instantiated sibling object.
One way to do that would be via a global variable:
class MyThing
def initialize
$latest_thing = self
end
def latest
$latest_thing
end
end
thing1 = MyThing.new
thing1.latest # => thing1
thing2 = MyThing.new
thing1.latest # => thing2
thing2.latest # => thing2
Now, that uses a global variable which is generally perceived as bad practice, one of the reasons being for pollution of the global namespace and risk of naming collision and/or someone else changing it.
If you are dealing with a situation like this where you need this shared state between instances, but no one outside needs to know about it, then you can use the class variable exactly like a global:
class MyThing
def initialize
##latest_thing = self
end
def latest
##latest_thing
end
end
thing1 = MyThing.new
thing1.latest # => thing1
thing2 = MyThing.new
thing1.latest # => thing2
thing2.latest # => thing2
That's just generally cleaner/safer/better encapsulated, since any 3rd party code can not simply do ##latest_thing = :something_else the way that they could do had a global been used.
Hope that helps. I think class variables are rarely used or encouraged in the wild, but on the rare occasion I've needed to use one it was for things like this.

Local variables in `class` definition scope versus `def` method scope

Here, I create a local variable in class scope:
class MyClass
x = 1
puts x
end
It prints 1 even if I don't create any instances of MyClass.
I want to use x in some method:
class MyClass
x = 1
def method
puts x
end
end
m = MyClass.new
m.method
And I can't. Why? I get that class definition creates a scope, but why is it not accessible in the method? Isn't scope of the method inside the scope of the class?
I can imagine that this is related to creation of a class. Since any class is an object of Class, maybe the scope of MyClass is the scope of some Class method, and the way of coupling methods of MyClass to that instance makes their scope completely different.
It also seems to me that I can't just create a scope with {} (like in C) or something like do..end. Am I correct?
Scope of a method is not inside the class. Each method has its own entirely new scope.
New scopes are created whenever you use the class, module, and def keywords. Using brackets, as in C, does not create a new scope, and in fact you cannot arbitrarily group lines of code using brackets. The brackets (or do...end) around a Ruby block create a block-level scope, where variables previously created in the surrounding scope are available, but variables created within the block scope do not escape into the surrounding scope afterward.
Instance methods share the scope of their instance variables with other instances methods. An instance variable defined in the scope of a class definition is available in class-level singleton methods, but not in instance methods of the class.
Illustration:
class Foo
x = 1 # available only here
#y = 2 # class-wide value
def self.class_x
#x # never set; nil value
end
def self.class_y
#y # class-wide value
end
def initialize(z)
x = 3 # available only here
#z = z # value for this instance only
end
def instance_x
#x # never set; nil
end
def instance_y
#y # never set; nil
end
def instance_z
#z # value for this instance only
end
end
Foo.class_x # => nil
Foo.class_y # => 2
Foo.new(0).instance_x # => nil
Foo.new(0).instance_y # => nil
foo3 = Foo.new(3)
foo4 = Foo.new(4)
foo3.instance_z # => 3
foo4.instance_z # => 4
You can access class-level instance variables from within instances using the class-level getter. Continuing the example above:
class Foo
def get_class_y
self.class.class_y
end
end
foo = Foo.new(0)
foo.get_class_y # => 2
There exists in Ruby the notion of a "class variable," which uses the ## sigil. In practice, there is almost never a reasonable use case for this language construct. Typically the goal can be better achieved using a class-level instance variable, as shown here.
Here, I create a local variable in class scope:
class MyClass
x = 1
puts x
end
It prints 1 even if I don't create any instances of MyClass.
Correct. The class definition body is executed when it is read. It's just code like any other code, there is nothing special about class definition bodies.
Ask yourself: how would methods like attr_reader/attr_writer/attr_accessor, alias_method, public/protected/private work otherwise? Heck, how would def work otherwise if it didn't get executed when the class is defined? (After all, def is just an expression like any other expression!)
That's why you can do stuff like this:
class FileReader
if operating_system == :windows
def blah; end
else
def blubb; end
end
end
I want to use x in some method:
class MyClass
x = 1
def method
puts x
end
end
m = MyClass.new
m.method
And I can't. Why? I get that class definition creates a scope, but why is it not accessible in the method? Isn't scope of the method inside the scope of the class?
No, it is not. There are 4 scopes in Ruby: script scope, module/class definition scope, method definition scope, and block/lambda scope. Only blocks/lambdas nest, all the others create new scopes.
I can imagine that this is related to creation of a class. Since any class is an object of Class, maybe the scope of MyClass is the scope of some Class method, and the way of coupling methods of MyClass to that instance makes their scope completely different.
Honestly, I don't fully understand what you are saying, but no, class definition scope is not method definition scope, class definition scope is class definition scope, and method definition scope is method definition scope.
It also seems to me that I can't just create a scope with {} (like in C) or something like do..end. Am I correct?
Like I said above: there are 4 scopes in Ruby. There is nothing like block scope in C. (The Ruby concept of "block" is something completely different than the C concept of "block.") The closest thing you can get is a JavaScript-inspired immediately-invoked lambda-literal, something like this:
foo = 1
-> {
bar = 2
foo + bar
}.()
# => 3
bar
# NameError
In general, that is not necessary in Ruby. In well-factored code, methods will be so small, that keeping track of local variables and their scopes and lifetimes is really not a big deal.
So just creating a class without any instances will lead to something
actually executing in runtime (even allocating may be)? That is very
not like C++. –
Check out this code:
Dog = Class.new do
attr_accessor :name
def initialize(name)
#name = name
end
end
If you execute that code, there won't be any output, but something still happened. For instance, a global variable named Dog was created, and it has a value. Here's the proof:
Dog = Class.new do
attr_accessor :name
def initialize(name)
#name = name
end
end
dog = Dog.new("Ralph")
puts dog.name
--output:--
Ralph
The assignment to the Dog constant above is equivalent to writing:
class Dog
...
...
end
And, in fact, ruby steps through each line inside the class definition and executes each line--unless the line of code is inside a def. The def is created but the code inside a def doesn't execute until the def is called.
A very common line you will see inside a class definition is:
attr_accessor :name
...which can be rewritten as:
attr_accessor(:name)
...which makes it obvious that it's a method call. Ruby executes that line--and calls the method--when you run a file containing the class definition. The attr_accessor method then dynamically creates and inserts a getter and a setter method into the class. At runtime. Yeah, this ain't C++ land anymore--welcome to NeverNever Land.
I get that class definition creates a scope, but why is it not
accessible in the method?
Because that is the way Matz decided things should be: a def creates a new scope, blocking visibility of variables outside the def. However, there are ways to open up the scope gates, so to speak: blocks can see the variables defined in the surrounding scope. Check out define_method():
class MyClass
x = 1
define_method(:do_stuff) do
puts x
end
end
m = MyClass.new
m.do_stuff
--output:--
1
The block is everything between do...end. In ruby, a block is a closure, which means that when a block is created, it captures the variables in the surrounding scope, and carries those variables with it until the the block is executed. A block is like an anonymous function, which gets passed to a method as an argument.
Note that if you use the Class.new trick, you can open two scope gates:
x = 1
MyClass = Class.new do
define_method(:do_stuff) do
puts x
end
end
m = MyClass.new
m.do_stuff
--output:--
1
Generally, ruby allows a programmer to do whatever they want, rules be damned.

Ruby instance variables and methods

Sometimes I see an instance variable defined as #my_variable. However, sometimes I see self.my_variable. When is each used?
Instance variables (#variable) correspond to private variables in other languages. self.myvariable is actually not a variable, but a call to a method. Similarly, if you write self.myvariable = something, it is actually a call to self.myvariable=(something). This corresponds to properties with getters and setters in other languages.
class Foo
def initialize
#bar = 42
end
def xyzzy
123
end
def xyzzy=(value)
puts "xyzzy set to #{value}!"
end
end
obj = Foo.new
puts obj.xyzzy # prints: 123
obj.xyzzy = 2 # prints: xyzzy set to 2
puts obj.bar # error: undefined method 'bar'
You can use attr_reader and attr_accessor to automatically define getters and setters for an instance variable. attr_reader will only generate a getter, while attr_accessor generates both.
class Parrot
attr_accessor :volts
def voom
puts "vooming at #{#volts} volts!"
end
end
polly = Parrot.new
polly.volts = 4000
polly.voom
Instance variables are more primary things than methods calling them. In self.myVariable, myVariable is a method referring to the instance variable #myVariable, and that method is defined usually by attr_reader or attr_accessor.
One purpose of object orientated programming is to encapsule things particular to an instance inside that instance and make it inaccessible from outside of it. This way, you can avoid unwanted conflicts of name. This is true for instance variables. They are usually parameters to be handeled within the instance, and not to be used outside of it.
Within an instance, its instance variables can be directly referred to, and hence there is no need to refer to them via method calls. You should directly call the variable #myVariable.
From outside of an instance, you cannot directly refer to the instance variables because of the reason mentioned above. But sometimes, you do need to refer to them. The purpose of using the method myVariable is to refer to the instance variable from outside of an instance.
#my_variable refers directly to the instance variable, and is (for the most part) inaccessible from outside that instance.
self.my_variable is using an accessor method (as defined with attr_reader, attr_writer or attr_accessor internally. This is in cases where there may not be an instance variable named #my_variable (as is the case with ActiveRecord model attributes) or where the internal state differs from what is exposed publicly.

How to access instance variables from one class while inside another class

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.
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.
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
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`

Resources