2

I'm trying to make a method with output arguments in ruby.

I read differents posts here and here about the discussion of wether ruby pass its arguments by-value or by-reference and

I undersand that on a strict sens, Ruby always pass-by-value, but the value passed is actually a reference. Reason why there is so much debate on this.

I find out that there are several ways to change the value of the referenced variable. For instance with the replace method when its an Array, a Hash or a String, or merge! when it's a hash.

I found out that with integer, I can change and pass the value outside my method without any special method use.

My question is about other objects. For instance I want to retrieve the 'id' attribute of an object, and the object reference itself :

  class RestaurantController < ApplicationController
    def pizza_to_deliver(pizza_name, id_of_the_order, pizza)

      # pizza to eat
      pizza = Pizza.where(:name => pizza_name).first

      # unknown pizza
      return false if pizza.nil?

      # first customer order about this pizza
      id_of_the_order = Orders.where(:pizza_id => pizza.id).first

      true
    end
  end

my_pizza_name = 'margerita'
My_order_id = nil
my_pizza = nil

my_restaurant = RestaurantController.new

if my_restauant.pizza_to_deliver(my_pizza_name, My_order_id, my_pizza) then
  puts "Pizza to deliver : #{my_order_id}"
  rex_dog.eat(my_pizza)
end

How to make this works ? (order_id and my_pizza remains with nil)

Community
  • 1
  • 1
Douglas
  • 5,229
  • 3
  • 43
  • 54

4 Answers4

3

Ruby has only pass by value, just like Python and Java. Also like Python and Java, objects are not values directly, and are manipulated through references.

It seems you already understand how it works -- assigning to a local variable never has any effect on a caller scope. And to "share" information with the caller scope other than returning, you must use some method on the object to "mutate" the object (if such a method exists; i.e. if the object is mutable) that is pointed to by the passed reference. However, this simply modifies the same object rather than giving a reference to a new object, which you want.

If you are not willing to return the value, you can pass a mutable container (like an array of one element) that the called function can then mutate and put whatever in there and have it be seen in the caller scope.

Another option is to have the function take a block. The function would give the block the new value of pizza, and the block (which is given by the caller) can then decide what to do with it. The caller can pass a block that simply sets the pizza in its own scope.

newacct
  • 119,665
  • 29
  • 163
  • 224
3

For the most part, out parameters are a workaround for languages that don't have multiple-value return. In Ruby, I'd just return an Array containing all the output values of the function. Or make the mutable values instance variables in an object and the function a method on that object.

Mark Reed
  • 91,912
  • 16
  • 138
  • 175
0

Thanks for both answers.

It seems I came out with an equivalent solution at last : the mutable container.

I created a new class 'OutputParameter' that contains (as attr_accessors) the parameters that I want to output from my method. Then I passed an instance of this class to my method.

  class OutputParameters
    attr_accessor :order_id, pizza
  end

  class RestaurantController < ApplicationController
    def pizza_to_deliver(pizza_name, output_parameters)

      # pizza to eat
      pizza = Pizza.where(:name => pizza_name).first

      # unknown pizza
      return false if pizza.nil?

      # first customer order about this pizza
      id_of_the_order = Orders.where(:pizza_id => pizza.id).first

      # Output values returned
      output_parameters.pizza = pizza
      output_parameters.order_id = id_of_the_order
      true
    end
  end

my_pizza_name = 'margerita'
my_output = OutputParameters.new
my_restaurant = RestaurantController.new

if my_restaurant.pizza_to_deliver(my_pizza_name, my_output) then
  puts "Pizza to deliver : #{my_output.order_id}"
  rex_dog.eat(my_output.pizza)
end

The hash or array you suggested seems even a better idea as it is more adaptative : I wouldn't have to declare a class.

I would just use the merge! method

  class RestaurantController < ApplicationController
    def pizza_to_deliver(pizza_name, output_hash)

      # pizza to eat
      pizza = Pizza.where(:name => pizza_name).first

      # unknown pizza
      return false if pizza.nil?

      # first customer order about this pizza
      id_of_the_order = Orders.where(:pizza_id => pizza.id).first

      # Output values returned
      output_hash.merge!({:pizza => pizza})
      output_hash.merge!({:id_of_the_order => id_of_the_order})
      true
    end
  end

my_pizza_name = 'margerita'
my_output_hash = {}
my_restaurant = RestaurantController.new

if my_restaurant.pizza_to_deliver(my_pizza_name, my_output_hash) then
  puts "Pizza to deliver : #{my_output_hash[:id_of_the_order]}"
  rex_dog.eat(my_output_hash[:pizza])
end
Douglas
  • 5,229
  • 3
  • 43
  • 54
0

You could use multiple return values like this:

def maybe_get_something
  ...
  return nil, "sorry" if bad_condition
  ...
  something, nil
end

...
something, err = maybe_get_something
if !err.nil?
  handle(err)
  return
end
do_something_with(something)

Very similar to what people do when using Go:

f, err := os.Open("filename.ext")
if err != nil {
    log.Fatal(err)
}
// do something with the open *File f
nurettin
  • 11,090
  • 5
  • 65
  • 85