Ruby Time marshaling bug in pre-1.9 0

Posted by luca
on Friday, August 29

Ruby's Time has a bug: when try to serialize an timezoned time, then unserialize it back, the result will use the server local time, instead of use the original time zone.

>> utc = Time.now.utc
=> Fri Aug 29 09:07:37 UTC 2008
>> marshaled = Marshal.dump utc
=> "\004\bu:\tTime\r\251\037\e\200\344\254T\036"
>> Marshal.load marshaled
=> Fri Aug 29 11:07:37 +0200 2008

This bug doesn't affects Ruby 1.9, but we still don't use that version for production purpose. If you use 1.8.x with Rails 2.1.0, your cached timestamps (including ActiveRecord objects), are probably wrong.

>> comment = Comment.first
=> #<Comment id: 865423346, post_id: 1, text: "Nice post.", created_at: "2008-08-29 09:27:48", updated_at: "2008-08-29 09:27:48">
>> Rails.cache.write('comment', comment)
=> true
>> Rails.cache.read('comment')

=> #<Comment id: 865423346, post_id: 1, text: "Nice post.", created_at: "2008-08-29 09:27:48", updated_at: "2008-08-29 09:27:48">
>> comment.update_attributes :text => "Nice post!"
=> true
>> Rails.cache.write('comment', comment)
=> true
>> Rails.cache.read('comment')
=> #<Comment id: 865423346, post_id: 1, text: "Nice post!", created_at: "2008-08-29 09:27:48", updated_at: "2008-08-29 11:28:42">


Look at the last updated_at attribute, it uses local time instead of UTC time zone.
The first time everything goes right, because #updated_at wasn't invoked and casted to a Time instance. It's a string, and the marshaling is ok.
But, when I update the object, ActiveRecord changes the value of that timestamp, but before, it cast it to a Time, and everything goes wrong.

I submitted a patch for this bug, it was committed, and will be available in the upcoming Rails 2.2. You are strongly encouraged to update your Rails version.

What's New In Rails Edge: i18n 1

Posted by luca
on Friday, July 18

Ruby on Rails has just integrated a basic support for i18n.

ActiveSupport

ActiveSupport now includes the i18n gem which provides the API and the settings for the default locale: en-US.
The gem abstracts the repository where the translations are stored, so all the plugin authors could write their own mechanism. The bundled repository is called Simple and stores all the settings in memory.

Declaring a locale is quite easy:

I18n.backend.store_translations :'it-IT', {
  :support => {
    :array => {
      :sentence_connector => 'e'
    }
  },
  :date => {
    :formats => {
      :default => "%d/%m/%Y",
      :short => "%d %b",
      :long => "%d %B %Y",
    },
    :day_names => %w(Luned&igrave; Marted&igrave; Mercoled&igrave; Gioved&igrave; Venerd&igrave; Sabato Domenica),
    :abbr_day_names => %w(Lun Mar Mer Gio Ven Sab Dom),
    :month_names => %w(Gennaio Febbraio Marzo Aprile Maggio Giugno Luglio Agosto Settembre Ottobre Novembre Dicembre).unshift(nil),
    :abbr_month_names => %w(Gen Feb Mar Apr Mag Giu Lug Ago Set Ott Nov Dic).unshift(nil),
    :order => [:day, :month, :year]
  },
  :time => {
    :formats => {
      :default => "%a, %d %b %Y %H:%M:%S %z",
      :short => "%d %b %H:%M",
      :long => "%B %d, %Y %H:%M",
    },
    :am => 'am',
    :pm => 'pm'
  }
}

How can I translate or localize?

I18n.locale = 'it-IT'
I18n.t :hello   # => Ciao
I18n.l Time.now # => "Ven, 18 Lug 2008 10:58:14 +0200"

I18n#t is also useful to fetch locale defaults:
I18n.t :'time.formats.short' # => %d %b %H:%M

ActiveRecord

ActiveRecord now returns localized error messages for validations.

