Can you access Ruby variables with outer scope inside of methods? - ruby

I'm learning Ruby and practicing by writing a Caesar cipher. Here's my code so far:
print "Enter rotation: "
rotation = gets.chomp
print "Enter string to encrypt: "
string = gets.chomp
def encrypt
keys = (' '..'z').to_a
values = (' '..'z').to_a.rotate(rotation)
hash = Hash[keys.zip(values)]
chars = string.split('')
encrypted_chars = chars.collect { |char| hash[char] }
encryptd_string = encrypted_chars.join
end
puts "Encrypted string: " + encrypt
It's saying that I don't have access to the rotation variable inside of the encrypt method. NameError: undefined local variable or method 'rotation' for main:Object.
From what I understand, rotation is a local variable with outer scope, and should be accessible inside of the encrypt method. Obviously something is wrong with that reasoning, so could someone explain what's wrong?

This is a duplicate of Ruby accessing outer variables in nested function.
You could make it an instance variable on the containing object by calling it #rotation, but why not just pass string and rotation into the encrypt method?

The reason for this behaviour is that Local variables exist in a local scope and defining a method creates a new local scope for that method. Local varables are only visible in the current scope.
I was confused by this and found that relating the OP's question to one of nested methods only added confusion. But I've done a bit more research and discovered this succinct question and, in particular, this answer that explains it clearly as well as demonstrating a way to define a method without creating a new scope.
(I'm just adding this answer for the benefit of others because my search for an answer led me to this question.)

Related

Ruby - Parameters by reference or by value? [duplicate]

This question already has answers here:
Is Ruby pass by reference or by value?
(14 answers)
Closed 8 years ago.
I don't understand why they say Ruby passes all parameters by value and at the same time the following code proves the opposite:
class MyClass1
#var1 = 123
def get1
#var1
end
def set1=value
#var1 = value
end
end
c1 = MyClass1.new
c1.set1 = 444
p c1.get1 # 444
def test1 mc
mc.set1 = 999
end
test1 c1
p c1.get1 # 999
If it were by value, it would print out 444, not 999.
This question confuses people because there is a thing called a reference type and there is a thing called pass-by-reference, but they don't actually have all that much to do with each other.
References and values and values that are references: A (sorta) brief overview
In a pass-by-reference scenario, the parameters of a function are references to the variables that were passed into the function, and modifying the parameters modifies the original variables. This is not what Ruby is. For example, let's look at the following code:
def inc(val)
val += 1
end
a = 1
inc a
puts a
If Ruby were a pass-by-reference language, this program would print 2, because the val += 1 in inc would increment the value of a. But that isn't what happens. The variable val is not a reference to the variable a — it's an independent variable that is given the same value.
"But wait!" you say. "What if we were dealing with objects? Surely object variables are passed by reference, right?"
Nope.
def change_string(str)
str << " I can insult you all you want"
str << " because you'll never see this"
str << " because I'm going to replace the whole string!"
str << " Haha you smell bad!"
str = "What? I didn't say anything." # I'm so sneaky
end
be_nice_to_me = "hello"
change_string(be_nice_to_me)
puts be_nice_to_me
If Ruby were pass-by-reference, you'd never see how mean the change_string method is, because the str = "What, I didn't say anything." would totally replace the value of be_nice_to_me with the string "What? I didn't say anything." But in fact change_string's sins are laid bare for all to see. How is this possible if Ruby doesn't pass by reference?
Well, remember those reference types I talked about earlier? Well, that's what objects are in Ruby. A reference type is a type whose value is a reference to something else. In this case, the variable's value is a reference to the string "hello". When you pass the string, the variable's value — which is a reference — is copied into the variable str. So now they both hold references to the same object, but str is not a reference to be_nice_to_me. So when you modify the object, those changes show up because they're both referring to the same object. But when you modify one variable, the other doesn't see it because neither variable is a reference to the other.
So is Ruby pass-by-reference or pass-by-value? It's pass-by-value, but all the values are references.

What's the cleanest way to define a constant string that involves a variable in Ruby?

For some context, a lot of my code has the same lines of text throughout it (we are using Calabash to do iOS automation, if that gives you an idea).
For example: "all label marked:'#{name}'" is used 8 times in a particular class.
I would prefer to be able to have a constant that uses that text, but if I throw it at the top of the class, of course the variable "name" has not been set yet. Without defining a method that takes a parameter and returns a string, is there a way to do something essentially like this that can exist at the top of the class, but not be evaluated until it's used?:
class ClassName
extend Calabash::Cucumber::Operations
#NAME_CONSTANT = "all label marked:'#{name}'"
def self.method_name(name)
query("#{#NAME_CONSTANT} sibling label marked:'anotherLabel' isHidden:0")
end
end
If you use the syntax I mentioned, you get this error: undefined local variable or method `name' for ClassName
You could use String#% to insert the string later.
class ClassName
#NAME_CONSTANT = "all label marked:'%{name}'"
def self.method_name(insert_name)
query("#{#NAME_CONSTANT} sibling label marked:'anotherLabel' isHidden:0" % {name: insert_name})
end
def self.query(string)
puts string
end
end
ClassName.method_name('test')
#=> "all label marked:'test' sibling label marked:'anotherLabel' isHidden:0"
I agree with #Sergio. Don't define a constant string that includes a variable. Just use a method. Including a variable in a constant seems like a bad idea. Constants shouldn't be dynamic, by definition.
If you really want to include a variable in a constant string, you can assign a lambda to a contstant, like so:
class ClassName
extend Calabash::Cucumber::Operations
NAME_CONSTANT = ->(name) { "all label marked:'#{name}'" }
def self.method_name(name)
query("#{NAME_CONSTANT.call(name)} sibling label marked:'anotherLabel' isHidden:0")
end
end
I removed the # before the constant, since including it creates a class-level instance variable, not a constant.
I really wouldn't use the code sample I posted, though. Just use a method. Avdi Grimm has a good post called "Do we need constants?" where he describes some of the benefits of using methods instead of constants.
The fundamental issue you're facing is that string interpolation occurs at the time the literal is interpreted and the scope of any referenced variables is determined by the location of the string in the code.
If you put the interpolated string in a method, then it won't have access to the local definition of any variables used in the string. You'd have to pass in the value of any variables used, as in:
def name_constant(name)
"all label marked:'#{name}'"
end
Alternatively, you'd need to declare the "constant" as an uninterpreted string as follows:
#name_constant = '"all label marked:''#{name}''"'
and then interpret it when you reference it, as follows:
eval(#name_constant)
BTW, I've ignored the issue of this not really being a "constant" and using instance variables vs. class variables.

Ruby: using strings to dynamically create objects

How do I use strings or symbols to create new variables || objects? Say I want 5 unique objects of an already made item class;
for x in 1..5
item_x = item.new() #where x is obviously the number value of the iterator
end
I have tried using eval() in this manner:
for x in 1..5
eval( "item_" << x << "= item.new()")
end
Hoping that putting the string I want executed into eval would have it executed as if I put it into the code.
I have searched for dynamic object creation and have not found anyone with this problem, sorry if this is mundane stuff. I have found references to people using .const_get and Openstruct but these don't seem to fix my problem in a manner I can understand.
Is there any reason you can't store your objects in an array? That would make them easier to both create and access:
items = []
5.times do
items << Item.new()
end
Then instead of item_1 through item_5, you have item[0] through item[4].
That said, if you really need/want to do the hard thing, instance_variable_set is an option if instance variables (with #) are okay:
for x in 1..5
self.instance_variable_set("#item_#{x}", Item.new())
end
I'd recommend just sticking with the array.
Update: From your comment, it sounds like your actual use case involves desired variable names that aren't consecutive. In that case, I'd use a Hash rather than an Array.
Think of it this way: whatever string you wish to use as the name of a local variable, just use it as a key in a local Hash. I can't think of any instance where that wouldn't be better than what you're trying, even if what you're trying worked.
You can use instance_variable_set to create instance variables in the manner you depict:
for x in 1..5
variable_name = "item_#{x}"
instance_variable_set("##{variable_name}", "value to assign to variable")
end
puts #item_1 #=> "value assigned to item_1"
Note, however, that this will not create local variables.

Use input name in return value variable name

I'm trying to make this simple method return a value related to the name of its input. For instance if I give the method "people_array" it should return "people_array_of_arrays."
If I were using the method in IRB I would get something like:
people_array = ["George\tMichael", "Kim\tKardashian", "Kanyne\tWest"]
=> ["George\tMichael", "Kim\tKardashian", "Kanyne\tWest"]
make_array_of_arrays(people_array)
=> people_array_of_arrays
people_array
=> ["George\tMichael", "Kim\tKardashian", "Kanyne\tWest"]
people_array_of_arrays
=> [["George", "Micahel"], ["Kim", "Kardashian"], ["Kayne", "West"]]
I have written this so far, but have not been able to figure out how to return a nicely named array of arrays. All I could think of was string interpolation but that isn't exactly what I need.
def make_array_of_arrays(array)
formatted_array = []
array.each do |feed|
mini_array = feed.split("\t")
formatted_array.push(mini_array)
end
#{array}_of_arrays = formatted_array
end
I saw there was a method variablize, but that returns an instance variable which isn't exactly what I want. Any pointers?
I do not think that it can be easily done. Suppose you were able to define a local variable in some way within the method definition. But the scope of that local variable is limited to the method definition. So the moment you go outside of the method definition, the local variable name is gone. So in order to do it, you have to somehow get the binding information of the environment outside of the method definition, and define a local variable within that. I do not know if that is possible.
With instance variables, things get a little easier using instance_variable_set, but I am not sure how to implement it fully. First of all, getting the name of the original variable is tricky.
And what you are trying to do is not the right approach. You should think of different ways.
I think the best you can do is to use an instance variable instead of a local variable, and also give the name of the variable explicitly instead of the array itself:
def make_array_of_arrays(variable_name)
array = instance_variable_get("##{variable_name}")
# Your code here
instance_variable_set("##{variable_name}_of_arrays", formatted_array)
end
#people_array = ["George\tMichael", "Kim\tKardashian", "Kanyne\tWest"]
make_array_of_arrays(:people_array)
#people_array_of_arrays
#=> [["George", "Micahel"], ["Kim", "Kardashian"], ["Kayne", "West"]]
This also might be useful.
No need for meta-programming (unless I misunderstand your question). Simply return your formatted array:
def make_array_of_arrays(array)
formatted_array = []
array.each do |feed|
mini_array = feed.split("\t")
formatted_array.push(mini_array)
end
formatted_array
end
Then you can assign the return value to whatever name you want:
people_array_of_arrays = make_array_of_arrays(people_array)
Note, you can use map to simplify your make_array_of_arrays method:
def make_array_of_arrays(array)
array.map do |feed|
feed.split("\t")
end
end
The big problem here is there's no good way to access the name of a variable.
Barring that and building on Sean Vieira's addition, you could do some eval magic to get this:
def make_array_of_arrays(array, array_name)
new_array = array.map { |feed| feed.split("\t") }
eval("def #{array_name}_of_arrays; return #{new_array}; end")
end
This basically creates a function for your *_of_arrays line that returns the array you're looking for.
If you could find a way to get the name of the variable, you'd have everything you want.
Not that I really officially endorse this method. And I can't for the life of me figure out why you'd want to do this. It's very unidiomatic, and will be confusing for anyone looking at that chunk of code.
This is not easy nor advisable.
Think of Ruby Objects as people (you and me) communicating by phone (phonenumbers being object_id's). Imagine I am in your list of phonenumbers under the name (variable) 'sTeEnSlAg' , and also under 'steenslg'. Then you phone me and ask "Please give me the name you are registered under on my phone, post-fixed with "_of_arrays".
What do you think would be the polite version of my answer?

What kind of Ruby variable do I want to use here?

I’m still learning Ruby, and I’m curious about whether it is appropriate to use a class variable, constant, or local variable in this scenario.
In my below code example (that generates random usernames out of a fixed character set), assigning #username as an instance variable is fairly obvious. But I’m curious whether I should assign the charset as a constant or maybe a class variable. What would be the advantages of using another variable type in this scenario?
In the current example, the _charset is computed in every instance. (Do correct me if my assumption is wrong.) I also assume the computation would be shared between instances (as opposed to recomputed) as both a class variable and as a constant?
class NewAccount
def initialize
#username = self.generate_username
end
def generate_username
_charset = ('a'..'z').to_a + ('0'..'9').to_a
_newusername = Array.new(20) { _charset[rand(_charset.length)] }.join
return _newusername
end
end
You can make it a class variable or constant, actually this would be the same: only one instance would exist. And Ruby constants are not really constants - you can change a constant so it is really a variable, only because of its name Ruby recognizes a constant and issue a warning if you try to change it.
But by declaring it as a constant and not a class variable you are documenting your aim: to have a constant value consisting of a character set, that is not designed to be changed. This will be obvious for anybody reading the code. This is what you want the character set for - so do it.
If you make it a class variable it will be a variable: so no problem if somebody tries to change. Of course if you plan on changing its value for whatever reason do it a class variable: again you will document your design.
Because _charset = ('a'..'z').to_a + ('0'..'9').to_a never changes from its definition, I'd create it as a class constant:
class NewAccount
CHARSET = ('a'..'z').to_a + ('0'..'9').to_a
def initialize
#username = self.generate_username
end
def generate_username
_newusername = Array.new(20) { CHARSET[rand(CHARSET.length)] }.join
return _newusername
end
end
I think you can make it class variable as the _charset is going to be used in NewAccount class only and its value would not be changed for NewAccount's instance.
In your example the #username would be computed once for each instance, and the _charset only computed once in your example– but the _charset is only a local variable, so it would be recomputed if you ran the method twice.
What you want is what the Tin Man suggests, set it as a constant and compute it once. Using a class-varible (##charset) could be misleading as the charset is not intended to change at any point.

Resources