Life beyond the each iterator

Today I will take a break from the IM integration with XMPP4r series and write about something completely different. Writing about XMPP4r just for the heck of it would become boring for everyone quite fast.
Let me dim the light, start some sentimental music, light some candles, make an exaggerated smile so I can become intimate with you and talk about the past.
More than 1 year ago… I was so excited. I was learning Ruby and, most precisely, I was learning blocks and block-based iterators. Once I grasped the concept, I made the mistake of using almost exclusively the each iterator in every situations.
each is like your oldest pair of slippers
Old slippers are comfortable and comforting. You put them on without shame, ignoring the laughs as well as the cruel remarks coming from your “friends”. The each iterator is also a comfortable pair of slippers. If you came to ruby from another language, it probably turned you on right away. Chances are that you said to yourself : “Damn, I will put my feet into that!”
There is no problem with the each iterator, it is simple, easy to use and easy to read. The problem is when you use it in every situations. When you do something like in the following example, you suffer from the syndrome of the comforting slippers.

@dogs = Dog.find(:all)
@dogs_to_keep = []
@dogs.each { |dog| @dogs_to_keep.push(dog) if calculate_emotional_intelligence(dog) >= 50 }

Here the programmer refused to remove his old slippers because he refused to see the life beyond the each iterator. A class like Array proposes other iterators that would have simplify the above code greatly.
This method is exactly what our programmer needed. Like each, it iterates over all elements in the array… but it does more. At each passage in the block, and if the value returned by the block is true, the corresponding element will be pushed into a new array.

@dogs = Dog.find(:all)
@dogs_to_keep = {|dog| calculate_emotional_intelligence(dog) >= 50}

You could also do it the other way around, rejecting dogs with little emotional intelligence.

@dogs = Dog.find(:all)
@dogs_to_keep = @dogs.reject {|dog| calculate_emotional_intelligence(dog) < 50}

Or you could modify the original array directly with reject!

@dogs = Dog.find(:all)
@dogs.reject! {|dog| calculate_emotional_intelligence(dog) < 50}

collect and map (same thing)
Finally, collect and map can be used when you want to create a new array based on the values returned at every passage in the block.

@dogs = [{:name => "Benji", :fear_factor => 3},{:name => "Rex", :fear_factor => 7}]
if outside_atmosphere == :dark_and_scary
  @fear_factors = @dogs.collect {|dog| dog[:fear_factor] + 5}
  #Output : @fear_factors contains [8,12]

You can also modify the original array with collect! or map!

@dogs = [{:name => "Benji", :fear_factor => 3},{:name => "Rex", :fear_factor => 7}]
if outside_atmosphere == :dark_and_scary! {|dog| dog[:fear_factor] + 5}
  #Output : @dogs contains [8,12]

Note how the resulting array contains 2 Fixnum items AND NOT 2 hashes. As you can see, the value returned by the block BECOMES an element in the new array. If you want to operate on the array directly, use the each slippers, that way :

@dogs = [{:name => "Benji", :fear_factor => 3},{:name => "Rex", :fear_factor => 7}]
if outside_atmosphere == :dark_and_scary
  @dogs.each {|dog| dog[:fear_factor] += 5}
  #Output : @dogs contains [{:name => "Benji", :fear_factor => 8},{:name => "Rex", :fear_factor => 12}]

Do you think of using select, reject and collect/map, or does the each slippers have too much influence on your life?

IM Integration with XMPP4r – 2 mistakes to avoid

In this 3rd part we’re going to be concrete but a little bit less technical. Here are 2 big mistakes you have to avoid when trying to setup an IM based application (Sorry for those who wanted more technical info about xmpp4r… maybe in the next one).
The #1 mistake : Trying to run XMPP4r under Rails
I’m not proud to say I did this mistake at first. Perhaps it’s because I was so excited to see xmpp4r in action that my head started making some strange electricity noises, thus short-circuiting my capacity to think rationally. Anyway, please, don’t try to put XMPP4r under your “vendor” directory believing it makes some sense… because it does not. You cannot make a XMPP robot in a Web context. The Web is stateless in nature. User ask for a resource, Apache kicks in and “tunnels” your request to some web framework, a response is returned to the user and then it’s over. (this was over-simplificated… but the main idea is there)
What we need here instead is something that runs on its own, something persistent, something completely independant of user requests on port 80.
So in order to build something out of XMPP4r, you need to create a separate program (in our case it makes sense to call it a listener) that will be running in permanence. But be careful because this could lead to mistake #2.
The #2 mistake : Putting business logic in the listener
I almost thought of doing this mistake but fortunately I didn’t. It would have been awful. Rails (or merb, or whatever) is your friend, you cannot ignore it and code your whole app in plain ruby! You want to get out of your XMPP4r listener as quickly as possible because it is not the “brain” of your application.
Try to make your listener as stupid as possible :

  1. Receives input (from a callback)
  2. Sends the data to a standard Rails application (data will be “understood” and “managed” there)
  3. Sends back to the IM user the response received from the Rails application

So basically the idea is to use the listener for IM interaction only and a Rails application for everything else. Said that way, some self-sufficient cool guys who like to show their attitude could be tempted to take a relax voice and say “Well, duh…”, but it might not be that evident for the rest of us.
So now you know 2 traps to not fall into… how you decide to design your application after that is completely up to you.