You may wish to declare your messages:

I18n.backend.store_translations :'it-IT', {
  :active_record => {
    :error_messages => {
      :inclusion => "non &egrave; incluso nella lista"
      # ...
    }
  }
}

ActionPack

ActionView now supports translations and localization for time and currency helpers (i.e. distance_of_time_in_words, number_to_currency).


UPDATE 2008-07-19: Sven Fuchs wrote a i18n Rails manifesto and a technical post about the i18n API.

Ruby on Rails: Test Model Domain Changes 0

Posted by luca
on Monday, April 28
You know how tests are fundamental for a well-developed project, for this reason we should create step-by-step a net of test cases. Of course we also should be able to change rapidly our tests as the same we do with our code. Ruby on Rails is a great framework, because its shortcuts, the wide usage of DSL etc.. All this stuff can save a lot of time, but what about tests? Are we really able to follow our code?

Create and Destroy

ActiveSupport provides few useful tools to improve our test, I really appreciate assert_difference and assert_no_difference. Basically, this two methods accepts as arguments a code chunk (as string) and a block. When the test run, it binds the block first, then it assert if the changements caused by the block call are the same expected by first argument.
def test_should_be_created
  assert_difference 'Person.count' do
    create_person
  end
end
We are testing a Person creation, we pass as first argument 'Person.count', and the code that should correctly save the person. If the model will be saved, a new record should exists into the database table. At this moment assert_difference evaluates the first argument, and assert if there are differences in the Person count.
def test_should_be_destroyed
  assert_difference 'Person.count', -1 do
    destroy_person
  end
end
This example is just a bit different, we are also passing a Fixnum as argument. This because we want assert another difference from the default one, which is +1. So, if the model will be correctly saved, we will have a negative difference, of one, into the Person count.
def test_should_require_first_name_on_create
  assert_no_difference 'Person.count' do
    create_person
    assert person.errors.on(:first_name)
  end
end
The third example uses assert_no_difference, to test aganist model validations. ActiveRecord, by default, prevents the creation of a model if a validation doesn't pass. In this case our model requires first_name as mandatory attribute, but unfortunately it's nil, so the creation fails and the brand new record will be not created.

Update

As you can see, those two methods are very useful for test creation and destruction of models, but totally missing the goal of the update. In fact, the update process of a record, doesn't produces numerical differences. I created two methods to supply this lack.
def assert_updated(model, message = nil, &block) 
  yield
  assert_not_equal model.attributes, model.reload.attributes, message
end
def assert_not_updated(model, message = nil, &block)
  yield
  assert_equal model.attributes, model.reload.attributes, message
end
Just add them to your test/test_helper.rb, and they will be available in all your test cases.
def test_should_update
  assert_updated person do
    update_person
  end
end
First, you should notice that the first argument it isn't a string but an ActiveRecord. The behaviour of this method is similar to the previous I illustrated, it first bind the block, then assert if the attributes of the model are different. It internally uses ActiveRecord::Base#attributes which returns an hash of model attributes, then assert the differences with Ruby's assert_not_equal.
def test_should_require_first_name_on_update
  assert_not_updated person do
    update_person
    assert person.errors.on(:first_name)
  end
end
Similarly to all other examples, it first performs the block, call then assert there are no changes in the model attributes.

Conclusion

Those methods should provide a rapid way to write and mantain your test cases. If you enjoyed this post, feel free to recommend me on Working With Rails.

Rails: How To Create Custom Validations 0

Posted by luca
on Friday, December 21

Often our model objects leaning toward to be confused or noisy, due to validations DSLs. Imagine a class Answer, with an attribute, that should be exactly a string representation of a boolean. Ok, I know it's an odd example, but: it's trivial enough to make this example clear, and.. It happened to me to deal with this situation. :-P

class Answer < ActiveRecord::Base
  validates_inclusion_of :value, :in => %w( true false ),
                         :message => "Should be exactly true or false."
end

