Skip to content

Creating and deploying a Rails app

This documents how I create Ruby on Rails applications and deploy them to my server.  I have created a bare bones apache web server that hosts ruby applications using MySQL.  It also assumes you have Ruby on Rails set up on a Macbook Pro.

Server Side

User

mkdir ~/myrumbleapp
mkdir ~/myrumbleapp/www
mkdir ~/myrumbleapp/www/current
mkdir ~/myrumbleapp/logs

GIT

sudo groupadd myrumbleapp
sudo usermod -a -G myrumbleapp {USER}
cd ~/myrumbleapp
mkdir git
sudo chown {USER}.myrumbleapp git
cd git
git init --bare --shared=group

MySQL

mysql -u root -p
mysql> create database myrumbleapp;
mysql> use myrumbleapp;
mysql> grant all on myrumbleapp.* to myrumbleapp@localhost identified by 'password';

Apache2

/etc/apache2/sites-available/myrumbleapp
<VirtualHost *:80>
ServerName www.myrumbleapp.com
ServerAlias myrumbleapp.com
DocumentRoot /home/{USER}/www.myrumbleapp.com/www/current/public
ErrorLog /home/{USER}/www.myrumbleapp.com/logs/error_log
TransferLog /home/{USER}/www.myrumbleapp.com/logs/access_log
</VirtualHost>
sudo ln -s /etc/apache2/sites-available/myrumbleapp /etc/apache2/sites-enabled/myrumbleapp
sudo /etc/init.d/apache2 restart

Client Side

On the client

First, create a skeleton rails application.  For this example, I will be using “myrumbleapp”.  We will also freeze the rails version into our application. That way we never have to worry about rails versions on serverrs.

cd ~/Sites/
rails myrumbleapp
cd ~/Sites/myrumbleapp
rake rails:freeze:gems

/.gitignore

tmp
log
mkmf.log
config/database.yml

Capistrano
cd ~/Sites/myrumbleapp
capify .

I use the following deploy.rb file.  The important part is that it creates a link from my shared/system/config/database.yml file into the release folder.  I keep the database.yml file out of GIT because it seems to be a best practice that other folks follow.  Not exactly sure it is necessary with my code since I host my own GIT repos accessible only via SSH.

/config/deploy.rb

default_run_options[:pty] = true
set :application, "myrumbleapp"
set :repository,  "{USER}@{SERVER.COM}:myrumbleapp/git"
set :user, "USER"
set :deploy_to, "/home/{USER}/myrumbleapp/www"
set :scm, :git
set :branch, "master"

role :web, "{SERVER.COM}"
role :app, "{SERVER.COM}"
role :db,  "{SERVER.COM}", :primary => true

namespace :deploy do
  desc "restart passenger"
  task :restart, :roles => :app, :except => { :no_release => true } do
    run "#{try_sudo} touch #{File.join(current_path,'tmp','restart.txt')}"
  end

  [:start, :stop].each do |t|
    desc "#{t} task is a no-op with passenger"
    task t, :roles => :app do ; end
  end

  desc "Symlink shared configs and folders on each release."
  task :symlink_shared do
    run "ln -nfs #{shared_path}/system/config/database.yml #{release_path}/config/database.yml"
  end
end

after 'deploy:update_code', 'deploy:symlink_shared'


GIT

Next we will check our application into GIT version control.

git init
cp config/database config/database.tmp.yml
git add .
git commit -m "initial checkin"

Configure the remote repository for GIT

git remote add origin {USER}@snowcaptech.com:myrumbleapp/git

Push the skeleton application to the server

git push origin master

Deploy the application

cap deploy:setup

cap deploy

cap deploy:migrations

Bare Ubuntu configuration for Ruby on Rails

General

Install Ubuntu 9.1 server using the Bare Server option

The first thing I like to do is add my account to the /etc/sudoers file. This just guarantees that I can always use sudo, even if I mistakenly mess up my group assignment.

sudo visudo
#  /etc/sudoers
root   ALL=(ALL) ALL
user ALL=(ALL) ALL

user should be replaced with the account you use to administer the box.  I like to do this because one time I messed up my group assignment on the only account that was able to admin the box.

alias sudo='sudo -E'

The reason we have that alias nonsense in there is because Ubuntu compiles the sudo command with the –with-secure-path option.  We want to pass our current path into the command.  The alias command is explained here.  I’m sure there are some security vulnerabilities with muti-user systems doing it this way, but I’m the only guy so no need to figure it out right now.

Next, update the apt-get configuration and then upgrade to make sure the latest packages are installed.

sudo apt-get update
sudo apt-get upgrade -y

Configure the server for static IP address.

SSH

Configure SSH.

Ruby

As I write this, the latest version of Ruby for apt-get is 1.8.7 patchlevel 174.

