TiHuan
TiHuan

Reputation: 65

Ruby Modify Argument Value

I'm practicing ducktyping now in Ruby, and am trying to modify an argument's value based on another argument passed to the same method. However, it just doesn't work.

For example:

  class Duck

  attr_reader :foo, :bar

  def initialize
    @foo = false
    @bar = false
  end

  def duck_foo
    ducktype(@foo, @bar)
  end

  def duck_bar
    ducktype(@bar, @foo)
  end

  def ducktype(duck1, duck2)
    p duck1 #=> false
    p duck2 #=> false
    puts "foo: #{foo} bar: #{bar}" #=> "foo: false bar: false"
    duck1 = true if duck2 == false #<= For #duck_foo: I want to make @foo = true if @bar == false. But it only changes duck1 to true. / For #duck_bar: I want to make @bar = true.
    p duck1 #=> true
    p duck2 #=> false
    puts "foo: #{foo} bar: #{bar}" #=> "foo: false bar: false" => @foo is still false. I want it true!
  end

end

duck = Duck.new
duck.duck_foo
duck.duck_bar

The output for #duck_foo, I expect to see is @foo becomes true. However, it only changes duck1 to true and @foo is still false.

How can I make it work?

In essence, I'm trying to make:

 def duck_foo
    p foo
    p bar
    puts "foo: #{foo} bar: #{bar}"
    @foo = true if @bar == false #=> change @foo to true.
    p foo
    p bar
    puts "foo: #{foo} bar: #{bar}"
  end

  def duck_bar
    p foo
    p bar
    puts "foo: #{foo} bar: #{bar}"
    @bar = true if @foo == false #=> change @bar to true.
    p foo
    p bar
    puts "foo: #{foo} bar: #{bar}"
  end

Into:

  def duck_foo
    ducktype(@foo, @bar)
  end

  def duck_bar
    ducktype(@bar, @foo)
  end

  def ducktype(duck1, duck2)
    #whatever code necessary to preserve the original methods' behavior.
  end

So the code is cleaner and easier to maintain.

Hope this makes sense. Thanks everyone!

Upvotes: 0

Views: 969

Answers (2)

PinnyM
PinnyM

Reputation: 35541

The problem here is that you are trying to modify the value held by an instance variable by assignment to a local variable reference. Sorry, but this isn't going to work in ruby - assigning to a local variable will simply make the local variable point to the new object without affecting the first object.

However, you can do what you need fairly simply with a small modification (though you'll want to use attr_accessor instead of attr_reader to keep away from ugly eval blocks):

class Duck

  attr_accessor :foo, :bar

  def initialize
    @foo = false
    @bar = false
  end

  def duck_foo
    ducktype(:foo, :bar)
  end

  def duck_bar
    ducktype(:bar, :foo)
  end

  def ducktype(duck1, duck2)
    p send(duck1) #=> false
    p send(duck2) #=> false
    puts "foo: #{foo} bar: #{bar}" #=> "foo: false bar: false"
    send(:"#{duck1}=", true) if send(duck2) == false 
    p send(duck1) #=> true
    p send(duck2) #=> false
    puts "foo: #{foo} bar: #{bar}" #=> "foo: true bar: false"
  end
end

In this implementation, instead of passing instance variables (which lose context when passed on to a local variable), we pass our intent as symbol 'messages'. We can then use these symbols to interact with our object as needed.

Upvotes: 1

Matt
Matt

Reputation: 20796

One way is to check the object_id:

  def ducktype(duck1, duck2)
    puts "foo: #{foo} bar: #{bar}" #=> "foo: false bar: false"
    if duck1.object_id == @foo.object_id
        @foo = true if !duck2
    elsif duck1.object_id == @bar.object_id
        @bar = true if !duck2
    end
    puts "foo: #{foo} bar: #{bar}" #=> "foo: true bar: false"
  end

Since you are passing in Booleans, you cannot change their value from within the function, like you can with Strings, using str.replace('true'), for example.


When do you need to check the object_id?

If instead @foo and @bar were Strings instead of Booleans, you could do:

  def ducktype(duck1, duck2)
    puts "foo: #{foo} bar: #{bar}" #=> "foo: false bar: false"
    duck1.replace('true') if duck2 == 'false'
    puts "foo: #{foo} bar: #{bar}" #=> "foo: true bar: false"
  end

The reason duck1 = true doesn't work regardless of its type is because a new instance is assigned to it. I.e:

duck1 = true # => true
duck1.object_id # => 2
duck1 = false # => false
duck1.object_id # => 0  <-- id changes
duck1 = 'true' # => "true"
duck1.object_id # => 6563940
duck1.replace('false') # => "false"
duck1.object_id # => 6563940 <-- id stays the same

Therefore if the data type doesn't possess a member function that can change its own value, then it seems one must resort to checking the object_id as above to accomplish this.

Upvotes: 0

Related Questions