How to write a method in ruby that takes hash-style, square bracketed arguments, like mymethod[arg]?

Viewed 72

I want to write a method in ruby to replace a current method that simply returns a hash.

So currently we have this

def some_stats
  {
    :foo => "bar",
    :zoo => "zar"
  }
end

which can be called like this:

some_stats[:foo]

I want to replace this with a method that is called with :foo, which does some more complicated things that just building a hash. However, I'd like it to be called in the same way, with the square bracket notation. EG

#this is a bit pseudo-cody but hopefully you get the idea.
def some_stats[](key)
  if key.to_s =~ /something/
    #do something
    return something
  else
    #do something else
    return something_else
  end
end

This would still be called like some_stats[:foo]

I can't work out the syntax for defining the method name: the above, def some_stats[](key) doesn't work. I feel like this should be possible though.

2 Answers

You're quite close. object[sth] is just a syntax sugar for object.[](sth). So to do what you need you have to define some_stats method that returns the object which defines [] method:

class Stats
  def [](key)
    if key.to_s =~ /something/
     #do something
     return something
  else
    #do something else
    return something_else
  end
end

def some_stats
  Stats.new
end

some_stats[:something_new] #=> something
some_stats[:not_new] #=> something_else

Your original method is not called with square brackets; it is called without any argument, but returns an object (in your case a Hash), which has method :[] defined and hence understands square brackets. Your method call is equivalent to

(some_stats())[:foo]

or, more explicitly, to

x = some_stats()
x[:foo]

Therefore you need - inside your method return an instance of some class which offers the [] method. Say you have written a class named MyClass which has a [] defined. You could then write a function

def some_stats
  if _your condition goes here_
    { ... } # Return your Hash
  else
    Myclass.new
  end
end

This would be used as

some_stats[:whatever]

However, to be really useful, your condition would make use of a parameter to the method, i.e.

def some_stats(param)
  if param < 4000
    { ... } # Return your Hash
  else
    Myclass.new
  end
end

And you would invoke it by

some_stats(4711)[:whatever]
Related