Informatika Mihelac

Articles tagged with rails

June 20 2007 rails

Now on Mephisto

Hello! Blog was down for almost a week. Sorry for that. Spam comments flooded all over the blog and I have to delete up to 50 comments a day. Suddenly blog stopped working, I am not sure what was the reason, nothing in rails log file, nothing in fast cgi log. It seemed to much work just to get it back on shared server, so I decided to host blog on VPS and to convert it from Typo to Mephisto engine with modified Lighter theme (thanks Brandon).

From the whole story I got few answers I was looking for some time:

Can Rails web site be hosted on shared server? No, even the smallest one or at least without a problems now and / or in the future. You need VPS and you need at least 30-40 MB per web site. If you want Apache, MySql and e-mail, by my experience VPS with 160 MB is minimum. VPS’s come without Cpanel / Plesk (if you are spare with memory and money at least), so you would need to learn some sysadmin as well. Webmin can surely make things easier.

Is it easy to migrate from Typo to Mephisto? Can Mephisto host multiple blogs? Yes and yes, very easy. I had two blogs, one running on Mephisto and one on Typo. I converted Typo blog to Mephisto and imported in same database in just few commands. Adding new theme to converted blog was easy too.

May 12 2007 rails 6 comments

Rails in practice - Group records by condition

Often, when it comes to views I have to group records and present it to the user grouped by some condition. For example, I would like to list all news articles grouped by year.

To accomplish this, I find it easiest that records are grouped in a hash, key should be year when article is published and value would be array of objects matching published on these year.

Before, I used custom function that would walk throught all elements of array calling custom block for each member and creating new hash with result value of the block as key and array of elements as value.

Today I discover classify method in Set class that would do just what I need (thanks to Ruby Cookbook for that).

So, the code to group all news articles by year published is:

@groups = NewsArticle.find(:all).to_set.classify {
  |article| article.published_on.year}

and view would be something like these:

