How to detect an array- or set-like value while avoiding type checks
I have a method which accepts an argument which can be an Array/Set-like object, or a Hash. The gist of the method is something like:
def find(query = {})
if Array === query or Set === query
query = {:_id => {'$in' => query.to_a}}
end
mongo_collection.find(query)
end
The method will accept a set of ID objects and turn it into a hash condition for MongoDB.
Two problems with above code:
- It will fail if 'set' is not required from standard library. I don't want to require the dependency just to perform a check.
- I don't want to do strict type comparisons. I want to accept any array- or set-like value and cast it to an array of values with
to_a
.
How would you perform this check? Some considerations to have in mind:
- I could check for
to_ary
method, but Set doesn't respond toto_ary
. Objects that implement this method should fundamentally be arrays, and I agree that Set isn't fundamentally an array. See Consequences of implementing to_int and to_str in Ruby - I can't check for
to_a
since Hash responds to it Methods that are common to Array and Set, but not to Hash are:
[:&, :+, :-, :<<, :collect!, :flatten!, :map!, :|]
I decided to go with something like this:
开发者_如何转开发query = {:_id => {'$in' => query.to_a}} if query.respond_to? :&
since intersection is likely an operator a set-like object would have. But I'm not sure about this.
Here's my take:
if not Hash === query and query.respond_to? :to_a
I'm just checking for to_a
, which is the only method I'm interested in, but also ensuring that it's not a Hash object. I'm using strict type checking for Hash, but only because this is the least likely object to be passed as a completely separate class that's fundamentally a hash.
How about trying to find out if the query is Hash like?
def find(query = {})
query = {:_id => {'$in' => query.to_a}} unless query.respond_to?(:has_key?)
mongo_collection.find(query)
end
It is reasonable to expect that the object will be a Hash or Hash like if it responds to has_key?.
Checking to see if Set is defined would solve your first issue. For the second, you could possibly check the ancestors of the class of query to see if Array is in them, but that probably won't catch all "array-like" objects. I probably wouldn't check for the existence of methods to test for arrayness, as you are testing names, not behavior. Arel in particular responds to (or did before it was deprecated) &, but this type of object wouldn't work like you wanted it to.
Personally I'm thinking...
def find(query = {})
mongo_collection.find(query_formatter(query))
end
def query_formatter(query)
if query.respond_to?(:to_a) && !query.kind_of?(Hash)
{:_id => {'$in' => query.to_a}}
else
query
end
end
精彩评论