Ruby Methods and Optional parameters

匿名 (未验证) 提交于 2019-12-03 02:45:02

问题:

I'm playing with Ruby on Rails and I'm trying to create a method with optional parameters. Apparently there are many ways to do it. I trying naming the optional parameters as hashes, and without defining them. The output is different. Take a look:

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

What's wrong with the second approach for optional parameters? The second method only works if I don't pass any optional parameter or if I pass them all.

What am I missing?

回答1:

The way optional arguments work in ruby is that you specify an equal sign, and if no argument is passed then what you specified is used. So, if no second argument is passed in the second example, then

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

is used. If you do use the hash syntax after the first argument, then that exact hash is passed.

The best you can do is

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


回答2:

The problem is the default value of options is the entire Hash in the second version you posted. So, the default value, the entire Hash, gets overridden. That's why passing nothing works, because this activates the default value which is the Hash and entering all of them also works, because it is overwriting the default value with a Hash of identical keys.

I highly suggest using an Array to capture all additional objects that are at the end of your method call.

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

I learned this trick from reading through the source for Rails. However, note that this only works if you include ActiveSupport. Or, if you don't want the overhead of the entire ActiveSupport gem, just use the two methods added to Hash and Array that make this possible.

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

So when you call your method, call it much like you would any other Rails helper method with additional options.

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


回答3:

If you want to default the values in your options hash, you want to merge the defaults in your function. If you put the defaults in the default parameter itself, it'll be over-written:

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


回答4:

In second approach, when you say,

my_info2 "Bill", age: 28

It will pass {age: 28}, and entire original default hash {age: 27, weight: 160, city: "New York"} will be overridden. That's why it does not show properly.



回答5:

For the default values in your hash you should use this

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


回答6:

You can also define method signatures with keyword arguments (New since, Ruby 2.0, since this question is old):

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

This allows for the following:

  • Optional parameters (:weight and :age)
  • Default values
  • Arbitrary order of parameters
  • Extra values collected in a hash using double splat (:favorite_food collected in rest_of_options)


回答7:

To answer the question of "why?": the way you're calling your function,

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

is actually doing

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

Notice you are passing two parameters, "Bill" and a hash object, which will cause the default hash value you've provided in my_info2 to be completely ignored.

You should use the default value approach that the other answerers have mentioned.



回答8:

#fetch is your friend!

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:

I don't see anything wrong with using an or operator to set defaults. Here's a real life example (note, uses rails' image_tag method):

file:

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

It feels similar to using the initialize method when calling a class.



回答10:

There is a method_missing method on ActiveRecord models that you can override to have your class dynamically respond to calls directly. Here's a nice blog post on it.



回答11:

Why not just use nil?

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


标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!