Dynamic Form and Dynamic Errors for Rails 3
Article posted by Joel Moss on 09 Aug 2010   |  

Rails 3 is awesome! BUT...

For some unknown reason, the powers that be decided to remove the error_message_on and error_messages_for form helpers. I'm not sure about every one else, but I use these in pretty much every single form that I create, so I just have no idea why it was removed.

Fortunately, all is not lost. The code for these helpers was extracted out into a plugin called dynamic_form. But unfortunately it doesn't seem to have been kept up to date, as the last commit was back on June 12th. Running the tests failed all over the place, simply because the HTML returned has changed slightly in the release candidate. So I took it upon myself to fork the code and managed to get the tests passing with Rails 3 RC. I then also packaged it up into a Gem which you can grab now and install in your Rails 3 app.

My next task was to get the custom-err-msg plugin working with Rails 3. But rather than fix and refactor that as it is, I decided to simply integrate it into the dynamic_form plugin, as it's functionality is closely related.

So the dynamic_form plugin has been enhanced a little, so please install and let me know what you think. I've put in a pull request to the official dynamic_form repo, so hopefully they will accept that soon. But in the meantime, you can still use my version by simply running gem install dynamic_form or adding to your Gemfile (if you are using Bundler) gem "dynamic_form".


Handling Dates in MongoDB
Article posted by Joel Moss on 09 Jul 2010   |  

I've been using MongoDB quite a bit recently in several different projects, and it kicks ass. But the hardest thing to learn and to get used to with Mongo, is it's schemaless structure. With SQL databases like MySQL, you always have a defined and very rigid structure, which is of course the schema for each table you create. You create a table with a few columns; each column has a set type, a length, and a whole host of other variables.

It's this rigid structure that most of us have gotten used to for years, and that is the hardest thing to shake off when using NoSQL databases such as MongoDB.

But that is not to say that Mongo has no defined structure. In fact, that is far from the truth. Mongo and all schemaless databases have defined structure, but the difference is that that structure is defined by you. And that is what makes Mongo so cool. No need to actually create any tables and define each column - Just insert some data in your own defined way.

But there is a side effect of this schemaless idea. Even though you no longer have to define a schema in the traditional sense, you still have to think about the structure of each collection, and how that will look. In fact, there is a lot more to think about, as there are a lot more ways to structure your data with Mongo and other schemaless databases.

It's somewhat of a steep learning curve, but well worth sticking with. I'm still leaning the best ways to structure certain data types, and dates is one that I think I've nailed.

Nearly all your collections will no doubt contain some sort of date or time stamp. While Mongo does have it's own native Date() object, it's only really useful within the Mongo shell. I need to use it within PHP and Ruby and other languages. Up until recently, I've been saving datetime as "2010-07-09 13:56:31". This works fine most of the time, but when you start needing to gather your data in more advanced ways, it starts to fall apart.

For example, I'm currently using Mongo and the equally awesome Redis for logging data for ShermansTravel.com. I needed to log a bunch of data each time a user clicks one of our paying ads. I ended up with collection records like this: (in PHP, as that is what this project calls for unfortunately)


  {
    "_id" : ObjectId("4bfea7246c6151d127f80100"),
    "button_rank" : "5",
    "category" : "flights",
    "class" : "Economy",
    "cpc" : "0.17",
    "destination_place_id" : "197868",
    "from_code" : "ORD",
    "from_code_include_nearby" : false,
    "travelers" : "1",
    "created" : "2010-03-29 20:15:34"
  }

Notice the the created timestamp on the last line there. Nothing special there, and I can find by the created date without any issue at all. But I need to now count all clicks grouped by day, not time. This is where the above falls apart.

This is what I tried:

  $keys = new MongoCode('function(doc) { return { created: doc.created.split(" ")[0] }; }');
  $reduce = new MongoCode('function(doc, prev) { prev.count++; }');
  $clicks = $this->group($keys, array('count' => 0), $reduce);

Again, this works... almost! This week, the clicks collection ended up with 900,000 records, and using the above group query kept timing out. The problem is the second line where instead of simply specifying which key(s) to group by, I have to pass a PHP MongoCode object containing a Javascript function that creates and returns a custom key. In this case, I am simply grabbing the created key of each record, and splitting the string, so I have the date and the time. I then return the date only, and can now group by date. This means that Mongo is iterating over every record and applying that function to each one, which obviously can take some time on larger collections, and completely ignores any indexes you may have created.

