Ruby assignment behavior - ruby

please help find some article of the next behavior.
a = 'qwer'
a = b
b << 'ty'
puts b # => 'qwerty'
puts a # => 'qwerty'
but if
a = 'qwer'
a = b
b = 'ty'
puts b # => 'ty'
puts a # => 'qwer'
I know why in this case
I know that it works well, but I can not find an explanation - why so
P.S.
if applicable - please give the links to the articles on this subject (or similar Maybe i miss more interesting feature like this).
Thn.

When you do
a = b
you make variable a keep reference to the same object as variable b. That's why when you type:
b << 'ty'
string contained in variable a will also change - this is the same String instance.
On the other hand, let's say you have variable b containing reference to string 'qwer'.
If you have:
a = b
b = 'ty'
in first line you assign variable a to the same object as b. In the second line, you assign a new String object to variable b. So in the end both variables have references to different objects.

Related

What is the comma character meaning in right side of expression in Ruby

Excuse me for this basic question but, I couldn't find the answer on Google.
I am new to Ruby and came across to this line of code:
self.primary_keys = :role_id, :action_name
What I understand from it is that self.primary_keys is a Class variable and is assigned an array or hash of symbols?
What means the right side of the expression (:role_id, :action_name)?
What is the type of it?
It's two symbols separated by a comma, and is an implicit array.
Equivalent to
self.primary_keys = [:role_id, :action_name]
It's more common to see the technique used on the left side of an assignment.
name, age = ["George", 21]
puts name
=> "George"
puts age
=> 21
The feature lets you swap the contents of variables without an intermediate variable.
For example, in some languages to swap a and b you need a temporary variable
temporary = a
a = b
b = temporary
In Ruby you can do
a, b = b, a
It's assignment
x, y = ["Srini", 25]
puts x
=> "Srini"
puts y
=> 25

How to refer to a variable via a string containing its name?

I'd like to classify the projects by searching keywords. For each category of project type, I have an array of key words. I will use the variable name as the project type and the contents of the variable as keywords for matching.
project_detail = "Build a school"
a = %w[education school]
b = ["Health Care", "Clean Water"]
projects = %w[a b]
project_type = String.new
projects.each do |e|
# How to refer to the variable a and b? (I used one of the answers)
eval(e).each do |keyword|
project_type = e if project_detail.match(/#{keyword}/)
end
end
You could use any of the below:
1. eval
a = 1
b = 2
c = 3
vars = %w[a b c]
vars.each { |v| puts eval(v) }
Output:
1
2
3
=> ["a", "b", "c"]
2. Binding#local_variable_get
vars.each { |v| puts binding.local_variable_get(v) }
I will use the variable name as the project type ...
Don't use variable names to represent data. Variables should refer to data, not be that data. Here's how I would approach it.
First of all, I would create a small class for project types. The class should have two attributes, name and keywords. This can be done using the class keyword:
class ProjectType
attr_accessor :name, :keywords
def initialize(name, keywords)
#name = name
#keywords = keywords
end
end
or via Struct:
ProjectType = Struct.new(:name, :keywords)
Then, I would create an array of all project types:
#project_types = [
ProjectType.new('a', %w[education school]),
ProjectType.new('b', ['Health Care', 'Clean Water'])
]
Now, we need a way to find a project by keyword. I would wrap that in a method:
def find_project_type(name)
#project_types.find do |prj|
Regexp.union(prj.keywords) =~ name
end
end
find traverses the #project_types array, returning the first element for which the block returns true. Within the block, we use Regexp.union to build a regexp matching all of the project's keywords and =~ to perform the match.
The method can now be used like this:
project_type = find_project_type("Build a school")
#=> #<struct ProjectType name="a", keywords=["education", "school"]>
The project type's name can be retrieved via:
project_type.name
#=> "a"
I guess you mean that you want to use the variable e to get to the contents of, say, a.
In this case, you could use eval(e), because e is of class String.
Of course the question is why you want to access a variable via a string holding its name .....

Ruby compact assignment syntax

I want to do a compact error checking assignment in ruby.
class User
attr_accessor :x
end
user = User.new
user.x = 5
a = b || user.x
I want to figure out which of these is the first valid attribute and assign it, similarly to how javascript handles different API's, i.e.:
var AudioContext = window.AudioContext||window.webkitAudioContext;
audioContext = new AudioContext();
and figure out which was valid.
With ruby however, similar syntax gives errors when I reference an undefined variable. i.e.:
a = 10
b = 7
c = a || b
c # => 10
vs
a = 10
c = b || a # => Error: b is undefined
Is there a clean way to do this? Or at the very least, what is the best way to do this?
I'm working with a large code that I haven't created, and I am not permitted to change it.
UPDATE:
I think the real use case is kind of relevant to this question so i'll explain it.
I have a module which saves something to the DB every time a model in rails is updated, this update requires an id field, this id field is inside the model that includes my module, however not every model maintains the same naming convention for this id. The ternary operator equivalent of what I want to do is
id = defined?(self.id) ? self.id : defined?(self.game_id) ? self.game_id : defined?(self.app_id) ? self.app_id : nil
which is hard to read and write compared to the js equivalent
You can use defined? to test if a name refers to something recognizable in current scope (method, local variable, etc):
c = defined?(b) ? b : a # of course this assumes that 'a' is defined
Although it's pretty iffy that you're assigning from local variables that haven't been defined - how does that come up?
Or are you always testing for properties? In which case, respond_do? may be the better choice.
Edit
I was using the ternary operator as an example, you could always use an if/elsif/else block, of course.
Since you're only testing methods, respond_to? is more convenient than defined? because it takes symbols, rather than expressions, to which you can apply any logic you want:
def invoke_first *names
names.each do |name|
if respond_to? name
return send name
end
end
return nil # or, more likely, raise something
end
or, more concisely:
def invoke_first *names
send names.find(lambda {raise 'no method'}){|n| respond_to?(n)}
end
include in your model and use as:
invoke_first(:foo_id, :bar_id, :baz_id)
Okay so there is a much more concise way of doing this, but it has a side-effect of assigning something to the undefined var.
This breaks
a = 1
c = b || a # => b is undefined
This works
a = 1
c = b ||= a # => c == 1, b == 1
The above assigns b to c if b is valid, then falls back on a. a is only assigned to b (and c) if b is undefined/invalid.

