ruby unintended variable assignment/change - ruby

i'm quite a layman in programming and a noob to ruby but find it useful for my work anyway. Cuirrently I work on a rather large script which brought the following unintended effect:
def my_reduce_method(value_hash,some_keys)
value_hash.delete(some_keys)
end
puts all_values
=> all_values
some_values = all_values # start my block with all values (class: hash)
some_values = my_reduce_method(some_values,keys_to_reduce)
# here only some_values should be effected!
puts all_values
=> some_values
Right in the block there is no damage, but the original all_values is lost! How can I ensure that in a certain code block a certain variable is definitely not changed?
Thank you in advance for any input!!!

All object assignments in Ruby are reference assignments.
That means, when you do:
some_values = all_values
You're copying the reference(or address) of the object which all_values is referencing(or pointing).
The solution for your case is simple:
some_values = all_values.clone
.dup also works usually (can be different depending on the object).
Another thing to be careful about is, when all_values[:x] has a string and you do:
some_values = all_values.clone
some_values[:x] += 'abc'
This will not change all_values[:x] because some_values[:x] gets (is assigned) a new string object.
But if you do:
some_values = all_values.clone
some_values[:x] << 'abc'
Both all_values[:x] and some_values[:x] change, because they both reference the same string object.
This is the effect of the shallow copy #Plasmarob mentioned.

Object#clone preserves the frozen attribute and singleton methods. If you
don't need those, what happens to your example, Object#dup is sufficient.

Related

Rails: Accessing a variable where the name of the variable was passed into the function as an argument

#string = "I love long strolls on the beach"
def keyword(string)
#keyword_array = ["ball","sand","spade"]
ball = ["red","green","blue"]
sand = ["beach","playbox","sea"]
spade = ["garden","beach","cards"]
#keyword_array.each do |item|
new_array = item
puts item # on the first loop for example this returns 'ball'
end
end
As you can see in the example above when I iterate through the first loop it returns the string ball (just using the first loop as an example, its the same on all loops) but I want to assign new_array with the value of ["red","green","blue"] from the ball array as I will be passing this array to a function.
So basically instead of assigning the ball array, it assigns the name of the array as a string.
I tried a ton of things including using #{item}, instance_variable_set but nothing works. I have a feeling I do not know the correct terminology for this example so I can't find the right answer.
I had a similar situation where I was passing the method name as a variable and I did this to fix it:
return method("#{name}").call
but that was for a def/method. How would I do it for a variable?
Go easy on me, new to rails :)
I think that what you are trying to accomplish can be done using eval, but be careful about how you use it since this can execute any code within that expression, so be sure you only use it on flows where you have full control (avoid the use of this when user input is part of the eval call).
a = 'pepe'
pepe = 'pepito'
p a
p eval a
The first put will print 'pepe', the second one will print 'pepito' (the value of the pepe variable), you can test that snippet here.
Hope this helps! 👍
Do the contents of #keyword_array have to be a string?
We can avoid doing tricky things like call or send if we just store the references to the arrays in #keyword_array.
#string = "I love long strolls on the beach"
def keyword(string)
ball = ["red","green","blue"]
sand = ["beach","playbox","sea"]
spade = ["garden","beach","cards"]
#keyword_array = [ball, sand, spade]
#keyword_array.each do |item|
new_array = item
puts item
end
end

Multiple method calling

This just returns the original name.
name = "George"
name.reverse.upcase!
puts(name)
I'm wondering why and if there is any way to do what I tried above.
reverse returns a new string. upcase! upcases the string it is called on in-place. You are creating a new reversed string, upcasing that new string, and then never using it again.
If you wanted to reverse and upcase the original string you could name.reverse!.upcase!
The methods you are calling do not affect the calling object. name.reverse will return a new String and leave the original alone. What you want to do is reassign name after your call.
name = George
name = name.reverse.upcase
There is a gotcha here in that bang methods, ending in ! will often modify the object being operated upon. So you could do something like below:
name = George
name.reverse!.upcase!
In general, I would avoid the ! methods unless you have a good reason. The first example of setting "name = " is very clear, easy to read and unambiguous.
Perhaps this will help explain what's happening:
name = "George" # => "George"
name.object_id # => 70228500576040
object_id is the memory reference for the actual variable, in other words, where it lives as the script is running.
reversed_name = name.reverse # => "egroeG"
reversed_name.object_id # => 70228500574980
We can tell that reverse created a new variable because the object_id is different from that of name.
upcased_reversed_name = reversed_name.upcase! # => "EGROEG"
upcased_reversed_name.object_id # => 70228500574980
The upcase! method modified the same variable as reversed_name.
If we use upcase instead, the object_id changes because a new version of the variable is created:
upcased_reversed_name = reversed_name.upcase # => "EGROEG"
upcased_reversed_name.object_id # => 70228500572960
upcased_reversed_name # => "EGROEG"
The short lesson is you can't assign a ! method result to a variable because it acts on the original variable and changes it in place.

