totally classless

Recently I had a requirement on an app that had 2 types of groups.

  1. public
  2. private

Anyone can join a public group.

However to join a private group you have to be registered on the private group’s website, e.g. pretend Yahoo is a private group, now in order to join the Yahoo group you have to have an account with Yahoo.

Let’s pretend that every private group’s website implements a simple API that says if the given username/password is registered with that website.

I’m going to write a client for the API and put it in a module in lib.

In lib/api_client.rb:

module ApiClient
  def authenticate(username, password)
    # make HTTP GET request to the private group's
    # website's API implementation URL
    # true/false return values
  end
end

I’ll use this library in my Membership model during a validation callback on create.

class Membership < ActiveRecord::Base

belongs_to :user
belongs_to :group

include ApiClient

attr_accessor :username, :password

def validate_on_create
  unless authenticate(username, password)
    errors.add_to_base "You are not registered with this group's website"
  end
end

end

Here we mix in the ApiClient module and use its #authenticate method to validate this membership’s username and password (username and password were added as virtual attributes using attr_accessor since I don’t want to store this remote site’s credentials in my apps db).

The problem I have with this implementation is that the #authenticate method that gets mixed into Membership via ApiClient is now a public instance method on Membership and can therefore be used anywhere. But #authenticate should only be used during validation during creation.

membership = Membership.find :first
membership.authenticate

That’s no good. It’s polluting my Membership public interface.

Let’s try something else in our Membership model.

class Membership < ActiveRecord::Base

  belongs_to :user
  belongs_to :group

  attr_accessor :username, :password

  def validate_on_create
    api_client = Object.new
    api_client.extend ApiClient
    unless api_client.authenticate(username, password)
      errors.add_to_base "You are not registered with this group's website"
    end
  end

end

Here I created an instance of Object, mixed ApiClient in just that instance and then used it to #authenticate the username/password. This is an example of per-object behavior and it gives me exactly what I want. I don’t pollute the public interface of Membership and yet still get to have my remote API behavior all in one place, ApiClient.

Can we shorten #validate_on_create a little more?

def validate_on_create
  api_client = returning(Object.new) {|instance| instance.extend ApiClient}
  unless api_client.authenticate(username, password)
    errors.add_to_base "You are not registered with this group's website"
  end
end

Oh man, an excuse to use the method all the cool kids are using, #returning. I might keep that. As long as it doesn’t go longer than my 80-column character limit but its already starting to push it!

The final implementation of this feature is an example of per-object behavior. A style similar to coding in JavaScript.

When the Rails guys tried to make JavaScript more Ruby-like in their prototype library they added a method to the Object constructor function named #extend to do the very same thing as Ruby’s Object#extend.

The above example might look like this in JavaScript.

var ApiClient = {
  authenticate : function () {
    // make XML HTTP GET request to the private group's website
    // API implementation URL
    // true/false return values
  };
};

function validateOnCreate () {
  var apiClient = new Object();
  Object.extend(apiClient, ApiClient);
  if (! apiClient.authenticate()) {
    // add/display errors
  }
}

David A. Black recently spoke about this at RubyConf 2007. In his slides he mentions how most Ruby programmers already use it when using class methods and how its also useful as a way to avoid changing core Ruby. Dave Thomas mentioned per-object or OOP without classes at RailsConf 2007 as well.

Both of these guys got my interest in removing classes from OOP; before them all I’d seen of this technique was the language Self and its descendant JavaScript. Anyway enough bs, its an interesting technique and one I’m going to monitor to see if I see myself moving more and more in this direction.