Problem

I’ve recently noticed I often use code like that.

  class Offer
    def available?
    end

    def cancelled?
    end

    def postponed?
    end
  end

  if offer.available?
    order.submit
  elsif offer.cancelled?
    order.reject
  elsif offer.postponed?
    order.postpone
  else raise UnknownStateError.new
  end

if/else statements looks like perfect candidates for case … when statement. In particular I’d like to have something like that:

  case offer
  when :available? then order.submit
  when :cancelled? then order.reject
  when :postponed? then order.postpone
  else raise UnknownStateError.new

Looks better, right? Of course it doesn’t work out of the box. We’ll try to overcome it but first some theory.

case/when uses === method internally. In particular ruby calls code like this:

  :available?.===(offer)

…which will always return false when compared to non-symbol. Triple equals (===) is slightly different that double equals (==) so be awared when each of them is called.

Solution

When we know what is going under the hood it is easy to slightly modify the behaviour to fit our needs.

class Symbol
  alias :original_triple_equals :"==="

  def ===(object)
    original_triple_equals(object) ||
      (object.respond_to?(self) && object.__send__(self))
  end

end

We override triple equals and try to call a desired method if original behaviour returns false. Let’s test it

case []
when :empty? then puts "that works!"
else puts "something went wrong"

Works like a charm.

Performance

There is no doubt performance will be affected. Let’s benchmark performance of comparing symbols.

REPEAT = 1_000_000
Benchmark.bm do |x|
  x.report("original  ===") do
    REPEAT.times { :s1.original_triple_equals(:s2) }
  end
  x.report("overriden ===") do
    REPEAT.times { :s1.===(:s2) }
  end
end

Ruby 1.8:

                  user     system      total        real
original  ===  0.610000   0.170000   0.780000 (  0.782712)
overriden ===  1.530000   0.330000   1.860000 (  1.852536)

Performance hit: 238%

Ruby 1.9:

                  user     system      total        real
original  ===  0.180000   0.000000   0.180000 (  0.187665)
overriden ===  0.580000   0.000000   0.580000 (  0.576526)

Performance hit: 322%

Benchmark isn’t the most accurate but let’s approximate that decrease in performance is 200%-400%. Is it much? It depends how often you symbols are compared but under some circumstances it may lead for serious performance problems. I warned you.

Ruby 1.9

Ruby 1.9 comes with a new method of calling blocks.

  proc { puts "it looks awkward" }.===

It gives us some cool capabilities. Take a look at that example.

How does it help us calling methods in case … when statement? Frankly… not much :) But we can do some interesting tricks for fun and profit.

case []
when :empty?.to_proc then puts "empty array"
else puts "Ruby 1.9?"
end

We convert Symbol to proc which was introduced in Ruby 1.8.7 and then case … when calls proc.=== implicity. Of course calling to_proc each time isn’t very helpful and readability is even worse.

Quite better solution which comes to my mind is to introduce a method which takes a symbol and call to_proc on it. The method should be globally accessible so we have to define it at kernel level.

module Kernel
  def is(symbol)
    symbol.to_proc
  end
end

case []
when is(:empty?) then puts "empty array"
else puts "Ruby 1.9?"
end

Unfortunately we can’t skip parentheses here.

There is a big benefit here. We don’t override symbol.=== so performance remains unaffected. On the other hand polluting Kernel namespace is a certain drawback.

Of course Ruby 1.9 solution may be implemented in Ruby 1.8 as well. You would just need to implement Proc#=== method.

Update: Jacob’s Solution

Jacob Rothstein introduced very neat solution using hashes and lambdas. It doesn’t use case … when statement at all but behavior is very similar.

class Object
  def switch( hash )
    hash.each {|method, proc| return proc[] if send method }
    yield if block_given?
  end
end

# which allows us to write functional-like code

offer.switch(
  available: -> { order.submit },
  cancelled: -> { order.reject },
  postponed: -> { order.postpone }
) { raise UnknownStateError.new }

It depends on new lambda syntax and ordered hashes so Ruby 1.9 is required. This is very clear and doesn’t pollute Symbol#=== method. Read full explanation here.

Filed under: ruby and tricks-and-quirks
Comments

Recently I’ve started using IRC again. What a great feeling! I use pretty standard technique. My irssi client is run on my server in screen session. I attach to the session via SSH to read and write some gossip.

  ssh -t michal@example.com screen -r

Only one thing bothers me. There is no sign of new messages on IRC channel. I have to switch terminals and check if there is something new.

Remote side

It is quite easy to get notifications having irssi on local machine but getting them from remote machine seems to be slightly more complicated. There is no easy way for remote machine to send us data about new traffic. I don’t want port forwarding or something similar to achieve so silly task. Polling the server seems to be the only way. It would be also nice if the task could be done with persisted connection to avoid connecting again and again.

Other problem: how to know that new message has been sent? I’ve enabled irssi logs. I’m not the irssi expert some maybe there is more accurate way. To enable logs open irssi and type:

  /set autolog

The logs are saved to ~/irclogs/IRC_SERVER/CHANNEL.log

