Multi-stage deployments with Capistrano
I'm seeing a recurring theme in my feed reader today - multi-stage deployments with Capistrano. It seems that those of us working in larger companies have all hit the same issue - we have some form of user acceptance, staging or other system which mirrors the production in configuration; and we want to use Capistrano to deploy to it as well as production. I know that my current company has a 5-stage process for pushing out releases (we're in financial services, and very paranoid. Whether they'd ever accept Capistrano is another issue).
So here are my lazy pointers to the discussions:
Ola Bini, who's been doing some great posts on ruby metaprogramming, calls out his need for a "production_test" environment.
Simon Harris shares some snippets of deploying to his staged server running mongrel.
Jamis Buck, the creator of Capistrano, weighed in a while ago on multiple deployment stages
Update: Michael Buffington shares his Mongrel deployment for multiple stages
The Reverse Captcha
Tim O’Reilly posed an interesting side-question on the O’Reilly Radar blog in a write-up on Google’s Image Labeler.
This is an interesting variation on the Turing test, in which humans generate and grade tests that most humans can pass, but current computer programs cannot pass. Is there another variation in the future, in which computers generate and grade tests that computers can pass, but humans cannot pass?
I’m not sure of the applicability of such a test, but the idea is intriguing none the less. I would think that there are plenty of problems that would be too difficult to solve by a human, but they are based on the assumption that a time limit is given for the production of a solution. You could simply give a very difficult mathematical question with a subsecond solve time.
One possible example without a need for a time-limit: a picture with a randomly generated steganographic message embedded and a text field with no explanation - computers fill it with the embedded message, humans would likely try to ascertain the image’s contents as the answer. Sort of a reverse captcha. The assumption here is that the algorithm for generating the random message is truly random, or not able to be reverse-engineered and the next value guessed by a human (sort of like those random card shufflers in Vegas that aren’t supposed to be beatable).
In any case, the idea is thought provoking. Anyone else have examples?
Prototype based Tag Autocompletion
I've been working on a website that uses tags internally. The acts_as_taggable plugin has been great, but it only takes care of the ActiveRecord portion of tagging. One of the pieces of functionality I needed was to create an autocompleter for users to enter a list of tags on a record. Below is a pure copy-paste dump of my implementation. I'm not claiming it's good code or the right way top do things, but for those looking to use an autocompleter for a text field holding tags might find it useful. The implementation is close to that used by delicious in the tags field on posting new bookmarks.
Here's an example usage:
<label for="tags">Tags</label>
<%= text_field_tag 'tags', @tags.join(' '), :size => 80, :autocomplete => "off" %>
<div class="auto_complete" id="tags_auto_complete"></div> And the prototype-based class in javascript. You'll probably want to stick this in your application.js file if you're using Rails.
String.prototype.trim = function(){ return this.replace(/^\s+|\s+$/g,'') }
Autocompleter.Tag = Class.create();
Autocompleter.Tag.prototype = Object.extend(new Autocompleter.Base(), {
lastEdit: '',
currentTag: {},
initialize: function(element, update, array, options) {
this.baseInitialize(element, update, options);
this.options.array = array;
},
getCurrentTag: function() {
if(this.element.value == this.lastEdit) return true // no edit
if(this.element == '') return false
this.currentTag = {}
var tagArray=this.element.value.toLowerCase().split(' '), oldArray=this.lastEdit.toLowerCase().split(' '), currentTags = [], matched=false, t,o
for (t in tagArray) {
for (o in oldArray) {
if(typeof oldArray[o] == 'undefined') { oldArray.splice(o,1); break }
if(tagArray[t] == oldArray[o]) { matched = true; oldArray.splice(o,1); break; }
}
if(!matched) currentTags[currentTags.length] = t
matched=false
}
// more than one word changed... abort
if(currentTags.length > 1) {
// hideSuggestions(); TODO Fix this!
return false;
}
this.currentTag = { text:tagArray[currentTags[0]], index:currentTags[0] }
return true
},
getUpdatedChoices: function() {
this.updateChoices(this.options.selector(this));
},
onKeyPress: function(event) {
if(this.active)
switch(event.keyCode) {
case Event.KEY_TAB:
case Event.KEY_RETURN:
this.selectEntry();
Event.stop(event);
case Event.KEY_ESC:
this.hide();
this.active = false;
Event.stop(event);
return;
case Event.KEY_LEFT:
case Event.KEY_RIGHT:
return;
case Event.KEY_UP:
this.markPrevious();
this.render();
if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
return;
case Event.KEY_DOWN:
this.markNext();
this.render();
if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
return;
}
else
if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
(navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return;
this.lastEdit = this.element.value;
this.changed = true;
this.hasFocus = true;
if(this.observer) clearTimeout(this.observer);
this.observer =
setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
},
updateElement: function(selectedElement) {
if (this.options.updateElement) {
this.options.updateElement(selectedElement);
return;
}
var value = '';
if (this.options.select) {
var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
} else
value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
var lastTokenPos = this.findLastToken();
if (lastTokenPos != -1) {
var newValue = this.element.value.substr(0, lastTokenPos + 1);
var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
if (whitespace)
newValue += whitespace[0];
this.element.value = newValue + value;
} else {
tagArray = this.element.value.trim().split(' ')
if (!this.getCurrentTag()) return; // We had a problem getting the current tag, just return?
tagArray[this.currentTag.index] = value;
this.element.value = tagArray.join(' ');
}
this.element.focus();
if (this.options.afterUpdateElement)
this.options.afterUpdateElement(this.element, selectedElement);
},
setOptions: function(options) {
this.options = Object.extend({
choices: 10,
partialSearch: true,
partialChars: 2,
ignoreCase: true,
fullSearch: false,
selector: function(instance) {
var ret = []; // Beginning matches
var partial = []; // Inside matches
var entry = instance.getToken();
entry = entry.trim().split(' ').pop()
var count = 0;
for (var i = 0; i < instance.options.array.length &&
ret.length < instance.options.choices ; i++) {
var elem = instance.options.array[i];
var foundPos = instance.options.ignoreCase ?
elem.toLowerCase().indexOf(entry.toLowerCase()) :
elem.indexOf(entry);
while (foundPos != -1) {
if (foundPos == 0 && elem.length != entry.length) {
ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
elem.substr(entry.length) + "</li>");
break;
} else if (entry.length >= instance.options.partialChars &&
instance.options.partialSearch && foundPos != -1) {
if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
foundPos + entry.length) + "</li>");
break;
}
}
foundPos = instance.options.ignoreCase ?
elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
elem.indexOf(entry, foundPos + 1);
}
}
if (partial.length)
ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
return "<ul>" + ret.join('') + "</ul>";
}
}, options || {});
}
});
Note to Subaru Marketing
Dear Subaru Marketing:
I appreciate your efforts to market your vehicles in my area; and I also appreciate your effort in creating regional specific advertisements - it lends a nice local feel to them. Bravo! However, you may want to actually make sure that the region you're airing the commercials within actually makes sense.
There are other cities in New York state besides New York city. Upstate / Western New York is not referred to as the tri-state area. So, your slogan to tell use that we "try everything" and maybe that's why we're the "try-state" area is cute as buttons and puppy dogs - and horribly wrong. The area you're referring to is a 400 mile, 7-hour drive across nowheresville from us. Perhaps you could create one specific for our area: Since we are "Western" New York you may want to mention cowboys and indians. Or perhaps use the title "Finger Lakes region" and talk about rude gestures.
Sincerely,
A member of your target audience