【问题标题】:Ruby Methods and Optional parametersRuby 方法和可选参数
【发布时间】:2012-04-19 18:24:26
【问题描述】:

我正在使用 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!

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

我错过了什么?

【问题讨论】:

    标签: ruby-on-rails ruby


    【解决方案1】:

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

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

    被使用。如果您确实在第一个参数之后使用了哈希语法,那么 exact 哈希将被传递。

    你能做的最好的就是

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

    【讨论】:

    • 这是我的问题的解决方案。在阅读了@texasbruce 关于覆盖原始散列的解释之后,将“默认”散列与传递散列合并是有意义的。谢谢!
    • 因为 options 将被重新分配,您不妨就地更新它:options.reverse_update(age: 27, weight: 160, city: "New York")
    【解决方案2】:

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

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

    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"
    

    【讨论】:

    • 这是一个很棒的模式,谢谢。当您将其与通过scoped 为 nil 时优雅地失败的链式作用域结合使用时,您可以执行一些强大的查询。
    【解决方案3】:

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

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

    【讨论】:

    • 这不会总是用默认值覆盖传入的值吗?
    • 是的,应该是reverse_merge或者改变合并的顺序。
    • 快速高效的解决方案。对我来说最好的答案。 +1
    【解决方案4】:

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

    my_info2 "Bill", age: 28

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

    【讨论】:

    • 这是最好的解释。谢谢。
    【解决方案5】:

    您还可以使用关键字参数定义方法签名(自 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 中)

    【讨论】:

      【解决方案6】:

      对于散列中的默认值,您应该使用它

      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
      

      【讨论】:

      • 如果您使用 merge() 而不是 merge!() 在这里显示的方式,它会生成并丢弃一个新的哈希,而不是修改现有的选项哈希。
      【解决方案7】:

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

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

      确实在做

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

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

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

      【讨论】:

        【解决方案8】:

        #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
        

        【讨论】:

          【解决方案9】:

          我认为使用 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方法。

          【讨论】:

            【解决方案10】:

            为什么不直接使用 nil?

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

            【讨论】:

            • 如果您打算将其作为散列访问,散列会成为更好的默认值。 option1[:undefined_key] 如果是空哈希则返回 nil,但如果为 nil 则引发异常。
            【解决方案11】:

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

            【讨论】:

              猜你喜欢
              • 2016-12-01
              • 2012-06-19
              • 2013-02-14
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-04-11
              • 1970-01-01
              相关资源
              最近更新 更多