How to monitor them without reconnecting? Simple tail -f to the rescue

  ssh michal@example.com tail -n0 -f ~/irclogs/*/*.log

I’ve also added -q and -n0 arguments.

  • -q - don’t print file headers
  • -n0 - don’t print trailing lines (by default tail prints 10 trailing lines)

For now on new messages appears on your local terminal.

Notifications

OK, half of the job is done. Now how to display notifications? LibNotify is excellent for that purpose.

  $ apt-get install libnotify-bin
  $ notify-send "hello" "notification sent from my libnotify-bin"

OS X users may use Growl which seems to be very nice tool too.

Join them

We have irc logs forwarded to our host and notification tool. Firstly I wanted to use xargs Finally there will be something about ruby :) Of course perl/python/C or others does their job as well.

  ssh michal@example.com "tail -n0 -f ~/irclogs/*/*.log" | \
   ruby -rshellwords -ne '`notify-send -t 500 #{$_.shellescape}`'

Quick explanation

  • -rshellwords means require ‘shellwords’
  • -n - read each line from stdin and assign to $_ (for perl lovers)
  • -e - exectues ruby code
  • `system_command` - executes some_command in your shell (backticks not quotes)
  • String.shellescape - prevents your ircmates from executing commands on your machine ;-)

notify-send -t 500 is quite more interesting. Basically it displays notification with 500ms timeout. Official documentation says that. However Ubuntu users have to install patched version. Debian/Gentoo/Fedora/Arch/etc users are OK. Why only Ubuntu? Well, Ubuntu core-team recons theirs users are too dumb to decide about notification timeout. Read ridiculous story here.

Improve

Above oneliner isn’t very sophisticated. There is plenty of room for improvements. For instance to display nickname as notification title and message as notification content only simple change is needed

ssh michal@example.com "tail -q -n0 -f ~/irclogs/*/*.log " | \
   ruby -rshellwords -ne '`notify-send -t 1000 #{$1.shellescape} #{$2.shellescape}`\
     if $_ =~ /<([^>]+)>(.*)/'

Don’t stick to oneliner unnecessarily. More complex scripts should be written to file.

Filed under: linux and ruby
Comments

DRUG Hackathon #1

02 Feb 2011

I’m greatly impressed how Wrocław Ruby User Group is doing. We’ve just started working on open-source ruby projects in addition to cyclic meetings. The idea is simple. We want to improve our skills, create software we need and have some fun.

At our first meeting we’ve worked on exceptioner. It’s swiss army knife meant to replace hoptoad and the likes. Shortly, we’re not satisfied with available solutions and want to provide something significantly better.

Exceptioner can work in 2 modes: offline and online. Offline mode is similar to exception_notification. It delivers information about exceptions through your local system. Online mode works as hoptoad does. Your exceptions are pushed to external server which notifies you and provides nice WWW interface to manage the exceptions. Our unique feature are different delivery methods. Exceptions may be pushed traditionally via email but also via jabber, IRC or campfire. We also plan to add support for bunch of project management tools. Support for redmine is done already.

Exceptioner is currently in early stage although it is used in some working rails projects.

We’ve worked on exceptioner about 6 hours. Our team consited of 7 developers who have made piece of amazing work. I think it’s worth to describe what each of use has done:

Paweł Pacana - Generally speaking he came with the idea of hackathon. Paweł developed our website along with voting system for hackathon ideas. He has worked on HTTP API and web interface for exceptioner. It’s crucial part of the project so I’m really happy Paweł has taken care about it.

Piotr Niełacny/LiTE - His story is quite funny. One day he came to bookstore to buy some python book. There was none so he bought a ruby one. Obviously he has become a passionate ruby developer. Piotr added support for IRC. Thanks to that contribution you can even talk with IRC bot about exceptions when you feel alone!

Darek Gertych - He has joined our group recently but immediately has made a great impact. He added support for redmine. It seemed to be really easy for him! Darek has some qualitative thoughts on testing external services. What’s more he promised to prepare presentation about continuous integration systems for next DRUG.

Jan Dudek - He admits he’s newcomer to ruby world although I’m really impressed how he is doing. You would have thought he is quite experienced developer after talking with him for some time. Janek added easy jabber setup through rake tasks. As far as I know he’s available for hire so don’t wait until it will be too late!

Łukasz Śliwa - His contribution was the most unsaspected one. When the hackathon was almost finished we’ve received pull request from Łukasz who was working at home. He added support for Campfire. Fortunately he joined us later at afterparty. Among strong Ruby skills Łukasz is facebook apps master. Ask him about any function from their API and he will describe each bug it has!

Marcin Jędras - Thanks to him we had everything computer enginner needs - chair, desk and WiFi. He invited us to his conference room. As a CEO he’s quite busy so he wasn’t able to contribute but at least he pointed us to great pizza ;)

Marcin Pietraszek/kbl - He started the hackathon pointing a mistake in my last talk. Point for him, shame on me. Unfortunately he has some troubles with his laptop so he wasn’t able to contribute. Let’s hope he’ll make up for it next time.

Besides of having really good time I’ve also contributed some features. In particular I added dispatching hooks. There are dozens of ideas for further features and there is a lot of work to do. I frankly believe Exceptioner will play a big role in exceptions market :) First RC will be out soon!

Next Hackathon on 26 February. Don’t hesitate to join us! It will be even better than now.

Filed under: DRUG and ruby
Comments
WrocLove.rb