Archive for the ‘Ruby’ Category

Creating MSN messenger bot with ruby.

Wednesday, July 8th, 2009

If you are willing to make MSN messenger bot program with ruby, there is a good libray, rubymsn. Though it doesn’t seem to be active now, you can download sources and use them. Throughout this article, I will introduce rubymsn library to share you what I’ve learned.

First, you can refer to MSN messenger protocol page.

Login to MSN messenger

Just create MSNConnection with MSN messenger id and password.

@conn = MSNConnection.new(msn[:id], msn[:password])
@conn.signed_in = lambda {
  @conn.change_nickname "Twitter Bot"
}

signed_in proc will be executed after successful login. Used change_nickname to change nickname as ‘Twitter Bot’. It does not do anything untils @conn.start is called, though.

Receiving a message and Reply

Use new_chat_session proc to indicate what to do when the session between the bot and a user is created.

@conn.new_chat_session = lambda do |tag, session|
  TwitterBot.info "=> new chat session created with tag '#{tag}'!"

  session.message_received = lambda do |sender, message|
    TwitterBot.info "=> #{sender} says: #{message}"
    session.say "hello"
  end

  session.start
end
@conn.start

message_received proc is called when the message from a user arrives. Possible procs for the session are  :message_received, :session_started, :session_ended, and :participants_updated

To reply or send a message to the user, use session.say. 
The  tag is used to a key to access the users’ session. A session is stored @conn.chatsessions as a hash. So a session can be retrieved like below.

session = @conn.chatsessions[tag]

How to know the state of friends.

A user has several states depending on his connection such as Online, Offline, Busy, etc. The state of user can be checked with @conn.contactlists. The following is how to know whether a user is offline.

def offline?(email)
  contact = @conn.contactlists["FL"][email]
  contact ? contact.status.code == "FLN" : true
end

Sending a message to the user

In order to send a message to the friend, you have to get or create the session first. To create a session, several handshake messages are needed. This handshake is triggered by @conn.start_chat method.

@conn.start_chat(tag, email)

A tag can be anything unique. An email is the email address of the user to chat. The session is not created immediately, it takes some time until the handshake finished. How to know when the session is created? A session_started proc is called as soon as the session is created.

@conn.new_chat_session = lambda do |tag, session|
  ...
  session.session_started = lambda do
    msg = get_message(tag) # assume that a message is possible to get with tag.
    session.say msg
    session.close
  end
  session.start
end

When the session is started, A message is sent. Above code assumes that a message is stored before and can be retrieved with a tag.

cattr_accessor and mattr_accessor

Friday, July 3rd, 2009

In ruby, there is no public variable. All variables in object are hidden encapsulated. To access objects’ variables, getter and setter methods are necessary. It may sound painful if I have to make all the getter/setter methods. But don’t worry. Ruby has a smart solution. attr_accessor.

attr_accessor :school
attr_reader :student
attr_writer :student

This is good. With a simple declaration, encapsulation is achieved. It is OK for instance variables, but what about class variables? Unfortunately, ruby core does not have the attr_xxx for class variables. However, It is not that difficult to implement.

class Class
  def cattr_accessor(name)
    class_eval <<-EOS
        unless defined? @@#{name}
          @@#{name} = nil
        end                      

        def self.#{name}
          @@#{name}
        end                      

        def self.#{name}=(obj)
          @@#{name} = obj
        end
    EOS
  end
end

class Test
  cattr_accessor :greet
end

Test.greet = "hello"
p Test.greet

ActiveSupport

There is a easier way if you set up your mind to use activesupport. Activesupport already implemented cattr_xxx and mattr_xxx serieses.

require "activesupport"
class ClassTest
  cattr_accessor :greet
end
module ModuleTest
  mattr_accessor :greet
end 

cattr_xxx works for class variables, and mattr_xxx wors for module variables.

Passenger and SE linux

Friday, July 3rd, 2009

