Uninistalling All Gems
Useful for testing that your environment.rb has everything it needs:
gem list --no-versions | xargs sudo gem uninstall -a
Useful for testing that your environment.rb has everything it needs:
gem list --no-versions | xargs sudo gem uninstall -a
Vlad the Deployer’s documentation barely exists. So, I took notes setting up vlad for my personal project, BeerTap.Me, and collected them into this post.
I’m using the following software:
The vlad gem itself only supports Subversion. There is a separate gem, vlad-git, which contains a plugin for Git. You have to install both gems:
sudo gem install vlad
sudo gem install vlad-git
Next, import vlad’s Rake tasks by adding the following to your Rakefile:
begin
require 'vlad'
Vlad.load :scm => :git
rescue LoadError
# do nothing
end
The server to which you are deploying does not need to have the vlad gem installed, so the begin/rescue allows the exception to be safely swallowed when running Rake on the server. The :scm => :git argument tells vlad to use the git plugin.
Next create a deployment file. By default, vlad looks for this in config/deploy.rb. Mine looks like this:
set :domain, 'beer.chendry.org' # where to ssh
set :deploy_to, '/var/www/BeerTap.Me/' # target directory on server
set :repository, 'git@github.com:chendry/BeerTap.Me' # git repo to clone
set :revision, 'master' # git branch to deploy
set :web_command, 'sudo apache2ctl' # command to start/stop apache
rake vlad:setupThis task needs to be ran only once. It connects to the remote machine and creates the :deploy_to directory and the following directotry structure inside of it:
scm/releases/shared/log/shared/pids/shared/system/You’re probably going to have to tweak the permissions a bit in order to get this to work. I created the deploy directory itself manually, enabled write access for my ssh user, and let vlad create the remaining directories.
rake vlad:updateThis is where most of the interesting stuff happens. Not much is documented about what goes on behind the scenes, though, so here’s a brief summary:
First, vlad ssh’s to your remote server’s deploy directory. Then, it blows away everything in scm/repo and replaces it with a fresh clone of the Git repository. Next it tar’s up the branch, creates a release subdirectory named after the current date and time, and extracts the tar into it. Then it deletes the log, public/pids, and tmp/system directories from the freshly deployed Rails site and replaces them with symlinks to shared/log, shared/pids, and shared/system directores, respectively.
Finally, once everything is deployed into the release subdirectory, vlad points the current symlink in the deploy directory to the new release (e.g., /var/www/BeerTap.Me/releases/20100222210521/.) Apache should point to the current directory, as it always points to the current release.
rake vlad:migrateThis task runs rake db:migrate RAILS_ENV=production for the current deployment on the server.
Here is where I ran into my first problem. There is no database.yml in a fresh clone of my rails app. Instead there is a database.yml.example. (I don’t like to keep my production passwords in version control.)
To resolve this, I created a shared/config directory and put my real database.yml there. Then I added a new Rake task to deploy.rb that copies the shared config files into the current release. (Thanks to Helder’s post for the idea:)
namespace :vlad do
remote_task :copy_config_files, :roles => :app do
run "cp #{shared_path}/config/* #{current_path}/config/"
end
end
Now, rake vlaid:migrate runs fine so long as rake vlad:copy_config_files is ran first.
rake vlad:startNOTE: In vlad 2.0.0, vlad:start has a bug which was reported, months ago, with solution, here and here. Maybe this will be fixed soon, but in the meantime, running the vlad:update and vlad:start tasks as part of the same Rake invocation is the easiest workaround.
The vlad:start task restarts the application by touching tmp/restart.txt and restarts the web server using apache2ctl. I ran into a permission issue here: my ssh user did not have permission to run apache2ctl. So, I changed :web_command to "sudo apache2ctl". However, that too failed with the message:
sudo: no tty present and no askpass program specified
rake aborted!
execution failed with status 1: ssh beer.chendry.org sudo apache2ctl restart
The problem is that there is no terminal and consequently sudo can’t prompt the user for the password. To fix this, I edited the sudoers file (visudo) on the server to allow my ssh user to run apache2ctl without a password:
chendry ALL=(ALL) ALL, (ALL) NOPASSWD: /usr/sbin/apache2ctl
Finally, add a rake task to your deploy.rb to do all the steps for a full deploy:
namespace :vlad do
task :deploy => %w[
vlad:update
vlad:copy_config_files
vlad:migrate
vlad:start
]
end
With that, vlad will do a full deployment to the server: install the latest code, install the configuration files, migrate the database, and (re)start the application/webserver.
One of the requirements in my current pet Ruby project is to, given the address of a bar, determine its latitude and longitude coordinates.
This is easy with the GeoKit gem:
class Bar < ActiveRecord::Base
validates_presence_of :name, :address1, :city, :state, :zip, :phone
acts_as_mappable :auto_geocode => { :field => :street_address }
private
def street_address
parts = [ address1, address2, city, "#{state} #{zip}" ]
parts.compact.join(', ')
end
end
Whenever a new bar is saved, GeoKit automatically calls the model object’s street_address method to get a string to pass to a provider. (Providers include Google, Yahoo, Geocoder.us, etc.) GeoKit uses the provider’s response to populate the model’s latitude and longitude attributes:

