tags:

views:

242

answers:

9

I have a method that can return either a single object or a collection of objects. I want to be able to run object.collect on the result of that method whether or not it is a single object or a collection already. How can i do this?

profiles = ProfileResource.search(params)
output = profiles.collect do | profile |
    profile.to_hash
end

If profiles is a single object, I get a NoMethodError exception when I try to execute collect on that object.

+1  A: 
profiles = [ProfileResource.search(params)].flatten
output = profiles.collect do |profile|
    profile.to_hash
end
Matt Haley
A: 

In the search method of the ProfileResource class, always return a collection of objects (usually an Array), even if it contains only one object.

ctcherry
+4  A: 

Careful with the flatten approach, if search() returned nested arrays then unexpected behaviour might result.

profiles = ProfileResource.search(params)
profiles = [profiles] if !profiles.respond_to?(:collect)
output = profiles.collect do |profile|
    profile.to_hash
end
fd
I accepted this answer because it deals with nested arrays properly, although in my particular case that wasn't a concern.
Kyle Boon
+4  A: 

Here's a one Liner:

[*ProfileResource.search(params)].collect { |profile| profile.to_hash }

The trick is the splat (*) that turns both individual elements and enumerables into arguments lists (in this case to the new array operator)

A: 

If the collection is an Array you could use this technique

profiles = [*ProfileResource.search(params)]
output = profiles.collect do | profile |
    profile.to_hash
end

That would guaranteed your profiles is always an array.

Zakaria
A: 
profiles = ProfileResource.search(params)
output = Array(profiles).collect do |profile|
    profile.to_hash
end
A: 

You could first check to see if the object responds to the "collect" method by using "pofiles.respond_to?".

From Programming Ruby

obj.respond_to?( aSymbol, includePriv=false ) -> true or false

Returns true if obj responds to the given method. Private methods are included in the search only if the optional second parameter evaluates to true.

patrickyoung
A: 

You can use the Kernel#Array method as well.

profiles = Array(ProfileResource.search(params))
output = profiles.collect do | profile |
    profile.to_hash
end
Farrel
A: 

Another way is to realise that Enumerable requires that you supply an each method.

So. you COULD mix in Enumerable to your class and give it a dummy each that works....

class YourClass
  include Enumerable

  ... really important and earth shattering stuff ...

  def each
    yield(self) if block_given?
  end
end

This way, if you get back a single item on its own from the search, the enumerable methods will still work as expected.

This way has the advantage that all the support for it is inside your class, not outside where it has to be duplicated many many times.

Of course, the better way is to change the implementation of search such that it returns an array irrespective of how many items is being returned.

fatgeekuk