A collection of computer systems and programming tips that you may find useful.
Brought to you by Craic Computing LLC, a bioinformatics consulting company.

Wednesday, March 19, 2008

Ensuring a DSL Connection Stays Up

I've got DSL service through Qwest, with an Actiontec DSL modem. Every so often the connection will just hang for no apparent reason. Qwest have not been able to help. I suspect the problem is due to a flaky modem or some accumulation of 'state' in the device that causes it to hang when it reaches a certain point. Power cycling the modem fixes it every time.

Most of the time the connection is fine but I can almost guarantee that it will hang if I go away for a few days. As I run my own web site and mail server it is crucial that the connection stays up.

The fix that I've arrived at is very low tech. Simply plug the modem into a cheap programmable timer and set that to go off for one minute everyday sometime in the middle of the night. That way the modem gets reset once a day whether it needs it or not.

Most importantly it will reset when I'm not here. So even it hangs during the day it will be back within 24 hours.

Since I've been using this I've not had a problem... famous last words...

My timer is an Intermatic with an LCD display, but any reasonable timer from Home Depot, etc. will do the job. It cost less than $20.

Ruby unable to find installed Gems? You may have 2 installations of ruby

I've been bitten by this before but it has been long enough that I forgot about the issue until it tripped me up again just now.

You've installed a gem, then go to use it and your Ruby script complains it can't find it. It does so with a cryptic message along the lines of "in 'require__': no such file to load -- postgres (LoadError)" In this example it can't find the postgres gem, which I know is installed and which 'gem list --local' tells me is installed.

Why can't it see it?

It may be that you have two versions of Ruby installed on your system. Your OS may place one in /usr/bin and you may have installed a more recent version in /usr/local/bin. That's what I have on one of my machines.

The thing is that each version has its own directory of gems. If you can't see a gem that you know is installed then you might be using the wrong installation of Ruby!

This was my problem. I have /usr/local/bin at the front of my path so I automatically pick up that installation of ruby and gem on the command line.

I had mistakenly put #!/usr/bin/ruby at the top of a script. That led the script to look for 'require'd gems in the wrong gem directory, and hence it did not find it.

You'll see some folks use "#!/usr/bin/env ruby" as the first line of their scripts. This should find the right one as it uses your PATH to find the first instance of ruby.

It's a nasty gotcha and it might explain quite a few pleas for help on Ruby mailing lists.

Ruby, ActiveRecord and Postgresql

Here are some tips on getting Postgresql and Ruby working together. This has not been as simple as I had hoped but after some experimentation, here are the steps that worked for me...

My environment:
RedHat Linux (Enterprise Linux ES Release 4 - Nahant Update 3)
Ruby 1.8.6
Postgres 8.3 (?)

1: Downloaded Postgresql as source and compiled as directed, installed into /usr/local/pgsql
NOTE that I had to be root to get make to successfully compile the code - don't know why

2: Install the Ruby postgres Gem
You will see references to the postgres, ruby-postgres and postgres-rb gems. As far as I can tell, the one you want is 'postgres'
As root run:
# gem install postgres
This may very well blow up with error messages! It did with me. For whatever reason it does not know where to find the Postgresql include and lib directories.
Look at the error message and cd to the gem install directory. For me this was:
Install the gem manually using these commands:
# ruby extconf.rb --with-pgsql-include=/usr/local/pgsql/include --with-pgsql-lib=/usr/local/pgsql/lib
# make
# gem install postgres -- --with-pgsql-include-dir=/usr/local/pgsql/include --with-pgsql-lib-dir=/usr/local/pgsql/lib
NOTE that the options are different for the ruby extconf.rb and gem install commands! They look very similar but the gem install options have a '-dir' suffix.

3: Create a test database with 'psql'
You will want to follow a basic tutorial on Postgresql and the client 'psql' to learn about this. They are plentiful and easy to find online.
For this example, let's assume that the database is called 'test', the table is called 'employees' (plural) and that it has two fields called 'id' (int) and 'name' (varchar(255)).
Populate that with a few rows of data.

4: Write a ruby script to interact with the database using ActiveRecord
ActiveRecord is the way that Rails interacts with databases, but you can use it outside of Rails with no problem.
Here is one that will fetch the 'employee' records from a database on the same machine as the script:

#!/usr/bin/env ruby
require 'rubygems'
require 'active_record'

# create a class for employee records (the class is singular but the table is plural)
class Employee < ActiveRecord::Base

# connect to the database
ActiveRecord::Base.establish_connection(:adapter => 'postgresql',
:host => 'localhost',
:username => 'postgres',
:database => 'test');

employees = Employee.find(:all)
employees.each do |employee|
print "#{employee.id} #{employee.name}\n"

NOTE the adapter is 'postgresql', not 'postgres', you don't need to explicitly require the 'postgres' gem, and the username is 'postgres'

To connect to a remote postgresql instance you will need to change the :host value in the establish_connection call. You may need to add the port that it is listening on (default 5432) with a :port directive.

You will also need to set up Postgres to allow remote calls. In /usr/local/pgsql/data/ you will need to edit pg_hba.conf to add a line allowing connections from a defined set of remote machines. Read the documentation to make sure you don't open up your database to the entire world.
Then you need to edit postgresql.conf and uncomment the 'listen_addresses' and 'port' lines. You want to add the IP address of the database server to the listen addresses so that the default line
listen_addresses = 'localhost'

becomes something like this
listen_addresses = 'localhost,'

You'll need to restart Postgresql for these to take effect.

If you have trouble making a connection from your remote ruby script then try connecting using 'psql' on the database machine, if that works, try it from the remote machine. That should help pin down where the problem lies.

Friday, March 7, 2008

Ruby, WEBrick and CGI scripts

Ruby on Rails is the obvious choice when you want to create a web application using Ruby. But this can be overkill for some projects where a regular CGI script would do the job.

Unfortunately the focus on Rails has led to CGI scripts being somewhat neglected in Ruby documentation. Here are the steps you need to get a basic CGI script up and running and the wrinkle you need to know about if you want to upload files.

1: Setting up a test web server using WEBrick
Ruby comes bundled with the WEBrick web server library and this allows you to create a basic server on your local machine for testing. Startup a server with this simple script:
#!/usr/bin/env ruby
require 'webrick'
include WEBrick
s = HTTPServer.new(
:Port => 3000,
:DocumentRoot => File.join(Dir.pwd, "/html")
trap("INT") { s.shutdown }

This will start a server at 'http://localhost:3000' that will respond to requests for files in the 'html' subdirectory of your project.
Put that into a file called, for example 'webrick.rb', 'chmod a+x webrick.rb' to make it executable and run it from your project top level directory.

WEBrick can be used in various ways, such as to serve up Java like servlets. BUt this simple configuration allows you to serve static content and CGI scripts from the given directory with no other configuration required. Great for testing, but use Apache or something else substantial for hosting real sites.

2: Create a CGI script
For WEBrick to recognize your scripts, with this minimal configuration, you MUST give them the suffix '.cgi'
Here is a minimal script that will return the value for parameter 'foo' as supplied in a corresponding form.
#!/usr/bin/env ruby
require 'cgi'
cgi = CGI.new
foo = cgi['foo']
print "Content-type: text/plain\n\n"
print "foo: #{foo}\n"

The CGI object contains a hash of the parameters passed to it and with either a GET or POST request you can access these as shown above.

Things get more complicated when you want to upload a file from your form. In this case you specify enctype="multipart/form-data" on your html form, telling the CGI script that you are including a file among the parameters. BUT, with Ruby CGI, that affects the way you access other regular parameters that are included in the form.

You can no longer access their values by a direct hash lookup. cgi['foo'] no longer returns a string, instead it returns a StringIO object. StringIO wraps the IO class around the string so that you can use IO methods on it. This is great for handling the contents of the file that you want to upload but it makes handling regular arguments totally confusing until you realize the trick. Instead of accessing cgi['foo'] you now need to 'read' that StringIO stream and use cgi['foo'].read. The equivalent script as above for handling multipart data, and echoing the contents of an uploaded file is:
#!/usr/bin/env ruby
require 'cgi'
cgi = CGI.new
foo = cgi['foo']
print "Content-type: text/plain\n\n"
print "foo: #{foo}\n"
print "foo.read: #{foo.read}\n"
print cgi['filename'].read

The incorrect direct use of cgi['foo'] will print a reference to a StringIO object.

You can test these out with this static HTML form:
<p>Two example HTML forms for testing Ruby CGI scripts</p>
Form with (method="post")
<form action="ruby_cgi_post.cgi" method="post">
Enter Value: <input type="text" name="foo"><br/>
<input type="submit">
Form with (method="post" enctype="multipart/form-data")
<form action="ruby_cgi_post_multipart.cgi" method="post" enctype="multipart/form-data">
Enter Value: <input type="text" name="foo"><br/>
Choose File: <input type="file" name="filename"><br/>
<input type="submit">

Archive of Tips