With this in place, you can query to find all bars within a certain distance from an origin location:
Bar.find(:all, :origin => '68506', :within => 5)
This generates the following SQL:
SELECT
*,
(
ACOS(
least(
1,
COS(0.71180079486811) * COS(-1.68663538782878) *
COS(RADIANS(bars.lat)) * COS(RADIANS(bars.lng)) +
COS(0.71180079486811) * SIN(-1.68663538782878) *
COS(RADIANS(bars.lat)) * SIN(RADIANS(bars.lng)) +
SIN(0.71180079486811) * SIN(RADIANS(bars.lat))
)
) * 3963.19
) AS distance
FROM
`bars`
WHERE
(
(
(
bars.lat > 40.7108964735732
AND bars.lat < 40.8554663264268
AND bars.lng > -96.7325543757321
AND bars.lng < -96.5416242242679
)
)
AND
(
(
ACOS(
least(
1,
COS(0.71180079486811) * COS(-1.68663538782878) *
COS(RADIANS(bars.lat)) * COS(RADIANS(bars.lng)) +
COS(0.71180079486811) * SIN(-1.68663538782878) *
COS(RADIANS(bars.lat)) * SIN(RADIANS(bars.lng)) +
SIN(0.71180079486811) * SIN(RADIANS(bars.lat))
)
) * 3963.19
) <= 5
)
)
I created my first Flex application today and it was remarkably painless. Here’s how:
First download and install the Flex 3 SDK. Unzip it anywhere and add the bin subdirectory to your path.
Next create a file called hello.mxml:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
import mx.controls.Alert;
]]>
</mx:Script>
<mx:Button id="button" label="Hello, World!"
click="Alert.show('hi!')" />
</mx:Application>
Then compile it using mxmlc:

Next, embed the compiled .swf file in a webpage using the following HTML:
<html>
<head>
<title>Chad's First Flash Application</title>
</head>
<body>
<object type="application/x-shockwave/flash" data="hello.swf"
width="200" height="200" />
</body>
</html>
Here’s the result:

This tutorial describes how to create an iPhone application using Xcode and Interface Builder. I’m assuming that you have installed Xcode and the iPhone SDK. The application prompts the user for a name and displays a message when the user touches a button.
To begin, start Xcode, and create a new view-based application:

Next, double-click on HelloViewController.xib. This will launch Interface Builder. Open the Library (Tools → Library) if it’s not already visible, and drag a Text Field, Round Rect Button, and Label onto the view window. Arrange them as they appear in the following screenshot. Double-click on the button to edit it’s text.

