Archive for the 'Rails' Category

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

15 Top Tools for a Rails Programmer

February 12th, 2008 by pyrat

toolbox

Here are my top 15 tools for rails development. I felt compelled to compose a list in an effort to help rails developers that are just starting out.

1. A mac / linux machine for development. (macports and apt-get are your friends.), with Rails 2 and Mongrel by Zed Shaw. I have not bothered to include all the individual tools on your development platform but recommend setting it up yourself as you will learn a lot and will end up with a setup which is tailored to your needs. Info for mac , Info on the rails wiki

2. A healthy collection of feeds. Ruby Inside, Err the Blog, Nubyonrails, and Riding rails. Also, new age feeds such as ruby on rails podcast, railscasts, rails envy podcast are top. Also, the peepcodes are all pretty good and if you want to learn about something in detail which is fairly meaty, they are worth the reasonable price.

3. A great editor you know inside out. The main reason I use a mac is because of the existance of textmate. Otherwise I would still be on windows. The book is a lifesaver, also this cheatsheet is very useful.

4. A shell. Instead of spending time with bloated IDE’s which take a year to load an use more RAM than a I eat waffles, spend time getting familiar with the shell. BASH is my favourite and is by far the most common. You should become familiar with the rails console commands, command line mysql, command line source control and command line filesystem commands. These take a bit of time to get used to, but are often the fastest way to do things. BASH aliases are MASSIVE time saver. Have a look at my ALIASES for some inspiration. Load them in with the following in your .bashprofile_:

  # Get the aliases and functions
  if [ -f ~/scripts/.personal_profile ]; then
          . ~/scripts/.personal_profile
  fi

5. An automated deployment tool, this should really be “one-click” stuff. Capistrano is the king of the hill, with projects like Vlad the deployer following in its wake. Try and get your deployment recipies into a plugin. Here is a plugin which includes a collection of recipies (including an update basecamp recipe) which I use in my projects at iformis. deployment_recipiez

6. Sysadmin skills. At the moment rails on shared hosting aint the best. Hopefully projects like mod_rubinius will sort that. You need to be able to install a rails stack from the ground up and deploy to it. Things like backups, database setup, process monitoring etc should be familiar. I was hoping to try Ruby Works the rails stack provided by Thoughtworks. If you have had the chance please let me know what its like. In terms of cheap dedicated hosting which is reliable I recommend slicehost. If you are not yet familiar with systems administration I recommend reading a few books on the subject as it isnt something you can learn overnight. Also a good tip is to maintain a collection of scripts which you keep in source control which you can easily checkout on the different servers that you work. It is essentially a mobile server toolkit which is customized to your needs.

7. Testing is very important for your rails application. This will save time in the future. I wont harp on about the many benefits of testing but point you to TDD and BDD.

8. Zentest especially autotest which runs your tests automatically each time you save a relevant file. Linking in the Growl notifications You might also want to get this working with Rspec. If you do then.. this might be of interest.

9. Know which are the top good quality plugins out there. Keep an eye on the rss for agilwebdevelopment.com for what is new and hot. For established plugins, I recommend will_paginate, exception_notification, attachment_fu, restful_authentication and arts. I am sure you know of some more goodies!

10. Monit is a great tool for your production servers. After it is properly configured it just keeps stuff up for you and can warn you if you experience high load. It also means that if your mongrels crash for any reason you can sleep soundly in the knowledge that they will be started for you. There is an example monit configuration which you can start from if you like.

11. Firefox/Firebug: Often referred to as ruby’s ugly-cool sister, Javascript is used a lot in rails apps. You should use firebug to debug errors with AJAX calls rather than having to dig through the server logs to try and find what you are looking for! This saves a LOT of time and other features such as the CSS inspector are top notch. As a side, also thing about other firefox plugins such as LiveHTTPHeaders which are shit hot also.

12. HAML : You either love it or you hate it. I love it, well, I like it. For developing templates really quickly this is an amazing tool. However, you have to watch out for long files as it can look ugly if you are doing too much in a template. This forces you to use more partials and helper methods. These are good design practices.

