Tuesday 8 September 2009

Capturing stdout for tests in Ruby

I found this on Thinking Digitally that allows me to capture stdout and test that it is correct.
require 'stringio'

def capture_stdout
out = StringIO.new
$stdout = out
yield
return out
ensure
$stdout = STDOUT
end

Which can be used as follows
class ATest < Test::Unit::TestCase
def zxc(text, count)
count.times {puts text.upcase}
end

def test_stdout_capture
out = capture_stdout do
zxc('yes', 3)
end

assert_equal "YES\nYES\nYES\n", out.string
end
end

Monday 7 September 2009

Finding the bad parts in a log file

This little script makes things easier by extracting the errors out of a rails log file
#!/usr/bin/env ruby

text = ''
completed = false
total = 0
good = 0

ARGF.each do |l|
if l =~ /^Processing /
puts text if completed == false and text.size > 0
text = ''
completed = false
total += 1
elsif l =~ /^Completed /
completed = true
good += 1
end
text << l
end

puts text if completed == false and text.size > 0

$stderr.puts "Read #{total} transactions, #{total - good} were errors"

To use it you do this: striplog.rb development.log > development.log.errors. It will print out a summary line at the end to show you how many errors it found

More fun with pid files

Another way of handling pid files is a more ruby like way is:
#!/usr/bin/env ruby

class PidFile
def initialize(filename, &code)
unless set_pid(filename)
raise RuntimeError.new("The process is already running")
end

code.call

File.delete(filename)
end

private

def set_pid(filename)
# Same code as before
end
end

And call it thus
PidFile.new("/var/run/app.pid") do
puts "What we want to do"
end

Friday 4 September 2009

Checking for processes and pid files

If you have a periodic task that might still be running when you try to run it and don't want both copies of the program running then you need to create a pid file and check it when your process starts.
# Returns true if it was able to create the pid file

def set_pid(filename)
if File.exist?(filename)
File.open(filename, "r").each do |l|
old_pid = l.chomp.to_i

begin
Process.kill(0, old_pid)
# The process is running
return false
rescue Errno::EPERM
# The process is running and you don't have permission
return false
rescue
# the process is not running or something
end
end

File.delete(filename)
end

# Create the new process
f = File.new(filename, "w")
f.puts $$
f.close

return true
end

At the start of you program call it as follows
unless set_pid("/var/run/app.pid")
puts "This process is already running!"
exit
end

This is good for OS X and Linux, not sure if it will work for Windows. Probably will

Thursday 3 September 2009

A rake task to install crons

Assuming the cron tasks you want to run are stored in script/crons/{daily,hourly,monthly,weekly}/... this rake task will create the necessary crons on the system
namespace :crons do
desc "Install the crons we need to run this site"
task :install do
text = ''

template = "@%s (cd %s ; /usr/local/bin/ruby script/runner -e production script/crons/%s/%s)\n"

Dir["#{RAILS_ROOT}/script/crons/*/*"].each do |file|
if file =~ /crons\/daily/
text << template % ['daily', RAILS_ROOT, 'daily', File.basename(file)]
elsif file =~ /crons\/hourly/
text << template % ['hourly', RAILS_ROOT, 'hourly', File.basename(file)]
elsif file =~ /crons\/monthly/
text << template % ['monthly', RAILS_ROOT, 'monthly', File.basename(file)]
elsif file =~ /crons\/weekly/
text << template % ['weekly', RAILS_ROOT, 'weekly', File.basename(file)]
else
puts "Unknown file #{file}"
end
end

cron_file = "#{RAILS_ROOT}/script/crons/crontab.txt"

if File.exist?(cron_file)
File.delete(cron_file)
end

unless text == ''
f = File.new(cron_file, "w")
f.puts text
f.close
end
end

desc "Remove any previously installed crons"
task :remove do
cron_file = "#{RAILS_ROOT}/script/crons/crontab.txt"

if File.exist?(cron_file)
File.delete(cron_file)
end
end
end

To run it just go rake crons:install and it will create the crontabs as a text file. Then we can use the capistrano task to set them up
namespace :deploy do
namespace :crons do
desc "Installing the crons"
task :install, :roles => :db do
run "(cd #{deploy_to}/current ; rake crons:install)"
run "cat #{deploy_to}/current/script/crons/crontab.txt | crontab -"
end
desc "Remove the crons"
task :remove, :roles => :db do
run "crontab -r"
run "(cd #{deploy_to}/current ; rake crons:remove)"
end
end
end

You could probably do it all in the rake task if you wanted. You might want to set up some hooks to run this after a deploy

Purging sessions in rails

Keeping it simple we first create a script:

logger = Logger.new("#{RAILS_ROOT}/log/sessions_purge.log")
logger.formatter = Logger::Formatter.new()

older_than = Date.today - 1

logger.info "Starting to purge sessions older than #{older_than}"

counter = 0
ActiveRecord::SessionStore::Session.find_by_sql(["SELECT * FROM sessions WHERE updated_at < ?", older_than]).each do |r|
counter += 1
begin
r.destroy
rescue Exception => e
logger.error "Unable to remove #{r.key} #{e}"
end
end

logger.info "Removed #{counter} sessions"

Which needs to be run from the users crontab

@daily (cd /home/project ; /usr/local/bin/ruby script/runner -e production script/crons/session_purge.rb)

If ActiveRecord::SessionStore::Session doesn't work for you then create a model for the sessions, ruby script/generate model session, and replace it with Session