问题
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