<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-7095459794498138151</id><updated>2012-02-16T10:06:59.569-08:00</updated><title type='text'>Coding Tank</title><subtitle type='html'>The life and times of a software engineer on the front lines.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://coding-tank.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://coding-tank.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Coding Tank</name><uri>http://www.blogger.com/profile/05509742139651470412</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://1.bp.blogspot.com/_2HAb5HYui54/STcsDiD1cnI/AAAAAAAAAAM/lHjlgVYDDeo/S220/un_tank.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>16</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-7095459794498138151.post-5387844320088932730</id><published>2009-06-17T23:37:00.000-07:00</published><updated>2009-06-17T23:39:18.300-07:00</updated><title type='text'>When Not to Use the Ternary Statement</title><content type='html'>The ternary operator is designed as a shorthand way to assign a value to a variable based on the result of an expression. The benefit of this is more compact code than the corresponding if/then/else structure. The drawback is that it's slightly less readable. But used properly it can enhance the quality of your code.&lt;br /&gt;&lt;br /&gt;An if/then/else statement reads much like an english sentence. The structure can be understood at a quick glance. On the other hand, the ternary operator requires at least a tiny bit thought before you understand what's happening. Some of that mental processing time can be mitigated by using the ternary operator consistently and only where it's appropriate so readers of your code can build familiarity with how you're using it. What is the appropriate way to use it?&lt;br /&gt;&lt;br /&gt;I'm a supporter of keeping lines of code shorter than 80 characters. That idea is certainly up for debate and is the subject for another post but just for now let's assume that anything longer than 80 characters is going to wrap and will greatly decrease the readability -- not to mention beauty -- of your code. It makes sense then to keep your ternary statements under 80 characters. This can be difficult since you're dealing with four different entities: a variable, an expression, the true condition and the false condition. With all of these things having to fit on a single line each part needs to be extremely terse. If you want to maintain readability anyway. My rule of thumb is that if I can't keep a ternary statement under 80 characters then it gets converted into an if/then/else structure. Readability is more important than conciseness in my book. Let's look at some examples.&lt;br /&gt;&lt;br /&gt;Good Example:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;&lt;br /&gt;$foo = ($_GET['foo']) ? intval($_GET['foo']) : 0;&lt;br /&gt;&lt;/pre&gt;&lt;/blockquote&gt;&lt;br /&gt;You might see this in a typical web application today. You want to initialize a variable to whatever was specified as a request parameter or a default value if a request parameter wasn't given.&lt;br /&gt;&lt;br /&gt;Bad Example:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;&lt;br /&gt;$foo = (count($articles) &lt; $min_article_count) ? get_articles_by_genre($status, $author, $site_id, $genre) : get_hot_topics($board_id, $platform_id, $min_date);&lt;br /&gt;&lt;/pre&gt;&lt;/blockquote&gt;&lt;br /&gt;You really have to comb through that statement understand what's going on. Every time someone comes across this in the code they're going to have to stop and figure it out. This example isn't even as half as bad as some of the stuff I've seen in the wild. The above would be better written like this.&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;&lt;br /&gt;if (count($articles) &lt; $min_article_count) {&lt;br /&gt;   $foo = get_articles_by_genre($status, $author, $site_id, $genre);&lt;br /&gt;}&lt;br /&gt;else {&lt;br /&gt;   $foo = get_hot_topics($board_id, $platform_id, $min_date);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/blockquote&gt;&lt;br /&gt;Big difference. The lengthy ternary statement when broken up into multiple lines in an if/then/else statement becomes much easier to read. You get a sense of the logic very easily. &lt;br /&gt;&lt;br /&gt;Another Bad Example:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;&lt;br /&gt;foreach (innitech_comment_operations($arg == 'approval' ? 'publish' : 'unpublish') as $key =&gt; $value) {&lt;br /&gt;   // stuff here&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/blockquote&gt;&lt;br /&gt;Some programmers feel compelled to stuff as much logic into a single line as possible. I don't understand this.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7095459794498138151-5387844320088932730?l=coding-tank.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://coding-tank.blogspot.com/feeds/5387844320088932730/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7095459794498138151&amp;postID=5387844320088932730' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/5387844320088932730'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/5387844320088932730'/><link rel='alternate' type='text/html' href='http://coding-tank.blogspot.com/2009/06/when-not-to-use-ternary-statement.html' title='When Not to Use the Ternary Statement'/><author><name>Coding Tank</name><uri>http://www.blogger.com/profile/05509742139651470412</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://1.bp.blogspot.com/_2HAb5HYui54/STcsDiD1cnI/AAAAAAAAAAM/lHjlgVYDDeo/S220/un_tank.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7095459794498138151.post-3236285799076480643</id><published>2009-05-05T09:33:00.000-07:00</published><updated>2009-05-05T09:37:40.554-07:00</updated><title type='text'>Iterating Over Form Elements With JQuery</title><content type='html'>I recently was tasked with making JQuery iterate over the elements of a form. . The form would be specified by name and the script has to loop over all of the input elements. After some Googling and trial-n-error this is the solution I came up with.&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;$("form[name=foo] :input").each(function(i) {&lt;br /&gt;      console.log($(this).attr('id') + " / " + $(this).val());&lt;br /&gt;}&lt;/pre&gt;&lt;/blockquote&gt;&lt;br /&gt;Instead of outputting to the console you'll probably want to do something more interesting with the results.&lt;br /&gt;&lt;br /&gt;Trying to explain a JQuery selector string is a bit like trying explain how to improvise a solo in music. But I'll try anyway.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;form&lt;/strong&gt;&lt;br /&gt;This will select all &lt;code&gt;&lt;strong&gt;form&lt;/strong&gt;&lt;/code&gt; elements in the DOM.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;[name=foo]&lt;/strong&gt;&lt;br /&gt;This limits the search to only the form with a name attribute with a value of 'foo'.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;:input&lt;/strong&gt;&lt;br /&gt;This selects all of the input elements of the previously selected form. An 'input' element in this case is any form control.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7095459794498138151-3236285799076480643?l=coding-tank.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://coding-tank.blogspot.com/feeds/3236285799076480643/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7095459794498138151&amp;postID=3236285799076480643' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/3236285799076480643'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/3236285799076480643'/><link rel='alternate' type='text/html' href='http://coding-tank.blogspot.com/2009/05/iterating-over-form-elements-with.html' title='Iterating Over Form Elements With JQuery'/><author><name>Coding Tank</name><uri>http://www.blogger.com/profile/05509742139651470412</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://1.bp.blogspot.com/_2HAb5HYui54/STcsDiD1cnI/AAAAAAAAAAM/lHjlgVYDDeo/S220/un_tank.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7095459794498138151.post-7022678944678979135</id><published>2009-03-30T16:39:00.000-07:00</published><updated>2009-03-30T16:40:53.326-07:00</updated><title type='text'>Being a Good Consumer</title><content type='html'>I'm in the "thinking" stage of a side project which has been on my mind for the past few weeks. A lot of ideas cross my mind but this one has stayed with me longer than any of the others. I've been programming long enough to know that thinking on this idea before jumping in and blindly coding will pay dividends later. In my thinking I find myself bouncing back-n-forth between two different approaches. Knowing some background about the problem will help understand my predicament.&lt;br /&gt;&lt;br /&gt;The main source of data for my application will come from a 3rd party API. It has a REST-based interface which any one in the world has access to. It's kind of like the Twitter API but it's not Twitter. Hitting the API to grab data is easy if I'm only concerned with getting data for a few entities. The problem is when I'm dealing with tens of thousands of entities. How often can I query the API before I negatively impact the service? So I start thinking of ways to minimize my use of the API but still provide timely data.&lt;br /&gt;&lt;br /&gt;Then I come back to one of my core philosophies of software development. That being "Don't Solve Problems That Don't Exist". I've been of projects in the past where people try to predict how the application will be used in the future and to code in functionality that may not be meaningful now and will be at some point in the future. The problem with that approach is that you can't predict how the application will be used or what uses will want out of it once they start using it in earnest. You can't second guess your users or usage patterns so it's futile to solve those problems before you know what they will be.&lt;br /&gt;&lt;br /&gt;However, one of my other core philosophies is "Don't Be Dumb". It's important to design an application in an intelligent way so you can quickly respond to shifting demands. I don't have to solve every problem right now but I do need to be able to get the application back on track quickly when things blow up. I really bad design decision could be a total breakdown once usage reaches a certain level.&lt;br /&gt;&lt;br /&gt;Adding new hardware isn't a viable solution to scaling since I expect the main bottleneck to be the 3rd party API. Much private testing will need to be done to determine if this really be the case or not.&lt;br /&gt;&lt;br /&gt;In any case, it's time to stop thinking and start doing. However, I don't expect to release anything to the public until I've done enough testing and benchmarking to have a better idea of how the API will response to a large number of users.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7095459794498138151-7022678944678979135?l=coding-tank.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://coding-tank.blogspot.com/feeds/7022678944678979135/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7095459794498138151&amp;postID=7022678944678979135' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/7022678944678979135'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/7022678944678979135'/><link rel='alternate' type='text/html' href='http://coding-tank.blogspot.com/2009/03/being-good-consumer.html' title='Being a Good Consumer'/><author><name>Coding Tank</name><uri>http://www.blogger.com/profile/05509742139651470412</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://1.bp.blogspot.com/_2HAb5HYui54/STcsDiD1cnI/AAAAAAAAAAM/lHjlgVYDDeo/S220/un_tank.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7095459794498138151.post-6872557058128900341</id><published>2009-03-20T10:38:00.001-07:00</published><updated>2009-03-20T10:39:01.313-07:00</updated><title type='text'>UNIX Shell Tip: Alias Frequently Used Directories</title><content type='html'>If you work in UNIX you probably spend most of your time in a directory several levels deep. That may or may not be relative to your home directory. Perhaps you switch back and forth between Apache's configuration directory and your home directory several times a day. It would be nice not to have to type those directory names out all the time. Even with tab completion you still have to type at least a couple of characters per directory and that assumes you've memorized the paths.&lt;br /&gt;&lt;br /&gt;For the directories you use the most often just alias them in your &lt;tt&gt;.bash_login&lt;/tt&gt; file. Here's a few examples.&lt;br /&gt;&lt;tt&gt;&lt;br /&gt;alias sb='cd /var/www/foo/app'&lt;br /&gt;alias mods='cd /var/www/foo/app/hosts/bar/modules'&lt;br /&gt;alias aconf='cd /etc/apache2/'&lt;br /&gt;alias logs='cd /opt/var/log'&lt;br /&gt;&lt;/tt&gt;&lt;br /&gt;Obviously, your own aliases should be suited to your own environment. This makes moving around the filesystem &lt;strong&gt;much&lt;/strong&gt; easier.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7095459794498138151-6872557058128900341?l=coding-tank.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://coding-tank.blogspot.com/feeds/6872557058128900341/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7095459794498138151&amp;postID=6872557058128900341' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/6872557058128900341'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/6872557058128900341'/><link rel='alternate' type='text/html' href='http://coding-tank.blogspot.com/2009/03/unix-shell-tip-alias-frequently-used.html' title='UNIX Shell Tip: Alias Frequently Used Directories'/><author><name>Coding Tank</name><uri>http://www.blogger.com/profile/05509742139651470412</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://1.bp.blogspot.com/_2HAb5HYui54/STcsDiD1cnI/AAAAAAAAAAM/lHjlgVYDDeo/S220/un_tank.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7095459794498138151.post-224660676929525742</id><published>2009-03-18T23:24:00.000-07:00</published><updated>2009-03-18T23:26:29.830-07:00</updated><title type='text'>Managing Users and Groups in OS X Leopard</title><content type='html'>&lt;a href="http://www.apple.com/macosx/"&gt;OS X&lt;/a&gt; is basically a flavor of UNIX with a really nice user interface. Anything you can do in a typical UNIX environment you can do in OS X. Well, almost anything. One of the ways OS X differs quite a bit from the typical UNIX environment is in how it manages users and groups. You're not going to find &lt;tt&gt;&lt;strong&gt;&lt;a href="http://linux.die.net/man/8/useradd"&gt;useradd&lt;/a&gt;&lt;/strong&gt;&lt;/tt&gt; or &lt;tt&gt;&lt;strong&gt;&lt;a href="http://linux.die.net/man/8/usermod"&gt;usermod&lt;/a&gt;&lt;/strong&gt;&lt;/tt&gt; anywhere. Instead OS X keeps all of that information in a directory service called &lt;a href="http://en.wikipedia.org/wiki/Apple_Open_Directory"&gt;Open Directory&lt;/a&gt;. Open Directory is Apple's implementation of LDAP and is how the operating system manages users and network resources. It's only the users we're interested in here so that's all I'm going to discuss in this post.&lt;br /&gt;&lt;br /&gt;How &lt;em&gt;do&lt;/em&gt; you manage users and groups then? There's a command line utility that ships with OS X Leopard (and Tiger I believe) called &lt;tt&gt;&lt;strong&gt;dscl&lt;/strong&gt;&lt;/tt&gt;. I would assume this stands for Directory Service Command Line. Enter the following command to start &lt;tt&gt;&lt;strong&gt;dscl&lt;/strong&gt;&lt;/tt&gt; in interactive mode.&lt;br /&gt;&lt;br /&gt;&lt;tt&gt;dscl .&lt;/tt&gt;&lt;br /&gt;&lt;br /&gt;Being a directory service, resources are arranged in a tree structure much the same way the filesystem is. Type &lt;tt&gt;ls&lt;/tt&gt; to see what items exist at the root level. You should notice a 'Users' entry near the bottom of that list. Switch to the Users directory the same way you would on the filesystem.&lt;br /&gt;&lt;br /&gt;&lt;tt&gt;cd Users&lt;/tt&gt;&lt;br /&gt;&lt;br /&gt;Do an &lt;tt&gt;ls&lt;/tt&gt; again and you'll see a bunch of system accounts and near the bottom of the list will be a few account names you might be more familiar with. Pick your own username and switch into that directory.&lt;br /&gt;&lt;br /&gt;&lt;tt&gt;cd codingtank&lt;/tt&gt;&lt;br /&gt;&lt;br /&gt;To see all the attributes associated with your account you enter the &lt;tt&gt;&lt;strong&gt;read&lt;/strong&gt;&lt;/tt&gt; command.&lt;br /&gt;&lt;br /&gt;You should see what appears to be a bunch of name/value pairs. Take some time to read through this information. It's good to know what kind of information the directory stores about users.&lt;br /&gt;&lt;br /&gt;How do you change the value for one of those attributes? For example, you've been using your Mac for so long that your account still uses the &lt;tt&gt;tcsh&lt;/tt&gt; shell and you want to switch it to the more popular and modern &lt;tt&gt;bash&lt;/tt&gt; shell. Since this involves &lt;em&gt;modifying&lt;/em&gt; the directory you should start &lt;tt&gt;&lt;strong&gt;dscl&lt;/strong&gt;&lt;/tt&gt; with &lt;tt&gt;&lt;strong&gt;sudo&lt;/strong&gt;&lt;/tt&gt;. To change your shell using the interactive prompt you'd do something like this:&lt;br /&gt;&lt;br /&gt;&lt;tt&gt;sudo dscl .&lt;/tt&gt;&lt;br /&gt;&lt;tt&gt;-create /Users/codingtank UserShell /bin/bash&lt;/tt&gt;&lt;br /&gt;&lt;br /&gt;The 'create' command will either create the attribute if it doesn't already exist or modify it if it does. The first argument is the user you're dealing with. The second argument is the attribute you're trying to add or change. The last argument is the value of that attribute. Alternatively you can do this with a single command:&lt;br /&gt;&lt;br /&gt;&lt;tt&gt;sudo dscl . -create /Users/codingtank UserShell /bin/bash&lt;/tt&gt;&lt;br /&gt; &lt;br /&gt;Now we want our user account to be associated with the 'www' group. We're not going to modify the user in this case. We're going to modify the group and add our account to the list of members. The command for that change looks like this.&lt;br /&gt;&lt;br /&gt;&lt;tt&gt;sudo dscl . -append /Groups/www GroupMembership codingtank&lt;/tt&gt;&lt;br /&gt;&lt;br /&gt;Use the 'append' command when you want to add something to an existing value instead of replacing it.&lt;br /&gt;&lt;br /&gt;To learn more about &lt;tt&gt;&lt;strong&gt;dscl&lt;/strong&gt;&lt;/tt&gt; you can study the &lt;a href="http://developer.apple.com/documentation/Darwin/Reference/ManPages/man1/dscl.1.html"&gt;online man page&lt;/a&gt;. It provides a few usage examples at the end. Also, people who appear to know much more about &lt;tt&gt;dscl&lt;/tt&gt; than myself have written some helpful articles on it.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;&lt;a href="http://www.malisphoto.com/tips/mysql-on-os-x.html"&gt;Compiling and Installing MySQL 5 on Mac OS X Leopard&lt;/a&gt;&lt;/strong&gt; - See section on creating MySQL group and user.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.macos.utah.edu/documentation/authentication/dscl.html"&gt;&lt;strong&gt;dscl at U Mac&lt;/strong&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://osxdaily.com/2007/10/29/how-to-add-a-user-from-the-os-x-command-line-works-with-leopard/"&gt;&lt;strong&gt;Add a User From the OS X Command Line&lt;/strong&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.mactech.com/articles/mactech/Vol.22/22.10/2210MacInTheShell/index.html"&gt;&lt;strong&gt;Easing Into dscl&lt;/strong&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7095459794498138151-224660676929525742?l=coding-tank.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://coding-tank.blogspot.com/feeds/224660676929525742/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7095459794498138151&amp;postID=224660676929525742' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/224660676929525742'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/224660676929525742'/><link rel='alternate' type='text/html' href='http://coding-tank.blogspot.com/2009/03/managing-users-and-groups-in-os-x.html' title='Managing Users and Groups in OS X Leopard'/><author><name>Coding Tank</name><uri>http://www.blogger.com/profile/05509742139651470412</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://1.bp.blogspot.com/_2HAb5HYui54/STcsDiD1cnI/AAAAAAAAAAM/lHjlgVYDDeo/S220/un_tank.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7095459794498138151.post-38557218360096976</id><published>2009-03-15T22:09:00.001-07:00</published><updated>2009-03-15T22:10:21.650-07:00</updated><title type='text'>The MySQL Query Log in OS X Leopard</title><content type='html'>I installed MySQL 5.1 Community Server on my MacBook Pro using the &lt;a href="http://dev.mysql.com/downloads/mysql/5.1.html"&gt;convenient installer&lt;/a&gt; provided by the folks at MySQL. Something you should be aware of when doing this is that it will &lt;strong&gt;not&lt;/strong&gt; install a configuration file anywhere. You'll be running with defaults unless you create a (or copy one of the provided) &lt;tt&gt;my.cnf&lt;/tt&gt; file in one of the appropriate locations.&lt;br /&gt;&lt;br /&gt;Default options may be fine for most local workstation installations. However, I wanted to see the query log. The easiest way to enable the query log is to add an entry to your &lt;tt&gt;my.cnf&lt;/tt&gt; file under &lt;tt&gt;[mysqld]&lt;/tt&gt; that looks something like this.&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;tt&gt;log=/var/log/mysqld.log&lt;/tt&gt;&lt;/blockquote&gt;&lt;br /&gt;I initially chose &lt;tt&gt;/var/log&lt;/tt&gt; becuase that's where OS X keeps some of its other log files and I thought it would be nice to keep log files together in the same place. I restarted the MySQL server and ran a couple of queries but the log file never showed up. It turns out there's a problem with the permissions on the &lt;tt&gt;/var/log&lt;/tt&gt; directory. It's owned by &lt;strong&gt;root:wheel&lt;/strong&gt; and is only writable by the owner. The &lt;strong&gt;mysql&lt;/strong&gt; user is not in the &lt;strong&gt;wheel&lt;/strong&gt; group and thus cannot create files in this directory.&lt;br /&gt;&lt;br /&gt;Given that OS X maintains this directory I know any changes I make to it might get reverted at any point in the future. So I chose to simply create my logs in another location. An easy candidate is &lt;tt&gt;/usr/local/mysql/data&lt;/tt&gt; but you can use any directory choose if you don't want to mix data and logs.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7095459794498138151-38557218360096976?l=coding-tank.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://coding-tank.blogspot.com/feeds/38557218360096976/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7095459794498138151&amp;postID=38557218360096976' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/38557218360096976'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/38557218360096976'/><link rel='alternate' type='text/html' href='http://coding-tank.blogspot.com/2009/03/mysql-query-log-in-os-x-leopard.html' title='The MySQL Query Log in OS X Leopard'/><author><name>Coding Tank</name><uri>http://www.blogger.com/profile/05509742139651470412</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://1.bp.blogspot.com/_2HAb5HYui54/STcsDiD1cnI/AAAAAAAAAAM/lHjlgVYDDeo/S220/un_tank.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7095459794498138151.post-5595620347012179044</id><published>2009-03-05T21:52:00.000-08:00</published><updated>2009-03-05T21:53:02.102-08:00</updated><title type='text'>Own Your Data Model</title><content type='html'>Application frameworks can be divided up into two broad categories. Those where you define your own data model and those where the data model is provided for you. Examples are the former include &lt;a href="http://www.symfony-project.org/"&gt;Symphony&lt;/a&gt;, and &lt;a href="http://rubyonrails.org/"&gt;Ruby on Rails&lt;/a&gt;. Examples of the later include &lt;a href="http://www.joomla.org/"&gt;Joomla&lt;/a&gt; and &lt;a href="http://drupal.org/"&gt;Drupal&lt;/a&gt;. Any web application framework where you do *not* create and own your own data model is probably *not* a framework you want to base a long-term business on.&lt;br /&gt;&lt;br /&gt;The more generic a solution is the more unlikely it is to solve any one particular problem that deeply. It may solve of bunch of problems &lt;em&gt;partially&lt;/em&gt;. But it does so superficially and it will never cover &lt;strong&gt;all&lt;/strong&gt; of your requirements and use cases. It most certainly will not keep up with your changing business needs. With a generic solution you will find yourself shaping your site to fit the framework.&lt;br /&gt;&lt;br /&gt;The people who design and write these packages are smart but they're subject to the same laws of software development as the rest of us. They still have to make assumptions about how the product will be used and what kind of users will be involved. They have to make assumptions about what kind of sites will be produced with their tools and how it may be customized and extended. Even with ones that are extensible there are still limits on just how far you can take your custom modules or plugins.&lt;br /&gt;&lt;br /&gt;When starting a new business it can be very tempting to use one of these off the shelf solutions to get a product out the door quickly. You can do your due-diligence and figure that the product will meet all of your requirements and will only need a few customizations. What these people don't take into account is that the business will change. It will change is unpredictable ways. It will change faster than anticipated. Then you start running into the limitations of your off-the-shelf solution really fast. Then you start spending a lot of engineering time slogging through the framework or outright fighting against it. Short term gain; long term loss.&lt;br /&gt;&lt;br /&gt;At the very least you need to go with a framework where you own your own data model. The data model is your most important asset. It is not an area to corners in or make compromises. Your data model informs the rest of your application. If your business is online then your database is the core of your business.&lt;br /&gt;&lt;br /&gt;Chances are good that an application framework that lets you specify your own data model will also implement a decent MVC pattern. Conversely, the all-in-one systems aren't likely to have any approximation of MVC at all. The code you invest in those system means a bigger mess to deal with. That mess is going to happen sooner than you thing. Remember the business will change in unpredictable ways very fast.&lt;br /&gt;&lt;br /&gt;Chances are also good that an application framework that lets you specify your own data model will also implement a decent testing framework and &lt;em&gt;may&lt;/em&gt; even allow for easier scalability. Easier than the all-in-one systems anyway.&lt;br /&gt;&lt;br /&gt;So, if you think you're smart and will save your project a lot of money and time by using a ready-made content management system which you can customize please please please reconsider. You're setting yourself up for a lot of headache in the near future.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7095459794498138151-5595620347012179044?l=coding-tank.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://coding-tank.blogspot.com/feeds/5595620347012179044/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7095459794498138151&amp;postID=5595620347012179044' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/5595620347012179044'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/5595620347012179044'/><link rel='alternate' type='text/html' href='http://coding-tank.blogspot.com/2009/03/own-your-data-model.html' title='Own Your Data Model'/><author><name>Coding Tank</name><uri>http://www.blogger.com/profile/05509742139651470412</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://1.bp.blogspot.com/_2HAb5HYui54/STcsDiD1cnI/AAAAAAAAAAM/lHjlgVYDDeo/S220/un_tank.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7095459794498138151.post-1623848138937751355</id><published>2009-03-02T23:31:00.001-08:00</published><updated>2009-03-02T23:35:52.559-08:00</updated><title type='text'>New Job</title><content type='html'>I'm happy to say I'm done doing interviews for a while. I started a new job today.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;The Good&lt;/strong&gt;&lt;br /&gt;Nice clean office furniture that appears to be fairly new.&lt;br /&gt;A better than average chair&lt;br /&gt;A group of guys who on first blush appear to be really cool.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;The Bad&lt;/strong&gt;&lt;br /&gt;The standard issue Lenovo laptop with Vista. I'll be working on my personal Mac Book Pro thankyouverymuch.&lt;br /&gt;&lt;br /&gt;Small company. Well all fit in the same room. Little to no privacy for the occasional personal phone call. Not that I make a habit of it but sometimes you gotta talk to the kids during the day. Ya, that's going to be weird.&lt;br /&gt;&lt;br /&gt;Drupal is not exactly the epitome of object oriented design.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7095459794498138151-1623848138937751355?l=coding-tank.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://coding-tank.blogspot.com/feeds/1623848138937751355/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7095459794498138151&amp;postID=1623848138937751355' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/1623848138937751355'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/1623848138937751355'/><link rel='alternate' type='text/html' href='http://coding-tank.blogspot.com/2009/03/new-job.html' title='New Job'/><author><name>Coding Tank</name><uri>http://www.blogger.com/profile/05509742139651470412</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://1.bp.blogspot.com/_2HAb5HYui54/STcsDiD1cnI/AAAAAAAAAAM/lHjlgVYDDeo/S220/un_tank.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7095459794498138151.post-4532765516323989987</id><published>2009-02-12T20:54:00.001-08:00</published><updated>2009-02-12T20:55:50.570-08:00</updated><title type='text'>SQL: Finding Duplicate Values</title><content type='html'>It turns out there's another use for the &lt;tt&gt;&lt;strong&gt;HAVING&lt;/strong&gt;&lt;/tt&gt; clause that I hadn't considered before. This came to me when taking a written quiz for a job interview.&lt;br /&gt;&lt;br /&gt;"Write a query to find duplicate 'name' values in a 'user' table"&lt;br /&gt;&lt;br /&gt;I had to think about this for a few minutes. It's really easy to suppress duplicates. But returning &lt;em&gt;only&lt;/em&gt; duplicates is not so obvious. Here's the answer I gave.&lt;br /&gt;&lt;blockquote&gt;&lt;pre style="background: silver; padding:5"&gt;select&lt;br /&gt;   name,&lt;br /&gt;   count(name) as name_count&lt;br /&gt;from&lt;br /&gt;   users&lt;br /&gt;group by&lt;br /&gt;   name&lt;br /&gt;having&lt;br /&gt;   name_count &gt; 1;&lt;/pre&gt;&lt;/blockquote&gt;After getting home a quick test with my local copy of MySQL revealed my answer was indeed correct. As an added bonus you also get the number of times each value appears.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7095459794498138151-4532765516323989987?l=coding-tank.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://coding-tank.blogspot.com/feeds/4532765516323989987/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7095459794498138151&amp;postID=4532765516323989987' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/4532765516323989987'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/4532765516323989987'/><link rel='alternate' type='text/html' href='http://coding-tank.blogspot.com/2009/02/sql-finding-duplicate-values.html' title='SQL: Finding Duplicate Values'/><author><name>Coding Tank</name><uri>http://www.blogger.com/profile/05509742139651470412</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://1.bp.blogspot.com/_2HAb5HYui54/STcsDiD1cnI/AAAAAAAAAAM/lHjlgVYDDeo/S220/un_tank.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7095459794498138151.post-5150517627120199479</id><published>2009-01-26T10:46:00.000-08:00</published><updated>2009-01-26T10:57:43.429-08:00</updated><title type='text'>Ruby or Python</title><content type='html'>I'm trying to figure out if I should learn Ruby or Python next. Both seem like popular languages and ideally I'd like to learn them both. But one has to come first. Since my current motivation for learning anything new is to make myself more valuable to potential employers I'm going to use that as the basis for my choice.&lt;br /&gt;&lt;br /&gt;I loaded up Craigslist (SF Bay Area) this morning and performed a few searches.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;All Jobs&lt;/strong&gt;&lt;br /&gt;Ruby = 125&lt;br /&gt;Python = 133&lt;br /&gt;&lt;br /&gt;This would seem to tip the scales in favor of Python. But I'm a web guy so I'm specifically interested in Internet Engineering jobs. Let's see how the numbers break down when I apply that filter.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Internet Engineering Jobs&lt;/strong&gt;&lt;br /&gt;Ruby = 45&lt;br /&gt;Python = 32&lt;br /&gt;&lt;br /&gt;That would seem to favor Ruby. Let's try the search terms for their respective web application frameworks.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Internet Engineering Jobs&lt;/strong&gt;&lt;br /&gt;Rails = 28&lt;br /&gt;Django = 10&lt;br /&gt;&lt;br /&gt;That seals it. I'm learning Ruby. Not that I'm trying to pit one language against the other. I'm just making a practical decision about which one to learn &lt;em&gt;first&lt;/em&gt;. I've heard very positive things about both would like to eventually get into both.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7095459794498138151-5150517627120199479?l=coding-tank.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://coding-tank.blogspot.com/feeds/5150517627120199479/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7095459794498138151&amp;postID=5150517627120199479' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/5150517627120199479'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/5150517627120199479'/><link rel='alternate' type='text/html' href='http://coding-tank.blogspot.com/2009/01/ruby-or-python.html' title='Ruby or Python'/><author><name>Coding Tank</name><uri>http://www.blogger.com/profile/05509742139651470412</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://1.bp.blogspot.com/_2HAb5HYui54/STcsDiD1cnI/AAAAAAAAAAM/lHjlgVYDDeo/S220/un_tank.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7095459794498138151.post-3682398224039129239</id><published>2009-01-15T16:01:00.001-08:00</published><updated>2009-01-15T16:34:50.817-08:00</updated><title type='text'>SQL Group By</title><content type='html'>Besides doing SQL joins the other thing you'll be asked in job interviews is to use a &lt;tt&gt;&lt;strong&gt;GROUP BY&lt;/strong&gt;&lt;/tt&gt; clause in some fashion. Even though &lt;tt&gt;&lt;strong&gt;GROUP BY&lt;/strong&gt;&lt;/tt&gt; is a rare sight in the wild, potential employers still want to know that you know how to use it.&lt;br /&gt;&lt;br /&gt;Let's take a look at our standard job interview tables.&lt;blockquote&gt;&lt;pre style="background: silver; padding:5; margin:0"&gt;+---------------+--------------+&lt;br /&gt;| Field         | Type         |&lt;br /&gt;+---------------+--------------+&lt;br /&gt;| employee_id   | int(11)      | &lt;br /&gt;| department_id | int(11)      | &lt;br /&gt;| name          | varchar(255) | &lt;br /&gt;| salary        | int(11)      | &lt;br /&gt;+---------------+--------------+&lt;br /&gt;&lt;br /&gt;+---------------+--------------+&lt;br /&gt;| Field         | Type         |&lt;br /&gt;+---------------+--------------+&lt;br /&gt;| department_id | int(11)      | &lt;br /&gt;| name          | varchar(255) | &lt;br /&gt;+---------------+--------------+&lt;/pre&gt;&lt;/blockquote&gt;We want to list all all departments along with how many employees are in each one. Here's the answer.&lt;blockquote&gt;&lt;pre style="background: silver; padding:5"&gt;select&lt;br /&gt;   d.name,&lt;br /&gt;   count(e.employee_id)&lt;br /&gt;from&lt;br /&gt;   department d&lt;br /&gt;left outer join&lt;br /&gt;   employee e using (department_id)&lt;br /&gt;group by&lt;br /&gt;   d.department_id;&lt;/pre&gt;&lt;/blockquote&gt;To better understand how &lt;tt&gt;&lt;strong&gt;GROUP BY&lt;/strong&gt;&lt;/tt&gt; works it sometimes helps to construct the query without the &lt;tt&gt;&lt;strong&gt;COUNT&lt;/strong&gt;&lt;/tt&gt; function and &lt;tt&gt;&lt;strong&gt;GROUP BY&lt;/strong&gt;&lt;/tt&gt; clause to see what the raw result set looks like. In this case the query would look something like this.&lt;blockquote&gt;&lt;pre style="background: silver; padding:5px"&gt;select&lt;br /&gt;   d.name,&lt;br /&gt;   e.employee_id&lt;br /&gt;from&lt;br /&gt;   department d&lt;br /&gt;left outer join&lt;br /&gt;   employee e using (department_id);&lt;/pre&gt;&lt;/blockquote&gt;Which produces a result set like the following.&lt;blockquote&gt;&lt;pre style="background: silver; padding:5px"&gt;department               employee id&lt;br /&gt;-----------------------  --------------&lt;br /&gt;accounting                1&lt;br /&gt;accounting                2&lt;br /&gt;accounting                3&lt;br /&gt;accounting                4&lt;br /&gt;accounting                5&lt;br /&gt;accounting                6&lt;br /&gt;human resources           7&lt;br /&gt;human resources           8&lt;br /&gt;human resources           9&lt;br /&gt;human resources          10&lt;br /&gt;human resources          11&lt;br /&gt;human resources          12&lt;br /&gt;human resources          13&lt;br /&gt;human resources          14&lt;br /&gt;information technology   15&lt;br /&gt;information technology   16&lt;br /&gt;information technology   17&lt;br /&gt;information technology   18&lt;br /&gt;information technology   19&lt;br /&gt;information technology   20&lt;br /&gt;marketing                21&lt;br /&gt;marketing                22&lt;br /&gt;marketing                23&lt;br /&gt;marketing                24&lt;br /&gt;research &amp; development   [NULL]&lt;br /&gt;engineering              25&lt;br /&gt;engineering              26&lt;br /&gt;engineering              27&lt;br /&gt;engineering              28&lt;br /&gt;engineering              29&lt;/pre&gt;&lt;/blockquote&gt;Note, because we did a &lt;tt&gt;&lt;strong&gt;LEFT OUTER JOIN&lt;/strong&gt;&lt;/tt&gt; we got a row for "Research &amp;amp; Development"" even though there aren't any corresponding employees. Now, let's add the &lt;tt&gt;&lt;strong&gt;COUNT&lt;/strong&gt;&lt;/tt&gt; function and see what happens.&lt;blockquote&gt;&lt;pre style="background: silver; padding:5px"&gt;select&lt;br /&gt;   d.name,&lt;br /&gt;   count(e.employee_id)&lt;br /&gt;from&lt;br /&gt;   department d&lt;br /&gt;left outer join&lt;br /&gt;   employee e using (department_id);&lt;/pre&gt;&lt;/blockquote&gt;This query results in...&lt;blockquote&gt;&lt;pre style="background: silver; padding:5px"&gt;department     count&lt;br /&gt;------------   ------&lt;br /&gt;accounting     29&lt;/pre&gt;&lt;/blockquote&gt;We only get one result. That's because we told the query to count &lt;em&gt;all&lt;/em&gt; the rows in the result set. In addition to the count we also told it to return the department name. Since &lt;tt&gt;&lt;strong&gt;d.name&lt;/strong&gt;&lt;/tt&gt; in this case is completely ambiguous MySQL just picks the first one from the uncounted/uncollapsed result set.&lt;br /&gt;&lt;br /&gt;Now instead of counting &lt;em&gt;all&lt;/em&gt; the rows we want to count the rows for each department. This is where &lt;tt&gt;&lt;strong&gt;GROUP BY&lt;/strong&gt;&lt;/tt&gt; comes into play.&lt;blockquote&gt;&lt;pre style="background: silver; padding:5px"&gt;select&lt;br /&gt;   d.name,&lt;br /&gt;   count(e.employee_id)&lt;br /&gt;from&lt;br /&gt;   department d&lt;br /&gt;left outer join&lt;br /&gt;   employee e using (department_id)&lt;br /&gt;group by&lt;br /&gt;   d.department_id;&lt;/pre&gt;&lt;/blockquote&gt;This query results in...&lt;blockquote&gt;&lt;pre style="background: silver; padding:5px"&gt;department                 employee id&lt;br /&gt;-----------------------    ------------&lt;br /&gt;accounting                 6&lt;br /&gt;human resources            8&lt;br /&gt;information technology     6&lt;br /&gt;marketing                  4&lt;br /&gt;research &amp; development     0&lt;br /&gt;engineering                5&lt;/pre&gt;&lt;/blockquote&gt;Now MySQL returns a row for each unique department. It also picks the department name from the first result in each group to return for the 'department' column.&lt;br /&gt;&lt;br /&gt;It's layoff season and management wants to know which departments have people making six figures. Let's look at adding a &lt;tt&gt;&lt;strong&gt;WHERE&lt;/strong&gt;&lt;/tt&gt; clause.&lt;blockquote&gt;&lt;pre style="background: silver; padding:5px"&gt;select&lt;br /&gt;   d.name,&lt;br /&gt;   count(e.employee_id) as emp_count&lt;br /&gt;from&lt;br /&gt;   department d&lt;br /&gt;inner join&lt;br /&gt;   employee e using (department_id)&lt;br /&gt;where&lt;br /&gt;   e.salary &gt;= 100000&lt;br /&gt;group by&lt;br /&gt;   d.department_id;&lt;/pre&gt;&lt;/blockquote&gt;Results in...&lt;blockquote&gt;&lt;pre style="background: silver; padding:5px"&gt;department                        emp_count&lt;br /&gt;-------------------------         ----------&lt;br /&gt;accounting                        1&lt;br /&gt;human resources                   1&lt;br /&gt;information technology            1&lt;br /&gt;engineering                       4&lt;/pre&gt;&lt;/blockquote&gt;So almost every department has at least one person who makes 100K or more. That's not interesting. What management &lt;em&gt;really&lt;/em&gt; wants to know is which departments have more than 2 people making six figures. If an interviewer asks you this chances are good they're slightly evil. Here's how you beat the evil.&lt;blockquote&gt;&lt;pre style="background: silver; padding:5px"&gt;select&lt;br /&gt;   d.name,&lt;br /&gt;   count(e.employee_id) as emp_count&lt;br /&gt;from&lt;br /&gt;   department d&lt;br /&gt;left outer join&lt;br /&gt;   employee e using (department_id)&lt;br /&gt;where&lt;br /&gt;   e.salary &gt;= 100000&lt;br /&gt;group by&lt;br /&gt;   d.department_id&lt;br /&gt;having &lt;br /&gt;   emp_count &gt; 2;&lt;/pre&gt;&lt;/blockquote&gt;Results in...&lt;blockquote&gt;&lt;pre style="background: silver; padding:5px"&gt;department              emp_count&lt;br /&gt;-------------           ----------&lt;br /&gt;engineering             4&lt;/pre&gt;&lt;/blockquote&gt;The &lt;tt&gt;&lt;strong&gt;HAVING&lt;/strong&gt;&lt;/tt&gt; clause! It works in conjunction with with the &lt;tt&gt;&lt;strong&gt;GROUP BY&lt;/strong&gt;&lt;/tt&gt; to further filter the result set. The &lt;tt&gt;&lt;strong&gt;GROUP BY&lt;/strong&gt;&lt;/tt&gt; clause takes the raw result set and performs a calculation and filters the results to a smaller set of rows. The &lt;tt&gt;&lt;strong&gt;HAVING&lt;/strong&gt;&lt;/tt&gt; clause picks up reduced result set and applies an additional filter. The column used in the &lt;tt&gt;&lt;strong&gt;HAVING&lt;/strong&gt;&lt;/tt&gt; condition must be an explicitly selected column. In this case that's either &lt;tt&gt;&lt;strong&gt;d.name&lt;/strong&gt;&lt;/tt&gt; or &lt;tt&gt;&lt;strong&gt;emp_count&lt;/strong&gt;&lt;/tt&gt;. &lt;br /&gt;&lt;br /&gt;Tip: It's a good idea to name your calculated columns to make them easier to reference later in the query if required.&lt;br /&gt;&lt;br /&gt;That should give you enough ammunition to pass any &lt;tt&gt;&lt;strong&gt;GROUP BY&lt;/strong&gt;&lt;/tt&gt; questions an interviewer throws at you. If you do get a crazy question not covered in the article let me know if the comments and I'll add it. Good luck.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7095459794498138151-3682398224039129239?l=coding-tank.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://coding-tank.blogspot.com/feeds/3682398224039129239/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7095459794498138151&amp;postID=3682398224039129239' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/3682398224039129239'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/3682398224039129239'/><link rel='alternate' type='text/html' href='http://coding-tank.blogspot.com/2009/01/besides-doing-sql-joins-other-thing.html' title='SQL Group By'/><author><name>Coding Tank</name><uri>http://www.blogger.com/profile/05509742139651470412</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://1.bp.blogspot.com/_2HAb5HYui54/STcsDiD1cnI/AAAAAAAAAAM/lHjlgVYDDeo/S220/un_tank.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7095459794498138151.post-4503709310208130349</id><published>2009-01-12T14:27:00.000-08:00</published><updated>2009-01-12T14:32:23.940-08:00</updated><title type='text'>SQL Joins</title><content type='html'>If you are interviewing for a web engineering position you are guaranteed to be asked to write an SQL join or two. This is an easy way for the interviewer to verify that you really do know how to write SQL and aren't lying about it. When I learned SQL back in the old days (early 90's) we didn't have fancy join statements. We did everything in the WHERE clause and we liked it. Seriously, I liked it. I had a hard time adjusting to reading and writing join statements. But over the last couple of years I've become accustomed to it. Let's look at a few different types of joins you can do. First let's define a couple of tables. Here's are your standard job interview test tables. NOTE: This article is written from a MySQL point of view.&lt;pre&gt;&lt;br /&gt;+---------------+--------------+------+-----+---------+----------------+&lt;br /&gt;| Field         | Type         | Null | Key | Default | Extra          |&lt;br /&gt;+---------------+--------------+------+-----+---------+----------------+&lt;br /&gt;| employee_id   | int(11)      | NO   | PRI | NULL    | auto_increment | &lt;br /&gt;| department_id | int(11)      | NO   |     | NULL    |                | &lt;br /&gt;| name          | varchar(255) | NO   |     | NULL    |                | &lt;br /&gt;+---------------+--------------+------+-----+---------+----------------+&lt;br /&gt;&lt;br /&gt;+---------------+--------------+------+-----+---------+----------------+&lt;br /&gt;| Field         | Type         | Null | Key | Default | Extra          |&lt;br /&gt;+---------------+--------------+------+-----+---------+----------------+&lt;br /&gt;| department_id | int(11)      | NO   | PRI | NULL    | auto_increment | &lt;br /&gt;| name          | varchar(255) | NO   |     | NULL    |                | &lt;br /&gt;+---------------+--------------+------+-----+---------+----------------+&lt;/pre&gt;&lt;br /&gt;I populated these tables with some random data. Most employees have corresponding records in the department table but some don't. Additionally, there are some departments without any associated employees.&lt;br /&gt;&lt;br /&gt;Let's look at a few questions and answers to demonstrate the various types of joins you can perform on these two tables.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;List all employees along with their department.&lt;/strong&gt;&lt;pre&gt;&lt;br /&gt;select&lt;br /&gt;   e.name,&lt;br /&gt;   d.name&lt;br /&gt;from&lt;br /&gt;   employee e&lt;br /&gt;inner join&lt;br /&gt;   department d using (department_id)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This uses an 'inner join' since the result set will only include rows from both tables where &lt;tt&gt;department_id&lt;/tt&gt; matches. We could have omitted 'inner' and just used 'join'. The two are equivalent. However, I prefer to use 'inner join' since it explicitly states what is happening.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;List all employees along with their department. Include employees who don't have a department.&lt;/strong&gt;&lt;pre&gt;&lt;br /&gt;select&lt;br /&gt;   e.name,&lt;br /&gt;   d.name&lt;br /&gt;from&lt;br /&gt;   employee e&lt;br /&gt;left outer join&lt;br /&gt;   department d using (department_id)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;When performing a join the 'left' table is the table specified in the 'from' clause. To include all rows from this table whether or not there is a matching record in the department table you need to use a 'left outer join'. Technically you could just write 'left join' but again I prefer the more verbose syntax since it leaves no question about the intent of the query.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;List all employees along with their department. Also include departments which don't have any employees.&lt;/strong&gt;&lt;pre&gt;&lt;br /&gt;select&lt;br /&gt;   e.name,&lt;br /&gt;   d.name&lt;br /&gt;from&lt;br /&gt;   employee e&lt;br /&gt;right outer join&lt;br /&gt;   department d using (department_id)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Using a 'right outer join' will force all rows from the &lt;tt&gt;department&lt;/tt&gt; table to be represented in the result set. Right joins in a production environment are extremely rare. Personally, I've never seen or used one in any of the code I've worked on in my 13 years of experience. The concept is confusing and performance is poor. I strongly recommend against using them. But in case you're asked to explain a 'right join' in an interview you now know.&lt;br /&gt;&lt;br /&gt;I'm not going to discuss the 'full outer join' since MySQL doesn't support it and like the 'right join' you shouldn't be using it anyway. But it does what you would expect it to. Include rows from both left and right tables regardless if there's a match or not.&lt;br /&gt;&lt;br /&gt;Something else you shouldn't ever do in MySQL is substitute 'cross join' for 'inner join'. Seriously, in &lt;a href="http://dev.mysql.com/doc/refman/5.0/en/join.html"&gt;MySQL the two are syntactically equivalent&lt;/a&gt; which makes no sense since the phrase 'cross join' has always meant a cartesian product in my experience.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7095459794498138151-4503709310208130349?l=coding-tank.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://coding-tank.blogspot.com/feeds/4503709310208130349/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7095459794498138151&amp;postID=4503709310208130349' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/4503709310208130349'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/4503709310208130349'/><link rel='alternate' type='text/html' href='http://coding-tank.blogspot.com/2009/01/sql-joins.html' title='SQL Joins'/><author><name>Coding Tank</name><uri>http://www.blogger.com/profile/05509742139651470412</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://1.bp.blogspot.com/_2HAb5HYui54/STcsDiD1cnI/AAAAAAAAAAM/lHjlgVYDDeo/S220/un_tank.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7095459794498138151.post-1575853494896124895</id><published>2009-01-08T15:05:00.001-08:00</published><updated>2009-01-08T15:06:47.029-08:00</updated><title type='text'>SQL Injection</title><content type='html'>SQL Injection is kind of old news. We've been hearing about it for years and things like cross-site scripting is more interesting now anyway. But how many of you have actually been paying attention to how SQL injections attacks happen? How many of you are actually doing something about it?&lt;br /&gt;&lt;br /&gt;An SQL injection is when someone passes arbitrary SQL to your application. This usually happens through the html forms on your site. For example, you have a user login form where a user types in their email address and password. Without knowing anything about the database structure of the application I could enter something like this in the email field.&lt;br /&gt;&lt;br /&gt;&lt;tt&gt;blah' or 'n'=='n&lt;/tt&gt;&lt;br /&gt;&lt;br /&gt;or worse...&lt;br /&gt;&lt;br /&gt;&lt;tt&gt;'; drop table user;--&lt;/tt&gt;&lt;br /&gt;&lt;br /&gt;When your code drops those strings into the middle of an SQL statement bad things are going to happen. I'm not going to spend a lot of time explaining how to mount an SQL injection attack. People much smarter than me have already done so. Fire up Google and learn how it's done by the pros. The question is, are you protected? Here's how.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Sanitize Input&lt;/strong&gt;&lt;br /&gt;All data entering your database should be considered suspect. Any value than can vary in an SQL statement needs to be sanitized before it's used. Don't try to code this routine yourself. Use what the database vendor already gives you. If you're using MySQL then you're going to want to use &lt;tt&gt;mysql_real_escape_string()&lt;/tt&gt;&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Hide Error Messages&lt;/strong&gt;&lt;br /&gt;The public should never ever see error messages generated by the application server. Those are for developers to look at and no one else. These messages give away all kinds of information useful to attackers. Many times you're going to want to avoid even indicating an error occurred. &lt;a href="http://www.unixwiz.net/techtips/sql-injection.html"&gt;This paper&lt;/a&gt; shows how an attacker was able to use the presence or absence of a 500 error page to know if they were able to generate valid SQL or not. How to handle errors and bad user input is entirely application dependent. Use your best judgement.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Limit Database Permissions&lt;/strong&gt;&lt;br /&gt;The web application should connect to the database as a user with a limited set of permissions. If the user anonymous you're going to want to use a read-only db user in that case. Again, this is application dependent. Use your judgment but think about database permissions. Think about it hard.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Parameterized Queries&lt;/strong&gt;&lt;br /&gt;I saved the best for last. In my experience 99% of all web application piece together SQL on the fly. This is bad for performance and even worse for security. I'm not judging because I've written tons of code like that myself. However, there is a better way. Using parameterized queries gives you some performance gains as well as a huge improvement in security. No longer can your original statement be modified into something you never intended.&lt;br /&gt;&lt;br /&gt;Parameterized queries is where you construct your SQL statement using '?' placeholders for variable data and then bind the actual values to those placeholders. Once again, an example speaks far better than my prose. This is in &lt;a href="http://www.php.net/manual/en/class.mysqli-stmt.php"&gt;PHP5&lt;/a&gt;.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$sql = "select first_name, last_name from user where email = ?";&lt;br /&gt;$stmt = $mysqli-&gt;prepare($sql);&lt;br /&gt;&lt;br /&gt;$email = "someone@yahoo.com";&lt;br /&gt;$stmt-&gt;bind_param("s", $email);&lt;br /&gt;&lt;br /&gt;$stmt-&gt;execute();&lt;br /&gt;$stmt-&gt;bind_result($first, $last);&lt;br /&gt;&lt;br /&gt;while($stmt-&gt;fetch()) {&lt;br /&gt; print("$first $last");&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7095459794498138151-1575853494896124895?l=coding-tank.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://coding-tank.blogspot.com/feeds/1575853494896124895/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7095459794498138151&amp;postID=1575853494896124895' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/1575853494896124895'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/1575853494896124895'/><link rel='alternate' type='text/html' href='http://coding-tank.blogspot.com/2009/01/sql-injection.html' title='SQL Injection'/><author><name>Coding Tank</name><uri>http://www.blogger.com/profile/05509742139651470412</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://1.bp.blogspot.com/_2HAb5HYui54/STcsDiD1cnI/AAAAAAAAAAM/lHjlgVYDDeo/S220/un_tank.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7095459794498138151.post-5262883858337627264</id><published>2009-01-05T17:13:00.000-08:00</published><updated>2009-01-08T10:15:27.112-08:00</updated><title type='text'>Cookie Security</title><content type='html'>&lt;em&gt;Describe the security model for browser cookies?&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;That question was asked of me in a job interview recently. Unfortunately, I didn't have a good answer because I haven't actually worked with the details of creating and reading cookies for a long time. When you don't exercise a muscle it loses its tone. If you don't exercise knowledge it fades. That's what happened here. Unless you're coding new sites frequently you're probably not dealing with cookie security that often. It was time for me to hit the books and brush up on my knowledge.&lt;br /&gt;&lt;br /&gt;So, what &lt;em&gt;&lt;strong&gt;is&lt;/strong&gt;&lt;/em&gt; the security model for browser cookies? Here goes.&lt;br /&gt;&lt;br /&gt;First, browsers will only send cookies to the site which set the cookie. In this case we define "site" by a domain name and path. If a cookie is set with a domain of &lt;tt&gt;news.google.com&lt;/tt&gt; then only &lt;tt&gt;news.google.com&lt;/tt&gt; can read that cookie. A cookie set with &lt;tt&gt;mail.google.com&lt;/tt&gt; can only be read by &lt;tt&gt;mail.google.com&lt;/tt&gt;.&lt;br /&gt;&lt;br /&gt;What if you want to share cookies among subdomains in a given domain? For example you want &lt;tt&gt;news.google.com&lt;/tt&gt; and &lt;tt&gt;mail.google.com&lt;/tt&gt; to read the same cookie. If the cookie is set to a subset of the fully qualified name then the cookie will be shared among any server whose tail matches the domain of the cookie.&lt;br /&gt;&lt;br /&gt;For example: a cookie with a domain of &lt;tt&gt;google.com&lt;/tt&gt; will be sent to &lt;tt&gt;mail.google.com&lt;/tt&gt; as well as &lt;tt&gt;www.google.com&lt;/tt&gt; and &lt;tt&gt;news.google.com&lt;/tt&gt;. &lt;br /&gt; &lt;br /&gt;Furthermore,  a cookie with a domain of &lt;tt&gt;news.google.com&lt;/tt&gt; will be sent to &lt;tt&gt;foo.news.google.com&lt;/tt&gt; and &lt;tt&gt;foo.bar.news.google.com&lt;/tt&gt;.&lt;br /&gt;&lt;br /&gt;When setting the domain for a cookie you must specify a domain name and not just a top-level-domain. Meaning, you can't set a cookie to just &lt;tt&gt;.com&lt;/tt&gt; or &lt;tt&gt;.edu&lt;/tt&gt;. Browsers won't let you do this. But what about &lt;tt&gt;co.uk&lt;/tt&gt;? That's a TLD but it has two parts so according to the specification it's fair game. Older browsers do in fact allow this to happen. However, newer browsers have restrictions to prevent cookies being tied to these particular two-part TLDs although each browser implements these restrictions a little differently.&lt;br /&gt;&lt;br /&gt;So now we understand domains and tail matching. We can also restrict a cookie to a particular path on the server. For example, if a cookie's path is set to &lt;tt&gt;/blah/&lt;/tt&gt; it will only be available to requests with the &lt;tt&gt;/blah/&lt;/tt&gt; directory and any sub-directory such as &lt;tt&gt;/blah/hooga/&lt;/tt&gt;.&lt;br /&gt;&lt;br /&gt;There is one extra layer of built-in protection that's worth noting. When creating a cookie you can specify if it is to be sent only over HTTPS or not. However, since the majority of my work exists in the plain HTTP world this feature doesn't do me a lot of good.&lt;br /&gt;&lt;br /&gt;We now understand the rules for which cookies get sent to which servers but there's a couple of other problems. First, cookies are sent as clear text across the net and can be eavesdropped on. Second, users can modify their cookies to contain values you may not want. How do we protect against eavesdropping and tampering?&lt;br /&gt;&lt;br /&gt;Let's address the eavesdropping question first. We'll use two-way encryption to reduce the chance of eavesdropping. We're using two-way encryption because we want to send an encrypted value over the net but we need to decrypt the value so we can actually read it once the cookie has landed on the server. This doesn't make the cookie bullet proof because it &lt;strong&gt;can&lt;/strong&gt; still be decrypted by a 3rd party with brute-force. Because of this and other reasons, you should store as little personal information as possible in cookies. If this cookie is intended to recognize a logged in user you may want to minimally include userid and ip address.&lt;br /&gt;&lt;br /&gt;Moving on... let's say a cookie has been modified by whatever means and it may contain bad values. Values that might allow a user to impersonate other users. Like admins. We don't want that.&lt;br /&gt;&lt;br /&gt;Here is where we're going to use one-way hashing on the cookie value itself. Let's use the logged in user example. We want to store username in a cookie for users who are logged in so the site will recognize them between sessions. We'll take the username, say, 'bob', add a salt to it and generate a hash using whatever algorithm floats your boat. Now we'll append the hash to the username and that becomes are full cookie value. Of course, you'll need a delimiter between the individual fields so you can pick them apart when you need to read it later. For example...&lt;br /&gt; &lt;br /&gt;&lt;tt&gt;bob|cac991e4b010585f61ed2e40641ec77e&lt;/tt&gt;&lt;br /&gt;&lt;br /&gt;This is our basic cookie value. This is what we're going to encrypt and decrypt. Upon decryption we're going to rehash the username and make sure it matches up with the hash sent in the cookie. If the hashes match we're good cookie. If not, then you know something fishy is going on.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7095459794498138151-5262883858337627264?l=coding-tank.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://coding-tank.blogspot.com/feeds/5262883858337627264/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7095459794498138151&amp;postID=5262883858337627264' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/5262883858337627264'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/5262883858337627264'/><link rel='alternate' type='text/html' href='http://coding-tank.blogspot.com/2009/01/describe-security-module-for-browser.html' title='Cookie Security'/><author><name>Coding Tank</name><uri>http://www.blogger.com/profile/05509742139651470412</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://1.bp.blogspot.com/_2HAb5HYui54/STcsDiD1cnI/AAAAAAAAAAM/lHjlgVYDDeo/S220/un_tank.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7095459794498138151.post-8409469847801843167</id><published>2008-12-04T22:18:00.000-08:00</published><updated>2008-12-04T22:21:13.487-08:00</updated><title type='text'>How Not to Use XML</title><content type='html'>One aspect of my job is to work with 3rd parties to include commerce links to buy stuff from our site(s). This involves the 3rd party giving us a data feed of products which we take and try to match up with products in our own database. Sometimes the feeds will be a delimited text file. Sometimes they're be XML. Here's a small sample of some XML I recently had the pleasure of working with. The actual element names and data have been changed to protect the guilty party.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;Thing&amp;gt;&lt;br /&gt; &amp;lt;stuff name="product_id"&amp;gt;0926INGBS10075810&amp;lt;/stuff&amp;gt;&lt;br /&gt; &amp;lt;stuff name="company"&amp;gt;Curabitur Fringilla Corp.&amp;lt;/stuff&amp;gt;&lt;br /&gt; &amp;lt;stuff name="title"&amp;gt;Nullam Enim Justo (PC/Mac)&amp;lt;/stuff&amp;gt;&lt;br /&gt; &amp;lt;stuff name="Price"&amp;gt;24.99&amp;lt;/stuff&amp;gt;&lt;br /&gt; &amp;lt;stuff name="Availability"&amp;gt;See Site&amp;lt;/stuff&amp;gt;&lt;br /&gt; &amp;lt;stuff name="upc"&amp;gt;0600100098554&amp;lt;/stuff&amp;gt;&lt;br /&gt; &amp;lt;stuff name="description"&amp;gt;Lorem ipsum dolor sit amet&amp;lt;/stuff&amp;gt;&lt;br /&gt; &amp;lt;stuff name="boxshot"&amp;gt;http://www.blah.com/product.gif&amp;lt;/stuff&amp;gt;&lt;br /&gt; &amp;lt;stuff name="purchase_url"&amp;gt;http://www.blah.com/buy.asp?id=0123456789&amp;lt;/stuff&amp;gt;&lt;br /&gt;&amp;lt;/Thing&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;At this point why not just provide the data in a delimited text format? Everything having the same element name and using attributes to identify different fields actually makes the parsing of the feed *more* difficult and error prone.&lt;br /&gt;&lt;br /&gt;It seems to me someone at this company said, "XML is what all the cool kids use. Our feed needs to be in XML. Then we'll be one of the cool kids too." Then someone who doesn't really understand what XML was intended to accomplish put this together.&lt;br /&gt;&lt;br /&gt;Would it have been so hard to do this instead?&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;thing&amp;gt;&lt;br /&gt; &amp;lt;product_id&amp;gt;0926INGBS10075810&amp;lt;/product_id&amp;gt;&lt;br /&gt; &amp;lt;company&amp;gt;Curabitur Fringilla Corp.&amp;lt;/company&amp;gt;&lt;br /&gt; &amp;lt;title&amp;gt;Nullam Enim Justo (PC/Mac)&amp;lt;/title&amp;gt;&lt;br /&gt; &amp;lt;price&amp;gt;24.99&amp;lt;/price&amp;gt;&lt;br /&gt; &amp;lt;availability&amp;gt;See Site&amp;lt;/availability&amp;gt;&lt;br /&gt; &amp;lt;upc&amp;gt;0600100098554&amp;lt;/upc&amp;gt;&lt;br /&gt; &amp;lt;description&amp;gt;Lorem ipsum dolor sit amet.&amp;lt;/description&amp;gt;&lt;br /&gt; &amp;lt;boxshot&amp;gt;http://www.blah.com/product.gif&amp;lt;/boxshot&amp;gt;&lt;br /&gt; &amp;lt;purchase_url&amp;gt;http://www.blah.com/buy.asp?id=0123456789&amp;lt;/purchase_url&amp;gt;&lt;br /&gt;&amp;lt;/thing&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7095459794498138151-8409469847801843167?l=coding-tank.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://coding-tank.blogspot.com/feeds/8409469847801843167/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7095459794498138151&amp;postID=8409469847801843167' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/8409469847801843167'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/8409469847801843167'/><link rel='alternate' type='text/html' href='http://coding-tank.blogspot.com/2008/12/how-not-to-use-xml.html' title='How Not to Use XML'/><author><name>Coding Tank</name><uri>http://www.blogger.com/profile/05509742139651470412</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://1.bp.blogspot.com/_2HAb5HYui54/STcsDiD1cnI/AAAAAAAAAAM/lHjlgVYDDeo/S220/un_tank.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7095459794498138151.post-2694558432191285320</id><published>2008-12-03T17:03:00.000-08:00</published><updated>2008-12-04T16:56:39.207-08:00</updated><title type='text'>What "Coding Tank" Means To Me</title><content type='html'>I've been wanting to write about my chosen profession of software development for a long time and have been patiently for the right context to present itself to me. I think I've finally found it. I should probably explain the title of the blog though. &lt;br /&gt;&lt;img align="right" src="http://img408.imageshack.us/img408/961/nightelfonslaughtplusnu1.jpg" border="0" hspace="3" vspace="3"/&gt;&lt;br /&gt;I play World of Warcraft in my free time and one of the roles you can take on is that of the "tank". The tank is responsible to facing off against the big bad monster and taking any punishment it has to dish out. If you're doing your job well as a tank you're the sole object of the monster's aggression leaving your other party members free to do their thing. You're the one who is first to engage with the scariest bosses. If you're fighting multiple enemies then you may have to frequently switch targets to ensure you're taking all the aggression and no monsters are beating on any of your squishier group members.&lt;br /&gt;&lt;br /&gt;My programming life has felt a bit like this recently. The analogy breaks down at some point but essentially I have been a coding tank for a while. I'm the guy people first come to with the toughest problems. I'm first to dive into the least understood parts of our code to figure out why a particular behavior occurs. When emergencies happen I'm the first responder. It doesn't matter if there's three or four fires all going on at once. I'm battling all four of them. I take care of all the annoying and undesirable crap so others can work relatively unhindered.&lt;br /&gt;&lt;br /&gt;This is not to boast. Far from it. I'd rather not be the tank. I'd much rather be one of those who can just sit down and work on something and not get interrupted every 15 minutes with a fresh crisis. I suppose something in my personality and work ethic has led me to this role even though I have mixed feelings about it.&lt;br /&gt;&lt;br /&gt;Anyway, we're slightly off-topic now and I think that's about enough for a first post.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7095459794498138151-2694558432191285320?l=coding-tank.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://coding-tank.blogspot.com/feeds/2694558432191285320/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7095459794498138151&amp;postID=2694558432191285320' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/2694558432191285320'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7095459794498138151/posts/default/2694558432191285320'/><link rel='alternate' type='text/html' href='http://coding-tank.blogspot.com/2008/12/test-post.html' title='What &quot;Coding Tank&quot; Means To Me'/><author><name>Coding Tank</name><uri>http://www.blogger.com/profile/05509742139651470412</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://1.bp.blogspot.com/_2HAb5HYui54/STcsDiD1cnI/AAAAAAAAAAM/lHjlgVYDDeo/S220/un_tank.jpg'/></author><thr:total>0</thr:total></entry></feed>
