Ben Summers’ blog

Java-style annotations in Ruby

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 humourous problems 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:


class SomeController
def show
end
_GetAndPost
def edit
if request.post?
      # update item
    else
      # display item
    end
end
_PostOnly
def delete
    # Destroy the item
  end
end

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.


class Example
post_only
def method1
end
  # Method name style blends in too much.

post_only
def method2
end
  # Well that’s one way to make it different.

post_only; def method3
end
  # Ugly ; symbol, and the ‘def’ is too far right to read easily.
  # Plus it’s too long if the annotation takes arguments.

_PostOnly
def method4
end
  # 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.

That I have railed against Rails’ DSL nature and then merrily extended the language myself has not escaped me.

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”
module Annotations
  # 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
  def annotate_method(annotation_name, value)
_annotation_storage(:@_annotated_next_method)[annotation_name] = value
end
  # Get a method annotation
  def annotation_get(method_name, annotation_name)
m = self.instance_variable_get(:@_annotated_methods)
(m == nil) ? nil : m[method_name][annotation_name]
end
private
  # Magic method to make this work
  def method_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)
end
super
end
  # 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
end
end

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


module MethodCheckClassMethods
  # Annotations for handler methods
  def _PostOnly
annotate_method(:post_allowable, :only)
end
def _GetAndPost
annotate_method(:post_allowable, :both)
end
end

Finally, to use these annotations in a class hierarchy:

  • extend the base class with both Annotations and MethodCheckClassMethods.
  • Apply the annotations to methods in base and derived classes, as required.
  • Retrieve the annotations with self.class.annotation_get(method_name, annotation_name).

Here’s an example bit of code to show the idea:


class ExampleController
extend Annotations
extend MethodCheckClassMethods
  # Use to annotate a method
  _PostOnly
def some_method
    # Do something requiring a POST method
  end
def pre_handle
    # Some code which chooses a method to call ...
    method_to_call = :some_method
    # and checks it like this:
    post_allowable = self.class.annotation_get(method_to_call, :post_allowable)
case post_allowable
when nil
      # Only GET allowed
      raise WrongHTTPMethod.new(GET expected) if exchange.request.post?
when :only
      # Only POST allowed
      raise WrongHTTPMethod.new(POST expected) unless exchange.request.post?
when :both
      # Do nothing
    else
raise Bad annotation
end
end
end

This code is purely to illustrate the points, and isn’t something you can use as is. In particular, it assumes some imaginary infrastructure.

Note that the extend methods aren’t needed in any class derived from your base class, so it’s nice and clean to use within a class hierarchy.

TODO: Quite a bit of work

Before this is generally applicable, there’s a bit more work that could be done:

  • If a method is inherited, querying a derived class for that method should return the base class’ annotation.
  • Class annotations could be implemented with nicer syntax.
  • It’d be nice to have some helper methods for creating the _Annotation methods.
  • Some way of querying the annotations, for example, “find all methods with an X annotation having value Y”.
  • And maybe even some compile time hooks to generate code based on annotated values, for example, decorating methods.

But as it stands, I’m finding it a useful way to improve the readability of my code.

 

Download the code, with your choice of liberal code license.

 

COMMENTS

blog comments powered by Disqus

 

Hello, I’m Ben.

I’m the Technical Director of ONEIS, a platform for information management.

 

About this blog

 

Twitter: @bensummers

 

Subscribe