So I quickly determined from this that I was storing the date and time in the wrong way. I need to split up the date and time. I now have this:


  {
    "_id" : ObjectId("4bfea7246c6151d127f80100"),
    "button_rank" : "5",
    "category" : "flights",
    "class" : "Economy",
    "cpc" : "0.17"
    "destination_place_id" : "197868",
    "from_code" : "ORD",
    "from_code_include_nearby" : false,
    "travelers" : "1",
    "created" : { "d" : "2010-03-29", "t" : "20:15:34" }
  }

The last line still shows the created date and time, but instead stores each in an embedded object or hash. This means that I can still find by created date and time with just a little change, but more importantly, it means I can group much easier and much, much faster:

  $key = array('created.d' => 1);
  $reduce = new MongoCode('function(doc, prev) { prev.count++; }');
  $clicks = $this->group($key, array('count' => 0), $reduce);

As you can from the second line in the above, I am no longer passing a function to build the key I want to group by - which was what was causing the problem. I now simply pass the key I want to group by: created.d. The code is leaner and the query is loads faster, and my indexes are respected.


Converting HTML Entities to Characters with Javascript
Article posted by Joel Moss on 29 Jun 2010   |  

So I use Ajax quite a bit in many of my projects. And why not? It kicks ass! I have one project which dynamically updates specific elements of an HTML page via Ajax. Something like this:

$.get('/travel_guide/Paris', function(data){
  $('#advice').text(data);
});

The problem I have, is that sometimes that data can include HTML entities like & and >, and for reasons I don't want to bore you with, I cannot do anything about it on the server side. When inserting a block of text that contain HTML entities into the DOM, those entities are not parsed as such, and get printed exactly as they are.

What I need is a Javascript function that can take a string and parse it for any HTML entities, then replace each one with their HTML character. And this is what I came up with...

function entityToHtml(string) {
	for (var i in entity_table) {
		if (i != 38) {
			string = string.replace(new RegExp(entity_table[i], "g"), String.fromCharCode(i));
		}
	}
	string = string.replace(new RegExp("&#(x?)(\\d+);", "g"), String.fromCharCode(((p1 == 'x') ? parseInt(p2, 16) : p2)));
	string = string.replace(new RegExp(entity_table[38], "g"), String.fromCharCode(38));
	return string;
}

