Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Could I improve this method with duck typing?

Hopefully I haven't misunderstood the meaning of "duck typing", but from what I've read, it means that I should write code based on how an object responds to methods rather than what type/class it is.

Here's the code:

def convert_hash(hash)
  if hash.keys.all? { |k| k.is_a?(Integer) }
    return hash
  elsif hash.keys.all? { |k| k.is_a?(Property) }
    new_hash = {}
    hash.each_pair {|k,v| new_hash[k.id] = v}
    return new_hash
  else
    raise "Custom attribute keys should be ID's or Property objects"
  end
end

What I want is to make sure that I end up with a hash where the keys are an integer representing the ID of an ActiveRecord object. I don't particularly enjoy having to iterate through the hash keys twice with all? to determine if I need to grab the ID's out.

Of course, I'll accept any other suggestions to improve this code as well :)

like image 704
Brent Dillingham Avatar asked Oct 16 '08 17:10

Brent Dillingham


2 Answers

How you write this method should depend on whether you expect an exception to be thrown during the course of normal program execution. If you want a readable exception message because an end-user might see it, then throwing one manually makes sense. Otherwise, I'd just do something like this:

def convert(hash)
    new_hash = {}
    hash.each_pair { |k,v| new_hash[ k.is_a?(Integer) ? k : k.id ] = v }
    return new_hash
end

This will accomplish exactly the same thing, and you'll still get an exception if an array key doesn't have an id field. Even better, this uses a little more duck typing because now anything that has an id field will be acceptable, which is better than explicitly checking for something being a Property. This makes your code more flexible, especially when unit testing.

We still have an explicit check for integer objects, but this kind of occasional special case is usually acceptable, especially when checking for built-in data types.

like image 93
Eli Courtwright Avatar answered Oct 06 '22 00:10

Eli Courtwright


Duck typing is really just a nuanced version of polymorphism. In a statically typed language like Java you'd have to create an explicit interface that told the compiler all of the methods that a particular variable can accept. With a dynamic language like Ruby the interfaces still exist in an abstract sense, they're just implicit.

The problem is the fact that you're accepting two different data structures into one method. The way to make duck typing work is to require that all the objects that get passed to your method obey the same contract (i.e. it's always a hash of Integers to [Foo] objects.) The process of converting a hash with Property keys into the correct structure should be the job of the client code. That can be done very easily with a simple wrapper class or a conversion function consisting of just the body of your elseif clause.

Bottom line it's up to the guy calling the method to make sure his parameters all quack the way your method expects them to quack. If they don't, he's the one who need's to figure out how to make his turkey quack like a duck, not you.

like image 37
Mike Deck Avatar answered Oct 05 '22 23:10

Mike Deck