Before passenger, it was a big issue to deploy rails appliation. We needed so many things to configure such as mogrel, mogrel cluster, and god. Now we just need a passenger and apache. It is as simple as PHP. What makes me more impressed is passenger is super easy to install. It uses gem system.

sudo gem install passenger

To install passenger as a apache module passenger-install-apache2-module command is used.

passenger-install-apache2-module

This command is very nice because it notifies me what to do next whenever there is a problem. And It shows apache configuration to set at the end. How nice!

clip_image001

SE linux

A week ago, I asked to set up a deploy environment with passenger and apache on fedora linux. I thought it would be a piece of cake at the first time, but I struggled all day. I installed passenger and configure apache settings and virtual hosts as always. After restarting apache, I found it failed. What’s wrong?

The folowing error occured. This error can be checked in /var/log/httpd/error_log file.

[Thu Jul 02 17:53:03 2009] [notice] SELinux policy enabled; httpd running as context unconfined_u:system_r:httpd_t:s0
[Thu Jul 02 17:53:03 2009] [notice] suEXEC mechanism enabled (wrapper: /usr/sbin/suexec)
*** Passenger ERROR (ext/common/ApplicationPoolServer.h:643):
Cannot execute /usr/lib/ruby/gems/1.8/gems/passenger-2.2.4/ext/apache2/ApplicationPoolServerExecutable: Permission denied (13)

A permission to execute passenger is denied. But permission is allowed to everybody if I check this file by ls command.

-rwxr-xr-x. 1 root root 2528880 2009-07-02 17:35 /usr/lib/ruby/gems/1.8/gems/passenger-2.2.4/ext/apache2/ApplicationPoolServerExecutable

This is thanks to the SELinux policy. SELinux is Secure Enhanced linux. I am not explainig SE linux here. One thing I know is httpd is affected by the SE linux policy and this policy prevents passenger from executing. The easist way to avoid this is not to use SE Linux. One time command is

setenforce Permissive

If you want not to use it permanently, edit /etc/selinux/config

SELINUX=permissive

If you have to keep using SE linux, there is how-to in passenger documents. I followed these steps once, but failed. It kept showing errors that it did not have a permission to create directory or sock. I stopped a little quickly, casue it was not that important to me. Wish you have a good luck and share me if you success.

How to sign in with PIN-based twitter using oauth gem

Sunday, June 28th, 2009

Twitter uses PIN-based oauth to the desktop applications. This document explains how it works. All things are same as normal oauth procedure except PIN. After finishing authorization with Twitter, user sees 6 digit PIN number on twitter. This PIN number has to be included to the access_token request with oauth_verifier parameter to get access token successfully.

oauth gem  is a great tool if you inted to use oauth with ruby. This is how to send oauth_verifier parameter with oauth gem. Until version 0.3.4, oauth_verifier parameter has to be included in the body.

acc_token = req_token.get_access_token({}, {:oauth_verifier => pin})

From version 0.3.5, oauth gem has the oauth_verifier in oauth parameters. So the below is also possible.

acc_token = req_token.get_access_token({:oauth_verifier => pin})

Good luck!

Using delegate in ruby

Sunday, June 28th, 2009

As I have been wandering ruby gems and rails plugin codes, I found delegate is used in many ways. For example, in attachment_fu plugin, 

delegate :content_types, :to => Technoweenie::AttachmentFu

And in mechanize gem,

def_delegator :parser, :search, :search

Things to do with above codes are intuitive. In the first, if it receives content_types request, then invoke Technoweenie::AttachementFu.content_types. And in second one, if it receives search request, then invoke parser.search method. It is what the delegate normally do.

This is handy beacause it reduces the effort to declare an additional function like below.

def content_types
  Technoweenie::AttachmentFu.content_types
end

As you can see in the above examples, there are two ways in using delegate.(Maybe more than two in the whole ruby world, but now I am focusing what I’ve found.) One is using activesupport, and the other way is using Forwardable module in Ruby standard libray.

