Ruby 1.9 Tips
February 7th, 2011 by pyratA runnable script to show off a lot of ruby 1.9 features. Hopefully this will increase the takeup of 1.9 as it is now stable enough to use for real in production.
Courtesy of igrigorik.
A runnable script to show off a lot of ruby 1.9 features. Hopefully this will increase the takeup of 1.9 as it is now stable enough to use for real in production.
Courtesy of igrigorik.

I needed a way of grabbing up to date currency information and an easy way to convert currencies using these up to date rates.
Normally this information requires signing up for premium accounts or writing web scrapers to do this for you.
It turns out that the European bank provides a daily xml for free that contains this information. It is specific to the euro but is fairly trivial to convert this into conversion rates for any of the other currency as the base currency.
gem install currency_ratesIt is a simple class for getting up to date currency rates in an array. You pass a base currency which the rates will be calculated against. Connects to the european bank to get a daily breakdown of the rates.
Deliberately lightweight to allow you to build on top of.
#!/usr/bin/env ruby require 'rubygems' require 'currency_rates' parser = CurrencyRates::Parser.new('GBP') puts parser.engage.inspect
Valid values for the initialize call are:
"USD" "JPY" "BGN" "CZK" "DKK" "EEK" "GBP" "HUF" "LTL" "LVL" "PLN" "RON" "SEK" "CHF" "NOK" "HRK" "RUB" "TRY" "AUD" "BRL" "CAD" "CNY" "HKD" "IDR" "INR" "KRW" "MXN" "MYR" "NZD" "PHP" "SGD" "THB" "ZAR" "EUR"
Example result array:
[{"currency"=>"USD", "rate"=>"1.58482"}, {"currency"=>"JPY", "rate"=>"130.28671"}, {"currency"=>"BGN", "rate"=>"2.26562"}, {"currency"=>"CZK", "rate"=>"28.05560"}, {"currency"=>"DKK", "rate"=>"8.63284"}, {"currency"=>"ILS", "rate"=>"5.71573"}, {"currency"=>"GBP", "rate"=>"1.00000"}, {"currency"=>"HUF", "rate"=>"317.67159"}, {"currency"=>"LTL", "rate"=>"3.99977"}, {"currency"=>"LVL", "rate"=>"0.81552"}, {"currency"=>"PLN", "rate"=>"4.48653"}, {"currency"=>"RON", "rate"=>"4.94642"}, {"currency"=>"SEK", "rate"=>"10.26180"}, {"currency"=>"CHF", "rate"=>"1.49690"}, {"currency"=>"NOK", "rate"=>"9.11266"}, {"currency"=>"HRK", "rate"=>"8.58558"}, {"currency"=>"RUB", "rate"=>"47.14741"}, {"currency"=>"TRY", "rate"=>"2.50090"}, {"currency"=>"AUD", "rate"=>"1.58992"}, {"currency"=>"BRL", "rate"=>"2.64361"}, {"currency"=>"CAD", "rate"=>"1.57892"}, {"currency"=>"CNY", "rate"=>"10.43290"}, {"currency"=>"HKD", "rate"=>"12.33837"}, {"currency"=>"IDR", "rate"=>"14323.90385"}, {"currency"=>"INR", "rate"=>"72.43649"}, {"currency"=>"KRW", "rate"=>"1770.37938"}, {"currency"=>"MXN", "rate"=>"19.10177"}, {"currency"=>"MYR", "rate"=>"4.83614"}, {"currency"=>"NZD", "rate"=>"2.06591"}, {"currency"=>"PHP", "rate"=>"70.20678"}, {"currency"=>"SGD", "rate"=>"2.02989"}, {"currency"=>"THB", "rate"=>"48.82247"}, {"currency"=>"ZAR", "rate"=>"11.25607"}, {"currency"=>"EUR", "rate"=>"1.15841"}]
Code is also available on github

