totally classless
Posted by Jared Carroll
Nov 19
Recently I had a requirement on an app that had 2 types of groups.
- public
- 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
lib/api_client.rb1 2 3 4 5 6 7 8 9 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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.
1 2 3 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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?
1 2 3 4 5 6 7 |
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.
Comments on this post
Nov 20
Ben Mabey said,
Why not just make the method protected in the module?
For the interested reader in “OOP without classes” they can read about it on wikipedia: http://en.wikipedia.org/wiki/Prototype-based_programming Also, there is is a small ruby library by Ara that allows you to easily play around with this style of programming in ruby: http://codeforpeople.com/lib/ruby/prototype/prototype-2.0.0/README
Nov 20
schmidt said,
If the ApiClient is stateless, what seems to be the case, why not make the authenticate method a method on ApiClient. This would make things a lot easier.
And you get rid of a bit of metaprogramming, where it is not needed.
Instance-specfic behaviour is still very useful, especially in tests. But hey, defining methods on classes and modules is instance specific behaviour as well and this is always useful.
Nov 20
Jon Yurek said,
Well, I wouldn’t exactly call extending an object metaprogramming, but I do agree with the sentiment that there’s no need to mix it in to an Object before calling it.
Nov 20
chako said,
What is the point of the returning call? Wouldn’t this do the same thing:
api_client = Object.new.extend ApiClient
Nov 20
theJareCare said,
that’s true chako but i think we both would agree that
or
is a lot more better looking than chaining a ton of messages together like
Nov 23
Yossef said,
Jare: I don’t necessarily agree that the first two look better (and if you’re worried about the 80-character limit, try |o| instance of |instance|) than chaining messages together. I also don’t think that’s “a ton” of messages.
I do think it’s not all that nice to depend on Object#extend returning self. I know that’s what it does, but it just seems an odd thing to do.
Sorry, comments are closed for this article.
© 2000 - 2008 by thoughtbot, inc.
written by a bushel of tiny robots
Archives
Thoughtbot
thoughtbot is a technology consulting firm that provides web application development and design services. We focus on building modern systems, embracing good ideas and delivering elegant solutions.
Interested in learning Rails?
Sign up for our training.
Come “ride the toad” on hoptoad, the app error app
Widgetfinger: simple content management for simple websites