var entity_table = {
  //	34: """,		// Quotation mark. Not required
  38: "&",		// Ampersand. Applied before everything else in the application
  60: "<",		// Less-than sign
  62: ">",		// Greater-than sign
  //	63: "?",		// Question mark
  //	111: "o",		// Latin small letter o
  160: " ",		// Non-breaking space
  161: "¡",		// Inverted exclamation mark
  162: "¢",		// Cent sign
  163: "£",		// Pound sign
  164: "¤",	// Currency sign
  165: "¥",		// Yen sign
  166: "¦",	// Broken vertical bar
  167: "§",		// Section sign
  168: "¨",		// Diaeresis
  169: "©",		// Copyright sign
  170: "ª",		// Feminine ordinal indicator
  171: "«",		// Left-pointing double angle quotation mark
  172: "¬",		// Not sign
  173: "­",		// Soft hyphen
  174: "®",		// Registered sign
  175: "¯",		// Macron
  176: "°",		// Degree sign
  177: "±",	// Plus-minus sign
  178: "²",		// Superscript two
  179: "³",		// Superscript three
  180: "´",		// Acute accent
  181: "µ",		// Micro sign
  182: "¶",		// Pilcrow sign
  183: "·",	// Middle dot
  184: "¸",		// Cedilla
  185: "¹",		// Superscript one
  186: "º",		// Masculine ordinal indicator
  187: "»",		// Right-pointing double angle quotation mark
  188: "¼",	// Vulgar fraction one-quarter
  189: "½",	// Vulgar fraction one-half
  190: "¾",	// Vulgar fraction three-quarters
  191: "¿",	// Inverted question mark
  192: "À",	// A with grave
  193: "Á",	// A with acute
  194: "Â",		// A with circumflex
  195: "Ã",	// A with tilde
  196: "Ä",		// A with diaeresis
  197: "Å",		// A with ring above
  198: "Æ",		// AE
  199: "Ç",	// C with cedilla
  200: "È",	// E with grave
  201: "É",	// E with acute
  202: "Ê",		// E with circumflex
  203: "Ë",		// E with diaeresis
  204: "Ì",	// I with grave
  205: "Í",	// I with acute
  206: "Î",		// I with circumflex
  207: "Ï",		// I with diaeresis
  208: "Ð",		// Eth
  209: "Ñ",	// N with tilde
  210: "Ò",	// O with grave
  211: "Ó",	// O with acute
  212: "Ô",		// O with circumflex
  213: "Õ",	// O with tilde
  214: "Ö",		// O with diaeresis
  215: "×",		// Multiplication sign
  216: "Ø",	// O with stroke
  217: "Ù",	// U with grave
  218: "Ú",	// U with acute
  219: "Û",		// U with circumflex
  220: "Ü",		// U with diaeresis
  221: "Ý",	// Y with acute
  222: "Þ",		// Thorn
  223: "ß",		// Sharp s. Also known as ess-zed
  224: "à",	// a with grave
  225: "á",	// a with acute
  226: "â",		// a with circumflex
  227: "ã",	// a with tilde
  228: "ä",		// a with diaeresis
  229: "å",		// a with ring above
  230: "æ",		// ae. Also known as ligature ae
  231: "ç",	// c with cedilla
  232: "è",	// e with grave
  233: "é",	// e with acute
  234: "ê",		// e with circumflex
  235: "ë",		// e with diaeresis
  236: "ì",	// i with grave
  237: "í",	// i with acute
  238: "î",		// i with circumflex
  239: "ï",		// i with diaeresis
  240: "ð",		// eth
  241: "ñ",	// n with tilde
  242: "ò",	// o with grave
  243: "ó",	// o with acute
  244: "ô",		// o with circumflex
  245: "õ",	// o with tilde
  246: "ö",		// o with diaeresis
  247: "÷",	// Division sign
  248: "ø",	// o with stroke. Also known as o with slash
  249: "ù",	// u with grave
  250: "ú",	// u with acute
  251: "û",		// u with circumflex
  252: "ü",		// u with diaeresis
  253: "ý",	// y with acute
  254: "þ",		// thorn
  255: "ÿ",		// y with diaeresis
  264: "Ĉ",		// Latin capital letter C with circumflex
  265: "ĉ",		// Latin small letter c with circumflex
  338: "Œ",		// Latin capital ligature OE
  339: "œ",		// Latin small ligature oe
  352: "Š",	// Latin capital letter S with caron
  353: "š",	// Latin small letter s with caron
  372: "Ŵ",		// Latin capital letter W with circumflex
  373: "ŵ",		// Latin small letter w with circumflex
  374: "Ŷ",		// Latin capital letter Y with circumflex
  375: "ŷ",		// Latin small letter y with circumflex
  376: "Ÿ",		// Latin capital letter Y with diaeresis
  402: "ƒ",		// Latin small f with hook, function, florin
  710: "ˆ",		// Modifier letter circumflex accent
  732: "˜",		// Small tilde
  913: "Α",		// Alpha
  914: "Β",		// Beta
  915: "Γ",		// Gamma
  916: "Δ",		// Delta
  917: "Ε",	// Epsilon
  918: "Ζ",		// Zeta
  919: "Η",		// Eta
  920: "Θ",		// Theta
  921: "Ι",		// Iota
  922: "Κ",		// Kappa
  923: "Λ",	// Lambda
  924: "Μ",		// Mu
  925: "Ν",		// Nu
  926: "Ξ",		// Xi
  927: "Ο",	// Omicron
  928: "Π",		// Pi
  929: "Ρ",		// Rho
  931: "Σ",		// Sigma
  932: "Τ",		// Tau
  933: "Υ",	// Upsilon
  934: "Φ",		// Phi
  935: "Χ",		// Chi
  936: "Ψ",		// Psi
  937: "Ω",		// Omega
  945: "α",		// alpha
  946: "β",		// beta
  947: "γ",		// gamma
  948: "δ",		// delta
  949: "ε",	// epsilon
  950: "ζ",		// zeta
  951: "η",		// eta
  952: "θ",		// theta
  953: "ι",		// iota
  954: "κ",		// kappa
  955: "λ",	// lambda
  956: "μ",		// mu
  957: "ν",		// nu
  958: "ξ",		// xi
  959: "ο",	// omicron
  960: "π",		// pi
  961: "ρ",		// rho
  962: "ς",	// sigmaf
  963: "σ",		// sigma
  964: "τ",		// tau
  965: "υ",	// upsilon
  966: "φ",		// phi
  967: "χ",		// chi
  968: "ψ",		// psi
  969: "ω",		// omega
  977: "ϑ",	// Theta symbol
  978: "ϒ",		// Greek upsilon with hook symbol
  982: "ϖ",		// Pi symbol
  8194: " ",		// En space
  8195: " ",		// Em space
  8201: " ",	// Thin space
  8204: "‌",		// Zero width non-joiner
  8205: "‍",		// Zero width joiner
  8206: "‎",		// Left-to-right mark
  8207: "‏",		// Right-to-left mark
  8211: "–",	// En dash
  8212: "—",	// Em dash
  8216: "‘",	// Left single quotation mark
  8217: "’",	// Right single quotation mark
  8218: "‚",	// Single low-9 quotation mark
  8220: "“",	// Left double quotation mark
  8221: "”",	// Right double quotation mark
  8222: "„",	// Double low-9 quotation mark
  8224: "†",	// Dagger
  8225: "‡",	// Double dagger
  8226: "•",		// Bullet
  8230: "…",	// Horizontal ellipsis
  8240: "‰",	// Per mille sign
  8242: "′",	// Prime
  8243: "″",	// Double Prime
  8249: "‹",	// Single left-pointing angle quotation
  8250: "›",	// Single right-pointing angle quotation
  8254: "‾",	// Overline
  8260: "⁄",	// Fraction Slash
  8364: "€",		// Euro sign
  8472: "℘",	// Script capital
  8465: "ℑ",	// Blackletter capital I
  8476: "ℜ",		// Blackletter capital R
  8482: "™",	// Trade mark sign
  8501: "ℵ",	// Alef symbol
  8592: "←",		// Leftward arrow
  8593: "↑",		// Upward arrow
  8594: "→",		// Rightward arrow
  8595: "↓",		// Downward arrow
  8596: "↔",		// Left right arrow
  8629: "↵",	// Downward arrow with corner leftward. Also known as carriage return
  8656: "⇐",		// Leftward double arrow. ISO 10646 does not say that lArr is the same as the 'is implied by' arrow but also does not have any other character for that function. So ? lArr can be used for 'is implied by' as ISOtech suggests
  8657: "⇑",		// Upward double arrow
  8658: "⇒",		// Rightward double arrow. ISO 10646 does not say this is the 'implies' character but does not have another character with this function so ? rArr can be used for 'implies' as ISOtech suggests
  8659: "⇓",		// Downward double arrow
  8660: "⇔",		// Left-right double arrow
  // Mathematical Operators
  8704: "∀",	// For all
  8706: "∂",		// Partial differential
  8707: "∃",	// There exists
  8709: "∅",	// Empty set. Also known as null set and diameter
  8711: "∇",	// Nabla. Also known as backward difference
  8712: "∈",		// Element of
  8713: "∉",	// Not an element of
  8715: "∋",		// Contains as member
  8719: "∏",		// N-ary product. Also known as product sign. Prod is not the same character as U+03A0 'greek capital letter pi' though the same glyph might be used for both
  8721: "∑",		// N-ary summation. Sum is not the same character as U+03A3 'greek capital letter sigma' though the same glyph might be used for both
  8722: "−",	// Minus sign
  8727: "∗",	// Asterisk operator
  8729: "∙",	// Bullet operator
  8730: "√",	// Square root. Also known as radical sign
  8733: "∝",		// Proportional to
  8734: "∞",	// Infinity
  8736: "∠",		// Angle
  8743: "∧",		// Logical and. Also known as wedge
  8744: "∨",		// Logical or. Also known as vee
  8745: "∩",		// Intersection. Also known as cap
  8746: "∪",		// Union. Also known as cup
  8747: "∫",		// Integral
  8756: "∴",	// Therefore
  8764: "∼",		// tilde operator. Also known as varies with and similar to. The tilde operator is not the same character as the tilde, U+007E, although the same glyph might be used to represent both
  8773: "≅",		// Approximately equal to
  8776: "≈",	// Almost equal to. Also known as asymptotic to
  8800: "≠",		// Not equal to
  8801: "≡",	// Identical to
  8804: "≤",		// Less-than or equal to
  8805: "≥",		// Greater-than or equal to
  8834: "⊂",		// Subset of
  8835: "⊃",		// Superset of. Note that nsup, 'not a superset of, U+2283' is not covered by the Symbol font encoding and is not included.
  8836: "⊄",		// Not a subset of
  8838: "⊆",		// Subset of or equal to
  8839: "⊇",		// Superset of or equal to
  8853: "⊕",	// Circled plus. Also known as direct sum
  8855: "⊗",	// Circled times. Also known as vector product
  8869: "⊥",		// Up tack. Also known as orthogonal to and perpendicular
  8901: "⋅",		// Dot operator. The dot operator is not the same character as U+00B7 middle dot
  // Miscellaneous Technical
  8968: "⌈",	// Left ceiling. Also known as an APL upstile
  8969: "⌉",	// Right ceiling
  8970: "⌊",	// left floor. Also known as APL downstile
  8971: "⌋",	// Right floor
  9001: "⟨",		// Left-pointing angle bracket. Also known as bra. Lang is not the same character as U+003C 'less than'or U+2039 'single left-pointing angle quotation mark'
  9002: "⟩",		// Right-pointing angle bracket. Also known as ket. Rang is not the same character as U+003E 'greater than' or U+203A 'single right-pointing angle quotation mark'
  // Geometric Shapes
  9642: "▪",	// Black small square
  9643: "▫",	// White small square
  9674: "◊",		// Lozenge
  // Miscellaneous Symbols
  9702: "◦",	// White bullet
  9824: "♠",	// Black (filled) spade suit
  9827: "♣",	// Black (filled) club suit. Also known as shamrock
  9829: "♥",	// Black (filled) heart suit. Also known as shamrock
  9830: "♦"   // Black (filled) diamond suit
}

