33

我正在使用 Ruby on Rails,并且正在尝试创建一个带有可选参数的方法。显然有很多方法可以做到这一点。我尝试将可选参数命名为散列,而不定义它们。输出不同。看一看:

# 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!

可选参数的第二种方法有什么问题?第二种方法仅在我不传递任何可选参数或全部传递时才有效。

我错过了什么?

4

11 回答 11

58

ruby 中可选参数的工作方式是您指定一个等号,如果没有传递任何参数,则使用您指定的参数。因此,如果在第二个示例中没有传递第二个参数,那么

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

用来。如果您确实在第一个参数之后使用哈希语法,则传递该精确哈希。

你能做的最好的就是

def my_info2(name, options = {})
  options = {age: 27, weight: 160, city: "New York"}.merge(options)
...
于 2012-04-19T18:32:02.633 回答
16

问题是您发布的第二个版本中的默认值options是整个Hash。因此,默认值,整个Hash,被覆盖。这就是为什么不传递任何内容的原因,因为这会激活默认值,Hash并且输入所有这些值也可以,因为它会用Hash相同的键覆盖默认值。

我强烈建议使用 anArray来捕获方法调用末尾的所有其他对象。

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

我从阅读 Rails 的源代码中学到了这个技巧。但是,请注意,这仅在您包含 ActiveSupport 时才有效。或者,如果您不想要整个 ActiveSupport gem 的开销,只需使用添加到的两种方法Hash即可Array

rails / activesupport / lib / active_support / core_ext / array / extract_options.rb

所以当你调用你的方法时,调用它就像调用任何其他带有附加选项的 Rails 辅助方法一样。

my_info "Ned Stark", "Winter is coming", :city => "Winterfell"
于 2012-04-19T18:51:50.313 回答
9

如果要默认选项哈希中的值,则需要合并函数中的默认值。如果您将默认值放在默认参数本身中,它将被覆盖:

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

  ...
end
于 2012-04-19T18:29:35.410 回答
5

在第二种方法中,当你说,

my_info2 "Bill", age: 28

它将通过 {age: 28},整个原始默认哈希 {age: 27, weight: 160, city: "New York"} 将被覆盖。这就是它无法正确显示的原因。

于 2012-04-19T18:34:49.457 回答
4

您还可以使用关键字参数定义方法签名(自 Ruby 2.0 以来的新问题,因为这个问题很旧):

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

这允许以下操作:

  • 可选参数 (:weight:age)
  • 默认值
  • 参数任意顺序
  • 使用 double splat 在哈希中收集的额外值(:favorite_food收集在rest_of_options
于 2016-02-07T22:19:33.957 回答
3

要回答“为什么?”的问题:您调用函数的方式,

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

实际上是在做

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

请注意,您传递了两个参数"Bill"和一个哈希对象,这将导致您提供的默认哈希值my_info2被完全忽略。

您应该使用其他回答者提到的默认值方法。

于 2012-04-19T18:38:28.697 回答
3

#fetch是你的朋友!

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
于 2012-04-19T18:41:40.080 回答
3

对于哈希中的默认值,您应该使用它

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
于 2012-04-19T18:28:39.290 回答
1

为什么不直接使用 nil?

def method(required_arg, option1 = nil, option2 = nil)
  ...
end
于 2014-05-08T18:38:32.483 回答
1

我认为使用 or 运算符设置默认值没有任何问题。这是一个真实的例子(注意,使用 rails 的image_tag方法):

文件:

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

安慰:

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

感觉类似于调用类时使用initialize方法。

于 2014-01-27T17:26:29.377 回答
0

ActiveRecord 模型上有一个 method_missing 方法,您可以覆盖该方法以使您的类直接动态响应调用。这是一篇不错的博客文章

于 2012-04-19T18:35:23.250 回答