Archive for the 'Ruby' Category

Ruby 1.9 Tips

February 7th, 2011 by pyrat

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.

Currency Rates Library

January 26th, 2011 by pyrat

The Rub

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.

Currency Rates is born

gem install currency_rates

It 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.

Example

#!/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

Google Storage Interoperability With Amazon S3

June 27th, 2010 by pyrat


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.

Deploy when github goes down

November 27th, 2008 by pyrat


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-all

Then 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 Sitemap Generator

February 26th, 2008 by pyrat

maps are good

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

Throw your own exceptions

February 24th, 2008 by pyrat

exceptions

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.)

Hpricot 0.6 Caveat Setting Attributes

February 6th, 2008 by pyrat

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

Rupdf – Simple Ruby PDF Rails Plugin

December 15th, 2007 by pyrat

Rupdf

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 ruport

Step 2: Install the plugin (from RAILS_ROOT)

./script/plugin install http://iformis.svnrepository.com/svn/rupdf

Step 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.

Tinder Continuous Integration to Campfire Script

November 30th, 2007 by pyrat

Campfire

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:

Ci Grab

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

2 techniques for rails testing without fixtures

November 13th, 2007 by pyrat

fixtures

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.

1. Have a phat test_helper.rb

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

2. Use the setup method in both unit and functional tests.

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.