<%@groups.each do |year, articles|%>
  <h1><%=year%></h1>
  <%=articles.collect {|a| \"<p>a.name</p>\"}.join%>
<%end%>

Short and sweet. Same method can be used to group anything, names by first letter, products by price range, etc.

April 28 2007 rails 0 comments

Create URL's outside of controllers and views

If you have a need to create URL’s outside of controllers and views include UrlWriter helper and set default_url_options.

include ActionController::UrlWriter
default_url_options = "www.example.com"

This way, you can create URL’s and use named routes from mailers, migrations, import scripts, etc. More info on Rails Framework Documentation

March 27 2007 rails 6 comments

Customized Mongrel Startup Script

This shell script allows to start and stop bunch of Rails application on Mongrel webserver. This is Tim Morgan’s script, while I added ability to customize port, rails environment and directory for every app while respecting default values.

I use it on linux development machine where there is no need for mongrel_cluster and I do not want to make extra configuration.

We can save this script as /etc/init.d/mongrel and start|restart|stop all mongrels with /etc/init.d/mongrel start (or stop|restart). And to configure for startup we can use:

# redhat
sudo /sbin/chkconfig --level 345 mongrel on
#debian/ubuntu
sudo /usr/sbin/update-rc.d -f mongrel defaults

And here is script:

#!/usr/bin/env ruby
#
# mongrel Startup script for Mongrel by Tim Morgan, modified by bmihelac
#
# chkconfig: - 85 15
# description: mongrel manages Mongrel
#

# this would add one mongrels for these two apps on 
# ports 8000 and 8001, mephisto would be started in production
apps = [
  {:app => 'myapp'},
  {:app => 'mephisto', :environment => 'production'},
]

default_port = 8000
default_options = {
  :app_dir => '/home/rails',
  :environment => 'production'
}

if ['stop', 'restart'].include? ARGV.first
  apps.each do |app|
    options = default_options.merge(app)
    path = File.join options[:app_dir], options[:app]
    puts "Stopping #{path}..." 
    `mongrel_rails stop -c #{path} -P log/mongrel.pid`
  end
end

if ['start', 'restart'].include? ARGV.first
  apps.each do |app|
    options = default_options.merge(app)
    path = File.join options[:app_dir], options[:app]
    port = options[:port] || default_port
    puts "Starting #{options[:app]} on #{port}..." 
    `mongrel_rails start -d -p #{port} -e #{options[:environment]} -c #{path} -P log/mongrel.pid`
    default_port = port + 1
  end
end

unless ['start', 'stop', 'restart'].include? ARGV.first
    puts "Usage: mongrel {start|stop|restart}" 
    exit
end

February 28 2007 rails 9 comments

Override helpers in Rails

Sooner or later need to override some built in Rails helpers would occur. For example, I like to use number_to_currency helper to format prices in my application, but I want all prices to be formated in euros instead of dollars.

Worth noting is that you cannot simple redefine number_to_currency in application_helper.rb what would be the first thing on my mind to do. You can not redefine original helper method in ActionView::Base class as well. That is because helpers are included as mixin and methods from module are made available to class that includes the module, they are not copied, so if module methods are changed later so are the modules method in a class. Original helper method should be replaced in module where it comes from and in case of currencies this is ActionView::Helpers::NumberHelper.

Here is example that replaces original number_to_currency method and set default currency to Euro:

module ActionView
  module Helpers
    module NumberHelper
      def number_to_currency_with_euro(number, options = {})
        defaults = {:unit => ''}
        s = number_to_currency_without_euro(number, defaults.merge(options))
        s << ' &euro;' unless options[:unit]
      end
      alias_method_chain :number_to_currency, :euro
    end
  end
end
February 21 2007 rails

Running database migration individually

Rails migrations are great, they allow continual evolution of database schema. Sometimes, especially when prototyping, I hate to run migrations down and up and load data, just to make small independent change in database table. In this situations I run only specific migration:
ruby script/runner 'require "db/migrate/005_create_blogs"; \n
CreateBlogs.migrate(:down)'
ruby script/runner 'require "db/migrate/005_create_blogs";\n
 CreateBlogs.migrate(:up)'

This is much typing (for just one migration at least ;) so here is the rake task to do the same:

namespace :db do
  task :migrate_one => :environment do
    file = Dir["db/migrate/#{ENV["VERSION"]}_*.rb"].first
    require(file)
    migration_class = file.scan(
/([0-9]+)_([_a-z0-9]*).rb/)[0][1].camelize.constantize
    migration_class.migrate(:down) unless ENV["DIRECTION"] == 'up'
    migration_class.migrate(:up) unless ENV["DIRECTION"] == 'down'
  end
end

Put this rake task in lib/tasks and you can call it with rake db:migrate_one VERSION=005. This would run migration down and up. You can also add DIRECTION=up or DIRECTION=down to control direction.

January 31 2007 rails 0 comments

Help! Rails test cases do not remove fixture data

Check if target database table is of MyIsam type. You have two options, first is not to use transactional fixtures in tests:
self.use_transactional_fixtures = false

Second and better option is to convert all tables to InnoDB tables. This migration can help converting:

class MyIsam2InnoDb < ActiveRecord::Migration
  def self.up
    ActiveRecord::Base.connection.tables.each {|t| 
        execute "ALTER TABLE #{t} TYPE=InnoDB"}
  end

  def self.down
  end
end
January 22 2007 rails 0 comments

Capistrano can download from remote server

Rails Changeset 6009 includes my patch that adds a “get” helper, to pull a file from a remote server to the localhost. More info about get and example to create backup of production server database is available in previuous posts.

January 15 2007 rails 0 comments

Backup remote database with Capistrano

Based on and using capistrano get method posted few days before here comes capistrano recipe to backup database on remote server.

desc <<DESC
Backup remote database from primary server.
DESC
task :remote_backup, :roles => :db, :only => { :primary => true } do
  filename = "dump.#{Time.now.strftime '%Y%m%dT%:%H%M%S'}.sql" 
  on_rollback { delete "/tmp/#{filename}" }
  run "mysqldump -uusername -ppassword database_name > \n
/tmp/#{filename}" do |channel, stream, data|
    puts data
  end
  get "/tmp/#{filename}", "c:/backup/#{filename}" 
  delete "/tmp/#{filename}" 
end
January 11 2007 rails 1 comments

Capistrano get method - download files from server

Still looking if there is better way to do this (and I bet there it is :), but until I realize, add to top of deploy.rb:

# Get file remote_path from FIRST server targetted by
# the current task and transfer it to local machine as path, SFTP required
def actor.get(remote_path, path, options = {})
    execute_on_servers(options) do |servers|
      self.sessions[servers.first].sftp.connect do |tsftp|
        logger.info "Get #{remote_path} to #{path}" 
        tsftp.get_file remote_path, path
      end
    end
end
Sample recipe to get production log from server:

task :download_log, :roles => :web, :only => { :primary => true } do
get "#{deploy_to}/current/log/production.log",
     "log/production.log.web" //download server log
end
December 16 2006 rails 0 comments

Working with dates in Ruby

Ruby has three classes for working with date and time: Date, DateTime which subclasses Date and Time. There is an interesting article why we need all 3 classes, and here are some notes about working with dates:

  • Time.now != DateTime.now && Time.superclass != Date
  • There is no method in Time class to convert object to a Date object, but you can write date = Date.new(time.year, time.mon, time.day). Rails extends DateTime and Time classes which allows to_date and to_time conversions for both classes.
  • Today is Date.today and now it is Time.now, there is no such thing as Date.now but there is Time.today (even I didn’t saw it in RDocs)
  • Tomorrow is Date.today+1, yestrday Date.today-1, to add or substract a month use Date.today >> 1 or Date.today << 1. See also Rails extensions to time class which allows to write things like Date.today.to_time.at_beginning_of_week
  • Date class includes Comparable module which allows us to write some_date.between?(Date.today, Date.today + 7)
  • Time.now.beginning_of_month.to_date.upto( Time.now.next_month.beginning_of_month.to_date-1) {|d| puts d.to_s} would print all days in current month in your Rails application.

Standard Ruby library also includes ParseDate library for parsing dates and returning array of values. Rails use ParseDate.parsedate to create dates so if you need to support input of additional date formats consider extending parsedate. I am not sure why Date.parse does not use ParseDate.parse but have its own implementation.

November 25 2006 rails 0 comments

Unit testing in Rails - state of database when test fail

After some code refactoring some tests failed. Ok, that means something in code is wrong and that is great thing to know. Usually it is enough easy to check the test case where the problem occured and fix code in model. However my yesterday problem was a bit complicated because the action that should change create and change objects from dependant models obviously didn’t do that in in the way it should. It would be easiest method to find where problem is to check the state of database just before test failed.

Here are few facts about unit testing that I learned after few hours trying to find way to see whats in the database when test failed:

  • before every unit test method is run specified fixture file would be loaded into table(s) after deleting all rows in these table(s)
  • all exceptions would be handled by TestCase except PASSTHROUGH_EXCEPTIONS [NoMemoryError, SignalException, Interrupt, SystemExit]
  • after any test method is finished or failed teardown() method would be called

So, I guessed it would be easiest to override teardown() to do nothing, exit from test method with exit! and inspect what’s in database. But, no, database was unchanged, I even try to sleep thread and watch database.

Nothing worked. After digging deeper in code solution come out – Transactional fixtures:

Transactional fixtures accelerate your tests by wrapping each test method in a transaction that’s rolled back on completion.

So the solution is to turn off transactional fixtures by adding following code in test:

self.use_transactional_fixtures = false

These way you can check what exactly is in the database when concrete test method failed or is about to fail. Note: transactional fixtures are much faster so it is preferred to have them enabled unless you need to turn off transactions.

November 13 2006 rails 19 comments

Creating docs for Rails, Rails Edge, Plugins

If you are using Rails and want to access docs locally, you can do it with typing gem_server in command prompt. Documentation for Rails and other installed gems would be available at http://localhost:8808.

For those working on latest Rails, you can create documentation from project path with these Rake task:

rake doc:rails

And creating docs for installed Rails plugins is easy as well:

rake doc:plugins

If you are unfortunate to tun latest task on windows machine, you would soon realize it doesn’t work. Until fixed, you can hack it work by changing line 78 of source file in lib/tasks/documentation.rake to:

sh %(rdoc.bat #{options * ’ ‘})

September 13 2006 rails 6 comments

Parsing european date format in Ruby/Rails

So you have date in european format DD.MM.YYYY. that you want to parse in ruby? Nothing easier, change parsedate method in ParseDate stdlib.

require ('parsedate')
module ParseDate
  class << self
    alias_method :old_parsedate, :parsedate unless
        method_defined?(:old_parsedate)
  end

  def self.parsedate(str)
    match = /(\d{1,2})\.(\d{1,2})\.(\d{2,4})\.?/.match(str)
    return ParseDate.old_parsedate(str) unless match
    [match[3].to_i, match[2].to_i, match[1].to_i, 
     nil, nil, nil, nil, nil] 
  end
end

On Rails, you can include it in environment.rb file with require ('parsedate_patches.rb').

June 19 2006 rails 11 comments

Creating PDF documents with tables in Ruby, Rails

I started to use Ruby FPDF to create PDF documents from our new Rails application MojGost.com. Ruby FPFD like it’s PHP counterpart does not have methods for creating tables. However, there are many examples and scripts available and that allowed me to quickly write small Ruby module which adds few methods for easy table creation.

Fpdf::Table main features:
  • word wraping text in cells, based on width of columns
  • page breaks on rows if table is too long

Download Fpdf::Table or see some examples below.

Example 1. simple table


require 'FPDF'
require 'fpdf/table'

class FPDF
  include Fpdf::Table
end

pdf = FPDF.new
pdf.AddPage
pdf.SetFont('helvetica','',10)
data = [
  ['100', 'lorem ipsum dorem'],
  ['100', 'lorem ipsum dorem'],
  ['100', 'lorem ipsum dorem'],
]
pdf.table(data)
pdf.Output('test_fpdf_table_1.pdf')

View Example 1 PDF

Example 2. width, word-wrap, aligment, page break


require 'FPDF'
require 'fpdf/table'

class FPDF
  include Fpdf::Table
end

pdf = FPDF.new
pdf.AddPage
pdf.SetFont('helvetica','',10)

data = []
30.times { |i| data << [i.to_s, '-', 'Lorem ipsum dolor sit' * 10] }

columns = [
  {:title => '#1', :aligment => 'R', :width => 20},  
  {:title => '#2', :width => 20},
  {:title => 'Text'}  
]

pdf.table(data, columns)
pdf.Output('test_fpdf_table_2.pdf')

View Example 2 PDF

About

I am Bojan Mihelac and this blog is dedicated to share code, thoughts, tools and advices I came up with while working in Informatika Mihelac.

Contact: bmihelac@mihelac.org

RSS feedSubscribe to RSS Feed