What is the scope of a ruby string?

This is what I tried:
a = "Hello world"
a.object_id # => -633222538
b = a
b.object_id # => -633222538
b << " i say" # => "Hello world i say"
a # => "Hello world i say"
Why is it that both the variables b and a have the same object id? Also, when I change b, how did a also change?
Update:
How about when the variable is passed as an argument to a method? Why is the receiving variable having the same reference?
They are referencing the same object:
a = "Hello world" # a now references #-633222538
b = a # b now references #-633222538, too
b << " i say" # this appends " i say" to #-633222538
a # a still references #-633222538
String#<< doesn't assign a new object, it appends to the given string, thus changing the receiver.
I you want a copy, you can use clone or dup:
b = a.clone
a == b #=> true (same string values)
a.equal? b #=> false (different objects)
Regarding integers
There's no difference in referencing:
a = 100
a.object_id #=> 201
b = a
b.object_id #=> 201
Now both, a and b reference the same object. The only difference is that an integer cannot be changed in Ruby, they are fixed.
Passing variables as arguments
Again, the reference is passed:
a = "foo"
p = proc { |x| x << "bar" }
p.call(a)
a
#=> "foobar"
ENTER REFERENCES
The answer is that variables in Ruby (with a few exceptions, most notably variables bound to integers) don’t hold object values. a doesn’t contain "Hello world". Rather, a contains a reference to a string object. It’s the string object that has the characteristic of containing the letters that make up "Hello World".
In an assignment with a variable name on the left and an object on the right, the variable receives a reference to the object. In an assignment from one variable to another (a = b), the variable on the left receives a copy of the reference stored in the variable on the right, with the result that both variables now contain references to the same object.
The fact that variables hold references to objects has implications for operations that change objects. The string-concat operation
b << " i say"
concats the characters of the string to which b is a reference with the text " i say". The variable a contains another reference to the same string object. Even though the replace message goes to b, it causes a change to the object to which the reference in b refers. When you print out a, you see the result: the contents of the string have changed.
Some objects in Ruby are stored in variables as immediate values. These include in- tegers, symbols (which look like :this), and the special objects true, false, and nil. When you assign one of these values to a variable (x = 1), the variable holds the value itself, rather than a reference to it.
Copied and modified from Manning The Well Grounded Rubyist.
Looks like you called a mutable function on a variable which shared the same object with another variable. if you instead did b = b + 'i say' a would be left unchanged.
The variables a and b are references to a String object. When you did the b = a assignment, you copied the reference. It doesn't make a new copy of the object. If you want to copy the string object into a new object, you might do something like this:
a = "abc"
b = "" [or, b = String.new]
b << a
Now a and b will be different, independent string objects with the value "abc".

When does variables in Ruby determine whether to hold a new reference?

I learned that in Ruby, variables hold references to objects, not the objects themselves.
For example:
a = "Tim"
b = a
a[0] = 'J'
Then a and b both have value "Jim".
However if I change the 3rd line to
a = "Jim"
Then a == Jim and b == Tim
I assume that means the code I changed created a new reference for a.
So why does changing a letter or changing the entire string make so much difference?
Follow-up question: Does Java work the same way?
Thank you.
The single thing to learn here is the difference between assignment and method call.
a = 'Jim'
is an assignment. You create a new string object (literal 'Jim') and assign it to variable a.
On the other side,
a[0] = 'J'
is a method call on an object already referenced by the variable a. A method call can't replace the object referenced by the variable with another one, it can just change the internal state of the object, and/or return another object.
I find that things like this are easiest to figure out using IRB:
>> a = 'Tim'
=> "Tim"
>> a.object_id
=> 2156046480
>> b = a
=> "Tim"
>> b.object_id
=> 2156046480
>> a.object_id == b.object_id
=> true
As you can see a and b have the same object_id, meaning they reference the same object. So when you change one, you change the other. Now assign something new to a:
>> a = 'Jim'
=> "Jim"
>> a.object_id
=> 2156019520
>> b.object_id
=> 2156046480
>> a.object_id == b.object_id
=> false
You made a point to a new object, while b still kept the old reference. Changing either of them now will not change the other one.
When you do a[0] = 'J', you're asking
Change the first character of the object referenced by a (which happens to be the same as b) to 'J'
While when you do a = "Jim", you're assigning an entirely new object reference (the string "Jim") to a. b is unaffected because you're not changing anything in the original reference.

Resources