nick.recoil.org

Mongrel init.d script

I’ve slightly modified Bojan Mihelac’s Mongrel init.d script to cope with the situation where there are stale PID files left from a server failure.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

#!/bin/env ruby
#
# mongrel Startup script for Mongrel by Tim Morgan, modified by bmihelac and Nick Ludlam
# Originally from http://source.mihelac.org/2007/3/27/customized-mongrel-startup-script
#
# chkconfig: 2345 85 15
# description: mongrel manages Mongrel
#

apps = [
  {:app => 'app1'},
  {:app => 'app2'},
  {:app => 'app3'}
]

default_port = 8000
default_options = {
  :app_dir => '/var/www',  :environment => 'production'
}

pid_location = "log/mongrel.pid"

if ['stop', 'restart'].include? ARGV.first
  apps.each do |app|
    options = default_options.merge(app)
    path = File.join options[:app_dir], options[:app], "current"
    puts "Stopping #{path}..."
    `/usr/local/bin/mongrel_rails stop -c #{path} -P #{pid_location}`
  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], "current"
    port = options[:port] || default_port
    pid_file = File.join path, pid_location

    # Check and remove stale PID file. Platform needs "ps -p" support
    if File.exists?(pid_file)
      old_pid = File.read(pid_file)
      `ps -p #{old_pid}`
      if old_pid.to_i > 0 && $?.exitstatus == 1
        puts "Removing stale PID file"
        File.unlink(pid_file)
      end
    end

    puts "Starting #{options[:app]} on #{port}..."
    `/usr/local/bin/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

Mephisto plugin to allow self-updating links

I’ve placed a page under code which details a little plugin I’ve been working on for Mephisto. It’s designed to make links in your templates which need to point to the latest software release always valid. It scans a directory according to a shell glob expression, and gives back a block with the file path, name and version as local variables.

The shell glob expression can be passed in via the liquid template, as per normal with mephisto plugins, or it can be passed in as a special tag attached to the page which is being rendered. This allows per-page links, rather than per-template, and gives greater flexibility.

More information is on the dynamic file link page.

Fetching Railscasts with Hpricot

In the spirit of why’s original nostrils , I’ve made a small Hpricot script to fetch me a set of screencasts from Railscasts

1
2
3
4
5
6
7
8
9
10
11

require 'rubygems'
require 'hpricot'
require 'open-uri'

uri='http://railscasts.com/episodes/'
from,to=*ARGV.map { |arg| arg.to_i }
(from..to).to_a.each do |ep|
  doc = Hpricot(open("#{uri}#{ep}"))
  puts (doc/'div.download').first.at('a')['href']
end

Then invoke by


ruby railscasts_downloader.rb 15 20 | xargs -n1 wget

Searching for a Rails 'delete_by_sql' method

Within ActiveRecord model objects, two methods are provided to allow arbitrary execution of SQL. These are find_by_sql() and count_by_sql(). These are fine for when you’re attempting some particularly tricky SQL SELECT or SELECT COUNT statements, but what happens when you have a tricky DELETE statement you’d also like to craft by hand? There’s no corresponding delete_by_sql()

It turns out you cannot use the aforementioned find_by_sql or count_by_sql to execute a DELETE statement, as they expect the returned data set to have columns defined, and will barf if you don’t have a SELECT. Matt Biddulph pointed me in the right direction, which was to use connection.execute(), or in this case, connection.delete(), from within the model object. These methods are a part of ActiveRecord::Base, so you don’t need to specify the full receiver if you’re already within a class inheriting from it.

In my current rails project, I have a deeply nested set of tables with one set to acts_as_tree, so deleting the top level object, and letting rails propagate the dependent deletions via object instantiation and a call to delete, would take hours. Unfortunately, if you have multiple levels of dependency, you cannot take advantage of the much quicker :dependent => :delete_all, as this will not propagate down your chain of dependent tables.

Instead, the approach I took was to implement a delete_contents() method in the top tier object, and have that delete everything in the dependent tables, without the need for instantiation. Imagine you have the following dependency hierarchy:

Top tier -> Tier 1 -> Tier 2 -> Tier 3

You can implement the following method to achieve a single-statement deletion of all dependent contents:

1
2
3
4
5
6
7
8
9
10

class TopTier < ActiveRecord::Base
  def delete_contents
    connection.delete("DELETE tier1, tier2, tier3
                       FROM tier1
                       LEFT JOIN tier2 ON (tier2.tier1_id = tier1.id)
                       LEFT JOIN tier3 ON (tier3.tier2_id = tier2.id)
                       WHERE tier1.top_tier_id = #{self.id}")
  end
end

connection.delete() will return the number of rows affected. It’s also worth looking at foreign key constraints in MySQL. This removes much of the pain of maintaining referential integrity yourself, but is not supported by all database systems and table type combinations.

Migrating Typo from MySQL over to SQLite

I've moved this blog installation from my locally hosted MythTV server over to the recoil.org box we've designated for hosting dynamic content. In doing so, I've re-hosted all of the content from MySQL to SQLite, as this is going to be easier to maintain in the future in a secure and simple way.

In doing this, I've discovered that there's apparently no neat way of doing this through the current Rails rake tasks. Eventually I've settled on preparing a blank SQLite database via:


rake db:schema:load

And then running the following script, which was modified from something found on the SQLite trac installation:


cp db/typo_blank.db /tmp/temp.db ; /usr/local/mysql/bin/mysqldump --no-create-info --compact --extended-insert=FALSE --quote-names=FALSE --complete-insert -u root -pYOURROOTPASSWORD typo | perl -pe '
        if (/^(INSERT.+?)\(/) {
                $a=$1;
                s/\\'\''/'\'\''/g;
                s/\\\"/"/g;
                s/\\n/\n/g;
                s/\\r/\r/g;
                s/\),\(/\);\n$a\(/g;
        }
' | sqlite /tmp/temp.db;  mv /tmp/temp.db db/typo_dev.db

And the last thing I found I needed to do, which may or may not have something to do with being on the SVN trunk:


UPDATE contents SET published='t';

After the migration, the contents.published column seemed to contain a 1 instead of a 't', which is related, I assume, to the way the differing systems choose to store boolean values

Ruby and Gem tip

Idling away in the #rubyonrails IRC channel sometimes makes you privy to tricks and tips concerning Ruby and Rails that you didn't already know. One such tip is the Ruby Gems documentation WEBrick incantation:

gem_server

which will magically invoke a local WEBrick server on port 8808. This instance lists all of your locally installed Ruby Gems and allow you to quickly and efficiently browse the rdoc contents for them. Very helpful indeed.

Fixing rails console support in Tiger

Just a quick post to point out an invaluable article on repairing the rails console in Mac OS X 10.4. It involves fetching libreadline and the Ruby readline extension, and reinstalling over the top of your existing environment. Quick, painless and very useful.

Getting on with Ruby on Rails

I'm just about to deploy the first internal application written in Ruby on Rails for Framestore. This will hopefully mark a nice change in my group towards using frameworks such as RoR and Django for database-driven web applications.

The application in this case is the primary archiving tool for the company's long-term storage of assets. The code is a mixture of Perl for the scanning and writing data to DTF2 or SAIT tapes, and a web interface for browsing and searching on the data, and generating requests for the Data Operations team on behalf of the various artists and producers working on internal projects.

This web interface was previously written in PHP, in a relatively nice way, but utilised no frameworks or modules other than the Pear MySQL class. It was also rapidly growing out of control, as the database grew in size, and the complexity of queries was growing with the functionality of the application.

Since we had the luxury of a ground-up rewrite of the Perl code over the past month or so, I've instigated a schema change to bring the database in line with ActiveRecord's expectations, which has made a huge difference to the ease in which I can sketch out the Rails code. The only odd problem I had was related to tables which logically needed no primary key, as they represented the 'zero-or-one' part of a 'one-to-zero-or-one' table mapping. In my case, a file may be part of a sequence, but it may not, so there are file entries, along with optional sequence entries.

Within the SequenceEntry model, I had to set the primary key to be the same as the foreign key, as ActiveRecord demands some sort of unique way of distinguishing records.

1
2
3
4
5

  class SequenceEntry &lt; ActiveRecord::Base
      belongs_to ::file_entry
      set_primary_key :file_entry_id
  end

One of the more annoying aspects of ActiveRecord I can't seem to get away from is the double SQL query you seem to need when you're looking at pagination of complex queries. I have a block which looks like:

1
2
3
4
5
6
7
8

file_count = FileEntry.count_by_sql("SELECT COUNT(*) FROM file_entries
                 LEFT JOIN directory_entries ON directory_entries.id =
                           file_entries.directory_entry_id
                 LEFT JOIN runs ON directory_entries.run_id = runs.id
                 LEFT JOIN tapes ON runs.tape_id = tapes.id
                 WHERE filename LIKE '#{search_pattern}'
                 AND tapes.barcode = '#{barcode}'")

which returns the number of entries I will be fetching, and then the subsequent find() query, but since each query takes of the order of 20 seconds, it's making the user wait double the amount of time that should be necessary.

The only other thing I'm finding a little difficult is where to place the code that cleans up form input variables before they are passed to the find() statements. It may either be a case of using a helper object, or possibly the prepend_before_filter, but the DRY principle has got me seeking an elegant cross-method solution.

Search

Sections

About Nick

I am a freelance technology consultant and developer working in London, with a particular interest in web development and video media.

This site contains my thoughts about technology, the universe and everything. If you would like to get in contact, have a look at the About me page.