Ruby object variables and why they always equal the same name? - ruby

In the books I'm reading, I always see
def initialize (side_length)
#side_length = side_length
end
If the object variable always equals the variable with the same name why not write the language to just equal it without having to type it out?
For example:
def initialize (side_length)
#side_length # this could just equal side_length without stating it
end
That way we don't have to type it over with over.

In the example given:
def initialize(side_length)
#side_length = side_length
end
The #side_length = side_length is just an assignment, passing the available argument to an instance variable, in this case it happens to be the same name as the argument.
However those two values don't have to have same names - it's usually named that way for readability/convention reasons. That same code could just as easily be:
def initialize(side_length)
#muffin = side_length
end
The above code is perfectly fine, it just wouldn't read as well (and might slightly confuse someone giving it a first glance). Here's another example of the argument not being assigned to an instance variable of the same name.
It would be possible to write the language in a way which assumes that the argument variable should automatically be assigned to an instance variable of the same name, but that would mean more logic for handling arguments, and that same assumption may result in more restrictions for the developer, for example, someone who may actually want to assign side_length to #muffin for whatever reason.
Here's a SO question similar to this one - the accepted answer provides an interesting solution.
Hope this helps!

It is because "the object variable [does not] always [equal] the variable withthe [sic] same name".

Related

Reference a variable in a test script