Unfortunately, there is no way to convert these entities without a big long list to look through. But it works nicely like so:

entityToHtml(string_with_entities);

Making my code now looking like this:

$.get('/travel_guide/Paris', function(data){
  $('#advice').text(entityToHtml(data));
});

jQuery UI Autocomplete and Caching Query Results
Article posted by Joel Moss on 14 May 2010   |  

I've never managed to find a really good jQuery based autocomplete plugins, especially one that is flexible and easily extensible. So when the jQuery UI team released 1.8 back in March, I was intrigued to find that they included - amongst other new widgets - an autocomplete widget. And it turns out to be pretty damn good, and flexible to boot.

So when I had a need to add the ability for a local data source for the autocomplete plugin that we use on ShermansTravel QuickSearch, I gave the new jQuery UI widget a go.

The jQuery UI Autocomplete widget can load in data in three ways... (examples taken straight from the jQuery UI docs)

In an array with local data:

Pass it an array with a list of strings, and it will use present a list of matching values based on your entered string.

$(function() {
	$("#tags").autocomplete({
		source: ["php", "javascript", "asp", "ruby", "python", "c", "scala", "groovy", "haskell", "perl"]
	});
});
A string, specifying a URL to get the data:

Pass it a string of a relative URL, and it will make an ajax call to that URL, passing it the users entered string.

