Ruby's method_missing()
Ruby programming language is a very unique programming language with tonnes of features. It's like a Swiss army knife that lets you do a lot of things without much frustration.
It's been a while since I posted. So let's talk about a very unique yet powerful feature in Ruby: method_missing()
Let's say you have a class or module, when you call an invalid method on that, you get a NoMethodError.
But Ruby lets you control the behaviour of what happens when you don't have a method on a class or a module.
Let's assume we want to set and get any items to values only using a Class or Module. We can't hardcode it because our program's users can call anything they want. In this case, we can use the method_missing() method which can be tuned to exactly fit our needs. Here's an equivalent code for a module:
module X
@h = {}
def self.method_missing(method, *args, &block)
m = method.to_s
##
# If method_missing() is called with =, it must be a setter.
# For example m=, hello=
#
# If method_missing is called without =, it must be a getter.
# For example, m(), hello
# There is no way setter could end with something other than =
m[-1] == '=' ? @h.store(m.chop, args.length == 1 ? args[0] : args) : @h[m]
# Note that Hash#store returns the value. So when setting item
# @h.store(a, b) will return b. So we don't need to explicitly return anything
end
end
X.hello = 500 # => 500
p X.hello # => 500
X.world = 500, 50 # => [500, 50]
p X.world # => [500, 50]
X.hello = 'hello' # => "hello"
p X.hello # => "hello"
X.lam = ->n { n ** 2 } # => #<Proc:0x000056298b8eb3d0 p.rb:27 (lambda)>
p X.lam === 5 # => 25
p X.none # => nil
p X.none! # => nil
If you want to do this on classes instead, no problem!
Recommended by LinkedIn
class X
def initialize
@h = {}
end
def method_missing(method, *args, &block)
m = method.to_s
##
# If method_missing() is called with =, it must be a setter.
# For example m=, hello=
#
# If method_missing is called without =, it must be a getter.
# For example, m(), hello
# There is no way setter could end with something other than =
m[-1] == '=' ? @h.store(m.chop, args.length == 1 ? args[0] : args) : @h[m]
# Note that Hash#store returns the value. So when setting item
# @h.store(a, b) will return b. So we don't need to explicitly return anything
end
end
x = X.new # => #<X:0x00005642d185deb8 @h={}>
x.hello = 500 # => 500
p x.hello # => 500
x.world = 500, 50 # => [500, 50]
p x.world # => [500, 50]
x.hello = 'hello' # => "hello"
p x.hello # => "hello"
x.lam = ->n { n ** 2 } # => #<Proc:0x000056259ea58bf0 p.rb:27 (lambda)>
p x.lam === 5 # => 25
p x.none # => nil
p x.none! # => nil
So there you go! You now have a Class / Module which can dynamically set and get items!
Explanation
When you call the method_missing() it's called whenever a missing method is called on a class instance (e.g., X.new.hello()) or module or a class itself (in this case, use self.method_missing()).
The method_missing() accepts 3 values:
So that's what method_missing() is. It's a simple concept that can be utilized in so many different ways. Rails use method_missing() often to build such dynamic methods.