delegate in ActiveSupport

Activesupport extends Module and adds delegate method to it. Therefore just by requiring activesupport, you can use delegate method at any Class.

irb(main):001:0> Object.respond_to? :delegate
=> false
irb(main):002:0> require 'activesupport'
=> true
irb(main):003:0> Object.respond_to? :delegate
=> true

Using delegate is easy. Just two things are needed. method name to delegate and target object.

require 'activesupport'
class SayHello
  def greet(name)
    "hello #{name}"
  end
end

class Foo
  attr_accessor :greeter
  delegate :greet, :to => :greeter
end

foo = Foo.new
foo.greeter = SayHello.new
foo.greet("Tom")  # => "hello Tom"

After declaring delegate, Foo comes to have instance method ‘greet’, which forwards greet request to greeter. To make delegate smarter, two more options, prefix and allow_nil are possible.

A prefix option adds prefix word to the method. For example ,

class Foo
  attr_accessor :greeter
  delegate :greet, :to => :greeter, :prefix => :hello
end
foo.hello_greet("Tom")  # => "hello Tom"

If allow_nil option is true, delegate method returns nil instead of Exception, when delegate object is absent. For example,

class Foo
  attr_accessor :greeter
  delegate :greet, :to => :greeter, :allow_nil => true
end
foo = Foo.new
foo.greeter = nil
foo.greet("Tom")  # => nil

delegate in Forwardable module

If you are not a big fan of activesupport, you can use Forwardable module in standard ruby library. To do the same thing using forwardable is like below.

require "forwardable"
class SayHello
  def greet(name)
    "hello #{name}"
  end
end

class Foo
  extend Forwardable
  attr_accessor :greeter
  def_delegator :greeter, :greet
end

foo = Foo.new
foo.greeter = SayHello.new
foo.greet("Tom")  # => "hello Tom" 

The parameters of def_delegator is target_object, method, alias_method(optional). An alias_method is the name of method that Foo creates. If alias_method is absent, method is used instead. So In the above, Foo come to have greet method, which calls greeter.greet. Any other options are not allowed.

OpenStruct instead of Hash

Tuesday, June 23rd, 2009

Hash is a great way to represent the data structure. It is easily converted to xml, json and yaml. Activesupport extends core Hash class and enables these methods.

hash = Hash.from_xml(xml)
hash = Hash.from_json(xml)


Very convenient. But “[ ]” notation to access hash value is sometimes anoying. I want to use “.” operation instead of “[ ]”. One solution is to make a Class and use method_missing.

class Box
  def initialize(data)
    @data = data.symbolize_keys
  end
  def method_missing(method, *args)
    return @data[method] if @data.has_key?(method)
    super
  end
end
data = {"box" => {"name" => "creative", "user" => {"name" => "Tom"}}}
box = Box.new(data["box"])
box.name


It works, but needs many codes like Box class. OpenStruct is an easier way to do the same thing.

require 'ostruct'
data = {'box' => {"name" => "creative", "user" => {"name" => "Tom"}}}
box = OpenStruct.new(data["box"])
box.name


Internally OpenStruct uses hash. And it creates methods of hash’s key. For example box has methods “name” and “name=”.

box.public_methods.include? "name"
=> true
box.public_methods.include? "name="
=> true


One thing insufficient of OpenStruct is it only converts 1 dimension of hash. So the box.user.name is not possible.

box.user
=> {"name"=>"Tom"}


In order to use box.user.name, nested converting is needed.

def to_openstruct(hash)
  hash.inject(OpenStruct.new) { |result, (key, value)|
    value = to_openstruct(value) if value.is_a? Hash
    result.send("#{key}=", value)
    result
  }
end
data = {"box" => {"name" => "creative", "user" => {"name" => "Tom"}}}
box = to_openstruct(data["box"])
box.user.name