sudo apt-get install build-essential libssl-dev libreadline-dev rake -y
sudo apt-get install ruby rdoc ri rubygems libopenssl-ruby ruby-dev -y
I like to create a current symbolic link that links to the current gem so I can easily upgrade and not have to redo all my paths.
ln -s /var/lib/gems/1.8 /var/lib/gems/current

Now, source the file you just created.  Sourcing a file uses the contents of the file as if you typed them at the command prompt.  You just type a period followed by a space and then the filename.

. /etc/profile.d/gems.sh

You will notice that we did not install Rails.  This is intentional.  We will use

rake rails:freeze:gems

to freeze a known version of rails into our applications.

MySQL

sudo apt-get install mysql-server libmysqlclient-dev
sudo gem install mysql

Apache

We will install Apache2 as well as Passenger (mod_rails or mod_rack).

sudo apt-get install apache2 libapr1-dev apache2-prefork-dev -y
sudo gem install passenger
sudo /var/lib/gems/current/bin/passenger-install-apache2-module
After you run this, you will see instructions to add some lines to your Apache configuration file.  Mine looked like this:
LoadModule passenger_module /var/lib/gems/1.8/gems/passenger-2.2.8/ext/apache2/mod_passenger.so
PassengerRoot /var/lib/gems/1.8/gems/passenger-2.2.8
PassengerRuby /usr/bin/ruby1.8
LoadModule passenger_module /var/lib/gems/1.8/gems/passenger-2.2.8/ext/apache2/mod_passenger.so   PassengerRoot /var/lib/gems/1.8/gems/passenger-2.2.8   PassengerRuby /usr/bin/ruby1.8

Just copy this to it’s own file in the conf.d directory of apache2.

sudo vi /etc/apache2/conf.d/passenger.conf

GIT

Install and configure GIT on the server and the client.

Even Mac’s blue screen of death is pretty

Well, my hopes of my Mac never crashing were dashed.  I go the equivalent of the Windows blue screen of death.  Since this time, it has also locked up several times.  But even the Mac’s screen of death is pretty…

Mac Crash

You can’t tell from this picture, but it just looks like a dialog box that pops up on top of your desktop.  You can’t move your mouse.  You can’t type anything at your keyboard.  Your computer is just as dead as the Windows Blue Screen of Death.

Configuring Ubuntu for a static IP address

The next thing we need to do is to set the network with your static IP addresses. First, edit the /etc/network/interfaces to include the following.

#   /etc/network/interfaces
 auto eth0
 iface eth0 inet static
 address xxx.xxx.xxx.xxx
 gateway xxx.xxx.xxx.xxx
 netmask xxx.xxx.xxx.xxx

This assumes you want the ethernet card on eth0 configured.  After this, we need to configure the primary and secondary DNS servers

#   /etc/resolve.conf
 nameserver xxx.xxx.xxx.xxx
 nameserver xxx.xxx.xxx.xxx

After these are set, you need to restart the networking

sudo /etc/init.d/networking restart

Using SSH on Mac to access Ubuntu server 9.10

First, make sure you can SSH to your server. If you need to install ssh on the Ubuntu server, use:

sudo apt-get install ssh
mkdir ~/.ssh
touch ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
chmod 700 ~/.ssh

If you are wondering what the touch does, it just creates an empty file if it doesn’t exist.  That way i don’t have to write two sets of instructions based on whether you’ve ever had SSH configured before.  Next we are going to generate our keys and upload them to the server

ssh-keygen -t rsa
scp user@server.com:~/.ssh/authorized_keys ~/.ssh
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
scp ~/.ssh/authorized_keys user@server.com:.ssh/

What we are doing is copying the existing authorized_keys file to our local mac to add our key and then uploading it back.  We should now be able to login to our server using our new keys.

ssh user@server.com

The Mac will now prompt you for the password you used to create your ssh key.  Add it to your keychain.  From this point forward, you will no longer be prompted with your password.  Yay!

Creating a remote GIT repository

Clean, readable code is one of my highest goals when writing code.  I’m always thinking about the next guy, even when the next guy is just me, several months from now.   Nothing is worse than going into spagetti code and not knowing what the heck is going on.  Because of this, I like to refactor my code A LOT.  When you are using source control, you never get yourself into situations that you can’t get out of.  It’s also nice to go back and see how the code has evolved over time, to see why certain files were changed.  Because of all these benefits, I use source control even on my own tiny projects where I’m the only developer.

I have used visual source safe when I first got started and like all vss users, quickly moved away from it.  I’ve spent most of my career using perforce for work.  I tried out Subversion a long time ago, but I got tired of my database getting corrupted and having to drop to the command line to repair it.  I used a very early version of subversion and I hear that it is MUCH better now.  Anyway, I used perforce for my personal stuff because the first two clients were free.  Since I’m learning everything else, why not throw in GIT too.  It’s gotten a lot of press so I want to see what the fuss is all about.  Here is how I installed my first GIT repository on my ubuntu server.

ssh to the remote box and type the following commands:

sudo apt-get install git-core
sudo mkdir /var/git
sudo mkdir /var/git/snowcaptech.git
sudo chown jason /var/git/snowcaptech.git
sudo chmod 775 /var/git/snowcaptech.git
cd /var/git/snowcaptech.git
git init --bare --shared=group

What we are doing is creating an empty directory in the /var/git folder where I like to keep all my git repositories.  We are then changing the owner to me and setting permissions for me to write and everyone else to be able to read.  The git init –bare creates a repository without a working copy.

then to add a project from your local client to the remote repository, you would first tell GIT about your project by going to the directory of your project and from a command prompt typing:

git init
git add .
git commit -m "initial checkin"

This initializes your repository and commits it to your local GIT repository.  This is all you need to do to have a working source control system for just yourself.

I like to make my source available on a server so I can access it from anywhere on the planet.  I never need to do this, but it is cool to be able to do this.  In order to “push” your local GIT repository to the remote repository, you have to tell your local repository about the remote.  Sorry if that was confusing.  Read it a few times. :)

git remote add origin jason@dev.snowtech.com:/var/git/snowcaptech.git
git push origin master

As long as your settings are all correct, your repository should now be at the remote repository.

Windows Mobile Smartphone vs Pocket PC

I recently encountered an issue where a Windows Mobile 5 application I wrote wasn’t working on a Windows Mobile 6 smart phone.  This particular phone was in Spain so I was not able to hold it in my hands.

Initially I believed it was because I didn’t have the Windows Mobile 6 SDK installed.  I needed the app to be backward compatible to Windows Mobile 5 so I haven’t messed around with the new SDK yet.  When I looked (skimmed) through the documentation, everything led me to believe that most windows mobile 5 apps will run on a Windows Mobile 6 device.  There is the exception that if you are using undocumented APIs, they aren’t guaranteed to work.  Since the app I’m working on logs GPS coordinates in the background, it was possible that I was using some calls through a 3rd party library.

Turns out my problem was much more basic.  Once I got my hands on an actual Windows Mobile 5 smart device to test on, I discovered the problem right away.  The app was failing when it tried to create a list box.  My simplistic thinking was that you write applications for “Windows Mobile 5″.  You actually write for either Windows Mobile Smartphones or Windows Mobile Pocket PCs.  One is more limited than the other.  The naming is still confusing to me.  My iPhone is a “smart phone” so I assumed that Smartphone was simply Pocket PC + phone.  WRONG.  Windows Mobile 6 makes it even worse.  They label one sdk “standard” and the other “professional”.  I am pretty sure professional = pocket pc = more functionality.

So I learned that if you want to write lowest common denominator applications for Windows Mobile, you need to target the smart phone.  And you should really test your interface at different resolutions because those smart phones have tiny screens.

Java ME Date add functionality

Programming for the blackberry can be frustrating.  It seems that they have taken every useful function I’ve used in the past and thought “is there a harder way to do this already?”  If the answer was yes, they ripped out the function.  The latest example I’ve uncovered while developing is simply adding time and dates to arrive at some time in the future.  I am trying to add a number of seconds to an existing timestamp.  In standard Java, there is a handy add function that does this for you.  Unfortunately, in Java ME, they have ripped it out.  To get that functionality back, you need to understand when you call getTime() on a date, it returns the number of milliseconds since January 1, 1970, 00:00:00 GMT as a long. The value “1″ represents one millisecond.  So, to add one day to your date, you simply need to determine how many milliseconds in one day and add that to the value returned by getTime().

int milliseconds = 1 * 1000 * 60 * 60 * 24;  // 1 day * 1000 milliseconds/second * 60 seconds/minute * 60 minutes/hour * 24 hours/day
Date newDate = new Date(myDate.getTime() + milliseconds);

This will add one day to your date.

Making an HTTP Request on BlackBerry

Seems like it is rather complex to make a simple HTTP request on BlackBerry.  The code for opening a connection is straightforward:

String url = "http://10.0.0.1";
HttpConnection conn = conn = (HttpConnection)Connector.open(url);

The complexity comes in because I am developing on a  BlackBerry device without service.  When I try to execute the above code, I get an IOException stating “Tunnel failed”.  From searching, it appears that I get this message because APN is not set.  I can not set it because I do not have service for this phone.  If you don’t have the APN set, it does not automatically try and use the WIFI connection.  Apparently there are six different methods for using a connection.  Each one of these requires a different string appended to the URL to determine which one gets used.

BlackBerry MDS