A google server
Google storage was recently released as an open beta for developers to work with. All in all it seems like an Amazon S3 clone and whilst they have their REST api for integration they also support the Amazon S3 api.
Originally I was planning on writing a ruby wrapper of this api but decided to see if I could modify with existing aws-s3 gem to work with google storage.
After some in depth reading of both apis and understanding of the aws-s3 gem I have modified it to also work with google storage.
My github fork contains these modifications.
To use:
require 'aws/s3' include AWS::S3 Base.establish_connection!( :access_key_id => 'abc', :secret_access_key => '123', :default_host => "commondatastorage.googleapis.com" )
Note the addition of the default_host to connect to google instead of AWS.
From then on you can use the gem as normal as is described in the Readme.

You are not screwed
As git is distributed, the repository on your local machine is good enough to deploy from.
First start a git server on your local machine.
git daemon --base-path=/projects/rails_apps/ --export-allThen change your capistrano recipe to deploy with the copy command and change the repo to point to your local machine.
set :deploy_via, :copy set :repository 'git://127.0.0.1/proj_name'
Where in this example the code resides at */projects/rails_apps/proj_name*
Now capistrano will deploy from your local repository, thus avoiding the currently melted github
There are other options which have been listed by chris wanstrath in the following article

Google sitemaps are nice for telling google what is where. Often clients want it for SEO or you have a site which has new content all the time and you want to keep google up to date.
Whatever the reason is thats you are interested in these little xml files, the following code allows you to generate a sitemap for a dynamic site in ruby.
Firstly the class:
require 'net/http' require 'uri' # A class specific to the application which generates a google sitemap from # the contents of the database. # Author: Alastair Brunton class GoogleSitemapGenerator def initialize(base_url, sources) @base_url = base_url @sources = sources end # The main generator method which in turn adds to the path_array from the different # sources. # Sources are: pages, events, properties def generate path_ar = Array.new @sources.each do |source| # initialize the class and call the get_paths method on it. path_ar = path_ar + eval("#{source}.get_paths") end xml = generate_xml(path_ar) save_file(xml) update_google end # This creates the xml document. def generate_xml(path_ar) xml_str = "" xml = Builder::XmlMarkup.new(:target => xml_str) xml.instruct! xml.urlset(:xmlns=>'http://www.google.com/schemas/sitemap/0.84') { path_ar.each do |path| xml.url { xml.loc(@base_url + path[:url]) xml.lastmod(path[:last_mod]) xml.changefreq('weekly') } end } xml_str end # Saves the xml file to disc. This could also be used to ping the webmaster tools def save_file(xml) File.open(RAILS_ROOT + '/public/sitemap.xml', "w+") do |f| f.write(xml) end end # Notify google of the new sitemap def update_google sitemap_uri = @base_url + '/sitemap.xml' escaped_sitemap_uri = URI.escape(sitemap_uri) Net::HTTP.get('www.google.com', '/webmasters/sitemaps/ping?sitemap=' + escaped_sitemap_uri) end end
You will notice that an array of strings are passed when calling the generator. These are names of object which implement the get_paths method. An example get_paths class method is as follows:
# for the google sitemap def self.get_paths path_ar = Array.new Property.live_properties.each do |property| path_ar << {:url => "/property/#{property.to_param}", :last_mod => property.updated_at.strftime('%Y-%m-%d')} end path_ar end
Basically, you need an array of hashes which each contain the url and the last_mod.
To call this little beastie it is best done from a cron on the production server. An example rake task to do this is as follows:
namespace :google_sitemap do desc "Generate a google sitemap from the site." task(:generate => :environment) do sources = ['Page', 'Event', 'Property'] sitemap = GoogleSitemapGenerator.new('http://www.your_url.com', sources) sitemap.generate end end
Remember when you are calling it from a cron to pass the RAILS_ENV. This generator does rely on rails but you could convert it to only rely on ruby by modifying the rake task and changing the RAILS_ROOT reference in the save_file method. Probably can be made to work with Merb but I am unsure of how merb and rake work together. Will hopefully get my hands dirty with Merb sometime soon.
cd /var/www/apps/site/current /usr/bin/rake RAILS_ENV=production google_sitemap:generate

