Accessing instance variables in Ruby OOP - ruby

I am learning Ruby OOP and have been faced with the following question.
What could we add to the class below to access the instance variable
#volume?
class Cube
def initialize(volume)
#volume = volume
end
end
My initial thought was to add attr_reader :volume to access the instance variable.
Instead the model answer suggests adding a new method as below.
def get_volume
#volume
end
Why is this the preferred method?
Both methods would output 100 if cube.volume or cube.get_volume were called.

attr_reader. In general methods with get_ prefix are rather avoided in Ruby community (in opposite to commonly seen in Java/C# code)

If it is a dynamically created variable, then you can use instance_vairable_get, like below -
instance_variable_get("#volume")

Since it is desired to get the variable volume ,I will suggest the use of attr_reader :volume. This creates a proxy method volume so no need to add( and later have to maintain) an extra method get_volume for this sole purpose.
https://ruby-doc.org/core-2.1.1/Module.html#method-i-attr_reader

You should use attr_reader :volume instead of using get_volume method.
Stil attr_reader and your method get_volume both are functioning same.
Generally get_ methods should be avoided in ruby.

Related

How to automatically log to console when a variable is changed in Ruby?

In Ruby I'd like to have a message printed to console whenever a given variable is changed at any time during execution.
How should I approach this? Should I monkey patch the method for assigning values to variables?
I could only find this related question Hook to be called when a variable changes where an answer is suggesting to redefine #freeze but this approach has limitations. Also it doesn't look right.
Isn't there a better and more consistent solution?
Add your own getter/setter.
Example:
class Person
def name
#name
end
def name=(s)
#name=s
puts 'name has changed!'
end
end
This is not possible.
Neither the set_trace_func nor the TracePoint API support tracing variable assignments, and …
Should I monkey patch the method for assigning values to variables?
… such a method does not exist.
Ruby just doesn't consider variables to part of the object-oriented fabric of the program, I guess. Only objects and methods.

Convention to prevent accidental call of instance variable

Let's say we have class
class Evaluator
attr_reader :response
def initialize(response)
#response = response
end
def order
#_order ||= Order.find(response.order_id)
end
end
I'd like to ask about #_order which exists only for caching purposes. This convention is to prevent accidental use of an instance variable (when it is not initialized yet, for example). I'd like to force using order instead of #order. What do you think about it? Do you know any better way of achieving this goal?
You cannot make instance variables inaccessible from subclasses, in Ruby. Instance variables are protected (in common programming terms) by default, which means that they are not accessible from the outside of the class, but are accessible from within the class hierarchy.
You can only control the accessibility of methods with the use of private, protected and public.
You can make it painfully obvious that one should not use the member, like calling it #lazy_loaded_order, or even generalize it like this:
def order
#lazy_members[:order] ||= Order.find(response.order_id)
end
This way, it is obvious that anyone trying to use #lazy_members will do it on his own risk...

In object.initialize, is it better to use self. over #?

There's a convention to reference an object's attributes over its instance variables, where possible. Practical Object-Oriented Design in Ruby says:
Always wrap instance variables in accessor methods instead of directly
referring to variables...
This is shown with an example, which I've paraphrased:
class Gear
attr_reader :chainring, :cog
...
def ratio
# this is bad
# #chainring / #cog.to_f
# this is good
chainring / cog.to_f
end
The most common way I see to create a new object with an instance variable is this:
class Book
attr_accessor :title
def initialize(title)
#title = title
end
end
#title= directly accesses the instance variable title. Assuming we are following the the 'attribute over instance variable' convention, is it more appropriate to use self.title=, which would tell the object to send itself the message title=, thereby using the attribute write method, over the instance variable directly?
class Book
attr_accessor :title
def initialize(title)
self.title = title
end
end
The book talks about 'attribute over instance variable' with reference to reading an instance variable, but doesn't it also apply to writing?
The book talks about 'attribute over instance variable' with reference
to reading an instance variable, but doesn't it also apply to writing?
Yes, it also applies to writing. However, the initialize method is special because it is responsible for setting up the object. When you use a setter method, you do that because the setter might be doing some extra work (e.g. attribute-setters in Rails). In an initializer, you usually don't want to have any side effects, so you access instance variables directly.
At first, in some case self.feature= is more preferably than #feature=, generally in that case, when assignment to feature property shell do more actions, then just assignment. For example, in a database access methods.
At second, in the good ruby style guide you can see that good style self.feature= is encontered only once, when the complex assignment is occurred. It means that not just assisnment to a instance variable. In case of read, construction like feature == "value" is always used.

Ruby Unit test - Instance variable declared in setUp takes value nil

Hello I have a trouble with Ruby unit testing, I'm new to it so some help would be lovely
class TestItem < Test::Unit::TestCase
def setUp
**#item**=Item.new('Food','Burger',120)
end
def testGetType
assert_equal(**#item**.getType,'Food')
end
end
Here the value of instance variable #item takes nil when I declare it in setUp() and use it in test functions! So I get an error like no method 'getType' for nil-class
But when I directly use it like assert_equal(Item.new('Food','Burger',120).getType,'Food'),it works fine.
Please point out to my mistakes, thanks in advance
The name of the setup method is setup, not setUp. In fact, you will never find a method called setUp in Ruby, since the standard Ruby style for method naming is snake_case, not camelCase. (The same applies to getType and testGetType, BTW. It should be get_type and test_get_type. Well, actually, in Ruby, getters aren't prefixed with get, so really it should be type and test_type. But note that in Ruby, all objects already have type method, although that is deprecated.)

How to make instance variables private in Ruby?

Is there any way to make instance variables "private"(C++ or Java definition) in ruby? In other words I want following code to result in an error.
class Base
def initialize()
#x = 10
end
end
class Derived < Base
def x
#x = 20
end
end
d = Derived.new
Like most things in Ruby, instance variables aren't truly "private" and can be accessed by anyone with d.instance_variable_get :#x.
Unlike in Java/C++, though, instance variables in Ruby are always private. They are never part of the public API like methods are, since they can only be accessed with that verbose getter. So if there's any sanity in your API, you don't have to worry about someone abusing your instance variables, since they'll be using the methods instead. (Of course, if someone wants to go wild and access private methods or instance variables, there isn’t a way to stop them.)
The only concern is if someone accidentally overwrites an instance variable when they extend your class. That can be avoided by using unlikely names, perhaps calling it #base_x in your example.
Never use instance variables directly. Only ever use accessors. You can define the reader as public and the writer private by:
class Foo
attr_reader :bar
private
attr_writer :bar
end
However, keep in mind that private and protected do not mean what you think they mean. Public methods can be called against any receiver: named, self, or implicit (x.baz, self.baz, or baz). Protected methods may only be called with a receiver of self or implicitly (self.baz, baz). Private methods may only be called with an implicit receiver (baz).
Long story short, you're approaching the problem from a non-Ruby point of view. Always use accessors instead of instance variables. Use public/protected/private to document your intent, and assume consumers of your API are responsible adults.
It is possible (but inadvisable) to do exactly what you are asking.
There are two different elements of the desired behavior. The first is storing x in a read-only value, and the second is protecting the getter from being altered in subclasses.
Read-only value
It is possible in Ruby to store read-only values at initialization time. To do this, we use the closure behavior of Ruby blocks.
class Foo
def initialize (x)
define_singleton_method(:x) { x }
end
end
The initial value of x is now locked up inside the block we used to define the getter #x and can never be accessed except by calling foo.x, and it can never be altered.
foo = Foo.new(2)
foo.x # => 2
foo.instance_variable_get(:#x) # => nil
Note that it is not stored as the instance variable #x, yet it is still available via the getter we created using define_singleton_method.
Protecting the getter
In Ruby, almost any method of any class can be overwritten at runtime. There is a way to prevent this using the method_added hook.
class Foo
def self.method_added (name)
raise(NameError, "cannot change x getter") if name == :x
end
end
class Bar < Foo
def x
20
end
end
# => NameError: cannot change x getter
This is a very heavy-handed method of protecting the getter.
It requires that we add each protected getter to the method_added hook individually, and even then, you will need to add another level of method_added protection to Foo and its subclasses to prevent a coder from overwriting the method_added method itself.
Better to come to terms with the fact that code replacement at runtime is a fact of life when using Ruby.
Unlike methods having different levels of visibility, Ruby instance variables are always private (from outside of objects). However, inside objects instance variables are always accessible, either from parent, child class, or included modules.
Since there probably is no way to alter how Ruby access #x, I don't think you could have any control over it. Writing #x would just directly pick that instance variable, and since Ruby doesn't provide visibility control over variables, live with it I guess.
As #marcgg says, if you don't want derived classes to touch your instance variables, don't use it at all or find a clever way to hide it from seeing by derived classes.
It isn't possible to do what you want, because instance variables aren't defined by the class, but by the object.
If you use composition rather than inheritance, then you won't have to worry about overwriting instance variables.
If you want protection against accidental modification. I think attr_accessor can be a good fit.
class Data
attr_accessor :id
private :id
end
That will disable writing of id but would be readable. You can however use public attr_reader and private attr_writer syntax as well. Like so:
class Data
attr_reader :id
private
attr_writer :id
end
I know this is old, but I ran into a case where I didn't as much want to prevent access to #x, I did want to exclude it from any methods that use reflection for serialization. Specifically I use YAML::dump often for debug purposes, and in my case #x was of class Class, which YAML::dump refuses to dump.
In this case I had considered several options
Addressing this just for yaml by redefining "to_yaml_properties"
def to_yaml_properties
super-["#x"]
end
but this would have worked just for yaml and if other dumpers (to_xml ?) would not be happy
Addressing for all reflection users by redefining "instance_variables"
def instance_variables
super-["#x"]
end
Also, I found this in one of my searches, but have not tested it as the above seem simpler for my needs
So while these may not be exactly what the OP said he needed, if others find this posting while looking for the variable to be excluded from listing, rather than access - then these options may be of value.

Resources