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:

Update: Michael Buffington shares his Mongrel deployment for multiple stages

Posted at 1pm on 09/29/06 | Posted in , , | 1 responses | read on

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?

Posted at 2pm on 09/20/06 | Posted in , | 2 responses | read on

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 || {});
  }
});
      
Posted at 3pm on 09/19/06 | Posted in , , , , | 11 responses | read on

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

Posted at 12pm on 09/15/06 | Posted in , | no responses | read on

Older posts: 1 ... 3 4 5 6 7 ... 33