Throwing and catching exceptions can be a good design pattern in your rails app. Especially when you want to be able to deal with the unexpected in a clean way. Overuse is a no no as with most techniques but it is nice here and there.
I find that a good idea is to make your own exceptions when writing a rails app. This means that standard low level exceptions which often should not be caught are not.
eg.
(in config/initializers/custom_config.rb)
class ApplicationError < RuntimeError end
This way in your application code you can do something like the following
begin @user = User.find_by_password_reset_code(params[:id]) raise ApplicationError if @user.nil? rescue ApplicationError => msg flash[:message] = "Sorry - That is an invalid password reset code. Please check your code and try again. (Perhaps your email client inserted a carriage return?" redirect_to logins_url end
In other news I have started testing deploying with git and capistrano. It is blazingly fast even for a full checkout. I cant get :remote_cache deploy via working due to an ancient version of git on LTS 6.06. But event with full checkout this is faster than deploy_via :remote_cache with subversion! (This is with 2 slices on a local network.)
Watch out with Hpricot 0.6 it doesnt seem to work properly with setting attributes. Use 0.5 instead.
sudo gem install hpricot -v0.5

This is a plugin which I have developed internally with Iformis. I realised it would be nice to share yet another pdf plugin with the rails community. This is designed to render pdfs with layouts. This is especially useful when working on projects where the pdf has to conform to a set design and where the data is presented in a generate report style. Banks often operate their tools in this manner.
Simple PDF reporting rails plugin designed to render layout based pdfs. Built on top of Ruport which is in turn built on top of pdf-writer.
Requires Ruport
Step 1: Install Ruport
gem install ruportStep 2: Install the plugin (from RAILS_ROOT)
./script/plugin install http://iformis.svnrepository.com/svn/rupdfStep 3: Create a class extending Rupdf::Base
Note: The only methods calls required are define_header, define_body and define_footer
The other methods are helper methods. Remember that html doesnt work in the body.
class Simple < Rupdf::Base define_variables :report_title renders :pdf, :for => Rupdf::Renderer define_header do add_header(report_title) end define_body do add_text("hello man\n\n\n") add_text("bye man.") add_text("bye man.") add_text("hello man\n\n\n") add_text("bye man.") add_text("hello man\n\n\n") add_text("bye man.") # add image (path defined at runtime) image(smile_path) add_text("hello man\n\n\n") add_text("bye man.") add_text("hello man\n\n\n") end define_footer do footer_text = %( This is the beautiful footer text. ) add_footer(footer_text) end def add_header(title) rounded_text_box("<b>#{title}</b>") do |o| header_color = Color::RGB.from_html("#FFDE16") o.fill_color = header_color o.stroke_color = header_color o.radius = 0 o.width = options.header_width || 550 o.height = options.header_height || 80 o.font_size = options.header_font_size || 12 o.x = pdf_writer.absolute_right_margin - o.width o.y = pdf_writer.absolute_top_margin end end def add_footer(text, options = nil) unless options options = OpenStruct.new(:font_size => 6) end rounded_text_box(text) do |o| footer_color = Color::RGB.from_html("#EAECEE") o.fill_color = footer_color o.stroke_color = footer_color o.radius = 0 o.width = options.header_width || 550 o.height = options.header_height || 60 o.font_size = options.font_size || 12 o.x = pdf_writer.absolute_right_margin - o.width o.y = pdf_writer.absolute_bottom_margin + o.height end end end
Step 4: Tie it into a controller and pass the variables at runtime.
class TestController < ApplicationController def pdf simple = Simple.new pdf = Rupdf::Renderer.render_pdf do |o| o.report_title = 'This is a test of a var being passed.' o.smile_path = RAILS_ROOT + '/public/images/smile.jpg' end send_pdf(pdf) end end
The send_pdf function sends the rendered pdf to the browser.
If you need to perform more complicated pdf rendering operation
please refer to the API Documentation for pdf-writer. The API docs for
ruport will also be useful if you are involved in presenting tabular
data from activerecord.
Example programming code along with how to call it is supplied in the
examples directory. I suggest you use these as a base for pdf generation code you write.
This has been tested with both Rails 1.2 and Rails 2.01. Thanks for listening.

