Super Secret Methods

??? words · ??? min read

Here is a quirk of the Ruby language that I discovered a few weeks ago.

Method names can not contain a period character.

def secret.squirrel
  :shhh
end
#=> undefined local variable or method `secret' (NameError)

This is because the period character is syntax used for defining methods on specific objects.

secret = "hello"
def secret.squirrel
  :shhh
end

p secret #=> "hello"
p secret.squirrel #=> :shhh

Even though the syntax does not allow periods in the method name, surprisingly, the Ruby runtime will allow it. Using define_method, the syntax limitations can be bypassed.

# this works
define_method('secret.squirrel') do
  :shhh
end

The method above can’t be called using normal Ruby syntax.

secret.squirrel
#=> undefined local variable or method `secret' (NameError)

But again, the syntax limitations can be bypassed, using send.

p send('secret.squirrel') #=> :shhh

To summarize, it is possible to define and call methods that are impossible to access via normal Ruby syntax.

Abuses Applications

Upon discovering this, my first thought was “this is a bug in Ruby.” My next thought, of course, was “what could I use this for?” The only use that I can think of is avoiding method pollution.

Sometimes, when writing mixins or base classes, you want to define methods for internal use only. You don’t want these internal methods to accidentally override or conflict with other peoples’ methods.

Roda is very concerned about pollution. […] Roda purposely limits the instance variables, methods, and constants available in the route block scope, so that it is unlikely you will run into conflicts. If you’ve ever tried to use an instance variable named @request inside a Sinatra::Base subclass, you’ll appreciate that Roda attempts to avoid polluting the scope.

Roda documentation

So this trick can be used to make “secret” methods which don’t pollute the namespace of normal methods – although the same trick does not work for instance variables or constants.

instance_variable_set("@secret.squirrel", :shhh)
#=> NameError: `@secret.squirrel' is not allowed as an instance variable name

String.const_set('SECRET.SQUIRREL', :shhh)
#=> NameError: wrong constant name SECRET.SQUIRREL

Disclaimer

I’m sure someone’s already frothing at the keyboard, writing a mean-spirited comment on Reddit, for daring to suggest that this trick might be useful in any way.

So let me be clear: this is probably a bad idea.

I’m still not convinced that it is intended behaviour. I know that method names are allowed to contain unicode, but allowing the period character seems inconsistent to me, since it’s not allowed for instance variables or constants.

I also haven’t tried it on JRuby, or any other implementations. It might only work on MRI.

It’s just an interesting insight into the internals of Ruby.

Got questions? Comments? Milk?

Shoot an email to [email protected] or hit me up on Twitter (@tom_dalling).

← Previously: Refactoring From Inheritance To Composition To Data

Next up: Replacing Mocks With Hand-Written Test Doubles →

Join The Pigeonhole

Don't miss the next post! Subscribe to Ruby Pigeon mailing list and get the next post sent straight to your inbox.