Ruby local_variable keeps referenced to #instance_variable

class Foo
def bar
#instance_variable = [['first']]
# make a duplicate object with the :dup method
local_variable=#instance_variable.dup
# They have different object_id
p #instance_variable.object_id
p local_variable.object_id
local_variable.each{|n|n.push('second')}
#instance_variable
end
end
f=Foo.new
p f.bar
=> 2000
=> 2002
=> [["first", "second"]]
It seems that the local_variable still references to the #instance_variable, although it is a different object. This behaviour is both with the push and unshift in the each block. With a normal assignment like local_variable='second', the result is as expected => [['first']]
I don't understand why local_variable.each{|n|n.push('second')} has an effect on the #instance_variable
Using Ruby-1.9.2p318
Both local_variable and #instance_variable have references to the same object, the inner array ['first']. And because it's a mutable Array, you can effect changes to one array through the other.
Object#dup in Ruby provides a shallow copy. In order to make a deep copy of an Array, you'd need to write some code (or find a library) that recursively walks the data structure, deep-cloning its pieces of mutable state.
The problem is you're not testing the right object. You say:
p #instance_variable.object_id
p local_variable.object_id
But that's not the object you're going to push onto. Try this instead:
p #instance_variable[0].object_id
p local_variable[0].object_id
They are the same object.
In other words, it is not the case that changing local_variable changes #instance_variable, but it just so happens that they both contain a reference to the same object, so obviously changing that object as pointed to by one changes that object as pointed to by the other.

Delete Instance Variables from Objects in an Array