$(function() {
	$("#birds").autocomplete({
		source: "search.php",
		minLength: 3
	});
});
Via a callback (function):

Pass it a callback and you can do pretty much anything you want. This example uses such a technique and caches the query results.

var cache = {};
$("#birds").autocomplete({
	source: function(request, response) {
		if (cache.term == request.term && cache.content) {
			response(cache.content);
			return;
		}
		if (new RegExp(cache.term).test(request.term) && cache.content && cache.content.length < 13) {
			response($.ui.autocomplete.filter(cache.content, request.term));
			return;
		}
		$.ajax({
			url: "search.php",
			dataType: "json",
			data: request,
			success: function(data) {
				cache.term = request.term;
				cache.content = data;
				response(data);
			}
		});
	},
	minLength: 2
});

A Caching Autocomplete for Remote Data

What I really needed was a way to cache every single query when loading data in via an ajax call. The Autocomplete widget does not cache your returned query data at all, which means that an ajax call is made on every single key press. Even if you set the minLength to something like 3 - which will only trigger the autocomplete once the user has entered at least 3 characters - every character entered after that will trigger an ajax call.

So I came up with a somewhat simple, but effective caching strategy using the callback method for loading data.

var cache = {};
$("#birds").autocomplete({
	source: function(request, response) {
	  var term          = request.term.toLowerCase(),
	      element       = this.element,
	      cache         = this.element.data('autocompleteCache') || {},
	      foundInCache  = false;

	  $.each(cache, function(key, data){
	    if (term.indexOf(key) === 0 && data.length > 0) {
	      response(data);
	      foundInCache = true;
	      return;
	    }
	  });

		if (foundInCache) return;
		
		$.ajax({
			url: 'search.php',
			dataType: "json",
			data: request,
			success: function(data) {
				cache[term] = data;
				element.data('autocompleteCache', cache);
				response(data);
			}
		});
	},
	minLength: 2
});

Here we are saving the cache as a hash in the corresponding element using jQuery's data function. And we are doing this for every query performed. We first loop through each element of the cache, and check if any of the keys begin with the current search term. If we find something, then we call the passed response callback function, and return from the each loop.

If no cache is found for the current term, then we call the ajax as usual. But if we do find a matching key in the cache, then we don't call the ajax, and instead return the cached data.

Simple, but works nicely.



Recent Comments