Month: April 2009

Quick Tip: Fix RSS feeds in Rails 2.3

Posted by on April 24, 2009

I saw a sudden drop in the RSS subscribers on one of my site's feeds and it led me to investigate what happened. Turns out that RSS feeds changed every so slightly in Rails 2.3. Actually, it might even be a bug. The symptom is that when you visit your sites rss feed you are prompted to download the file instead of viewing it in an RSS program.


If you download the file and inspect it, you'll likely see a bunch of stuff that doesn't belong in an RSS feed at all. Even more confounding is that emptying out your index.rss.builder file still makes your browser download a file. The culprit is that Rails 2.3 shows the layout file for RSS feeds just like it would for a respond_to html. To fix it add a render_layout => false to your controller like so:

 def index
@articles = Article.find(:all, :limit => 8, :order => 'created_at desc')
respond_to do |format|
format.html
format.rss { render :layout => false}
format.xml { render :xml => @articles.to_xml }
end
end


Tada!

Install Ruby Rails on Ubuntu 9.04 Jaunty Jackalope

Posted by on April 20, 2009

It's getting easier to install Ruby on Rails all the time. I thought about skipping actually writing this tutorial until I heard that Phusion Passenger 2.2.0 supported Nginx. I was pretty psyched and so ran off to do an install with that new stack. If you want a head start on it or need a step by step tutorial then by all means click through for instructions.

Update: Version for Karmic Koala now posted.




Step 1: As usual, the first thing we'll want to do is make sure your version of Ubuntu is up to date.:
sudo apt-get update
sudo apt-get dist-upgrade



Step 2: We'll be installing some software that needs to be built so we'll need to get packages required for compiling. Luckily, you can type this single command and get everything you need:
sudo apt-get install build-essential



Step 3: Once you've got the tools, it's time to install MySQL and Ruby. If you're using SQLite you may not need all this stuff. Otherwise, just copy and paste this command into your terminal if you're in a hurry.
sudo apt-get install ruby ri rdoc mysql-server libmysql-ruby ruby1.8-dev irb1.8 libdbd-mysql-perl libdbi-perl libmysql-ruby1.8 libmysqlclient15off libnet-daemon-perl libplrpc-perl libreadline-ruby1.8 libruby1.8 mysql-client-5.0 mysql-common mysql-server-5.0 rdoc1.8 ri1.8 ruby1.8 irb libopenssl-ruby libopenssl-ruby1.8 libhtml-template-perl mysql-server-core-5.0

If you hadn't previously installed MySQL you'll be asked to set a root password. You don't have to, of course (it will bug you no fewer than 3 times if you opt not to) but I strongly recommend it. You'll need this password when you populate your rails config/database.yml file so be sure not to forget it.


Step 4: Grab the latest ruby gems and install them. As always be sure to check rubyforge.org to make sure you're grabbing the latest one. As of this writing it's 1.3.4 but it never hurts to confirm.
wget http://rubyforge.org/frs/download.php/57643/rubygems-1.3.4.tgz
tar xvzf rubygems-1.3.4.tgz
cd rubygems-1.3.4
sudo ruby setup.rb

Once it's done you can remove the .tgz file and erase the rubygems-1.3.4 directory too.


Step 5: On the command line, type gem -v. if you get this message we need to make some symlinks:

The program 'gem' can be found in the following packages:
* rubygems1.8
* rubygems1.9
Try: sudo apt-get install
-bash: gem: command not found

Let's create those symlinks now:

sudo ln -s /usr/bin/gem1.8 /usr/local/bin/gem
sudo ln -s /usr/bin/ruby1.8 /usr/local/bin/ruby
sudo ln -s /usr/bin/rdoc1.8 /usr/local/bin/rdoc
sudo ln -s /usr/bin/ri1.8 /usr/local/bin/ri
sudo ln -s /usr/bin/irb1.8 /usr/local/bin/irb



Step 6: Install Ruby on Rails! You can leave off the --no-rdoc and --no-ri switches if you're on a machine with plenty of ram. But just in case you've got a more modest setup (say 256MB or less) I'd just use the following:
sudo gem install rails --no-rdoc --no-ri


If you're just doing local development then you are basically done. You may want to install additional gems (such as mongrel). If you want to deploy Ruby on Rails onto a server then it's time to setup Nginx and Phusion.


Step 7: The folks responsible for Phusion (Hongli Lai & Ninh Bui) have done a remarkable job. They truly understand what it means to make using your product easy for customers. It's one reason we love Phusion. Nginx doesn't support loadable modules like Apache does, so Phusion will download, compile, and install it for you. What could be easier? To get started, let's download the packages we'll need:

sudo apt-get install libc6 libpcre3 libpcre3-dev libpcrecpp0 libssl0.9.8 libssl-dev zlib1g zlib1g-dev lsb-base



