Skip to content

Instantly share code, notes, and snippets.

@tomdalling
Last active April 18, 2018 13:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tomdalling/7404e22cf21a298a5b0a7331e34d1504 to your computer and use it in GitHub Desktop.
Save tomdalling/7404e22cf21a298a5b0a7331e34d1504 to your computer and use it in GitHub Desktop.
jekyll-include_snippet gem usage behind article: https://www.rubypigeon.com/posts/super-secret-methods/
title layout date snippet_source
Super Secret Methods
post
2017-07-16
code/super_secret_methods.rb

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

Method names can not contain a period character.

{% include_snippet singleton_def_bad %}

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

{% include_snippet singleton_def_ok %}

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.

{% include_snippet define_method_with_dot %}

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

{% include_snippet call_secret_method_bad %}

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

{% include_snippet call_secret_method_ok %}

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 {:.author}

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.

{% include_snippet bad_ivar_set %}

{% include_snippet bad_const_set %}

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.

module SuperSecretMethods
extend self
def singleton_def_bad
#begin-snippet: singleton_def_bad
def secret.squirrel
:shhh
end
#=> undefined local variable or method `secret' (NameError)
#end-snippet
end
def singleton_def_ok
#begin-snippet: singleton_def_ok
secret = "hello"
def secret.squirrel
:shhh
end
p secret #=> "hello"
p secret.squirrel #=> :shhh
#end-snippet
secret
end
def define_method_with_dot(receiver)
receiver.instance_eval do
#begin-snippet: define_method_with_dot
# this works
define_method('secret.squirrel') do
:shhh
end
#end-snippet
end
end
def call_secret_method_bad
#begin-snippet: call_secret_method_bad
secret.squirrel
#=> undefined local variable or method `secret' (NameError)
#end-snippet
end
def call_secret_method_ok(receiver)
receiver.instance_eval do
return begin
#begin-snippet: call_secret_method_ok
p send('secret.squirrel') #=> :shhh
#end-snippet
end
end
end
def bad_ivar_set
#begin-snippet: bad_ivar_set
instance_variable_set("@secret.squirrel", :shhh)
#=> NameError: `@secret.squirrel' is not allowed as an instance variable name
#end-snippet
end
def bad_const_set
#begin-snippet: bad_const_set
String.const_set('SECRET.SQUIRREL', :shhh)
#=> NameError: wrong constant name SECRET.SQUIRREL
#end-snippet
end
end
require_relative '../code/super_secret_methods.rb'
RSpec.describe SuperSecretMethods do
#silence stdout
before { allow($stdout).to receive(:write) }
it 'does the stuff' do
expect { subject.singleton_def_bad }.to raise_error(NameError, /secret/)
expect(subject.singleton_def_ok).to eq("hello")
expect(subject.singleton_def_ok.squirrel).to eq(:shhh)
receiver = Class.new
subject.define_method_with_dot(receiver)
expect{ subject.call_secret_method_bad }.to raise_error(NameError, /secret/)
expect(subject.call_secret_method_ok(receiver.new)).to eq(:shhh)
expect { subject.bad_ivar_set }.to raise_error(NameError, /not allowed/)
expect { subject.bad_const_set }.to raise_error(NameError, /wrong constant/)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment