Programming in Paradise

Monday, January 30, 2006

Rails Recipe #1 - Adding Items in a HABTM Relationship

First recipe in what may turn out to be an ongoing thing.

If you have a HABTM relationship, say between people and their pets, and you want to assign multiple pets to a person in one shot, then your view would have something like this:

<%= form_tag :action => 'add_pet', :id => @person.id %>
<% @pets.each do |pet| -%>
<div><%= check_box_tag 'pet[]', pet.id %></div>
<% end -%>
<%= end_form_tag %>

Your controller would have something like this:

def add_pet
@person = Person.find(params[:id])
if request.post?
Pet.find(params[:pet]).each do |pet|
@person.pets << pet
end
redirect_to(:action => 'list_pets', :id => @person.id)
end
@pets = Pet.find(:all).delete_if { |p| @person.pets.include?(p) }
end

When receiving a GET request the action loads the person and a list of pets not already assigned to the person. That last line basically goes through the array returned by Pet.find(:all) and deletes the array item if the expression in the block (@person.pets.include?(p)) returns true.

When receiving a Post request the action finds all pets which match the supplied ids in the :pet array. That is accomplished by setting the check box name to pet[] which indicates that the HTTP param should be an array filled with all of the checked items from the view. Then it loops through those items and adds each one to @people.pets and finally redirects to some list of the person's pets.

Guy Kawasaki on the Art of Execution

If you aren't reading Guy Kawasaki's blog, start now. And if you have problems following through on your ideas, read his post on the Art of Execution. Yet another gem in a pile of gems.

Monday, January 23, 2006

HABTM and Migrations Gotcha

If you are using migrations in your Rails apps (and you should) and you are also using has-and-belongs-to-many relationships then there is a gotcha to be aware of. The join tables in a HABTM relationship should *not* include an ID column, however migrations, by default, creates ID columns from all of your tables. Therefore, to avoid this problem, all join tables in HABTM relationships should include :id => false in the create_table invocation.

Example:
create_table :people_pets, :id => false do |t|
t.column :person_id, :integer, :null => false
t.column :pet_id, :integer, :null => false
end
Good stuff to know.

Wednesday, January 18, 2006

File Column for Rails File Uploads

There's a nifty little plugin called File Column which provides file functionality above and beyond what is built into Rails. Some of the features include the ability to store files temporarily to disk in a temp directory, long term storage of the file to disk in a customizable location, storage of the relative path in the DB, helpers for constructing the public URL for the file as well as support for getting absolute file system paths if necessary. It also integrates with ImageMagick through RMagick (although I didn't need this functionality).

There are a few deficiencies. First of all the documentation is minimal. Figuring out how to do certain things takes looking at the source code. Additionally you cannot get an IO stream directly from the ActiveRecord object. Finally, FileColumn actually adds methods to your ActiveRecord class for accessing paths and such, and that could be a problem for some people (although it wasn't in my case).

Anyhow, overall it's a good plugin and worth getting if you are doing file uploads in Rails.

Saturday, January 14, 2006

Article On Questions Posed By Rails Skeptics

Fantastic article about the common questions posed by Rails skeptics and some basic answers to them. One of the things Rails definitely has is evangelists, but there's a fine reason for that: Rails helps you get the job done with less code and less work and everyone feels good when they get more for less.

Thursday, January 12, 2006

Constructing Ruby Class Instances from a String

OK, little bit of Rubyfoo here:

class Person
def say_hello
puts "hello"
end
end

class_name = "Person"
person_class = Object.const_get(class_name)
person = person_class.new
person.say_hello

I should spend some more time in the Pickaxe book to try to pick up some more things like this...

Wednesday, January 11, 2006

Pecuniarius Has a New Home

I moved Pecuniarius to a new server tonight and it is running much, much faster thanks to it. It took me about a day to get everything set up but the new server (a Virtual Private Server) has Ruby 1.8.4, Rails 1.0.0, Apache with proxy pass through to Lighttpd, as well as Subversion for hosting the source code. Switchtower deployment is working fine at the moment but it still feels sketchy. Anyhow, I'm glad to be moved over there and I'm looking forward to improving the app now that it has a better home.

Switchtower Tidbits

Our setup is Apache 2.x on a Linux server. As per my previous post on Switchtower, there are some things which aren't built in. Here are two tasks which appear in my deploy recipes when deploying to the setup described above:

desc "Restart the web server"
task :restart, :roles => :app do
sudo "apachectl graceful"
end

task :after_update_code, :roles => :app do
sudo "chmod a+x #{release_path}/public/dispatch.*"
sudo "chmod -Rf a+x #{release_path}/script/*"
end
The first one replaces the built in restart task with one for apache. The second one chmods the dispatch scripts as well as all of the scripts in the app's script directory. If you are running Lighttpd (either standalone or proxied through Apache) then you don't need the restart task.

Tuesday, January 10, 2006

Cyberduck - Wow!

Cyberduck is a great little app for OS X. It handles FTP, FTP over SSL and SFTP. The interface is simple and intuitive, it is nicely customizable, and the keychain integration is very cool. Best of all, it's free. I donated 5 EUR to the author.

Keychain Lovin

OK, I'm easily amused, but I really liked the fact that Cyberduck automatically inserted a password to an account which I had already configured in another application thanks to the fact that account information is stored in Apple's Keychain app. It's the little stuff that counts.

Switchtower Thoughts

I just created my first switchtower deployment which is actually working. Switchtower is a good idea but it definitely has some areas which need improvement. First of all the documentation is not that great. It's ok if you follow the existing recipe exactly, but as soon as things start to fall apart it is quite difficult to find information on how to do things the right way. Case in point: if you want to execute one of your tasks defined in deploy.rb you must use the following syntax:

rake remote_exec ACTION=mytask

Not exactly intuitive, and its not explicitly stated in the documentation on the wiki. Initially I tried the following:

rake mytask

But was told that Rake didn't know about mytask. To make matters worse, the Rake documentation site at http://docs.rubyrake.org/ comes up with a Rails Application Error message. Oops.

Next concern is that you have to have your repository available from your deployment machine. In my company there are a lot of times where this is not possible. There needs to be some sort of solution which allows the Rails app to be packaged up on the local machine and then installed on the remote machine. Perhaps an actual install builder which would be able to execute the same tasks when on the deployment machine might be a good solution. This would also help with Windows deployment...which brings me to another issue: you can't use Switchtower to deploy to Windows.

Ouch, no Windows deployment. This is a bummer and looks like it will be much more difficult to fix. The key here is to remove dependencies on POSIX commands (such as chmod) and instead use Ruby equivilents with graceful failure when those commands can't be executed (or don't need to be executed). This is what I did with Pyb back in the days, and what Ant does, and its the only way to really deal with cross-platform builds.