I have created a deck class and have defined one of the functions to insert a card at the beginning of the array. However everytime I try to test it in my test script I receive `add_to_bottom': wrong number of arguments (1 for 0) (ArgumentError). Can someone please help me I know I am close to figuring it out.
Deck Class
def add_to_bottom
#cards.insert(0, c)
end
Test Script
d = Deck.new
c = Card.new(7, "S")
d.add_to_bottom(c)
print d, "\n"
add_to_bottom has c in the method body, which is neither a method nor a variable. If that is meant to be an argument passed, then you need to write def add_to_bottom(c). If you do that, then that would also resolve your error.
Your add_to_bottom expect no arguments while in the test code you provide an argument. Did you forget to declare it in your function declaration?
I'm not sure how your Deck or Card classes are implemented, so it is hard to say. One very obvious problem is what #sawa and #VuMinhTan have already pointed out. So, as they said, you definitely should be taking in parameter c in the method you defined (add_to_bottom).
It seems evident to me that Card.new(7, "S") is making a 7 of Spades. This is just my curiosity, and nothing "wrong" with your code, but I'm also wondering how one would consider index 0 the "bottom". Of course, it really makes no difference, but that seems to be the "top" to me.

Ruby: Setting a global variable by name

I am trying to dynamically set (not create, it already has to exist) a global ruby variable in a method. The variable name is determined from the passed symbol. What I am currently doing is the following:
def baz(symbol)
eval("$#{symbol}_bar = 42")
end
$foo_bar = 0
baz(:foo)
puts $foo_bar # => 42
But to me, this kind of feels very wrong. Is this the way to do this? Or can it be done differently? Also, I don't know how evals perform in ruby. Does it run much slower than
$foo_bar = 42
The method looks fine to me. This guy says that eval efficiency is much worse, though the post is 3 years old.
I will point out that this method suggests you have a lot of global variables, which is generally a code smell if the code base is significant.
If you can use an instance variable instead, there is Object#instance_variable_set.
def baz(symbol)
instance_variable_set("##{symbol}_bar", 42)
end
Note that it only accepts variable names that can be accepted as an instance variable (starting with #). If you put anything else in the first argument, it will return an error. For the global variable counterpart to it, there is a discussion here: Forum: Ruby
Either way, you also have the problem of accessing the variable. How are you going to do that?

Ruby weird assignment behaviour

Is this a ruby bug?
target_url_to_edit = target_url
if target_url_to_edit.include?("http://")
target_url_to_edit["http://"] = ""
end
logger.debug "target url is now #{target_url}"
This returns target_url without http://
You need to duplicate the in-memory object because variable names are just references to in-memory objects:
target_url_to_edit = target_url.dup
Now target_url_to_edit gets assigned a new copy of the original object.
For your case this code probably does the same in just one line (no dup, no if):
target_url_to_edit = target_url.sub(%r{^http://}, "")
No, this is not a bug in Ruby, this is just how shared mutable state works, not just in Ruby but in any programming language.
Think about it this way: my mom calls me "son", my friends call me "Jörg". If I cut my hair, then it doesn't matter which name you use to refer to me: I am the same person, regardless of whether you call me "son" or "Jörg" or "Mr. Mittag" or "hey, douchebag", therefore my hair will always be short. It doesn't magically grow back if you call me by a different name.
The same thing happens in your code: you refer to the string by two different names, but it doesn't matter which name you use; if the string changes, then it changes.
The solution is, of course, to not share mutable state and to not mutate shared state, like in #hurikhan77's answer.
That is not a bug. It is the intended behavior because target_url_to_edit points to the same object in memory as target_url since Ruby uses references for object assignment. If you know C, it is similar to pointers.
Here is how to change its behaviour to force passing by value (note the star sign):
target_url_to_edit = *target_url.to_s
if target_url_to_edit.include?("http://")
target_url_to_edit["http://"] = ""
end
logger.debug "target url is now #{target_url}"
And just like many things in ruby, hard to find where it's documented...

Using a string as a variable at run time

I have a string, which has been created at runtime. I want to use this string as a variable to store some data into it. How can I convert the string into a variable name?
If you can forgive an # sign in front of the variable name, the following will work:
variable_name = ... # determine user-given variable name
instance_variable_set("##{variable_name}", :something)
This will create a variable named #whatever, with its value set to :something. The :something, clearly, could be anything you want. This appears to work in global scope, by declaring a spontaneous Object instance which binds everything (I cannot find a reference for this).
The instance_variable_get method will let you retrieve a value by name in the same manner.
instance_variable_get("##{variable_name}")
You can use eval() for this provided that you've declared your variable first:
>> foo = []
>> eval("foo")[1] = "bar"
>> foo[1]
=> "bar"
Here are the docs.
Rather than do that directly, why not consider using a hash instead. The string would be the key, and the value you want to store would be the value. Something like this:
string_values = { }
some_string = params[:some_string] # parameter was, say "Hello"
string_values[some_string] = 42
string_values # { 'Hello' => 42 }
some_number = string_values[some_string]
some_number # 42
This has a couple of benefits. First, it means you're not doing anything magic that might be hard to figure out later. Second, you're using a very common Ruby idiom that's used for similar functionality throughout Rails.
Now simply using instance_variable_set method, you can create a instance variable at runtime.
instance_variable_set('#' + 'users', User.all)
I don't mean to be negative, but tread carefully. Ruby gives you a lot of features for highly dynamic programming such as define_method, storing blocks as Proc objects to be called later, etc. Generally these are cleaner code and far safer. 99% of the time using eval() is a mistake.
And absolutely never use eval() on a string that contains user submitted input.
As Jason Watkins says, a lot of people might be quick to use eval() first thing and this would be a serious mistake. Most of the time you can use the technique described by Tack if you've taken care to use a class instance variable instead of a local one.
Local variables are generally not retrievable. Anything with the # or ## prefix is easily retrieved.

defined? in Ruby works in irb but not in my class file

I was writing a small Heap implementation and upon creating my Node class I noticed some weird behaviour. I wanted to call defined?(x) to ensure x was defined, then check if x was an Integer, before storing it in the Node's value class variable. In IRB I can call
defined?(x) and the result is nil.
However, in the class, I try this:
def change_value value
#value = value if defined?(value)
end
and the result when I call the change_value with a random letter, let's say 'e', is the standard undefined local variable or method error. Again, in IRB it seems to work fine and I am wondering if I have some kind of environment issue or if this is not the 'best' way to check if value is really there.
Thanks.
(Edit: DigitalRoss has since cleaned up the question's formatting, so the comment on formatting and the initial re-write may no longer apply. I'll leave them in until I get some feedback from the OP.)
That's a nearly unreadable way to write a method and it doesn't even parse on my machine (Ruby 1.8.7).
I'm assuming you mean the following:
def change_value(value)
#value = value if defined?(value)
end
This works fine when I call it, but it's incorrect. nil and undefined are two different beasts in Ruby; value will always be defined in that context because it's a declared method parameter. I suspect what you are really after is:
def change_value(value)
#value = value unless value.nil?
end
Note that some people would simply write this as:
def change_value(value)
#value = value if value
end
because nil is "falsy". However, this form conflates nil and false, so it's not a good habit to get into.
This isn't the best way to check if it's really there. If change_value is called without providing any parameters, you would get:
ArgumentError: wrong number of arguments (0 for 1)
Instead, you might want to check to make sure value is not nil. Of course you can do this a variety of ways:
if !value.nil?
#...
end
if value
# this will be exeuted if value is either not `nil` or not `false`
end
Best of luck!
Would it have to do with calling the method from the class instance and not the object instance?

Resources