Object Assignment in Ruby

Object assignment in Ruby

If you're familiar with C++, then you might want to consider every variable in Ruby, instance or otherwise, as a reference to another object. Since everything in Ruby is an object, even nil, which is of type NilClass, this holds true under every circumstance.

To determine which object you're referencing, you can use the object_id method to differentiate. That's similar to converting to a pointer using & in C++.

Consider this:

a = "foo"
b = a

a.object_id == b.object_id
# => true

Since a is a reference to that string, and b is a copy of a, then they are actually different references to the same object.

This is important because operations that modify an object affect all references to that equally:

a << "bar"
# => "foobar"
b
# => "foobar"

However, operations that create a new object will not modify all copies:

a += "baz"
# => "foobarbaz"
b
# => "foobar"

Many methods in Ruby are identified by a ! to distinguish in-place versus new-copy versions, but this is not always the case, so you have to be familiar with each method in order to be sure.

Generally an assignment will replace an old reference with a new one, so as a rule of thumb, = will replace old references. This applies to +=, -=, ||=, &&= and so on.

Edit: Updated based on Phrogz's comment about using ObjectSpace._id2ref(object_id) to convert an object identifier into an object.

Object assignment and pointers

There are a lot of questions in this question. The main thing to know is assignment never makes a copy in ruby, but methods often return new objects instead of modifying existing objects. For immutable objects like Fixnums, you can ignore this, but for objects like arrays or Foo instances, to make a copy you must do bar.dup.

As for the array example, foo += is not concatenating onto the array stored in foo, to do that you would do foo.concat(['a']). Instead, it is making a new array and assigning foo to that. The documentation for the Array class mentions which methods mutate the array in place and which return a new array.

Ruby object mass assignment

tap let's you do exactly that:

user.tap do |u|
u.name = "abc"
u.email = "abc@test.com"
u.mobile = "12312312"
end

How does Ruby handle assignment semantically?

Variables are, in a technical sense, just pointers to objects. There's nothing remarkable about that, but a simple variable assignment to an existing object does not involve any method calls or messages being sent.

Remember variables are just there so that programmers can refer to objects by name instead of by some kind of internal identifier or memory location. So there's a bit of "magic" here, = is special when making an assignment as there's rules for what you can do on the left and right side of it.

The only way you can send messages to something, that is make method calls, is if you've defined it in a way the compiler understands. x = 1 is sufficient, it means x refers to the Fixnum in question.

Note that the Ruby interpreter will need to determine if x refers to a variable or method call, as x= may be a method that's defined on the object context in which this is evaluated.

For example:

class Example
def x=(value)
@x = value
end

def test
# Equivalent to send(:x=, 1) because x= is a method
x = 1

# Is a variable definition because y= is not a method
y = 2

# Is always a method call because self is referenced.
self.x = 3
end
end

# Is a variable definition because x= is not defined in this context
x = 4

If there's no x= method for your object, x is automatically presumed to be a variable.

You can't have a := message because that would imply you can replace one object with another, something that's not allowed. Once an object is created, it cannot magically change type. For that you need to create a new instance of a different object. Variables only appear to change types, but in fact, they just end up pointing to different objects.

So in short, there's no := method call, but there may be special methods like :x= that work in very specific cases.

self referencing an object in its assignment

Ruby evaluates the right side of the equals sign before setting the left side equal to it, so if i already exists you can do what you're talking about. A simple example:

i = 10
i = i + 1 # now i = 11

However, you can't use i to define itself. You could use the following two lines:

i = expression.match(/\d+[\+|-|\*|\/]/)
i = i[0..i.length - 1] # Note: this is the same as i = i[0...i.length]

Ruby: using Object.send for assigning variables

a.send(:c=, b.send(:c))

foo.bar = baz isn't a call to the method bar followed by an assignment - it's a call to the method bar=. So you need to tell send to call that method.

How to assign multiple attributes at once to an object in Ruby

You can implement the mass-assignment method yourself.

One option is to set the corresponding instance variables via instance_variable_set:

class Foo
attr_accessor :a, :b, :c

def assign_attributes(attrs)
attrs.each_pair do |attr, value|
instance_variable_set("@#{attr}", value)
end
end
end

Note that this will by-pass any custom setters. As said in the docs:

This may circumvent the encapsulation intended by the author of the class, so it should be used with care.

Another way is to dynamically invoke the setters via public_send:

  def assign_attributes(attrs)
attrs.each_pair do |attr, value|
public_send("#{attr}=", value)
end
end

This is equivalent to setting each single attribute sequentially. If a setter has been (re)defined to include constraints and controls on the value being set, the latter approach respects that.

It also raises an exception if you try to set undefined attributes: (because the corresponding setter doesn't exist)

f = Foo.new
f.assign_attributes(d: 'qux')
#=> NoMehodError: undefined method `d=' for #<Foo:0x00007fbb76038430>

In addition, you might want to ensure that the passed argument is indeed a hash and maybe raise a custom exception if the provided attributes are invalid / unknown.

ruby - alternative to using eval to assign object variables from xml data

There's absolutely no reason to use eval here. Everything you need is already available. The standard form is simply:

infos[iid] = v[iid]

This is because iid is just a string. Doing things like eval("v['#{iid}']) is making things way, way more complicated than they need to be.

You can also refine this code by recognizing that the list of keys never changes, so that can be made a constant, and that your OpenStruct object can be composed based on a simple mapping:

XML_KEYS = %w[ model vehicleIdentifier make modelYear ]

Later in your method:

OpenStruct.new(
XML_KEYS.map do |iid|
[ iid, vv[iid] ]
end.to_h
)

You can apply that map technique on a broader level too:

vehicles = xml['vehicles'].map do |vv|
OpenStruct.new(
XML_KEYS.map do |iid|
[ iid, vv[iid] ]
end.to_h
)
end

Where that can side-step the need to create a variable and then force-populate it with <<.

The key to using Ruby effectively is to identify when you can do things as a series of simple transformations with Enumberable-type tools instead of doing it the hard, procedural way other languages dictate by their design.



Related Topics



Leave a reply



Submit