Next we need to add two instance variables and one method to the HelloViewController class. The two instance variables (outlets) will hold pointers to the UITextField and UILabel controls we added using Interface Builder. The method (action) will be wired up to execute in response to the user touching the Hello button. Go back to Xcode and edit your HelloViewController.h file to look like this:

Next, save everything (important!) and go back to Interface Builder. We need to tell Interface Builder to “wire up” our variables (outlets) and methods (actions) to the controls we placed on the view. To do this, control+click “File’s Owner,” drag to the text field, release, and select the outlet you want to attach. In this case, select “name.”

Do the same thing to attach the UILabel to the message outlet: Control+Click “File’s Owner,” drag to the label, release, and select message.
Now we need to attach the button to the hello: action. This is done similiarly to how we connected our outlets, except in the reverse direction. Control+click the button, drag to “File’s Owner,” release, and select the “hello:” action:

Now that we have our user interface wired up to our view controller, the only thing left to do is implement the hello: action. Open up the HelloViewController.m file in Xcode. You’ll notice a bunch of commented-out code and empty methods. These are all safe to delete. Edit the file so that it looks like this:

At this point, everything is ready to run. To recap, we have:
name for the text field, and another called message for the label.hello: to our view controller.hello: action in our view controller.Click Run → Go. This will compile the application and run it using the iPhone Simulator:

UPDATE: 2009-09-25: Roughly two months after introducing this console application to our team, the vast majority of the team voted to enable multiple checkouts. Next stop, Git!
Well, not really. But at least now I have a compromise to help wean the company off exclusive checkouts. Here’s the trick: when somebody has a file checked out that you need to modify, do this:
If the other person beats you to the checkin, he will notice nothing. However, if you check in first, TFS will prompt him to merge before checking in. (Obviously, if your team is accustomed to pessimistic locking, you should let the the other person know what you’re up to.)
I’ve created a program to do this quickly so that others don’t catch on to your mischief. It has a few quirks (no error checking and some assumptions about the working directory) but it works rather nicely:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
class Program
{
static VersionControlServer _sourceControl;
static Workspace _workspace;
static TeamProject _project;
static void Main(string[] args)
{
ConnectToTfs();
try
{
_project.ExclusiveCheckout = false;
UnlockFiles(args);
CheckoutFiles(args);
}
finally
{
_project.ExclusiveCheckout = true;
}
}
private static void ConnectToTfs()
{
var wsInfo = Workstation.Current.GetLocalWorkspaceInfo(".");
var tfs = TeamFoundationServerFactory.GetServer(wsInfo.ServerUri.ToString());
_sourceControl = (VersionControlServer)tfs.GetService(typeof(VersionControlServer));
_workspace = _sourceControl.GetWorkspace(wsInfo);
_project = _workspace.GetTeamProjectForLocalPath(".");
}
static void UnlockFiles(string[] localPaths)
{
var serverPaths = GetServerPaths(localPaths);
var pendingSets = _sourceControl.QueryPendingSets(serverPaths, RecursionType.Full, null, null);
foreach (var pendingSet in pendingSets)
UnlockItemsFromPendingSet(serverPaths, pendingSet);
}
static string[] GetServerPaths(string[] localPaths)
{
return new List<String>(localPaths)
.Select(arg => _workspace.GetServerItemForLocalItem(arg))
.Select(arg => arg.ToLower())
.ToArray();
}
static void UnlockItemsFromPendingSet(string[] serverPaths, PendingSet pendingSet)
{
foreach (var change in pendingSet.PendingChanges)
{
if (serverPaths.Contains(change.ServerItem.ToLower()))
{
var ws = _sourceControl.GetWorkspace(pendingSet.Computer, pendingSet.OwnerName);
if (ws.SetLock(change.ServerItem, LockLevel.None) > 0)
Console.WriteLine("Lock unset on {0}", change.ServerItem);
}
}
}
private static void CheckoutFiles(string[] args)
{
_workspace.PendEdit(args);
}
}