web-dev-qa-db-fra.com

Méthodes Ruby et paramètres facultatifs

Je joue avec Ruby on Rails et j'essaie de créer une méthode avec des paramètres facultatifs. Apparemment, il y a plusieurs façons de le faire. J'essaie de nommer les paramètres facultatifs comme des hachages et sans les définir. La sortie est différente. Regarde:

# This functions works fine!
def my_info(name, options = {})
    age = options[:age] || 27
    weight = options[:weight] || 160
    city = options[:city] || "New York"
    puts "My name is #{name}, my age is #{age}, my weight is #{weight} and I live in {city}"
end


my_info "Bill"
-> My name is Bill, my age is 27, my weight is 160 and I live in New York
-> OK!

my_info "Bill", age: 28
-> My name is Bill, my age is 28, my weight is 160 and I live in New York
-> OK!

my_info "Bill", weight: 200
-> My name is Bill, my age is 27, my weight is 200 and I live in New York
-> OK!

my_info "Bill", city: "Scottsdale"
-> My name is Bill, my age is 27, my weight is 160 and I live in Scottsdale
-> OK!

my_info "Bill", age: 99, weight: 300, city: "Sao Paulo"
-> My name is Bill, my age is 99, my weight is 300 and I live in Sao Paulo
-> OK!

****************************

# This functions doesn't work when I don't pass all the parameters
def my_info2(name, options = {age: 27, weight: 160, city: "New York"})
    age = options[:age]
    weight = options[:weight]
    city = options[:city]
    puts "My name is #{name}, my age is #{age}, my weight is #{weight} and I live in #{city}"
end

my_info2 "Bill"
-> My name is Bill, my age is 27, my weight is 160 and I live in New York
-> OK!

my_info2 "Bill", age: 28
-> My name is Bill, my age is 28, my weight is  and I live in 
-> NOT OK! Where is my weight and the city??

my_info2 "Bill", weight: 200
-> My name is Bill, my age is , my weight is 200 and I live in 
-> NOT OK! Where is my age and the city??

my_info2 "Bill", city: "Scottsdale"
-> My name is Bill, my age is , my weight is  and I live in Scottsdale
-> NOT OK! Where is my age and my weight?

my_info2 "Bill", age: 99, weight: 300, city: "Sao Paulo"
-> My name is Bill, my age is 99, my weight is 300 and I live in Sao Paulo
-> OK!

Qu'est-ce qui ne va pas avec la deuxième approche pour les paramètres facultatifs? 

Qu'est-ce que je rate?

31
Guillermo Guerini

La manière dont les arguments facultatifs fonctionnent dans Ruby est que vous spécifiez un signe égal et si aucun argument n'est passé, alors ce que vous avez spécifié est utilisé. Donc, si aucun second argument n'est passé dans le second exemple, alors 

{age: 27, weight: 160, city: "New York"}

est utilisé. Si vous utilisez la syntaxe de hachage après le premier argument, alors le hash exact est passé.

Le mieux que vous puissiez faire est

def my_info2(name, options = {})
  options = {age: 27, weight: 160, city: "New York"}.merge(options)
...
55
Marlin Pierce

Le problème est que la valeur par défaut de options est l'intégralité de Hash dans la deuxième version publiée. Ainsi, la valeur par défaut, l’ensemble Hash, est remplacée. C'est pourquoi rien ne fonctionne, car cela active la valeur par défaut qui est la Hash et que toutes les entrer est également valable, car il écrase la valeur par défaut avec une Hash de clés identiques.

Je suggère fortement d'utiliser une variable Array pour capturer tous les objets supplémentaires se trouvant à la fin de votre appel de méthode. 

def my_info(name, *args)
  options = args.extract_options!
  age = options[:age] || 27
end

J'ai appris cette astuce en lisant le code source de Rails. Toutefois, notez que cela ne fonctionne que si vous incluez ActiveSupport. Ou, si vous ne souhaitez pas que tout le joyau ActiveSupport soit surchargé, utilisez simplement les deux méthodes ajoutées à Hash et Array pour rendre cela possible. 

Rails/actifsupport/lib/active_support/core_ext/array / extract_options.rb

Ainsi, lorsque vous appelez votre méthode, appelez-la comme toute autre méthode d'assistance Rails avec des options supplémentaires.

my_info "Ned Stark", "Winter is coming", :city => "Winterfell"
15
Charles Caldwell