Now, we try to clean-up a bit this code.
First, create a file named validations.rb into lib, then copy and paste this code:

module ActiveRecord
  module Validations
    module ClassMethods
      @@boolean_values = %w( true false )
      @@validates_boolean_msg = "Should be exactly #{@@boolean_values.join(' or ')}."

      # Check if the value is a boolean: <tt>true</tt> or <tt>false</tt>.
      def validates_boolean(*attr_names)
        configuration = { :message   => @@validates_boolean_msg,
                          :in        => @@boolean_values }

       configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
       validates_inclusion_of attr_names, configuration
      end
    end
  end
end

Then we are going to add the following line at the end of environment.rb
require 'validations'

Let's clean the code:

class Answer < ActiveRecord::Base
  validates_boolean :value
end

Is it better? Maybe.. ;-)

Acts As Resource: Rails 2.0 Ready 0

Posted by luca
on Sunday, December 09

Acts As Resource is ready for Rails 2.0!!

This plugin combines both ActiveRecord and ActiveResource features in one class. It easily allows to deal with a remote REST service or with a local database.

If you want read more visit the plugin page or install with:

$ ./script/plugin install http://dev.23labs.net/svn/rails/plugins/acts_as_resource

Acts As Resource: Combining ActiveRecord and ActiveResource

Posted by luca
on Wednesday, November 28

Would you use both ActiveRecord and ActiveResource in one class?
Now with Acts As Resource you can!!

Example

About

You can find many informations about Acts As Resource on related page on my blog.

Vote

If you find it useful feel free to add to your favs on agilewebdevelopment.com

Acts As Resource 0

Posted by luca
on Wednesday, November 28

Acts As Resource

ActsAsResource combines ActiveRecord and ActiveResource features in one class.

Example

class Carrot
  acts_as_resource
  self.site = 'http://localhost:3000'
  
  belongs_to :bunny

  validates_presence_of :color
  validates_uniqueness_of :color
  validates_length_of :color, :within => 2..23,
                      :if => lambda { |c| c.color && !c.color.empty? }
  validates_format_of :color,
                      :with => /[\w\s]+$/,
                      :if => lambda { |c| c.color && !c.color.empty? }

  before_create :please_call_me_before_create
  def self.validate
    logger.debug("VALIDATE #{color}")
  end
  
  def please_call_me_before_create
    logger.debug("Ohhh, so you called me..")
  end  
end

Usage

For convention each operation is local unless specified with :remote flag.

carrot = Carrot.find(23)                   # => local request
carrot = Carrot.find(23, :remote => false) # => local request
carrot = Carrot.find(23, :remote => true)  # => remote request

Carrot.create(:color => 'orange')                  # => create local
Carrot.create(:color => 'orange', :remote => true) # => create remote

Prerequisites

For 2.0 railists: no problem guys!
For old 1.2.x people you should live in the edge, cause ActiveResource is needed.

Install

Rails 2.1.x

$ ./script/plugin install git://github.com/jodosha/acts-as-resource.git
Rails <= 2.0.x
$ ./script/plugin install http://dev.23labs.net/svn/rails/plugins/acts_as_resource

Uninstall

$ ./script/plugin remove acts_as_resource

Common Issues

  • Actually ActsAsResource doesn't support Single Table Inheritance.
  • Sometimes, when class relations are used, ActiveRecord generates a warning like:
    warning: toplevel constant Bunny referenced by Carrot::Bunny

Contribute

  • Check out the code and test it:
    $ git clone git://github.com/jodosha/acts-as-resource.git
        $ rake resource
  • Create a ticket on: http://dev.23labs.net/rails/trac.cgi
  • Create a patch and add as attachement to the ticket.
  • The SVN repo has been deprecated in favor of the Git one.

Repository

git://github.com/jodosha/acts-as-resource.git

Vote

If you find it useful feel free to add to your favs on agilewebdevelopment.com.

Copyright

Copyright © 2007 Luca Guidi - 23 Labs, released under the MIT license