The first method is using BlackBerry MDS.  This forces all traffic to go through the BlackBerry enterprise server.  It encrypts all traffic using AES or Triple DES.  My task is to specifically avoid using the MDS because we are creating a cloud service where the customer does not need to have any server let alone a BlackBerry enterprise server.

String url = "http://10.0.0.1;deviceside=false";

BlackBerry Internet Service

The second method is to use the BlackBerry Internet Service.  This is only available to approved BlackBerry Alliance Program members.  There are different tiers. The public tier which is free does not offer the BlackBerry Internet service feature.  The minimum tier that supports this is the BlackBerry Alliance Associate Member which costs $2,000/yr and requires 45 member points.  To get this, you basically have to make losts of money using the BlackBerry platform or promot it in other ways.  You can look at their FAQ for more details, but basically we don’t have this option available to me right now.

TCP Stack

The third method is to use the TCP stack directly.  In order to use this, you need to have the APN username and password.

String url = "http://10.0.0.1;deviceside=true";

Wi-Fi

The fourth method is to use wi-fi.  This is what I’m interested in right now.

String url = "http://10.0.0.1;interface=wifi";

WAP 1.x

The fifth method is to use WAP 1.x gateway.  This is supported over the carriers networks.  You have to contact the carriers to get a list of their WAP gateway parameters.

String url = "http://10.0.0.1;WAPGatewayIP=127.0.0.1;WAPGatewayAPN=carrier.com.gprs";

WAP 2.0

The sixth way is to use WAP 2.0.  You need to loop through the records and find the uid of the desired ServiceRecord in the ServiceBook.  This is simpler than WAP 1.x because you don’t need all the gateways and settings from the carriers.  This is supported as of 4.2 on the BlackBerry.

// String uid = get from the device
String url = "http://10.0.0.1;ConnectionUID=" + uid;

Going Forward

I would really like the device to try wi-fi first before attempting to use the potentially expensive data plan.  I am going to have to write some code to test the wi-fi first, and then attempt the data connection if this fails.  It is conceivable that the user wouldn’t want the app to transmit unless the wi-fi connection is available.  Not as simple as just opening up a connection and firing off a GET request.

Blackberry development environment

Blackberry applications are written in Java.  Since the java mantra is write once, test everywhere.  er.  sorry, run everywhere.  You would think that you could choose whatever OS you would like to develop for the Blackberry.  While the documentation claims you can write Blackberry code on any platform, the reality for a new developer is quite different.  The Eclipse plugin they offer only runs on Windows.  Very irritating considering I can run eclipse on my Mac as well as my Windows machine.  Rather than fight it, I am going with the flow and using Windows XP 32bit inside my Parallels virtual machine.

Very important that you pay attention to the version of eclipse that the plugin works with.  I didn’t and installed the lastest eclipse (3.5 as I write this).  I went to “Help > install new software…” using “http://www.blackberry.com/go/eclipseUpdate”.  I got the following error:

Cannot complete the install because one or more required items could not be found.
  Software being installed: BlackBerry JDE Plug-in for Eclipse 1.0.0.67 (net.rim.EclipseJDE.feature.group 1.0.0.67)
  Missing requirement: BlackBerry JDE Plug-in for Eclipse 1.0.0.67 (net.rim.EclipseJDE.feature.group 1.0.0.67)
  requires 'org.eclipse.debug.ui [3.4.0,3.5.0)' but it could not be found

I had to download the gannymede 3.4.2 version of eclipse and install it.  Then I did the software update using the http://www.blackberry.com/go/eclipseUpdate URL.  It prompted me half a dozen times for my Blackberry Developer zone ID.  I dutifully entered it each time (annoying).  After this, I had a working environment.

In the BlackBerry JDE Plug-in for Eclipse Installation and Configuration demo video, They show configuring the BlackBerry workspace.  Well, this option is not enabled until you created a BlackBerry project.  Also in this video, you will see them run the simulator just by clicking run.  This did not work for me.  In order to actually launch the application in the simulator, you need to right click the project and select “Activate for Blackberry”.  If you don’t do this, Eclipse will launch the simulator and you will see debug output, but your project will not show up on the device.

The next step is to get it working on an actual BlackBerry device.  When I first attempted this, it would not work with my old version of parallels.  I updated to the latest build of parallels and then Windows could recognize the USB device.  My mac took control and parallels did not prompt me like it usually does when I connect a USB device.  There is an icon in the lower right of the frame.  Click the USB icon there and select “Research in Motion”

I haven’t been able to get the Eclipse IDE to install and run the application on my device.  I’ve had to use the tool JavaLoader.exe which is located in the bin directory of your JDE installation.

JavaLoader.exe -usb load HelloWorld.jad
JavaLoader.exe -usb erase -f HelloWorld.jad

These commands will install and remove your application.  This assumes that the bin directory is in your path and you are in the location where your *.jad and *.cod file.