Mr. J
Mr. J

Reputation: 1839

Ruby: Change Instances from Within Class Method

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

Answers (3)

Cary Swoveland
Cary Swoveland

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

Max
Max

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

Christopher Oezbek
Christopher Oezbek

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

Related Questions