Passing `nil` to method using default named parameters

随声附和 提交于 2020-02-04 01:48:27

问题


On a Rails project, I am gathering a hash with 10-15 key-value pairs, and passing it to a class (service object) for instantiation. The object properties should be set from the values in the hash except when there is no value (or nil). In this case, the property would desirably get set to a default value.

Instead of checking whether every value in the hash is not nil before creating an object, I would like to find a more efficient way of doing this.

I'm trying to use named parameters with default values. I don't know if this makes sense, but I would like to use the default value when the parameter is called with nil. I created a test for this functionality:

class Taco
  def initialize(meat: "steak", cheese: true, salsa: "spicy")
    @meat = meat
    @cheese = cheese
    @salsa = salsa
  end
  def assemble
    "taco with: #@meat + #@cheese + #@salsa"
  end
end

options1 = {:meat => "chicken", :cheese => false, :salsa => "mild"}
chickenTaco = Taco.new(options1)
puts chickenTaco.assemble
# => taco with: chicken + false + mild

options2 = {}
defaultTaco = Taco.new(options2)
puts defaultTaco.assemble
# => taco with: steak + true + spicy

options3 = {:meat => "pork", :cheese => nil, :salsa => nil}
invalidTaco = Taco.new(options3)
puts invalidTaco.assemble
# expected => taco with: pork + true + spicy
# actual => taco with: pork +  +

回答1:


If you want to follow a Object-Oriented approach, you could isolate your defaults in a separate method and then use Hash#merge:

class Taco
  def initialize (args)
    args = defaults.merge(args)
    @meat   = args[:meat]
    @cheese = args[:cheese]
    @salsa  = args[:salsa]
  end

  def assemble
     "taco with: #{@meat} + #{@cheese} + #{@salsa}"
  end

  def defaults
   {meat: 'steak', cheese: true, salsa: 'spicy'}
  end  
end

Then following the suggestion by @sawa (thanks), use Rails' Hash#compact for your input hashes that have explicitly defined nil values and you will have the following output:

taco with: chicken + false + mild
taco with: steak + true + spicy
taco with: pork + true + spicy

EDIT:

If you do not want to use Rails' wonderful Hash#compact method, you can use Ruby's Array#compact method. Replacing the first line within the initialize method to:

args = defaults.merge(args.map{|k, v| [k,v] if v != nil }.compact.to_h)



回答2:


Once you pass a value with a named parameter, access to the default value for that parameter is gone for that method call.

You either have to (i) assign the default value not in the method profile but in the method body as in sagarpandya82's answer, or (ii) remove the nil values before passing the arguments to the method like this using Rails' Hash#compact:

options3 = {:meat => "pork", :cheese => nil, :salsa => nil}
invalidTaco = Taco.new(options3.compact)



回答3:


I don't think keyword arguments would be appropriate in your case. It seems a Hash is a better fit.

class Taco
    attr_accessor :ingredients

    def initialize(ingredients = {})
        @ingredients = ingredients
    end

    def assemble
        "taco with: #{ingredients[:meat]} + #{ingredients[:cheese]} + #{ingredients[:salsa]}"
    end
end

You can even shorter the assemble method to list all the ingredients

def assemble
    string = "taco with: " + ingredients.values.join(" + ")
end

And it will work as you'd expect

options1 = {:meat => "chicken", :cheese => false, :salsa => "mild"}
chicken_taco = Taco.new(options1)
puts chicken_taco.assemble() # output: taco with: chicken + false + mild

It is worth to mention that Ruby prefers chicken_tacos over chickenTacos.



来源:https://stackoverflow.com/questions/35731009/passing-nil-to-method-using-default-named-parameters

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