Archive for the 'Ruby' Category

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.

Upgrading Ruby on Ubuntu Dapper

November 2nd, 2007 by pyrat

dapper

Ubuntu Dapper will only install ruby as high as 1.8.4 which is not ideal sometimes. To upgrade an existing ruby installation to 1.8.6 do the following.

  sudo apt-get install build-essential
sudo apt-get install libreadline5 libreadline5-dev
wget ftp://ftp.ruby-lang.org/pub/ruby/1.8/ruby-1.8.6.tar.gz
tar -xzf ruby-1.8.6.tar.gz
cd ruby-1.8.6
./configure --prefix=/usr/local --enable-pthread --with-readline-dir=/usr/local
make
sudo make install

Even if you had rubygems you need to install it and all your gems! Again.

   wget http://rubyforge.org/frs/download.php/35283/rubygems-1.1.1.tgz
tar -xzf rubygems-1.1.1.tgz
cd rubygems-1.1.1
sudo ruby setup.rb

Now this is where you need to do a little fix. Openssl will not work out of the box and some application need it so you should just set it up now.

Note: If apt-get libssl-dev complains about a not found run: sudo apt-get update

  sudo apt-get install libopenssl-ruby
sudo apt-get install libssl-dev
 
cd ~/src/ruby-1.8.6/ext/openssl
ruby extconf.rb
make
sudo make install

And thats it!

Twitter for SMS Notification

October 10th, 2007 by pyrat

Twitter

This is more of an idea than something I have needed yet but I don’t see why it wouldn’t work.

  • Open a twitter account if you haven’t already got one.
  • Open another twitter account and call it something_notifier or something fun like that.
  • Setup your mobile on the first account and set it up to follow the notifier account and receive SMS notifications when it updates.
  • Test this.
  • Now whenever the notifier twitter account it updated you will receive a text with the contents of the update.

For example, the next step could be to install a twitter API implementation on your servers then update twitter if something catastrophic happens. Remember to alter the privacy settings and set it so that only your followers can see your updates.

But I’m sure there are loads of uses for this; any ideas? Although SMS messaging from the internet is easy in the USA, its a lot more complicated in the UK and Europe.

Apache Configuration Tool

September 28th, 2007 by pyrat

apache-gunship-flickr

I have been doing a bit of sysadmin stuff with iformis recently which has involved a bit of virtual host configuration for a number of sites. Although Nginx is top for rails, apache is still the king of PHP.

We use apache in a debian / ubuntu environment so I have gone and written a wee gem to make vhost configuration a bit easier.

1. Install apacheconf


sudo gem install apacheconf

2. Setup YAML Config


site_name: fun
server_name: fun.com
server_alias: www.fun.com
document_root: /var/www/apps/fun

3. Run apacheconf


apacheconf --enable fun.yml

4. There is no step 4!

Apacheconf

Please contact me with any issues and ideas for improvements.