Wednesday, January 14, 2009

Easily Typable

For the Rubyists of us, I created a new mixin module called EasilyTypable:

http://github.com/AndyObtiva/easilytypable/tree/master

I am pasting the README for it over here:

Easily Typable
==============


Introduction:
-------------

Often when working with models that belong to an inheritance hierarchy,
it is useful to verify if a particular model is of a certain type to
perform some behavior specific to it. For example, this is needed when
the view needs to handle a special rendering case when encountering a
certain type.

The call typically made to accomplish the task is:
model.is_a?(CertainType)

Often, to do so in a more readable fashion, developers add a more
English-like method that hides the details of type checking:
model.certain_type?

Writing such methods gets repetitive after a while, so an easier way
to get these methods automatically is to mixin the EasilyTypable
module.

When mixed into classes in an inheritance hierarchy, each class gets
"certain_type?" methods for its type and all of its subclass types.


Example:
--------

require 'rubygems'
require 'spec'
require File.dirname(__FILE__) + '/../lib/easily_typable'

class TypeA
include Obtiva::EasilyTypable
end

class TypeB < TypeA
end

class TypeC < TypeB
end

describe "Obtiva::EasilyTypable" do
it "should add type_a? method to TypeA object" do
TypeA.new.type_a?.should be_true
end
it "should add type_b? method to TypeB object" do
TypeB.new.type_b?.should be_true
end
it "should add type_b? method to TypeA object" do
TypeA.new.type_b?.should be_false
end
it "should add type_c? method to TypeC object" do
TypeC.new.type_c?.should be_true
end
it "should add type_c? method to TypeA object" do
TypeA.new.type_c?.should be_false
end
it "should add type_c? method to TypeB object" do
TypeB.new.type_c?.should be_false
end
end

Keep in mind that this is no substitute for good Object-Oriented design and is not an excuse to type-check everything in your code instead of letting behavior live in the models or rely on patterns like Strategy and State.

The type checking methods are simply useful in cases where type-related behavior really should not live on the model to maintain its cohesion and avoid strong coupling. An example of that is View logic that depends on the type of the model, but should not live in the model.

11 comments:

Renzo Borgatti said...

Andy, thanks for the gem. When I see an is_a? in the code I think something is wrong. It's a bad smell to me (it can be either an LSP or DIP violation). It's usually easy to remove a type checking like that, especially in Rails where everything supports naming lookup (down to layouts). If you have a portion of a view that should behave differently for different kind of users, for example, you can remove the is_a? with a new layout and one partial (to avoid DRY). Just don't let an application to proliferate that direction. The day you'll add a new type is very possible you'll need to fix those conditionals (OCP this time). What do you think?

Andy Maleh said...

Usually, whenever I see "is_a?", I immediately become suspicious and either get rid of it and move the behavior to a method on a Model (simple polymorphism) or refactor it to a pattern like State/Strategy.

With View code though (e.g. Rails views,) sometimes you need to display extra information for one specific Model type only, but not others. In that case, it may be easier to check the type of the Model as long as you're not doing it everywhere (I'm trying to learn not to be too dogmatic in Ruby land.) Otherwise, if you rely on partial View polymorphism for example, you'd have to create empty partials for the other potential Model types.

Of course, the conditional can be hidden in a Helper for testabililty and more readable View code.

Ryan Platte said...

Looks interesting. Can you give an example of a use case for this?

Andy Maleh said...

Here is a Rails HAML code snippet:
- if @tree_node.unit?
= link_to "(+ Page Break)", tree_node_break_path(@tree_node), :method => :post

It adds a Page Break link only if the @tree_node is of type Unit (checking the type via the "unit?" method. )

Renzo Borgatti said...

= @tree_node.page_break { link_to "(+ Page Break)", tree_node_break_path(@tree_node), :method => :post }

Inside TreeNodeUnit a method page_break that yields. Inside TreeNodeNotUnit an empty one.

Andy Maleh said...

Interesting idea. I'll give it a try. I'm still getting used to the Ruby/Rails way of doing things, so any advice is greatly appreciated.

Can we name the "page_break" method something more meaningful though like "accept_page_break_visitor" or simply "on_page_break_support" (the latter is similar to event listener names in Javascript and Glimmer)?

I also wonder if we could have otherwise added a "supports_page_break?" method instead and used it with an if statement instead of "unit?". In general, I don't like if statements, but if we don't have a good name for a method that yields a block, I wonder if we're making the code harder to understand by using such a method.

Ryan Platte said...

I agree with your take on Renzo's idea, Andy. The view isn't the worst place for simple and domain-related conditionals.

I like Renzo's idea too -- very Smalltalky. I would name it render_page_break or maybe_render_page_break.

And I think if we were pairing I'd rejoice when you came up with supports_page_break?, as that's clearest of all IMO. It's also specific to this business decision, so that when business changes its mind you don't accidentally introduce other bugs making the change.

Cool stuff, thanks to both of you.

Andy Maleh said...

Thanks Ryan. Our discussion here is sort of like remote trio-programming. Got a lot of good ideas out of it. :)

Renzo Borgatti said...

Hey, did I win an invitation to the next geek fest? :) Good to see Obtivians are always so active.

Julik said...

Hi Andy!
I've implemented something similar once (I called it ducklet back then), however I've stumbled upon something that makes it unusable, at least in Rails applications or other environments that use reloadable code. For example, considering a class hierarchy of

Animal -> [Mammal, Bird]

when we use Bird for the first time it will load Animal but not Mammal. We will not be able to ask if any_animal.mammal? before we've actually loaded the Mammal class, however we certainly do want to do it for a Bird instance.

Considering that such a hack always happens through the inherited() callback, we do not get the mammal? method until the Mammal class is actually loaded. If we want to check if an instance of Animal is bird? (which we certainly would want to do once), and Bird is not yet loaded our code will explode in a huge NoMethodError (and this is hideous to debug because class loading order is not guaranteed in a test). So this might work in context of a library where you are totally sure that all the classes are preloaded before you call the ducking? methods.

The only sensible way I see to fix it is to overload the global method_missing (that would in turn summon a NameError to trigger autoload), but it feels like overkill to me. This is why I didn't continue the ducklet library back then, curious if you can overcome that.

Andy Maleh said...

Thanks for mentioning this Julik.

In our Rails app, we do actually preload all the models, so EasilyTypable ends up working just fine.

That said, I will mention that as a pre-requisite in the README file. Thanks again for bringing it up.