Another problem occurs when deploying from Windows to a *nix environment. Ruby paths are not the same on different platforms and basically things go bonkers when you take a Rails app authored in Windows and deploy it to a *nix system. This really should be easy to fix, perhaps by determining the platform and then having a task alter the scripts as needed. The same goes for chmodding the CGI and FCGI scripts - this should be done automatically as part of a build.

Anyhow, like I said, the ideas behind Switchtower are good, and I'm glad to see thought being put into the problem of deployment, but there is clearly more work to be done.

Monday, January 09, 2006

Pecuniarius Update

New Pecuniarius code went up tonight! Yay! Go get ya some.

Ruby on OS X

I originally installed Ruby on OS X using Darwin ports. Unfortunately that installs Ruby 1.8.2 with OpenSSL 0.9.8 and this results in a broken net module. There is a good article from the Hivelogic Narrative which covers installing ruby, rails, lighttpd and mysql on OS X. I walked through that yesterday and it worked perfectly, so now I have ruby 1.84, openssl 0.9.8, rails 1.0 and lighttpd running on my OS X box.

Sunday, January 08, 2006

Sanitizing Form Data

I love JJ because I give him something which works and he quickly pokes it so full of holes that it would sink if placed in water. The particular issue at hand is sanitizing incoming form data. Put simply, I wasn't doing it for Pecuniarius and now I am. The fix was actually quite simple. I created two methods in my application controller which handle the sanitizing:

def sanitize_params
sanitize_hash(@params)
end

def sanitize_hash(hash)
hash.each do |key, value|
if value.kind_of? Hash
sanitize_hash(value)
elsif value.kind_of? Array
hash[key] = value.collect {|x| sanitize(x)}
else
logger.info("Sanitizing #{key}")
hash[key] = sanitize(value)
logger.info("After sanitize: #{hash[key]}")
end
end
end
Then in any controller which has user input I added the following filter:
  before_filter :sanitize_params
Et, voila! The sanitize_params method calls the sanitize_hash method passing the paramters. The sanitize_hash method will then handle nested hashes, arrays or plain values. One minor addition which I might add in the future is the ability to omit certain parameters from sanitizing, but at the moment that isn't a necessity. Feedback is always welcome. If you are doing this in a different fashion then I'd love to hear about it.

P.S. Another good blog entry on handling form data this can be found in the Rails Diary.

Friday, January 06, 2006

Deploying Rails with Apache 2

James Duncan Davidson has published his 4th article on real-world deployment issues with Rails. The first three articles have been very informative and I am looking forward to reading number 4 this evening.

Rails Webapps for Review

Here a few webapps from the wiki list at wiki.rubyonrails.com which look interesting:
I've looked at NumSum in the past, but I suppose it's a good time to look again. Chain Reading may provide a good way to track the books I've read and get ideas for new ones (this is something I was talking about with my wife and parents and all of them were interested in having something like this). CalendarHub is something I looked at before as well, but calendering is becoming more and more important as I travel more.

Thursday, January 05, 2006

TPOSSCON 2006

TPOSSCON 2006 starts on January 9th and runs to January 12th here in Hawaii at the Honolulu Convention Center. I went to this conference and while it was small it was still well organized and provided a good networking opportunity. This year a recent transplant, Scott Thompson has arranged to demo Ruby and Ruby on Rails in the "petting zoo" which should be interesting. Jim Thompson (no relation to scott as far as I know) of NetGate (a maker of wireless chips) will also be speaking. Jim is a great guy and is very passionate about technology, so it should be a good presentation. I'll probably end up only going one day, but stop by the Rails demo and perhaps we will meet up.

New Blog

Ah, it's that time again, time to start a new blog. I'm fed up with dealing with hosting my own blog, so this time around I'm going to just host at blogger. Topics of writing will revolve around software development, most likely with Ruby and Rails (since that is where my focus is at the moment.) From time to time I may even dip into Java (although it is soooo 2004). I'll also pimp my projects like Pecuniari.us, personal finance manager. Let the good times roll.