twilio_mime: A Twilio TwiML mime type plugin for Rails

Twilio is a great telephony in the cloud service that I've been playing around with recently. It allows you to really easily build telephone applications using your existing web site, by simply making http requests to the server and then interpreting the responses, which should come back as TwiML (essentially an XML resonse). Since Rails provides a great mechanism for handling mime types with respond_to I've created a simple plugin to add a TwiML mime type response so that you can use it just like you'd use any other mime type.

It can be found here http://github.com/dalecook/twilio-mime

Using class_eval And instance_eval To Extend a Class Inside Itself

I was reading Dynamically Creating Class and Instance Methods in Ruby which posits the question; Why does this

class ExtendMe
  def self.extend_it
    class_eval "def added_by_class_eval; end"
    instance_eval "def added_by_instance_eval; end"
  end
end


ExtendMe.extend_it

Cause this:

 >> ExtendMe.respond_to? :added_by_class_eval
 => false
 >> ExtendMe.respond_to? :added_by_instance_eval
 => true

In other words why does class_eval create an instance method and instance_eval create a class method?

They said they'd answer it in a later post but never did, so here it goes.

Seems confusing right? Well, it's got a lot to do with understanding what happens to the current class definition when these functions are called. When you call class_eval it sets the current class to be the class it is evaluating against (in this case ExtendMe), while instance_eval changes the current class to the eigenclass of the reciever, so in this case it's the eigenclass of ExtendMe.
When I call class_eval the class remains ExtendMe, hence this would be the same as

class ExtendMe  
  def added_by_class_eval  
  end
end

which gives us our instance method. When instance_eval is called it's evaluated against the eigenclass of ExtendMe. Instance methods of the eigenclass are class methods of the class, so this is equivalent to

class ExtendMe  
  def self.added_by_instance_eval
  end
end

Easy, no?

Changing Authlogic's Login Field

I've recently been playing around with Authlogic (API), and it does a great job of handling a lot of things for you automatically, including the field used for the username. In fact all you need to do is have a field called login or username in the table of the object you're authorizing against and, hey presto, Authlogic assumes it's the username for your user and takes care of it. But what if you want to use an alternative field, let's say "screen_name". 

Let's say you have an object User that you're authenicating against. The migration might look like this

  def self.up
    create_table :users do |t|

      t.string    :screen_name,          :null => false
      t.string    :email,               :null => false
      t.string    :first_name,          :null => false
      t.string    :last_name,           :null => false
      t.string    :crypted_password,    :null => false
      t.string    :password_salt,       :null => false
      t.string    :persistence_token,   :null => false
      t.string    :single_access_token, :null => false
      t.string    :perishable_token,    :null => false      
      t.timestamps
    end

and you app/models/user.rb might look like this

class User < ActiveRecord::Base
  
  acts_as_authentic

end

To make sure Authlogic uses :screen_name as the username field change user.rb to

class User < ActiveRecord::Base
  
  acts_as_authentic do |c|
    c.login_field = :screen_name
  end

end

Easy. the field "screen_name" can now be used to log in users and Authlogic will make sure it's unique and valid.

Now, I don't like the default validations for the username, it allows spaces and other symbols and it's anywhere from 3 to 100 characters. I want to have only letters and numbers and screen names between 5 and 20 characters. Once again change user.rb to

class User < ActiveRecord::Base
  
  acts_as_authentic do |c|
    c.login_field = :screen_name
    c.validates_format_of_login_field_options = {:with => /^[a-zA-Z0-9]+$/, :message => I18n.t('error_messages.login_invalid', :default => "should use only letters and numbers, no other characters.")} 
    c.validates_length_of_login_field_options = {:within => 5..20}
  end
  
end

validates_format_of_login_field_options allows us to tell Authlogic how to validate the contents of "screen_name". In this case it's letters and number only. If it;s not valid it'll throw a login_invalid error with the message "should use only letters and numbers, no other characters.". For more options see the Rails validates_format_of API.

validates_length_of_login_field_options tells Authlogic how many characters the field should contain (in this case between 5 and 20). For more options see the Rails validates_length_of API.

Once again, nice and easy. Thanks Authlogic.

Working with Chronic and Time-zones

Chronic is an amazing gem. It allows you to do things like take "tomorrow at 10" and get an actual date and time from it. It's easy to use

>> require 'chronic'
=> []
>> Chronic.parse("tomorrow at 10")
=> Thu Nov 12 10:00:00 -0800 2009

This essentially allows you to process free-form text into Time objects. However, there is one big gotcha if you're using time-zones that I recently had some trouble with.
Chronic calculates the time in the local time-zone - thats the machines time-zone, not the time-zone set as the default in the environment.rb file using

config.time_zone = 'UTC'

nor the time-zone that you explicitly set using

Time.zone = 'Tokyo'

Let's say your machine (your development laptop let's say) is on Pacific US time. If we do this

>> Time.zone = 'Tokyo'
>> Chronic.parse("tomorrow at 10am")

we get Thu Nov 12 10:00:00 -0800 2009 (assuming tomorrow is November 12th 2009). This is the time formatted for the local time-zone, Pacific US time, not the one for Tokyo as you might expect - whoops. This can be especially confusing/fustrating if your development machines and production machines doens't share the same local time. Everything could look correct during development because the local time and your default time-zone are the same, let's say Pacific US - great everything looks good, your times are coming out correct. But when you move to production your local time in production is UTC and now your times are off by 8 hours and you don't know why.

OK, now that we know it's a problem, how do we fix it?

>> Time.zone = 'Tokyo'
>> my_time = Chronic.parse("tomorrow at 10am")
>> real_time = Time.zone.parse(my_time.strftime("%Y-%m-%d %H:%M:%S"))

which will give us Thu, 12 Nov 2009 10:00:00 JST +09:00 which is the time correctly formatted for Tokyo, 9 hours ahead of UTC time. So unfortunately we need to do an extra conversion step, but it's a small price to pay to get the power of Chronic.

EDIT: Thanks to Cuth for pointing this out. This is not in the Chronic RDoc here, but is mentioned in the Git readme here.

You can also set the time zone explicitly like this

>> Chronic.time_class = Time.zone

So now, if we use the example above, where the time zone is Tokyo, Chronic.parse("tomorrow at 10am") should now correctly return Thu, 12 Nov 2009 10:00:00 JST +09:00