13. Continuous Integration is a great technique which makes sure you dont have any broken windows in your project. Often testing on a development machine is tied too much to the environment on the development machine. Often Davey, your designer will delete an erb tag by mistake. CI involves automatically building the project after each subversion commit. Cruisecontrol.rb is your tool of choice here. As the number of projects you have configured with it grows, make sure your server has quite a bit of RAM. You can also integrate this with campfire and basecamp to use these tools as a centre to the development in your work environment.

14. SCM should probably be higher up the list but it isnt. This is because it is rubbish. No only joking, you are not one of the ‘enlightened’ if you dont use this on all your projects which have more than one file! Subversion is the defacto standard but there is a new kid on the block called Git which is really good for open source projects but maybe isnt so good for Davey the designer.

15. Javascript is a core element of rails application development. You should have a working knowledge of the language itself. You should also know what can be done with prototype and scriptaculous the javascript frameworks which ship with rails. The downside of these frameworks is the overhead. There are some fanboys of jquery which is more lightweight and apparently can do the same I recommend you check out jquery but knowledge of prototype and scriptaculous is a must. Have a look at the book on prototype or the peepcode screencast for further info.

I hope this helps you get an overview of all the main tools and sources of information in the rails development space. If you would like to point out any glaring errors or give me a pat on the back please do so in the comments.

Inline Button To

January 18th, 2008 by pyrat

buttons

Happy New Year. I have been a bit quiet of late as I have moved to Trondheim, Norway and am getting to grips with everything here.

Every wanted to use the button_to helper and need it to display items on the same name as the button?

Like this :

buttons

If you are running rails2 slap this in your config/initializers directory.

 
 module ActionView
    module Helpers
      module UrlHelper
 
        def inline_button_to(name, options = {}, html_options = {})
 
          html_options = html_options.stringify_keys
          convert_boolean_attributes!(html_options, %w( disabled ))
 
          if(other_code = html_options.delete('inline'))
            other_code = " or " + other_code
          end
 
          method_tag = ''
          if (method = html_options.delete('method')) && %w{put delete}.include?(method.to_s)
            method_tag = tag('input', :type => 'hidden', :name => '_method', :value => method.to_s)
          end
 
          form_method = method.to_s == 'post' ? 'post' : 'get'
 
          request_token_tag = ''
          if form_method == 'post' && protect_against_forgery?
            request_token_tag = tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token)
          end
 
          if confirm = html_options.delete("confirm")
            html_options["onclick"] = "return #{confirm_javascript_function(confirm)};"
          end
 
          url = options.is_a?(String) ? options : self.url_for(options)
          name ||= url
 
          html_options.merge!("type" => "submit", "value" => name)
 
 
 
          "<form method=\"#{form_method}\" action=\"#{escape_once url}\" class=\"button-to\"><div>" +
          method_tag + tag("input", html_options) + request_token_tag + other_code + "</div></form>"
        end
 
      end
    end
  end

The you can call:

<%= inline_button_to 'title', url, {:inline => 'what you want in here'}%>

Cheers.

FAQ_U Rails FAQ Generator Plugin

December 20th, 2007 by pyrat

Faq u

For a christmas present I give you the Rails FAQ Generator plugin FAQ_U.

This is a simple layout-less unbranded Frequently Asked Questions system. The idea is you generate the default system and then customise it to the requirements of the particular project that you are working on.

Remember to add some authentication to the faq_admin controller.

Requires HAML. To install it:

gem install --no-ri haml
haml --rails path/to/app

To install the plugin:

./script/plugin install http://vorlich.ath.cx/code/excited/plugins/faq_u

To run the generator:

./script/generate faq

This generates a FAQ admin controller, FAQ view controller, the necessary views, a model, db migration and a collection of
tests.

After the generator is complete run the migration to get the database up to scratch.

rake db:migrate

The faq admin is available at */faq_admin* and the front end at */faq*. As you can see below it looks pretty filthy, but as you can see below that, with the introduction of a layout it looks nicer.