Step 8: We're going to create a directory for your application. In anticipation of Capistrano, let's put it in /var/www/myapp/current. Swap out the myapp with anything you like.
sudo mkdir -p /var/www/myapp/current

If you've got an existing Rails application, it doesn't hurt to populate that directory now. Just make sure that the root to the public directory is /var/www/myapp/current/public. Don't forget to chown it for www-data:

sudo chown -R www-data:www-data /var/www/myapp/current/



Step 9: Now it's time to install Phusion Passenger and let it do it's magic. It's critical that you have Phusion Passenger 2.2.1 or greater installed. This may not work with 2.2.0.

sudo gem install passenger
sudo passenger-install-nginx-module

At this point you'll be greeted with two options. The first will allow Passenger to do all the work while the second, for advanced users, allows you to point Phusion to your pre-installed Nginx. We're going to make life easy on ourselves and choose option 1. I'd also recommend keeping the default path (/opt/nginx).

The Nginx configuration file (nginx.conf) file is in /opt/nginx/conf/ They've done you the favor of adding the lines you need to get going. Defining the configuration file is beyond the scope of this tutorial, but I've included a sample file which you can download. This file assumes your application is in /var/www/myapp/current.

Sample nginx.conf file for Ruby on Rails, Nginx, Phusion Passenger.


Step 10: Time to make some Nginx tweaks for Ubuntu/Debian. If you're used to running nginx as installed by Debian or Ubuntu then you might notice that you've lost the familiar way to start and stop nginx. Gone is /etc/init.d/nginx stop and /etc/init.d/nginx/start. To get it back, copy (or download) this into a file called nginx in /etc/init.d/ (as root)
#! /bin/sh

### BEGIN INIT INFO
# Provides: nginx
# Required-Start: $all
# Required-Stop: $all
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: starts the nginx web server
# Description: starts nginx using start-stop-daemon
### END INIT INFO

PATH=/opt/nginx/sbin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/opt/nginx/sbin/nginx
NAME=nginx
DESC=nginx

test -x $DAEMON || exit 0

# Include nginx defaults if available
if [ -f /etc/default/nginx ] ; then
. /etc/default/nginx
fi

set -e

case "$1" in
start)
echo -n "Starting $DESC: "
start-stop-daemon --start --quiet --pidfile /opt/nginx/logs/$NAME.pid \
--exec $DAEMON -- $DAEMON_OPTS
echo "$NAME."
;;
stop)
echo -n "Stopping $DESC: "
start-stop-daemon --stop --quiet --pidfile /opt/nginx/logs/$NAME.pid \
--exec $DAEMON
echo "$NAME."
;;
restart|force-reload)
echo -n "Restarting $DESC: "
start-stop-daemon --stop --quiet --pidfile \
/opt/nginx/logs/$NAME.pid --exec $DAEMON
sleep 1
start-stop-daemon --start --quiet --pidfile \
/opt/nginx/logs/$NAME.pid --exec $DAEMON -- $DAEMON_OPTS
echo "$NAME."
;;
reload)
echo -n "Reloading $DESC configuration: "
start-stop-daemon --stop --signal HUP --quiet --pidfile /opt/nginx/logs/$NAME.pid \
--exec $DAEMON
echo "$NAME."
;;
*)
N=/etc/init.d/$NAME
echo "Usage: $N {start|stop|restart|reload|force-reload}" >&2
exit 1
;;
esac

exit 0


Next, let's set the permissions and make nginx load on a reboot:

sudo chmod +x /etc/init.d/nginx
sudo /usr/sbin/update-rc.d -f nginx defaults


Now you should be able to start and stop nginx using the command you're used to:

sudo /etc/init.d/nginx start



Bonus step In case it's not already installed, let's grab the perquisites and then install the mysql gem. This will improve performance on your webserver:
sudo apt-get install libmysqlclient-dev
sudo gem install mysql --no-rdoc --no-ri



Troubleshooting If things didn't go smoothly, here are some things to check:

1. There's an error log for nginx in /opt/nginx/logs/ Try looking to see if you get any hints there.

2. If you see a 403 forbidden error, make sure you have permissions set correctly. I'm running Nginx as www-data and I've set the group/user owners of my rails app to match using the chown -R www-data:www-data command. It doesn't hurt to confirm it.

3. Try running your rails app in development mode using the standard script/server. If it doesn't work there then it obviously won't work in production mode either

For help, you should check out the great railsforum or Mailing list front-end.

Swapping Control & Alt in Linux

Posted by on April 19, 2009

More for my own reference and in preparation of me completely gutting my system, I'm posting the contents of my .xmodmaprc file which swaps the left control and alt-keys. This allows me to use my linux laptop more like I used my mac, with the command key.