Recently signal vs noise showcased how their continuous integration tool posts to campfire using the tinder api. Well I thought that it looked cool so have thrown together a quick and dirty ruby script to do it as well.
It will integrate with anything which has an RSS feed. Cruisecontrol.rb does and works nicely. Its a bit rough around the edges but works as is. Run it from the crontab and you are sorted.
Improvements welcome.
Here is a screenshot of it posting on campfire:

Here is the code:
#!/usr/bin/ruby require 'rubygems' require 'active_record' require 'simple-rss' require 'open-uri' require 'tinder' include Tinder # Author: Alastair Brunton # http://www.simplyexcited.co.uk # Rss to Campfire campfire_domain = 'simplyexcited' campfire_email = 'cheer@cheerfactory.co.uk' campfire_password = 'xxxx' feeds = %w(http://feedserver/edflats.rss) ActiveRecord::Base.logger = Logger.new(STDERR) ActiveRecord::Base.colorize_logging = false ActiveRecord::Base.establish_connection( :adapter => "mysql", :username => "username", :password => "password", :database => "ci_to_campfire" ) # uncomment this section the first time to create the table # ActiveRecord::Schema.define do # create_table :items do |table| # table.column :feed_identifier, :string # table.column :title, :string # table.column :link, :string # table.column :description, :text # end # end class Item < ActiveRecord::Base def to_s %(Theres been some action:\n #{self.title}\n\n #{self.description} ) end end campfire = Campfire.new campfire_domain campfire.login campfire_email, campfire_password room = campfire.find_room_by_name('The Office') feeds.each do |rss_url| rss_user_agent = "RSS to Campfire" rss_items = SimpleRSS.parse open(rss_url ,"User-Agent" => rss_user_agent) for item in rss_items.items Item.transaction do unless existing_item = Item.find(:all, :conditions => ["link=? AND feed_identifier=?", item.link, rss_url]).first new_item = Item.create(:title => item.title, :link => item.link, :description => item.description, :feed_identifier => rss_url) room.paste(new_item.to_s) end end end end room.leave campfire.logout

After it taking me 6 months to be converted to TDD, the last 6 months I have been improving my skills in the TDD arena. After developing some fairly complex systems I was finding fixtures to be far too brittle and was relying on the ar_fixtures plugin to generate fixture data.
The thing is, this isn’t right. You shouldn’t be generating the fixtures from data created by untested code!
To get straight to the point, the following 2 techniques are great for fixture-less testing.
Use transactional fixtures.
In your test_helper.rb you should be extracting out all the object creation code so you can use it over multiple tests.
eg.
def create_property(options={}) Property.create!( { :title => 'Flat in Skye', :address_line_1 => 'Flat 2/1', :address_line_2 => '75 Old Dumbarton Road', :city => 'Glasgow', :postcode => 'G3 8RG', :short_description => 'Great flat in Yorkhill', :full_description => 'This is in a great location and has top views of the university', :min_people => 1, :max_people => 10, :tag_list => "Dishwasher\nTV", :landlord_id => 1, :is_live => true }.merge(options) ) end
This is great because in my tests I can run something like.
def test_special_dom p = create_property(:is_live => false) special_dom = p.special_dom('new') assert_not_nil(special_dom) assert_equal("property_#{p.id}_new", special_dom) end
This means that the setup and tear down for this test is done in the test method meaning I know exactly what I am working with. If you look at line 2 you will see you I can override the default options in the test method by passing a hash of new options. This is the beauty of the merge method in test_helper.rb
This is not only great for logging in users, but it also is great for running the data creation methods before you run the test and make all your test methods a little more DRY.
eg.
def setup @controller = LandlordAjaxController.new @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new @property = create_property login_landlord end
Now all my tests in this functional test can use the @property object.
eg.
def test_back_calendar xhr :post, :back_calendar, :property_id => @property.id, :month_date => Date.today.to_s(:db) assert_rjs :replace_html, 'months' end
Thats all for now. If you have any more tips for fixtureless testing please add them to the comments.