unstyled

styled

A would be nice would be a nice AJAX ordering interface in the faq_admin. Feel free to send me some svn patches of any improvements you make. Please write some tests to prove the new functionality works. Cheers now, Merry Christmas.

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.

Attachment Fu Validates As Attachment Hack

November 28th, 2007 by pyrat

Attachment Fu Hack

Recent err the blog posted about the my evil twin hack technique

In this post they detail how to hack plugins nicely and provide an example on how to hack attachment_fu. It turns out that I have been spending quite a bit of time with attachment_fu on various projects recently and have started to need it to do slightly different things.

Below is a hack I have written to improve validates_as_attachment. Using the evil twin technique this will just ask for a file to be uploaded if you dont upload anything. Instead of spewing out a whole load of errors that dont help the user much.

klass = Technoweenie::AttachmentFu::ClassMethods
klass.module_eval do
 
def validates_as_attachment
validate :uploaded_data_is_present
validates_presence_of :size, :content_type, :filename, :if => :uploaded_data?
validate              :attachment_attributes_valid?
end
 
end
 
klazz = Technoweenie::AttachmentFu::InstanceMethods
klazz.module_eval do
def uploaded_data?
  return false unless filename
  true
end
 
# validates the size and content_type attributes according to the current model's options
def attachment_attributes_valid?
if uploaded_data?
[:size, :content_type].each do |attr_name|
enum = attachment_options[attr_name]
errors.add attr_name, ActiveRecord::Errors.default_error_messages[:inclusion] unless enum.nil? || enum.include?(send(attr_name))
end
end
end
 
def uploaded_data_is_present
unless uploaded_data?
errors.add_to_base("You must select a file to upload.")
end
end
 
end

This serves as a drop in replacement for validates_as_attachment

Installing Nginx on Ubuntu Dapper (LTS 6.06)

November 15th, 2007 by pyrat

There is a great slicehost article which goes into more detail than this. But the following script will install it from source in one go!.

#!/bin/bash
 
sudo apt-get install libpcre3 libpcre3-dev libpcrecpp0 libssl-dev
wget http://sysoev.ru/nginx/nginx-0.5.33.tar.gz
tar -zxvf nginx-0.5.33.tar.gz
cd nginx-0.5.33
./configure --sbin-path=/usr/local/sbin --with-http_ssl_module
make
sudo make install
cd ~
cd sources
sudo wget http://notrocketsurgery.com/files/nginx -O /etc/init.d/nginx
sudo chmod 755 /etc/init.d/nginx
sudo update-rc.d nginx defaults

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.

Checkbox Select Rails Plugin

November 5th, 2007 by pyrat

checkbox

I had the problem of there not being a simple form rendering solution for has_and_belongs_to_many ActiveRecord relations. I found a partial solution here: http://railshacks.blogspot.com/2007/09/helper-checkboxcollection.html

I have modified the original code slightly, and extracted it to a plugin. To install it:

  ./script/plugin install http://vorlich.ath.cx/code/excited/plugins/checkbox_select

In your model you are manipulating in the form:

  class Advertisement < ActiveRecord::Base
 
    include CheckboxSelectable
    has_and_belongs_to_many :categories
    validates_presence_of :name
    validates_uniqueness_of :name
 
  end

The include is the important bit.

In your view:

  <%= checkbox_select('advertisement', 'categories', @categories, 'name') %>

In your controller add the update_check_list method:

  def create
    @advertisement = Advertisement.new(params[:advertisement])
    @advertisement.update_check_list(params, 'Category')
 
    respond_to do |format|
      if @advertisement.save
        flash[:message] = 'Advertisement was successfully created.'
        format.html { redirect_to advertisements_url }
        format.xml  { head :created, :location => advertisement_url(@advertisement) }
      else
        assigns
        format.html { render :action => "new" }
        format.xml  { render :xml => @advertisement.errors.to_xml }
      end
    end
  end

I still need to write a collection of tests for this plugin. If you have the time please write them for me!