Reputation: 1839
I have two instances of a class that I want to swap. Both instances are arrays. I want to swap them using a class method. How do I change/access the instances from within the class method self.collide
?
class MagicBus < Array
attr_writer :seating
def self.collide(bus1, bus2)
stored_arr1 = bus1
stored_arr2 = bus2
bus1 = stored_arr2
bus2 = stored_arr1
return bus1, bus2
end
end
def test_two_magic_buses_collide_and_swap_their_passengers
bus1 = MagicBus.new(["Mark","Dale","Peter"])
bus1_object_id = bus1.object_id
bus2 = MagicBus.new(["James","Patrick","Bardoe"])
bus2_object_id = bus2.object_id
MagicBus.collide(bus1, bus2)
assert_equal ["James","Patrick","Bardoe"], bus1
assert_equal bus1_object_id, bus1.object_id
assert_equal ["Mark","Dale","Peter"], bus2
assert_equal bus2_object_id, bus2.object_id
end
I've tried the code below, doesn't work, but should illustrate what I am trying to do.
def self.collide(bus1, bus2)
stored_arr1 = bus1
stored_arr2 = bus2
bus1 = stored_arr2
bus2 = stored_arr1
self.bus1 = bus2
self.bus2 = bus1
end
Test results are...
....E
Error:
TestMagicBus#test_two_magic_buses_collide_and_swap_their_passengers:
NoMethodError: undefined method `bus1=' for MagicBus:Class
magic_bus.rb:56:in `collide'
magic_bus.rb:126:in `test_two_magic_buses_collide_and_swap_their_passengers'
Upvotes: 1
Views: 556
Reputation: 110675
Considering that class instances are characterized entirely by the values of their instance variables (provided singleton methods have not been defined on specific instances), we may simply swap those values.
class C
attr_reader :seating, :colour
def initialize(seating, colour)
@seating = seating
@colour = colour
end
def self.collide(bus1, bus2)
var2 = bus2.instance_variables.map { |v| bus2.instance_variable_get(v) }
bus2.instance_variables.each do |v|
bus2.instance_variable_set(v, bus1.instance_variable_get(v))
bus1.instance_variable_set(v, var2.shift)
end
end
end
bus1 = C.new(46, 'blue')
bus2 = C.new(32, 'red')
[bus1.seating, bus1.colour]
#=> [46, "blue"]
[bus2.seating, bus2.colour]
#=> [32, "red"]
C.collide(bus1, bus2)
[bus1.seating, bus1.colour]
#=>[32, "red"]
[bus2.seating, bus2.colour]
#=> [46, "blue"]
Upvotes: 0
Reputation: 22325
This is possible in Ruby. When you pass an object to a method, the method receives a pointer to the object itself. That means that any mutable methods called on the object will affect it.
Your code doesn't work because you are merely creating new local variables rather than modifying the passed objects
def self.collide(bus1, bus2)
stored_arr1 = bus1 # assign bus1 address to stored_arr1
stored_arr2 = bus2 # assign bus2 address to stored_arr2
bus1 = stored_arr2 # create new local variable named bus1, assign it bus2's original address
bus2 = stored_arr1 # create new local variable named bus2, assign it bus1's original address
return bus1, bus2 # return bus2's original address followed by bus1's
end
You have to use mutating methods to modify the passed objects
def self.collide bus1, bus2
tmp = bus1.dup # create a copy of bus1
bus1.replace bus2 # replace bus1 in-place
bus2.replace tmp # replace bus2 in-place
# no need to return, bus1 and bus2 have been modified. the caller can use the ones passed in
end
Upvotes: 1
Reputation: 26323
What you are trying to do isn't possible in Ruby, because Ruby does not have "pass by reference" parameters, but always passes by pointer. This means when you make an assignment in a method to a parameter then this doesn't change the value of the variable on the outside:
a = "Hello"
def make_message(param)
param = "Hello, my Friend"
end
make_message(a)
a => Still has the value "Hello"
Next, in your code example you refer to self.bus1
, but you haven't declared such a member field. This is why you get the error.
To achieve what you are trying to do, you need to use methods of the Array class, which change the provided instances:
a = [1,2,3]
b = [4,5,6]
def swap_array_content(x, y)
temp = y.dup # Need to make a temporary copy here
y.replace(x)
x.replace(temp)
end
swap_array_content(a, b)
p a
p b
Upvotes: 0