I'm currently learning the basics of Ruby and OOP in general. From what I've read so far, I can use attr_reader to grab the value of an instance variable but not give it access to be overwritten. However, given the code block below, the end result is not what I intended and the instance variable was completely changed from outside of the class. What would be the best way where I can simply read the value and return the intended changes into another variable instead of overwriting the instance variable itself?
class X
def initialize
#y = [1,2,3,4]
end
attr_reader :y
end
class Z
def initialize
end
def self.change(arr)
arr[1] = 0
arr[2] = 0
return arr
end
end
x = X.new
z = Z.change(x.y)
p z
p x.y
From what I've read so far, I can use attr_reader to grab the value of an instance variable but not give it access to be overwritten.
Yes and no.
attr_reader :y creates a so-called getter which is equivalent to:
def y
#y
end
attr_writer :y creates the corresponding setter:
def y=(value)
#y = value
end
And attr_accessor creates both.
The getter allows you to conveniently access #y from the outside. The setter allows you to re-assign #y.
But even with just a getter, you can still send messages to the object. And if the object is mutable, like your array, you can modify it that way:
x = X.new
x.y #=> [1, 2, 3, 4]
x.y.push(5)
x.y #=> [1, 2, 3, 4, 5]
In the above example, #y is not re-assigned, it still refers the same object. But the message push caused the object to change itself.
What would be the best way where I can simply read the value [...]
There are several options. If you want to prevent modification form the outside, you could return a copy of the original array:
class X
def initialize
#y = [1, 2, 3, 4]
end
def y
#y.dup
end
end
dup creates a shallow copy of the array and returns it. "shallow" means that a new array is created containing the same elements. Any modification from the outside to the array will only affect the copy.
But you could still modify its elements (via messages) and that change would be reflected by both, original and copy.
Fortunately, your array contains integers which are immutable.
However, given the code block below, the end result is not what I intended and the instance variable was completely changed from outside of the class.
No, it wasn't. The object that the instance variable references was changed. That is to be expected: arrays can be changed, and you handed the caller an array, so the caller can obviously change the array.
But the instance variable was not changed: it still points to the exact same object as before.
My boss and my mother call me by different names. But if I shave my beard, both of them will see my clean-shaven face.
Understanding the difference between a thing and the name of the thing is fundamental in programming.
What would be the best way where I can simply read the value and return the intended changes into another variable instead of overwriting the instance variable itself?
The best way is to not expose internal representation in the first place. It's not quite clear from your question how a client is expected to use X (the naming is pretty terrible). So, for example, if clients are expected to iterate over the contents of #y, then you offer them a way to do exactly that and only that.
class X
def initialize
self.y = [1, 2, 3, 4]
end
def each_y(...)
return enum_for(__callee__) unless block_given?
y.each(...)
self
end
private attr_accessor :y
end
See Overriding the << method for instance variables for another example of the same problem and how to solve it.
by using clone: https://ruby-doc.org/core-2.7.2/Object.html#method-i-clone
Produces a shallow copy of obj—the instance variables of obj are copied, but not the objects they reference.
class X
def initialize
#y = [1,2,3,4]
end
attr_reader :y
end
class Z
def self.change(arr)
arr2 = arr.clone
arr2[1] = 0
arr2[2] = 0
return arr2
end
end
x = X.new
z = Z.change(x.y)
p z
p x.y
Related
How do I get the original numbers?
For example when I type:
r = Rational(2, 10)
# (1/5)
2 and 10 will be changed to 1 and 5:
r.numerator # 1
r.denominator # 5
How do I get 2 & 10 from instance of Rational class(r)?
I monkey-patched Rational class and created new method(Rational_o):
def Rational_o *args
x, y = args
r = Rational *args
r.x = x
r.y = y
r
end
class Rational
attr_accessor :x, :y
end
It works, but is there build-in method or variable(s) where original x & y are stored?
No, there isn't. Reduction is a basic and common way to normalize rational numbers. Why would a rational number keep the original numerator and denominator? It does not make sense.
Your question is like asking "Does a string created by "foo" + "bar" (which becomes "foobar") keep the original substrings "foo" and "bar"? Where are they stored?"
If you really want to keep the original numbers, then a rational number is not what you want, and subclassing Rational is not the right way to go. You should use an array holding a pair of numbers.
Rational numbers get normalized on initialization, so you can not know which numbers where given as original arguments. You can also not subclass Rational to install an initializer of your own, and monkeypatching as you do is not really the optimal way to achieve what you want to achieve (which I think you know).
What you can do is create a proxy around Rational with BasicObject which preserves the original arguments and will not disturb normal operation of the original Rational class
class RationalWithArgumentStore < BasicObject
attr_accessor :original_arguments, :rational
def initialize *args
#original_arguments = args
#rational = Rational *args
end
# Basic Object is a basic object, but defines ==
# so let's overwrite it to route to rational
def == other
#rational == other
end
# Route all unknown method calls to rational
def method_missing meth, *args, &block
#rational.send meth, *args, &block
end
end
def RationalWithArgumentStore(*args)
RationalWithArgumentStore.new(*args)
end
So now you can do
my_rational = RationalWithArgumentStore(2,10)
my_rational.original_arguments #=> [2, 10]
#use it like a normal rational
my_rational * 3
my_rational.truncate
No, there is no such inbuilt private or public method that does what you want to do.
If you really want to store the original numbers inside the instance method, your monkey-patch is definitely one of the ways to do so.
In fact, the method Rational(a,b) is an instance method defined outside the class Rational, kind of like Array() and String() methods.
I was reading up on metaprogramming and came across this exercise:
http://ruby-metaprogramming.rubylearning.com/html/Exercise_1.html
The question is:
Given this class definition:
class A
def initialize
#a = 11
##a = 22
a = 33
end
#a = 1
##a = 2
a = 3
end
Retrieve all the variables from outside the class, with output like so:
1
2
3
11
22
33
Now, it's pretty straightforward to get the instance and class variables dynamically, even the local variable inside the constructor (it's the return value of the initialize method). But I'm stumped as to how to get the local variables a=3.
As far as I know, this is not possible because the local variables cease to exist after the class definition is first read.
The only roundabout way that I have made this work is to set a variable to the "return" value (for lack of a better term) of when the class is declared, like so:
val = class A
a = 3
end
puts val # => 3
Is this the only way?
The question seems to boil down to this: given the following:
class A
attr_reader :instance_var
def initialize
#instance_var = (#instance_var ||= 0) + 1
instance_local_var = 33
puts "instance_local_variables = #{ local_variables }"
instance_local_var = 33
end
class_local_var = 3
puts "class_local_variables = #{ local_variables }"
class_local_var = 3
end
# class_local_variables = [:class_local_var]
#=> 3
can one determine the values of instance_local_var and class_local_var?
Determine the value of class_local_var
The answer to this question is clearly "no" because class_local_var no longer exists (has been marked for garbage collection) after end is executed1:
A.send(:local_variables)
#=> []
Determine the value of instance_local_var
a = A.new
# instance_local_variables = [:instance_local_var]
#=> #<A:0x007ff3ea8dbb80 #instance_var=1>
Note that #instance_var #=> 1.
A.new does not return the value of instance_local_var, but because that variable is assigned a value in the last line of initialize, that value can be obtained by executing initialize once again.2
instance_local_var = a.send(:initialize)
#=> 33
There is a problem, however:
a.instance_var
#=> 2
Executing initialize a second time has caused an unwanted side effect. My definition of initialize is artificial, but it highlights the fact that many undesirable side effects could occur by executing initialize a second time.
Now let's obtain a new instance.
b = A.new
# instance_local_variables = [:instance_local_var]
#=> #<A:0x007fee0996e7c8 #instance_var=1>
Again, #instance_var=1. One possible workaround to the side-effects of calling initialize twice for a given instance is to subclass A and use super.
class B < A
attr_reader :b
def initialize
#b = super
end
end
B.new.b
#=> 33
a.instance_var
#=> 1
There is no guarantee that undesirable side-effects can be avoided with this approach (e.g., initialize for any instance may perform a database operation that should occur only once), but it appears to leave the initial instance a unaffected. This is of course all hypothetical.
1. send must be used because A.private_methods.include?(:local_variables) #=> true
2. A.new.send(:initialize) is required because iniitialize is private.
Your question is unclear. In the title, you write "local variables", but in the example, you mention only instance- and class-variables.
As for the instance variables, you can use Object#instance_variables to get a list of known instance variables at this point. Note however that instance variables are created dynamically, not at the time of class declaration. For example, given the class
class AA;
def initialize; #x=1; end;
def f; #y=1; end;
end
the expression
AA.new.instance_variables
would return [:#x] - the :#y is missing, because it doesn't exist yet.
You don't have a way to automatically (i.e. without modifying the class) retrieve local variables. As mudasobwa explained in his answer, you would have to explictly pass back a binding.
One might get an access to all the local variables of the class by returning it’s binding from the class definition (see Binding#local_variables for details):
a = class A
v1 = 3.14
v2 = 42
binding
end
a.local_variable_get(:v1)
#⇒ 3.14
a.local_variable_get(:v2)
#⇒ 42
But the main question is why would you want to do that? The local variable is meant to remain local. This is how it is supposed to behave. Also, one is unable to modify the local variable in the original binding (what is returned is a read-only copy of an original binding.)
I am very new to ruby. I am facing issue in getting the variable value in self.method
My ruby class/script look like this:
class A1
def initialize
#myUtil = MyUtil.new log
#test_hash = #myUtil.loadPropertiesFile(File.dirname(__FILE__) + '/my-tests.properties')
end
def self.testCases
tests = {}
#test_hash.each do |k, v|
puts "#{k}"
end
tests
end
def launch test, testbed, h = {}
#test_hash.each do |k, v|
puts "#{k}"
end
end
end
I am able to print value in #test_hash in launch method but not in self.testCases. Where I am making the mistake?
I see a few errors in your code.
In your initialize method, you created a global variable $test_hash, anytime you put a $ in front of a variable, it becomes available everything. That is not something you want to do in this case. An instance variable would work best for you in this case.
Your testCases method is a class method, which means you can call it without creating a new instance of the class, ie A1.new. So you call the method like A1.testCases (Ruby uses snake case for method names, test_cases, just fyi). You initializes the test_hash variable in the initialize method, which does not automatically gets call until you create a new instance of the class. Therefore, test_hash does not exist when you simply run 'A1.testCases`.
for the line puts "#{k}" is not good practice. puts stands for put string, and it will automatically convert the variable into a string. You should use puts k. the #{} is meant for string interpolation. Such as "Hi, my name is #{name}, and I am #{age} years old."
Here's how I would do this. I replaced the test_hash with a simple hash for testing purposes.
class A1
def initialize
#test_hash = {a: 1, b: 2, c: 3, d: 4, e: 5}
end
def testCases
#test_hash.each do |k, v|
puts k
end
end
end
Now, you create a new instance of A1 and call the testCases method (now an instance method, instead of a class method)
A1.new.testCases
The hard part is to understand, that in Ruby classes are also object. So a1 = A1.new is an object and A1 is also an object.
By using # inside initialize, you create an instance variable belonging to a1 = A1.new object.
By using # inside self.testCases method, you create instance variable belonging to A1 object.
I have a class that lazily loads data from a database into an instance variable, they are events in an array in numeric order. The class has several methods that analyse this array, here is an example of how I use it.
class Foo
def initialize
#a = [1,2,3,4,5] # data from database
end
def analyse
#a.reduce(:+)
end
end
d = Foo.new
result = d.analyse
I wanted to be able to apply these methods to the data after a very basic filter (eg: <= 3) and I imagined being able to call it like so:
d.at(3).analyse
and that the at method only affected the instance variable for the chained analyse call. i.e.
d = Foo.new # data loaded into instance var [1,2,3,4,5]
d.analyse # 15
d.at(3).analyse # 6
d.analyse # 15
I'm not sure how I can do this without re-creating a completely new object within the at call and this feels inefficient. I have a work around which would change how I call the at method - not the end of the world but I wondered if what I want is feasible whilst remaining efficient.
I'm not sure how I can do this without re-creating a completely new object within the at call and this feels inefficient.
I don't think creating a new object is too expensive. You could return a new Foo from at, initialized with a subset of the original data. You'd have another Foo instance and another Array instance, but the array would contain the very same objects:
class Foo
def initialize(a = nil)
#a = a || [1,2,3,4,5] # use a or fetch data from database
end
def analyse
#a.reduce(:+)
end
def at(max)
Foo.new(#a.take_while { |x| x <= max })
end
end
Example:
d = Foo.new
d.analyse #=> 15
d.at(3).analyse #=> 6
d.analyse #=> 15
You can prepare another instance variable which is set by at, overrides #a when defined, and is reset by analyse.
class Foo
def initialize
#a = [1,2,3,4,5]
end
def at i
#b = #a.select{|e| e <= i}
self
end
def array
if instance_variable_defined?(:#b)
#b.tap{remove_instance_variable(:#b)}
else
#a
end
end
def analyse
array.reduce(:+)
end
end
I have this code:
def setVelocity (x, y, yaw)
setVelocity (Command2d.new(x,y,yaw))
end
def setVelocity (vel)
......
end
vel is a Command2D class that has 3 attributes, is Comparable and defines + , basically is a convenient class for me to manage those 3 attributes, so I want to use it internally in my library (dont want to make them private, either give them weird names).
But Ruby seems to keep only the last setVelocity even when the number of parameters is different. so when I call setVelocity with 3 parameters will say that I need to call that method with only one parameter.
Ruby doesn't really support overloading.
This page gives more details and a workaround. Basically you create a single method with a variable number of parameters, and deal with them appropriately.
(I'd personally recommend writing one method to recognise the two different "faked overloads" and then one method for each overload, with different names reflecting the different parameters.)
Alternatively, just provide different names to start with :)
Just for comparison, here's how I would solve it:
#!/usr/bin/env ruby
class Command2D
def initialize(x, y, yaw)
#command = [x, y, yaw]
end
end
class Vehicle
def velocity=(command_or_array)
case command_or_array
when Command2D
self.velocity_from_command = command_or_array
when Array
self.velocity_from_array = command_or_array
else
raise TypeError, 'Velocity can only be a Command2D or an Array of [x, y, yaw]'
end
end
private
def velocity_from_command=(command)
#velocity = command
end
def velocity_from_array=(ary)
raise TypeError, 'Velocity must be an Array of [x, y, yaw]' unless ary.length == 3
#velocity = Command2D.new(*ary)
end
end
v1 = Vehicle.new
v1.velocity = Command2D.new(1, 2, 3)
v2 = Vehicle.new
v2.velocity = [1, 2, 3]
p v1
p v2
Use attr_accessor to add attributes and you will get getters and setters automatically.
Alternatively use attr_reader or attr_writer to get read-only or write-only attributes.
class Foo
attr_accessor :velocity
end
You can now set and get the value of this attribute like this:
foo = Foo.new
foo.velocity = 100
puts foo.velocity # => 100
If you want to add methods to set the attribute based on some parameters, use a name reflecting what kind of input are expected:
def velocity_from_yaw(x, y, yaw)
velocity = Command2d.new(x, y, yaw)
end
You can probably find a much better name in this case, but I don't know what your x, y and yaw really mean in your context.