I'm new to Ruby and I'm just having a play around with ideas and what I would like to do is remove the #continent data from the country_array I have created. Done a good number of searches and can find quite a bit of info on removing elements in their entirety but can't find how to specifically remove #continent data. Please keep any answers fairly simple as I'm new, however any help much appreciated.
class World
include Enumerable
include Comparable
attr_accessor :continent
def <=> (sorted)
#length = other.continent
end
def initialize(country, continent)
#country = country
#continent = continent
end
end
a = World.new("Spain", "Europe")
b = World.new("India", "Asia")
c = World.new("Argentina", "South America")
d = World.new("Japan", "Asia")
country_array = [a, b, c, d]
puts country_array.inspect
[#<World:0x100169148 #continent="Europe", #country="Spain">,
#<World:0x1001690d0 #continent="Asia", #country="India">,
#<World:0x100169058 #continent="South America", #country="Argentina">,
#<World:0x100168fe0 #continent="Asia", #country="Japan">]
You can use remove_instance_variable. However, since it's a private method, you'll need to reopen your class and add a new method to do this:
class World
def remove_country
remove_instance_variable(:#country)
end
end
Then you can do this:
country_array.each { |item| item.remove_country }
# => [#<World:0x7f5e41e07d00 #country="Spain">,
#<World:0x7f5e41e01450 #country="India">,
#<World:0x7f5e41df5100 #country="Argentina">,
#<World:0x7f5e41dedd10 #country="Japan">]
The following example will set the #continent to nil for the first World object in your array:
country_array[0].continent = nil
irb(main):035:0> country_array[0]
=> #<World:0xb7dd5e84 #continent=nil, #country="Spain">
But it doesn't really remove the continent variable since it's part of your World object.
Have you worked much with object-oriented programming? Is your World example from a book or tutorial somewhere? I would suggest some changes to how your World is structured. A World could have an array of Continent's, and each Continent could have an array of Country's.
Names have meaning and variable names should reflect what they truly are. The country_array variable could be renamed to world_array since it is an array of World objects.
99% of the time I would recommend against removing an instance variable, because it's extra code for no extra benefit.
When you're writing code, generally you're trying to solve a real-world problem. With the instance variable, some questions to ask are:
What real world concept am I trying to model with the various states the variable can be in?
What am I going to do with the values stored in the variable?
If you're just trying to blank out the continent value stored in a World object, you can set #continent to nil as dustmachine says. This will work fine for the 99% of the cases. (Accessing a removed instance variable will just return nil anyway.)
The only possible case (I can think of) when removing the instance variable could be useful is when you're caching a value that may be nil. For example:
class Player
def score(force_reload = false)
if force_reload
# purge cached value
remove_instance_variable(:#score)
end
# Calling 'defined?' on an instance variable will return false if the variable
# has never been set, or has been removed via force_reload.
if not defined? #score
# Set cached value.
# Next time around, we'll just return the #score without recalculating.
#score = get_score_via_expensive_calculation()
end
return #score
end
private
def get_score_via_expensive_calculation
if play_count.zero?
return nil
else
# expensive calculation here
return result
end
end
end
Since nil is a meaningful value for #score, we can't use nil to indicate that the value hasn't been cached yet. So we use the undefined state to tell us whether we need to recalculate the cached value. So there are 3 states for #score:
nil (means user has not played any games)
number (means user played at least once but did not accrue any points)
undefined (means we haven't fetched the calculated score for the Player object yet).
Now it's true that you could use another value that's not a number instead of the undefined state (a symbol like :unset for example), but this is just a contrived example to demonstrate the idea. There are cases when your variable may hold an object of unknown type.

Access variables programmatically by name in Ruby

I'm not entirely sure if this is possible in Ruby, but hopefully there's an easy way to do this. I want to declare a variable and later find out the name of the variable. That is, for this simple snippet:
foo = ["goo", "baz"]
How can I get the name of the array (here, "foo") back? If it is indeed possible, does this work on any variable (e.g., scalars, hashes, etc.)?
Edit: Here's what I'm basically trying to do. I'm writing a SOAP server that wraps around a class with three important variables, and the validation code is essentially this:
[foo, goo, bar].each { |param|
if param.class != Array
puts "param_name wasn't an Array. It was a/an #{param.class}"
return "Error: param_name wasn't an Array"
end
}
My question is then: Can I replace the instances of 'param_name' with foo, goo, or bar? These objects are all Arrays, so the answers I've received so far don't seem to work (with the exception of re-engineering the whole thing ala dbr's answer)
What if you turn your problem around? Instead of trying to get names from variables, get the variables from the names:
["foo", "goo", "bar"].each { |param_name|
param = eval(param_name)
if param.class != Array
puts "#{param_name} wasn't an Array. It was a/an #{param.class}"
return "Error: #{param_name} wasn't an Array"
end
}
If there were a chance of one the variables not being defined at all (as opposed to not being an array), you would want to add "rescue nil" to the end of the "param = ..." line to keep the eval from throwing an exception...
You need to re-architect your solution. Even if you could do it (you can't), the question simply doesn't have a reasonable answer.
Imagine a get_name method.
a = 1
get_name(a)
Everyone could probably agree this should return 'a'
b = a
get_name(b)
Should it return 'b', or 'a', or an array containing both?
[b,a].each do |arg|
get_name(arg)
end
Should it return 'arg', 'b', or 'a' ?
def do_stuff( arg )
get_name(arg)
do
do_stuff(b)
Should it return 'arg', 'b', or 'a', or maybe the array of all of them? Even if it did return an array, what would the order be and how would I know how to interpret the results?
The answer to all of the questions above is "It depends on the particular thing I want at the time." I'm not sure how you could solve that problem for Ruby.
It seems you are trying to solve a problem that has a far easier solution..
Why not just store the data in a hash? If you do..
data_container = {'foo' => ['goo', 'baz']}
..it is then utterly trivial to get the 'foo' name.
That said, you've not given any context to the problem, so there may be a reason you can't do this..
[edit] After clarification, I see the issue, but I don't think this is the problem.. With [foo, bar, bla], it's equivalent like saying ['content 1', 'content 2', 'etc']. The actual variables name is (or rather, should be) utterly irrelevant. If the name of the variable is important, that is exactly why hashes exist.
The problem isn't with iterating over [foo, bar] etc, it's the fundamental problem with how the SOAP server is returing the data, and/or how you're trying to use it.
The solution, I would say, is to either make the SOAP server return hashes, or, since you know there is always going to be three elements, can you not do something like..
{"foo" => foo, "goo" => goo, "bar"=>bar}.each do |param_name, param|
if param.class != Array
puts "#{param_name} wasn't an Array. It was a/an #{param.class}"
puts "Error: #{param_name} wasn't an Array"
end
end
OK, it DOES work in instance methods, too, and, based on your specific requirement (the one you put in the comment), you could do this:
local_variables.each do |var|
puts var if (eval(var).class != Fixnum)
end
Just replace Fixnum with your specific type checking.
I do not know of any way to get a local variable name. But, you can use the instance_variables method, this will return an array of all the instance variable names in the object.
Simple call:
object.instance_variables
or
self.instance_variables
to get an array of all instance variable names.
Building on joshmsmoore, something like this would probably do it:
# Returns the first instance variable whose value == x
# Returns nil if no name maps to the given value
def instance_variable_name_for(x)
self.instance_variables.find do |var|
x == self.instance_variable_get(var)
end
end
There's Kernel::local_variables, but I'm not sure that this will work for a method's local vars, and I don't know that you can manipulate it in such a way as to do what you wish to acheive.
Great question. I fully understand your motivation. Let me start by noting, that there are certain kinds of special objects, that, under certain circumstances, have knowledge of the variable, to which they have been assigned. These special objects are eg. Module instances, Class instances and Struct instances:
Dog = Class.new
Dog.name # Dog
The catch is, that this works only when the variable, to which the assignment is performed, is a constant. (We all know that Ruby constants are nothing more than emotionally sensitive variables.) Thus:
x = Module.new # creating an anonymous module
x.name #=> nil # the module does not know that it has been assigned to x
Animal = x # but will notice once we assign it to a constant
x.name #=> "Animal"
This behavior of objects being aware to which variables they have been assigned, is commonly called constant magic (because it is limited to constants). But this highly desirable constant magic only works for certain objects:
Rover = Dog.new
Rover.name #=> raises NoMethodError
Fortunately, I have written a gem y_support/name_magic, that takes care of this for you:
# first, gem install y_support
require 'y_support/name_magic'
class Cat
include NameMagic
end
The fact, that this only works with constants (ie. variables starting with a capital letter) is not such a big limitation. In fact, it gives you freedom to name or not to name your objects at will:
tmp = Cat.new # nameless kitty
tmp.name #=> nil
Josie = tmp # by assigning to a constant, we name the kitty Josie
tmp.name #=> :Josie
Unfortunately, this will not work with array literals, because they are internally constructed without using #new method, on which NameMagic relies. Therefore, to achieve what you want to, you will have to subclass Array:
require 'y_support/name_magic'
class MyArr < Array
include NameMagic
end
foo = MyArr.new ["goo", "baz"] # not named yet
foo.name #=> nil
Foo = foo # but assignment to a constant is noticed
foo.name #=> :Foo
# You can even list the instances
MyArr.instances #=> [["goo", "baz"]]
MyArr.instance_names #=> [:Foo]
# Get an instance by name:
MyArr.instance "Foo" #=> ["goo", "baz"]
MyArr.instance :Foo #=> ["goo", "baz"]
# Rename it:
Foo.name = "Quux"
Foo.name #=> :Quux
# Or forget the name again:
MyArr.forget :Quux
Foo.name #=> nil
# In addition, you can name the object upon creation even without assignment
u = MyArr.new [1, 2], name: :Pair
u.name #=> :Pair
v = MyArr.new [1, 2, 3], ɴ: :Trinity
v.name #=> :Trinity
I achieved the constant magic-imitating behavior by searching all the constants in all the namespaces of the current Ruby object space. This wastes a fraction of second, but since the search is performed only once, there is no performance penalty once the object figures out its name. In the future, Ruby core team has promised const_assigned hook.
You can't, you need to go back to the drawing board and re-engineer your solution.
Foo is only a location to hold a pointer to the data. The data has no knowledge of what points at it. In Smalltalk systems you could ask the VM for all pointers to an object, but that would only get you the object that contained the foo variable, not foo itself. There is no real way to reference a vaiable in Ruby. As mentioned by one answer you can stil place a tag in the data that references where it came from or such, but generally that is not a good apporach to most problems. You can use a hash to receive the values in the first place, or use a hash to pass to your loop so you know the argument name for validation purposes as in DBR's answer.
The closest thing to a real answer to you question is to use the Enumerable method each_with_index instead of each, thusly:
my_array = [foo, baz, bar]
my_array.each_with_index do |item, index|
if item.class != Array
puts "#{my_array[index]} wasn't an Array. It was a/an #{item.class}"
end
end
I removed the return statement from the block you were passing to each/each_with_index because it didn't do/mean anything. Each and each_with_index both return the array on which they were operating.
There's also something about scope in blocks worth noting here: if you've defined a variable outside of the block, it will be available within it. In other words, you could refer to foo, bar, and baz directly inside the block. The converse is not true: variables that you create for the first time inside the block will not be available outside of it.
Finally, the do/end syntax is preferred for multi-line blocks, but that's simply a matter of style, though it is universal in ruby code of any recent vintage.

Resources