You got one chance!
Just because you can does not mean you should. But, it is still fun!
Today, I want to take a detour and get a little silly. Recently, I discovered the undef
keyword that can be used un-define stuff.
Suppose you are building an object that accepts a message to reveal a secret. But, you want to ensure that once the secret is revealed, the object has “no-memory” of ever being able to reveal secrets
class ForgetfulCourier
def reveal_secret
undef :reveal_secret
"this is a secret message"
end
end
Let’s call reveal_secret
twice.
3.0.2 :012 > courier = ForgetfulCourier.new
=> #<ForgetfulCourier:0x000055759d0c7e30>
3.0.2 :013 > courier.reveal_secret
=> "this is a secret message"
3.0.2 :014 > courier.reveal_secret
(irb):14:in `<main>': undefined method `reveal_secret' for #<ForgetfulCourier:0x000055759d0c7e30> (NoMethodError)
Once the secret is revealed, it can no-longer be used. Okay, that was fun. But, please don’t use this in Production. Why? because the behaviour will be confusing and frustrating to the caller.
Ruby has some similar methods, some of which (including undef
) are used in Rails. But, I cannot
think of any real scenario in my projects, where this technique would be useful. But, if you have used this, I would love to hear about your use-case.
Update
One of the limitations (or so I thought) of the class above is that if the secret message was stored as an attribute, inspecting the courier
object
would allow the caller to access the secret. Let me show you. Let’s modify ForgetfulCourier
so that the secret message is stored at initialization:
class ForgetfulCourier
def initialize(secret)
@secret = secret
end
def reveal_secret
undef :reveal_secret
@secret
end
end
As before, the caller can still call reveal_secret
only once. But, the caller can still see the message by just calling courier.inspect
3.0.2 :049 > courier.inspect
=> "#<ForgetfulCourier:0x000055c66c7ca640 @secret=\"secret message\">"
But, then some fellow Rubyists introduced me to remove_instance_variable
, which I can use to completely remove the secret from its existance. Here is the modified class
class ForgetfulCourier
def initialize(secret)
@secret = secret
end
def reveal
undef :reveal
remove_instance_variable :@secret
end
end
Now, if I inspect after revealing the message, the secret is gone
3.0.2 :063 > c.inspect
=> "#<ForgetfulCourier:0x000055c66c910db0>"
There is still some stuff that needs to be done to ensure that the caller has no way of fetching the secret more than once. For instance, the caller may never call
reveal
; instead only call inspect to fetch the value. It is possible to use the same techniques discussed above to make our ForgetfulCourier
more secure. I will leave
that as an exercise for the reader.