Feel free to copy the contents to a file called .xmodmaprc and run it using the xmodmap command:

xmodmap .xmodmaprc



remove Control = Control_L
remove Mod1 = Alt_L
keysym Control_L = Alt_L
keysym Alt_L = Control_L
add Control = Control_L
add Mod1 = Alt_L

Named Scopes and Thinking Sphinx

Posted by on April 18, 2009

I'm really not a fan of the Thinking Sphinx conditions syntax. It's different than what I'm used to using in ActiveRecord and has caused me some problems with dates and such. Instead I use named scopes whenever I can. For example, in Railfood, a site for locomotive parts, I use a named scope to show only products that are in stock. That causes some issues, not only because of all the nil records but because the default number of results returned is 20.

Note that this article is no longer that useful. Instead of named scopes please look at sphinx scopes. Article coming soon.


For example:
@p =Product.current.search("injector").size 

=> 20


or
@p =Product.current.search("123").size 

=> 20


One of the first gotchas (covered here too) is that if you actually inspect that array you'll see that it's filled with nil.

[nil, nil, nil, nil nil,nil, nil, nil, nil nil,nil, nil, nil, nil nil,nil, nil, nil, nil nil,]


To get around it, call the compact method on the array:

@p =Product.current.search("injector").compact.size 

=> 0


The other gotcha here is that if you actually have 20000 records that match "injector" but only 1 of them is current, you'll get zero results. To work around that you need to increase the maximum amount of results per page:

@p =Product.current.search("injector", :per_page => 20000).compact.size 

=> 1


But what about pagination? Thinking Sphinx does give us this, but the fact that both the methods are called :per_page kind of screws things up. To work around this, just call paginate on your results:

@p =Product.current.search("injector",
:per_page => 20000).paginate(:per_page => 8, :page => params[:page])


And that should do it. Of course, depending on how many results you have you may need to up the initial per_page from 20000 to something larger. This isn't ideal in my humble opinion, but it works for the moment. Other suggestions are welcome!

While we're on the subject of Thinking Sphinx, be sure to set :enable_star => 1 in your index block if you want to use wildcards in your searches.

Testing Attachment Fu in Rails Test Unit

Posted by on April 9, 2009

Whether you're using standard Test Unit or Shoulda, you should be testing models. But if a User model has to have a Photo, how do you set up a unit test to make sure it passes? I've read blog posts and forums threads on the issue but never found anything comprehensive enough for a entry-level rails tester to understand. So, with that in mind, read on for a quick tutorial.


I'm using Shoulda so my tests are geared for that syntax. If you're using Test Unit, feel free to modify accordingly. The meat of the answer is still the same.

First of all, let's assume you're in the test/unit/user.rb file doing your unit test. Your file starts off looking like this:
require File.join(File.dirname(__FILE__),"..","test_helper")

class UserTest < ActiveSupport::TestCase
end


You'll want to add your context block, setup block, and an assertion too.


context "A new user with a valid photo" do
setup do
@user = User.create(:email => "test@example.com", :login => "test", :password => "password123", :password_confirmation => "password123")
end

should "be a valid user" do
assert(@user.valid?)
end
end


Running the above test won't work if you're requiring a photo because you haven't actually set one up. To do that you'll have to use create the photo in the test like this:

@photo = Photo.create(:uploaded_data => fixture_file_upload("/files/mugshot.png",'image/png')


The tricky part is that the fixture_file_upload is a part of ActionController::TestProcess. So you'll need to include it for it to work. The final user.rb will therefore look like this:

require File.join(File.dirname(__FILE__),"..","test_helper")

class UserTest < ActiveSupport::TestCase
include ActionController::TestProcess
context "A new user with a valid photo" do
setup do
@photo = Photo.create(:uploaded_data => fixture_file_upload("/files/mugshot.png",'image/png'))
@user = User.create(:email => "test@example.com", :login => "test", :password => "password123", :password_confirmation => "password123", :photo => [@photo])
end

should "be a valid user" do
assert(@user.valid?)
end
end
end


Now when you run your test, it should pass:
ruby test/unit/user.rb

Started
.
Finished in 0.196 seconds.

1 tests, 1 assertions, 0 failures, 0 errors


A few things to look out for:

  • - The above assumes a User has many Photos which is why it's photos => [@photo] and not photo => @photo.

  • - You'll need to create a file called mugshot.png and put it in the test/fixtures/files directory (which you probably need to create)

  • - I'm using Shoulda, if you use straight test::unit or rspec, you'll need to change things appropriately.

  • - My example also assumes the only things required in your user model is the email, login, password, and photo. Adjust accordingly.


Hopefully the above works for you. Now that you're validating attachments correctly there's no reason not to do more testing!