2012-04-19 26 views
29

Estoy jugando con Ruby on Rails y estoy intentando crear un método con parámetros opcionales. Aparentemente hay muchas formas de hacerlo. Intento nombrar los parámetros opcionales como hashes y sin definirlos. La salida es diferente. Eche un vistazo:Ruby Métodos y parámetros opcionales

# 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é pasa con el segundo enfoque para los parámetros opcionales? El segundo método solo funciona si no paso ningún parámetro opcional o si los paso a todos.

¿Qué me estoy perdiendo?

Respuesta

52

La forma más argumentos opcionales trabajan en rubí es que especifique un signo igual, y si no se pasa ningún argumento entonces lo que has especificado se usa Por lo tanto, si no se pasa un segundo argumento en el segundo ejemplo, se usa

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

. Si usa la sintaxis hash después del primer argumento, se pasa el hash exacto.

Lo mejor que puede hacer es

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

Esta es la solución para mi problema. Después de leer la explicación de @texasbruce sobre anular el hash original, tiene sentido fusionar el hash "predeterminado" con el hash de pases. ¡Gracias! –

+3

dado que 'options' va a ser reasignado, puede actualizarlo también en el sitio:' options.reverse_update (age: 27, weight: 160, city: "New York") ' – tokland

2

Para los valores por defecto en su hash que debe usar esta

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

Si utiliza merge() en lugar de combinación!() La forma de mostrar aquí, que produce y los descartes un nuevo hash en lugar de modificar el hash de opciones existente. – Winfield

9

Si desea los valores por defecto en las opciones de hachís, que desea combinar los valores por defecto en su función. Si pones los valores por defecto en el parámetro por defecto en sí, que va a ser sobre-escrito:

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

    ... 
end 
+0

¿Esto no sobrescribiría siempre los valores pasados ​​con los valores predeterminados? – Christian

+0

Sí, debería ser reverse_merge o cambiar el orden de la fusión. – Winfield

+0

Solución rápida y eficiente. La mejor respuesta para mi +1 – konsolebox

4

En segundo enfoque, cuando se dice,

my_info2 "Bill", age: 28

Ya pasará {edad: 28}, y la totalidad de hash original por defecto {edad : 27, peso: 160, ciudad: "Nueva York"} será anulado. Es por eso que no se muestra correctamente.

+0

Esta fue la mejor explicación. Gracias. –

0

Hay un método method_missing en los modelos de ActiveRecord que puede anular para que su clase responda dinámicamente a las llamadas directamente. Aquí hay un bonito blog post en él.

1

Para responder a la pregunta de "¿por qué?": La forma en que está llamando a su función,

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

que realmente está haciendo

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

Aviso estás pasando dos parámetros, y un "Bill" objeto hash, que hará que el valor hash predeterminado que ha proporcionado en my_info2 se ignore por completo.

Debe utilizar el enfoque de valor predeterminado que los otros respondedores han mencionado.

1

#fetch es tu amigo!

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 
14

El problema es el valor por defecto de options es toda la Hash en la segunda versión informados. Por lo tanto, el valor predeterminado, el Hash completo, queda anulado. Es por eso que pasar nada funciona, porque esto activa el valor predeterminado que es Hash y también funciona al ingresar todos ellos, porque sobrescribe el valor predeterminado con un Hash de teclas idénticas.

Sugiero usar un Array para capturar todos los objetos adicionales que se encuentran al final de su llamada a un método.

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

Aprendí este truco al leer la fuente de Rails. Sin embargo, tenga en cuenta que esto solo funciona si incluye ActiveSupport. O bien, si no desea la sobrecarga de toda la gema ActiveSupport, simplemente use los dos métodos agregados a Hash y Array que lo hacen posible.

rieles/ActiveSupport/lib/active_support/core_ext/gama/ extract_options.rb

Así que cuando llama a un método, llamarlo al igual que lo haría con cualquier otro método de ayuda rieles con opciones adicionales.

my_info "Ned Stark", "Winter is coming", :city => "Winterfell" 
+1

Este es un patrón impresionante, gracias. Cuando combina esto con alcances encadenados que fallan con gracia cuando son nulos mediante '' 'scoped''', puede hacer algunas consultas potentes. – Kelseydh

0

No veo nada de malo al usar un operador o para establecer los valores predeterminados. He aquí un ejemplo de la vida real (tenga en cuenta, utiliza image_tag método rieles):

archivo:

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 

consola:

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\" />" 

Se siente similar a usar el método initialize cuando llamando a una clase.

0

¿Por qué no usar simplemente nil?

def method(required_arg, option1 = nill, option2 = nil) 
    ... 
end 
+0

un hash realiza un mejor valor predeterminado si planea acceder a él como un hash. option1 [: undefined_key] devuelve nil si es un hash vacío, pero genera una excepción si nil. – jay

1

También puede definir firmas de los métodos con argumentos de palabra clave (Nueva puesto, Ruby 2.0, ya que esta cuestión es de edad):

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') 

Esto permite lo siguiente:

  • opcional parámetros (:weight y :age)
  • Valores predeterminados
  • orden arbitrario de parámetros
  • valores extra recoge en un hash utilizando doble splat (:favorite_food recogida en rest_of_options)
Cuestiones relacionadas