My favourite thing about starting to use another programming language is the new perspective it gives you on programming in general. Experiencing another language’s culture and how it solves problems is a great way to think about the underlying nature of your craft.

After years of avoiding Java, I’ve been writing quite a bit recently to take advantage of the libraries and infrastructure in the JVM. I’ve been pleasantly surprised: it’s like a better C++ with an emphasis on avoiding the treacherous details in the language and the C libraries. While its reputation for verbosity is well deserved, there are some nice features.

For instance, Java allows you to annotate methods and classes. If that was a feature of Ruby, it would be a neat solution to an problem I was trying to solve.

Motivation

When handling requests to a web application, you should only respond to the correct HTTP method. For example, destructive methods should be only performed when they’re POSTed. Not only does this protect against humourousproblems with web crawlers, but it’s a vital security feature to prevent vunerabilies like CSRF.

While it’s always important to write code carefully, an application framework can make it easier to prevent mistakes. In particular, I wanted to make it impossible to use anything other than GET requests unless specifically declared in the code.

Rails implements this kind of check with the verify directive. (Although Rails allows both GET and POST by default, which is not what I wanted.)

verify:method=>:post,:only=>[:delete_something,:update_something]

The disadvantage is that it moves the declaration away from the method itself, which makes it harder to scan the code. What I wanted was something like this:

where the show method can only use GET requests, delete only POST, and edit either. Of course, you still have to make sure that GET requests don’t do anything destructive, but it’s easier to read and the default will quickly pick up problems when a POSTed form shows an error.

The concept can be extended to add other properties to methods, for example, the permissions that a user must have to be able to invoke the method.

There are dangers of this style. I can imagine that you could write some completely unreadable code if you overused it.

Syntax

I thought long and hard about how I’d like to write the annotations, within the restrictions imposed by the Ruby syntax. There’s quite a lot of choice, and it’s probably largely down to personal taste.

classExample
post_only
defmethod1end# Method name style blends in too much.
post_only
defmethod2end# Well that’s one way to make it different.
post_only;defmethod3end# Ugly ; symbol, and the ‘def’ is too far right to read easily.
# Plus it’s too long if the annotation takes arguments.
_PostOnly
defmethod4end# Looks different to normal syntax, has a trailing _ to distinguish.
end

In the end, I choose the last style. It’s kind of Java-like, but you can’t have everything. Having used the convention in code for a few weeks, it feels right.

Implementation and use

It’s actually quite easy to implement. Ruby calls the method_added method on classes whenever a method is defined. This allows annotations to be implemented simply by storing “pending” annotations and recording them against the next method to be defined. Like this:

# Annotations module, enable in base class with “extend Annotations”
moduleAnnotations# NOTE: See end of article for a link to download this code
# along with a simple unit test. Also included are methods for
# annotating classes.
# Annotate the next method defined
defannotate_method(annotation_name, value)
_annotation_storage(:@_annotated_next_method)[annotation_name]= value
end# Get a method annotation
defannotation_get(method_name, annotation_name)
m =self.instance_variable_get(:@_annotated_methods)(m ==nil)?nil: m[method_name][annotation_name]endprivate# Magic method to make this work
defmethod_added(method_name)
a =self.instance_variable_get(:@_annotated_next_method)if a !=nil
_annotation_storage(:@_annotated_methods,{})[method_name]= a
self.instance_variable_set(:@_annotated_next_method,nil)endsuperend# Store data in the class variable
def_annotation_storage(name, default =nil)
a =self.instance_variable_get(name)if a ==nil
a =Hash.new(default)self.instance_variable_set(name, a)end
a
endend

This implements the basic tools, from which the annotation methods can be defined.