Si vous souhaitez définir les valeurs par défaut dans votre hachage d'options, vous souhaitez fusionner les valeurs par défaut de votre fonction. Si vous mettez les valeurs par défaut dans le paramètre default lui-même, il sera écrasé:

def my_info(name, options = {})
  options.reverse_merge!(age: 27, weight: 160, city: "New York")

  ...
end
9
Winfield

En deuxième approche, quand vous dites, 

my_info2 "Bill", age: 28

Il passera {age: 28} et le hachage complet par défaut original {age: 27, poids: 160, ville: "New York"} sera remplacé. C'est pourquoi cela ne s'affiche pas correctement.

5
texasbruce

Vous pouvez également définir des signatures de méthodes avec des arguments de mots clés (Nouveau depuis, Ruby 2.0, cette question étant ancienne):

def my_info2(name, age: 27, weight: 160, city: "New York", **rest_of_options)
    p [name, age, weight, city, rest_of_options]
end

my_info2('Joe Lightweight', weight: 120, age: 24, favorite_food: 'crackers')

Cela permet de:

  • Paramètres facultatifs (:weight et :age)
  • Les valeurs par défaut
  • Ordre arbitraire des paramètres
  • Les valeurs supplémentaires collectées dans un hachage à l'aide de double splat (:favorite_food collecté dans rest_of_options)
4
Henry Tseng

#fetch est votre ami!

class Example
  attr_reader :age
  def my_info(name, options = {})
    @age = options.fetch(:age, 27)
    self
  end
end

person = Example.new.my_info("Fred")
puts person.age #27
2
Jesse Wolgamott

Pour les valeurs par défaut dans votre hash, vous devriez utiliser ceci

def some_method(required_1, required_2, options={})
  defaults = {
    :option_1 => "option 1 default",
    :option_2 => "option 2 default",
    :option_3 => "option 3 default",
    :option_4 => "option 4 default"
  }
  options = defaults.merge(options)

  # Do something awesome!
end
2
Christian

Pour répondre à la question "pourquoi?": Comment vous appelez votre fonction,

my_info "Bill", age: 99, weight: 300, city: "Sao Paulo"

est en train de faire

my_info "Bill", {:age => 99, :weight => 300, :city => "Sao Paulo"}

Notez que vous transmettez deux paramètres, "Bill" et un objet de hachage, ce qui entraînera l'ignorance complète de la valeur de hachage par défaut fournie dans my_info2.

Vous devez utiliser l’approche de la valeur par défaut mentionnée par les autres répondants.

2
Abe Voelker

Je ne vois rien de mal à utiliser un opérateur ou pour définir les valeurs par défaut. Voici un exemple concret (note, utilise la méthode image_tag de Rails):

fichier:

def gravatar_for(user, options = {} )   
    height = options[:height] || 90
    width = options[:width] || 90
    alt = options[:alt] || user.name + "'s gravatar"

    gravatar_address = 'http://1.gravatar.com/avatar/'

    clean_email = user.email.strip.downcase 
    hash = Digest::MD5.hexdigest(clean_email)

    image_tag gravatar_address + hash, height: height, width: width, alt: alt 
end

console:

2.0.0-p247 :049 > gravatar_for(user)
 => "<img alt=\"jim&#39;s gravatar\" height=\"90\" src=\"http://1.gravatar.com/avatar/<hash>\" width=\"90\" />" 
2.0.0-p247 :049 > gravatar_for(user, height: 123456, width: 654321)
 => "<img alt=\"jim&#39;s gravatar\" height=\"123456\" src=\"http://1.gravatar.com/avatar/<hash>\" width=\"654321\" />" 
2.0.0-p247 :049 > gravatar_for(user, height: 123456, width: 654321, alt: %[dogs, cats, mice])
 => "<img alt=\"dogs cats mice\" height=\"123456\" src=\"http://1.gravatar.com/avatar/<hash>\" width=\"654321\" />" 

Cela ressemble à l'utilisation de la méthode initialize lors de l'appel d'une classe.

1
Starkers

Pourquoi ne pas utiliser nil?

def method(required_arg, option1 = nil, option2 = nil)
  ...
end
1
LLL

Il existe une méthode method_missing sur les modèles ActiveRecord que vous pouvez remplacer pour que votre classe réponde de manière dynamique aux appels directement. Voici un article de Nice blog on it.

0
RonanOD