<?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-8506302596139279537</id><updated>2012-02-08T08:24:37.575-05:00</updated><title type='text'>The Sleepless Geek</title><subtitle type='html'>A blog about coding.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>36</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-6929793648011781631</id><published>2012-02-01T13:59:00.001-05:00</published><updated>2012-02-01T13:59:12.242-05:00</updated><title type='text'>The beauty of xargs</title><content type='html'>Every time we deploy our Rails application, our deployment tool creates a new folder based on the date and time, puts the right files in it, and when everything is ready to go, updates a symlink to point to the newest release.&lt;br/&gt;&lt;br/&gt;Yesterday I found that I couldn't deploy because we were out of disk space. I needed to delete some of the old release folders.&lt;br/&gt;&lt;br/&gt;When I sshed into the server and looked in the releases folder, it had a ton of folders named like this:&lt;br/&gt;&lt;pre class="code"&gt;20120126191222&lt;br /&gt;20120126193901&lt;br /&gt;20120127153732&lt;br /&gt;20120127171244&lt;br /&gt;20120127204235&lt;br /&gt;20120127204517&lt;br /&gt;20120130172837&lt;br /&gt;20120131152908&lt;br /&gt;20120131160422&lt;br /&gt;&lt;/pre&gt;Here you see some from 2012, but they went back all the way to 2010. I wanted to delete everything older than 2012.&lt;br/&gt;&lt;br/&gt;Enter the magic of &lt;strong&gt;xargs&lt;/strong&gt;. It lets you take the output of one command and input each line of it as an argument to another command.&lt;br/&gt;&lt;br/&gt;Here's what I did:&lt;pre class="code"&gt;ls # shows me all the folders&lt;br /&gt;ls | grep ^201[01] # shows only the ones starting with 2010 or 2011&lt;br /&gt;ls | grep ^201[01] | xargs rm -rf # delete all those&lt;br /&gt;&lt;/pre&gt;Building up the command bit-by-bit lets me verify that I'm going to delete the right things. And xargs knocks it out.&lt;br/&gt;&lt;br/&gt;Here's another useful example. Vim creates a temporary .swp files that sometimes don't get cleaned up. To find and delete them all out of a folder:&lt;br/&gt;&lt;pre class="code"&gt;find . -name '*.swp' | xargs rm&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/8506302596139279537-6929793648011781631?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/6929793648011781631/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2012/02/beauty-of-xargs.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/6929793648011781631'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/6929793648011781631'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2012/02/beauty-of-xargs.html' title='The beauty of xargs'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-9223260203121316875</id><published>2011-12-22T06:25:00.000-05:00</published><updated>2011-12-22T06:25:00.658-05:00</updated><title type='text'>Faster feedback</title><content type='html'>In programming, the faster you get feedback from your code, the more productive you can be.&lt;br /&gt;&lt;br /&gt;For example, suppose you're going to use the Javascript &lt;span class="code"&gt;Date&lt;/span&gt; class in a web page to do some time calculations. Do you:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Write some code, save the file, reload the page, look at the results, and repeat?&lt;/li&gt;&lt;li&gt;Open a Javascript console (like Chome Developer Tools or Firebug) and experiment?&lt;/li&gt;&lt;/ul&gt;The second method gives you instant feedback. This means you can find the right approach more quickly and not lose focus in the meantime. The same is true when coding Ruby: if you want to see what &lt;span class="code"&gt;Array#select&lt;/span&gt; does, irb will give you much faster feedback than a Rails app.&lt;br /&gt;&lt;br /&gt;Other ways you can get feedback faster include:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;To learn more about Git, create a temporary folder and a dummy project: &lt;span class="code"&gt;mkdir ~/test; cd ~/test; git init; echo 'hello' &amp;gt; test.txt; git add .; git commit -m"first";&lt;/span&gt; Now you can experiment: branch and merge, rebase, and whatever else you want to try without fear of screwing up a real project. When you're satisfied, just delete that folder.&lt;/li&gt;&lt;li&gt;If you're thinking of adding a command to one of your runtime configuration files, like .bashrc or .vimrc, run the command directly first and see what it does.&lt;/li&gt;&lt;li&gt;Take the time to make your automated tests run faster: you'll be more likely to run them often and less likely to lose focus when you do.&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8506302596139279537-9223260203121316875?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/9223260203121316875/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/12/faster-feedback.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/9223260203121316875'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/9223260203121316875'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/12/faster-feedback.html' title='Faster feedback'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-8220159176972877189</id><published>2011-08-26T14:47:00.003-04:00</published><updated>2011-08-26T16:40:02.543-04:00</updated><title type='text'>Testing a Rails 3.1 Engine's Javascript with Jasmine</title><content type='html'>At work, we've got a Rails engine that provides some complex drop-in forms to various applications. The forms have associated Javascript which, on Rails 3.0, we had to copy and paste between applications. That's both annoying and fragile, since things can get out of sync.&lt;br /&gt;&lt;br /&gt;To test our engine, we had created a dummy app with the Enginex gem, located in &lt;span class="code"&gt;spec/dummy&lt;/span&gt;. &lt;br /&gt;&lt;br /&gt;In upgrading the engine to 3.1 (currently on release candidate 6), we wanted to move the associated JS into the engine, so that they would be versioned together in a single gem.&lt;br /&gt;&lt;br /&gt;That wasn't so hard, but we also needed our associated Jasmine tests to run. For that, we needed to precompile our Javascript before &lt;span class="code"&gt;rake jasmine&lt;/span&gt; ran so that Jasmine could load and test it.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Precompile Problems&lt;/h2&gt;&lt;br /&gt;And that's where things got sticky. According to &lt;a href="http://ryanbigg.com/guides/asset_pipeline.html#precompiling-assets"&gt;Ryan Biggs' documentation&lt;/a&gt;, the default matcher for precompiling files is:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: ruby"&gt;[ /\w+\.(?!js|css).+/, /application.(css|js)$/ ]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Basically, besides application.js and application.css, any js (or css) files with multiple dots in it, whether it's &lt;span class="code"&gt;foo.js.coffee&lt;/span&gt; or &lt;span class="code"&gt;jquery.cookie.js&lt;/span&gt;, will get compiled to its own file.&lt;br /&gt;&lt;br /&gt;We had two problems with that:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;It &lt;strong&gt;didn't&lt;/strong&gt; match our engine's manifest file, because it's not named application.js. (It's not named that because in the host application, we will already have an application.js and we don't want a conflict.)&lt;/li&gt;&lt;li&gt;It &lt;strong&gt;did&lt;/strong&gt; match a bunch of other JS files which didn't need to be compiled separately. For example, the manifest requires &lt;span class="code"&gt;jquery.cookie.js&lt;/span&gt;, so that file gets compiled and added into the manifest file. It doesn't need to &lt;strong&gt;also&lt;/strong&gt; be compiled as &lt;span class="code"&gt;jquery-aaff723a97d782d30e3fc2f0dee5d849.cookie.js&lt;/span&gt;; we don't plan to serve it separately.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;To solve the first problem, in Engine.rb, we added:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: ruby"&gt;initializer "myEngine.asset_pipeline" do |app|&lt;br /&gt;  app.config.assets.precompile &amp;lt;&amp;lt; 'myEngine-manifest.js'&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;To solve the second problem, we... did nothing. We don't care. We're letting it create extra compiled files that we don't need.&lt;br /&gt;&lt;br /&gt;We &lt;strong&gt;could&lt;/strong&gt; have changed the match regex to only match files with endings like in &lt;span class="code"&gt;.js.something&lt;/span&gt; or &lt;span class="code"&gt;.css.something&lt;/span&gt;, instead of the broad rule that will also match &lt;span class="code"&gt;.min.js&lt;/span&gt;. But doing that in an engine might have broken a host application if it were compiling something we didn't foresee. Maybe at some point a host app will want to compile &lt;span class="code"&gt;.csv.erb&lt;/span&gt; files or something; we don't want to preclude that.&lt;br /&gt;&lt;br /&gt;So we chose "be unobtrusive to the host app" over "prevent unnecessary compilation".&lt;br /&gt;&lt;br /&gt;(We also could have prevented &lt;span class="code"&gt;jquery.cookie.js&lt;/span&gt; from being compiled separately by renaming it to &lt;span class="code"&gt;jquery-cookie.js&lt;/span&gt;, but we think that's annoying.)&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Jasmine Setup&lt;/h2&gt;&lt;br /&gt;Now that our &lt;span class="code"&gt;myEngine-manifest.js&lt;/span&gt; was being compiled to &lt;span class="code"&gt;myEngine-manifest-20309309d9309330309390.js&lt;/span&gt;, we needed to let Jasmine know to load that single file, containing all our compiled Javascript, rather than all the separate files it had to load previously. So, in jasmine.yml, we now have:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: ruby"&gt;src_files:&lt;br /&gt;  - spec/dummy/public/assets/myEngine-manifest-*.js&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The * will match whatever hash fingerprint is added to the filename.&lt;br /&gt;&lt;br /&gt;Now, before we can run &lt;span class="code"&gt;rake jasmine&lt;/span&gt;, we needed to make sure everything was compiled appropriately. So we created this simple Rake task in the engine's Rakefile:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: ruby"&gt;task :precompile_jasmine =&amp;gt; ['assets:clean', 'assets:precompile', 'jasmine']&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Engine Rake tasks don't necessarily need access to Rails tasks, so to get the task above to work, we had to put the following above it so that we'd have access to those asset-related Rails tasks: &lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: ruby"&gt;load File.expand_path('../spec/dummy/Rakefile', &lt;strong&gt;FILE&lt;/strong&gt;)&lt;/p&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h2&gt;Turning on Sprockets&lt;/h2&gt;&lt;br /&gt;To get Sprockets working in our dummy application, we had to make two changes to &lt;span class="code"&gt;spec/dummy/config/application.rb&lt;/span&gt;:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Below the other railties requirements, add &lt;span class="code"&gt;require sprockets/railtie&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Within the app settings block, add &lt;span class="code"&gt;config.assets.enabled = true&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;You may not need to do this; it's a side-effect of the fact that we generated our engine with a version of the Enginex gem that preceded Rails 3.1.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Other setup&lt;/h2&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;To get Jasmine working with Rails 3.1, make sure you've got a new enough version. Jasmine 1.0.2.1 worked for us.&lt;/li&gt;&lt;li&gt;You don't want to check in all your compiled assets, so be sure to add this to your &lt;span class="code"&gt;.gitignore&lt;/span&gt;: &lt;span class="code"&gt;spec/dummy/public/assets/*&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h2&gt;Weirdness&lt;/h2&gt;&lt;br /&gt;Something weird about &lt;span class="code"&gt;assets:precompile&lt;/span&gt; makes it, and any task that runs after it, run twice. This means that `rake jasmine` requires two interrupts to shut down properly in our current setup. (In a previous attempt, it tried to run again while it was running, and got an error because it couldn't re-bind to the same port.)&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Addendum&lt;/h2&gt;&lt;br /&gt;There's no way I could have figured this out by myself, and I'm not sure that I'll be able to answer questions about it. &lt;a href="https://github.com/adamhunter"&gt;Adam Hunter&lt;/a&gt; and I worked on it together, and he contributed more brainpower than I did. But after all the blind alleys and frustration, I was determined to write up what we did, and got Adam's help doing so. So: good luck replicating this. :)&lt;br /&gt;&lt;br /&gt;Also, if you're looking to use Jasmine in a Rails 3.1 app with coffeescript, &lt;a href="http://pivotallabs.com/users/jdean/blog/articles/1778-writing-and-running-jasmine-specs-with-rails-3-1-and-coffeescript"&gt;this blog post from Pivotal Labs&lt;/a&gt; may be more helpful. We got some ideas from it, too.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8506302596139279537-8220159176972877189?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/8220159176972877189/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/08/testing-rails-31-engines-javascript.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/8220159176972877189'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/8220159176972877189'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/08/testing-rails-31-engines-javascript.html' title='Testing a Rails 3.1 Engine&apos;s Javascript with Jasmine'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-8631433800401564605</id><published>2011-07-14T12:43:00.001-04:00</published><updated>2011-07-14T13:32:34.135-04:00</updated><title type='text'>Sometimes you can be too DRY</title><content type='html'>In a Rails app, my favorite idiom for describing what users can and cannot do goes like this:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: ruby"&gt;# In view code, for example...&lt;br /&gt;  link_to "Edit Widget", edit_widget_path if current_user.can_edit?(widget)&lt;br /&gt;&lt;br /&gt;  # which calls this method&lt;br /&gt;  class User&lt;br /&gt;    def can_edit?(resource)&lt;br /&gt;      resource.editable_by?(self)&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt; # ... which in turn calls this method&lt;br /&gt; class Widget&lt;br /&gt;   def editable_by?(user)&lt;br /&gt;     # whatever logic makes sense in this particular app&lt;br /&gt;   end&lt;br /&gt; end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;I first saw this approach a couple of years ago, when I was new to Rails and found &lt;a href="http://pivotallabs.com/users/nick/blog/articles/272-access-control-permissions-in-rails"&gt;Nick Kallen's post about&lt;/a&gt; from 2007. John Nunemaker has since created the &lt;a href="https://github.com/jnunemaker/canable/"&gt;Canable gem&lt;/a&gt; to make this even easier.&lt;br /&gt;&lt;br /&gt;Defining these methods yourself is not that hard. But because they are all so similar, you might be tempted to use metaprogramming to shorten the code and make it more DRY.&lt;br /&gt;&lt;br /&gt;This is a rare example where sacrificing a little bit of DRY can make the code a lot more readable and maintainable: readable because your eyes don't have to bounce around as much, and maintainable because you can edit each method separately.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: ruby"&gt;  class User&lt;br /&gt;     # Metaprogramming way&lt;br /&gt;    {&lt;br /&gt;      :create =&gt; 'creatable', &lt;br /&gt;      :read =&gt; 'readable', &lt;br /&gt;      :edit =&gt; 'editable', &lt;br /&gt;      :delete =&gt; 'deletable', &lt;br /&gt;      :assign =&gt; 'assignable'&lt;br /&gt;    }.each do |verb, adjective|&lt;br /&gt;      define_method "can_#{verb}?".to_sym do |resource|&lt;br /&gt;        resource.send("#{adjective}_by?", self)&lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt;&lt;br /&gt;    # Long-form way: less DRY but easier &lt;br /&gt;    # to understand at a glance&lt;br /&gt;    def can_create?(resource)&lt;br /&gt;      resource.creatable_by?(self)&lt;br /&gt;    end&lt;br /&gt;&lt;br /&gt;    def can_read?(resource)&lt;br /&gt;      resource.readable_by?(self)&lt;br /&gt;    end&lt;br /&gt;  &lt;br /&gt;    def can_edit?(resource)&lt;br /&gt;      resource.editable_by?(self)&lt;br /&gt;    end&lt;br /&gt;&lt;br /&gt;    def can_delete?(resource)&lt;br /&gt;      resource.deletable_by?(self)&lt;br /&gt;    end&lt;br /&gt;&lt;br /&gt;    def can_assign?(resource)&lt;br /&gt;      resource.assignable_by?(self)&lt;br /&gt;    end&lt;br /&gt;  end&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/8506302596139279537-8631433800401564605?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/8631433800401564605/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/07/sometimes-you-can-be-too-dry.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/8631433800401564605'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/8631433800401564605'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/07/sometimes-you-can-be-too-dry.html' title='Sometimes you can be too DRY'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-1929297222866734537</id><published>2011-06-12T17:52:00.004-04:00</published><updated>2011-06-12T17:55:28.391-04:00</updated><title type='text'>Metaprogramming Danger Box</title><content type='html'>In &lt;a href="http://sleeplessgeek.blogspot.com/2011/06/using-execute-around-pattern-in-ruby.html"&gt;my last post&lt;/a&gt;, I showed how one can use the "execute around" pattern in Ruby to log any calls to a method, without cluttering the methods themselves with logging calls.&lt;br /&gt;&lt;br /&gt;That gave us code like this:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: ruby"&gt;def defuse_bomb&lt;br /&gt;    with_log('defuse_bomb') {puts "Snip the red wire..."}&lt;br /&gt;  end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Our &lt;span class="code"&gt;with_log&lt;/span&gt; method took care of printing out some logging info before and after executing the block given to it.&lt;br /&gt;&lt;br /&gt;But as Adam pointed out in the comments, this is still kind of ugly. We don't have all the actual logging code inside of the "defuse_bomb" method, but have had to modify it to use with_log. What if we could add logging more transparently?&lt;br /&gt;&lt;br /&gt;We can! With some metaprogramming magic added to the Logging module, here's the way the DangerBox class looks now:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: ruby"&gt;class DangerBox&lt;br /&gt;    extend Logging&lt;br /&gt;&lt;br /&gt;    add_logging_to(:defuse_bomb, :throw_error)&lt;br /&gt;&lt;br /&gt;    def defuse_bomb&lt;br /&gt;      puts "Snip the red wire..."&lt;br /&gt;    end&lt;br /&gt;&lt;br /&gt;    def throw_error&lt;br /&gt;      puts 'I feel an error coming on...'&lt;br /&gt;      raise 'Success: an error!'&lt;br /&gt;    end&lt;br /&gt;&lt;br /&gt;    # This method's output won't be logged&lt;br /&gt;    def eat_poison&lt;br /&gt;      puts "Mmmmm, poison!! Nom Nom Nom Nom!!!!"&lt;br /&gt;    end&lt;br /&gt;&lt;br /&gt;  end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;It still works the same way as it did before:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: ruby"&gt;d = DangerBox.new&lt;br /&gt;  d.defuse_bomb&lt;br /&gt;  # ==== Sun Jun 12 16:55:49 -0400 2011 ====&lt;br /&gt;  # About to execute 'defuse_bomb'...&lt;br /&gt;  # Snip the red wire...&lt;br /&gt;  # Executed 'defuse_bomb' successfully.&lt;br /&gt;&lt;br /&gt;  d.throw_error&lt;br /&gt;  # ==== Sun Jun 12 16:55:49 -0400 2011 ====&lt;br /&gt;  # About to execute 'throw_error'...&lt;br /&gt;  # I feel an error coming on...&lt;br /&gt;  # Error while executing 'throw_error': 'Success: an error!'!&lt;br /&gt;&lt;br /&gt;  d.eat_poison&lt;br /&gt;  # Mmmmm, poison!! Nom Nom Nom Nom!!!!&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Adam, I see your "hmmmm" and raise you a "BOOYAH!" (Although I didn't write tests for my version of this, so you still win on that front.)&lt;br /&gt;&lt;br /&gt;But how the heck did we get all this logging output? Obviously, it's something to do with the line that says: &lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;add_logging_to(:defuse_bomb, :throw_error)&lt;/div&gt;&lt;br /&gt;Well, yes: that and a spiffy new Logging module that redefines our methods on the fly to use logging. Here's the code:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: ruby"&gt;module Logging&lt;br /&gt;    # Keeps track of the methods we want to log&lt;br /&gt;    def add_logging_to(*args)&lt;br /&gt;      @logged_methods ||= []&lt;br /&gt;      @logged_methods.concat(args)&lt;br /&gt;    end&lt;br /&gt;&lt;br /&gt;    # Anytime a method gets added to our class (like with "def"),&lt;br /&gt;    # this will execute&lt;br /&gt;    def method_added(method_name)&lt;br /&gt;      if @logged_methods.include?(method_name)&lt;br /&gt;        # A lock to prevent infinite recursion; since we're&lt;br /&gt;        # going to add a method below, we don't want that&lt;br /&gt;        # to make method_added fire again, which would&lt;br /&gt;        # make it fire again, etc...&lt;br /&gt;        unless @adding_with_metaprogramming&lt;br /&gt;          @adding_with_metaprogramming = true&lt;br /&gt;          stashed_method = "stashed_#{method_name}".to_sym&lt;br /&gt;          # Creates a new method named, for example,&lt;br /&gt;          # "stashed_defuse_bomb", which does the same&lt;br /&gt;          # thing as "defuse_bomb"&lt;br /&gt;          alias_method stashed_method, method_name&lt;br /&gt;          # Now we redefine "defuse_bomb" to do this:&lt;br /&gt;          define_method method_name do&lt;br /&gt;            begin&lt;br /&gt;              puts "==== #{Time.now} ===="&lt;br /&gt;              puts "About to execute '#{method_name}'..."&lt;br /&gt;              # Calls "stashed_defuse_bomb" to get the original version&lt;br /&gt;              send(stashed_method)&lt;br /&gt;              puts "Executed '#{method_name}' successfully."&lt;br /&gt;            rescue Exception =&gt; e&lt;br /&gt;              puts "Error while executing '#{method_name}': '#{e.message}'!"&lt;br /&gt;            ensure&lt;br /&gt;              puts "\n\n"&lt;br /&gt;            end&lt;br /&gt;          end&lt;br /&gt;        end&lt;br /&gt;        # Remove the lock&lt;br /&gt;        @adding_with_metaprogramming = false&lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Metaprogramming! It sounds terrifying, doesn't it? Like, if programs can write programs, next thing you know, the Terminator is going to be ripping through your door! But it's actually &lt;b&gt;slightly&lt;/b&gt; less terrifying than that.&lt;br /&gt;&lt;br /&gt;Anyway, I got the idea for the last post from reading one chapter in "Eloquent Ruby," and I got the idea for this post while lying in bed after reading another chapter. So: thumbs up, Eloquent Ruby! I hereby recommend you.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8506302596139279537-1929297222866734537?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/1929297222866734537/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/06/metaprogramming-danger-box-in-my-last.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/1929297222866734537'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/1929297222866734537'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/06/metaprogramming-danger-box-in-my-last.html' title='Metaprogramming Danger Box'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-4532949629538127071</id><published>2011-06-10T17:10:00.003-04:00</published><updated>2011-06-10T17:14:08.144-04:00</updated><title type='text'>Using the Execute Around pattern in Ruby</title><content type='html'>At my last job, I wrote a script that was run by a cron job overnight. The script was supposed to go through a MySQL table of scheduled database queries and, if they met some sanity checks, run them. This was so we could run slow queries like &lt;span class="code"&gt;ALTER TABLE&lt;/span&gt; during the wee hours of the morning, when customers weren't hitting our database. It may sound odd to execute MySQL queries that had been stored in MySQL, but it worked. (Just don't drop the stored queries table!)&lt;br /&gt;&lt;br /&gt;The first time my script ran, it failed, and I didn't know why. My boss wisely suggested that I add some logging: print messages to standard output. We were a PHP shop, so that meant &lt;span class="code"&gt;echo&lt;/span&gt; statements; all this output was captured in a log file by the cron job, which ran my script like this:&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;query_runner.php &gt; some_log_file.txt&lt;/div&gt;&lt;br /&gt;I went crazy with the logging, announcing up front how many queries I had to run, my progress ("Running query 4 of 7..."), how long each query took, whether it had errors, etc. At the end I printed a nice summary.&lt;br /&gt;&lt;br /&gt;All this made the log files very useful when something went wrong: I could tell which queries had and hadn't run, and generally why. I learned the value of logging, and that day, I became a man.&lt;br /&gt;&lt;br /&gt;No wait, I'm confusing my stories. I just learned the value of logging, that's all.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Execute Around&lt;/h3&gt;&lt;br /&gt;Anywho, these days I'm coding Ruby, and I'm currently reading "Eloquent Ruby" by Russ Olsen. In one section, he shows a nice way of adding logging to any method you like, without cluttering up the internals of the method itself.&lt;br /&gt;&lt;br /&gt;The secret is the "execute around" pattern: you pass your method, as a block, to another method, which can do things before and after executing it. In this case, the logging method will log a message, execute the block, log another message, and even catch and log any exceptions.&lt;br /&gt;&lt;br /&gt;Here's an example:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: ruby"&gt;module Logging&lt;br /&gt;    def with_log(description, &amp;block)&lt;br /&gt;      begin&lt;br /&gt;        puts "==== #{Time.now} ===="&lt;br /&gt;        puts "About to execute '#{description}'..."&lt;br /&gt;        block.call&lt;br /&gt;        puts "Executed '#{description}' successfully."&lt;br /&gt;      rescue Exception =&gt; e&lt;br /&gt;        puts "Error while executing '#{description}': '#{e.message}'!"&lt;br /&gt;      ensure&lt;br /&gt;        puts "\n\n"&lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  class DangerBox&lt;br /&gt;    include Logging&lt;br /&gt;&lt;br /&gt;    def defuse_bomb&lt;br /&gt;      with_log('defuse_bomb') {puts "Snip the red wire..."}&lt;br /&gt;    end&lt;br /&gt;&lt;br /&gt;    def throw_error&lt;br /&gt;      with_log('throw_error') do &lt;br /&gt;        puts 'I feel an error coming on...'&lt;br /&gt;        raise 'Success: an error!'&lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt;&lt;br /&gt;  end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Here's what it looks like in action:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: ruby"&gt;d = DangerBox.new&lt;br /&gt;  d.defuse_bomb&lt;br /&gt;  # ==== Fri Jun 10 16:41:54 -0400 2011 ====&lt;br /&gt;  # About to execute 'defuse_bomb'...&lt;br /&gt;  # Snip the red wire...&lt;br /&gt;  # Executed 'defuse_bomb' successfully.&lt;br /&gt;&lt;br /&gt;  d.throw_error&lt;br /&gt;  # ==== Fri Jun 10 16:41:54 -0400 2011 ====&lt;br /&gt;  # About to execute 'throw_error'...&lt;br /&gt;  # I feel an error coming on...&lt;br /&gt;  # Error while executing 'throw_error': 'Success: an error!'!&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Lovely! All the logging and error handling is done in one place, and the methods that use it can be much cleaner.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8506302596139279537-4532949629538127071?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/4532949629538127071/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/06/using-execute-around-pattern-in-ruby.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/4532949629538127071'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/4532949629538127071'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/06/using-execute-around-pattern-in-ruby.html' title='Using the Execute Around pattern in Ruby'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-5461420732715678881</id><published>2011-06-09T17:19:00.001-04:00</published><updated>2011-06-10T17:28:22.292-04:00</updated><title type='text'>Understanding Rack applications</title><content type='html'>I'm starting to understand Ruby's Rack application architecture, and it's really cool. It's a simple concept, but very powerful, because of the ways it lets you stack up different components with different responsibilities. &lt;br /&gt;&lt;br /&gt;First, the basics. According to &lt;a href="http://rack.rubyforge.org/doc/files/SPEC.html"&gt;the spec&lt;/a&gt;:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;A Rack application is an Ruby object (not a class) that responds to call.  It&lt;br /&gt;takes exactly one argument, the environment and returns an Array of exactly&lt;br /&gt;three values: the status, the headers, and the body.&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;So each component in a rack stack &lt;b&gt;is like a rung in a ladder&lt;/b&gt;. An incoming request is stashed inside a hash called the environment, which includes info about the request itself and othert things (see the spec). That environment is passed down the ladder from one component to the next until it gets to the app at the end. The app receives the environment, generates a response, and returns it. Since the app was called by the final rack component, it hands it response back to that component. That component then returns the response to the component which called it, etc, effectively passing it back up the ladder, until it's returned to the user.&lt;br /&gt;&lt;br /&gt;Got that? &lt;b&gt;Requests go down the ladder, responses come back up&lt;/b&gt;.&lt;br /&gt;&lt;br /&gt;Of course, in that description, each component is just passing the environment down and then passing the response back up. If that's all a component did, it would be useless. To be useful, it will have to actually something. Specifically, &lt;b&gt;a given component will do (at least) one of three things&lt;/b&gt;:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;&lt;b&gt;Screen the requests&lt;/b&gt;. For example, if the component is for authorization, it verifies that the user has entered a valid login. If it's an invalid login, it will return a "failed login" response and never call the next component. If it's a valid login, it will pass the request to the next component - maybe a Rails app.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Modify the environment&lt;/b&gt; before passing it on to the next component. Our authorization component might do this in addition to screening: if there's a valid login, it might set a user object in the environment before passing that to the next component. With this setup, the next component can assume that the user object will always be set whenever it gets a request.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Modify the response&lt;/b&gt; after getting it back from the next component. A silly example would be to translate the response into Japanese.&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;Notice that each component needs to know what the &lt;strong&gt;next&lt;/strong&gt; component in the chain is, so that it knows who to call. That's why whatever you give to Rack::Builder#use needs to have an &lt;code&gt;initialize&lt;/code&gt; method that accepts the next component, in addition to a &lt;code&gt;call&lt;/code&gt; method that accepts the env. &lt;br /&gt;&lt;br /&gt;The application at the end, however, doesn't need to call anybody else; it just needs to take the env as it then stands and generate a response. So whatever you pass to &lt;code&gt;Rack::Builder::run&lt;/code&gt; doesn't need an &lt;code&gt;initialize&lt;/code&gt; method; it just needs to respond to &lt;code&gt;call(env)&lt;/code&gt;. Even a lambda can do that.&lt;br /&gt;&lt;br /&gt;Some pseudocode examples of call methods:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: ruby"&gt;# A component with this call method would be useless.&lt;br /&gt;# Still, it's worth remembering that this:&lt;br /&gt;def call(env)&lt;br /&gt;  @next_component.call(env)&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;# ... is Ruby shorthand for this:&lt;br /&gt;def call(env)&lt;br /&gt;  result = @next_component.call(env)&lt;br /&gt;  return result&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;# Instead, we can do something useful on the way in:&lt;br /&gt;def call(env)&lt;br /&gt;  valid_user = contains_valid_login?(env)&lt;br /&gt;&lt;br /&gt;  if valid_user&lt;br /&gt;    add_current_user_object_to(env)&lt;br /&gt;    # Next component can rely on having a current_user object&lt;br /&gt;    @next_component.call(env)&lt;br /&gt;&lt;br /&gt;  else&lt;br /&gt;    redirect_to_login_page_with_error&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;# Or we can do something useful on the way out:&lt;br /&gt;def call(env)&lt;br /&gt;  result = @next_component.call(env)&lt;br /&gt;  result.translate_to_russian!&lt;br /&gt;end&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/8506302596139279537-5461420732715678881?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/5461420732715678881/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/06/understanding-rack-applications.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/5461420732715678881'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/5461420732715678881'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/06/understanding-rack-applications.html' title='Understanding Rack applications'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-5324337258059466919</id><published>2011-06-03T09:22:00.001-04:00</published><updated>2011-06-03T09:46:56.244-04:00</updated><title type='text'>Ruby's Set class</title><content type='html'>Yesterday at work, we ran into an interesting problem. We're creating the new version of an application and discarding the old, ugly code. But we need to migrate some data: the old system has (let's say) widgets, and the new system has widgets, too. The old system uses 5 different databases (see how ugly?) with weird row schemas, but it does reliably have widget color, size, and shapes. The new system uses one database and has a nice row schema, but it also has widget color, size, and shapes.&lt;br /&gt;&lt;br /&gt;We need to know: which widgets are only in the old system? Which widgets are only in the new? Which are in both?&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Enter Sets&lt;/h3&gt;&lt;br /&gt;Asking these questions tripped a switch in my mind. "I know about this!", I thought. "This is a job for sets! And Ruby has a Set class."&lt;br /&gt;&lt;br /&gt;I'd never used them yet, but sets are made for this kind of thing. &lt;a href="http://en.wikipedia.org/wiki/Set_(mathematics)"&gt;Sets&lt;/a&gt; are often illustrated with &lt;a href="http://en.wikipedia.org/wiki/Venn_diagram"&gt;Venn diagrams&lt;/a&gt;: overlapping circles, where you ask "which things are only in the left circle? What's in the overlap?", etc.&lt;br /&gt;&lt;br /&gt;For instance:&lt;br /&gt;&lt;br /&gt;&lt;img src="http://nathanlong.webfactional.com/blogimages/oneoffs/sets_venn_diagram.png"&gt;&lt;br /&gt;&lt;br /&gt;A set is a list of items where no item is repeated. If you have more than one set, you can compare them and answer the kinds of questions we've been asking. Here's a demo I just threw together:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: ruby"&gt;require 'set'&lt;br /&gt;&lt;br /&gt;  def sets_demo&lt;br /&gt;&lt;br /&gt;    # Sets ignore duplicate values&lt;br /&gt;    game_words = Set.new(['duck','duck','duck','goose'])&lt;br /&gt;    puts "Unique game words                 : #{game_words}\n\n"&lt;br /&gt;    #=&gt;   Unique game words                 : goose, duck&lt;br /&gt;     &lt;br /&gt;&lt;br /&gt;    # Here are two sets with one thing in common&lt;br /&gt;    fast  = Set.new(['bullet', 'cheetah'])&lt;br /&gt;    round = Set.new(['bullet', 'beach ball'])&lt;br /&gt;&lt;br /&gt;    # All the ways we can compare them&lt;br /&gt;    puts "Round                             : #{round}"&lt;br /&gt;    #=&gt;   Round                             : bullet, beach ball&lt;br /&gt;    &lt;br /&gt;    puts "Fast                              : #{fast}"&lt;br /&gt;    #=&gt;   Fast                              : cheetah, bullet&lt;br /&gt;    puts ''&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    puts "Round and Fast (&amp;)                : #{(fast &amp; round)}"&lt;br /&gt;    #=&gt;   Round and Fast (&amp;)                : bullet&lt;br /&gt;    #&lt;br /&gt;    puts "Round but not Fast (-)            : #{(round - fast)}"&lt;br /&gt;    #=&gt;   Round but not Fast (-)            : beach ball&lt;br /&gt;    &lt;br /&gt;    puts "Fast but not Round (-)            : #{(fast - round)}"&lt;br /&gt;    #=&gt;   Fast but not Round (-)            : cheetah&lt;br /&gt;&lt;br /&gt;    puts "Round OR Fast (|)                 : #{(round | fast)}"&lt;br /&gt;    #=&gt;   Round OR Fast (|)                 : cheetah, bullet, beach ball&lt;br /&gt;&lt;br /&gt;    puts "Round OR Fast, but NOT both (XOR) : #{((round | fast) - (fast &amp; round))}"&lt;br /&gt;    #=&gt;   Round OR Fast, but NOT both (XOR) : cheetah, beach ball&lt;br /&gt;&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  # Formatting the way the sets print&lt;br /&gt;  class Set&lt;br /&gt;    def to_s&lt;br /&gt;      to_a.join(', ')&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  sets_demo&lt;br /&gt;  &lt;/pre&gt;&lt;br /&gt;Got it?&lt;br /&gt;&lt;br /&gt;In my examples, the items in the sets were strings, but they could be anything. In our case at work, we used hashes: a widget was represented by a hash containing its color, shape and size. So, we just had to:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Connect to each of the databases in the old system, getting all the widgets, creating a hash for each one, and dropping each into an old_system_widgets set (which automatically ignores duplicates)&lt;/li&gt;&lt;li&gt;Connect to the new system's database and make a similar set of its widgets&lt;/li&gt;&lt;li&gt;Do the kinds of set operations illustrated above&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;Voila! Now we knew which widgets were new and which ones still needed to be migrated to the new system.&lt;br /&gt;&lt;br /&gt;In conclusion: sets are swell!&lt;br /&gt;&lt;br /&gt;Hmmm. That's a pretty weak ending.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8506302596139279537-5324337258059466919?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/5324337258059466919/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/06/rubys-set-class.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/5324337258059466919'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/5324337258059466919'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/06/rubys-set-class.html' title='Ruby&apos;s Set class'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-8546252303324686206</id><published>2011-05-04T06:37:00.002-04:00</published><updated>2011-05-05T09:03:16.615-04:00</updated><title type='text'>Ruby's String#unpack and Array#pack and Unicode</title><content type='html'>At work, I'm exporting some data for use by a system that doesn't understand Unicode. I ran across &lt;a href="http://www.jroller.com/obie/entry/fix_that_tranny_add_to"&gt;an Obie Fernandez post&lt;/a&gt; explaining his approach.&lt;br /&gt;&lt;br /&gt;One method he showed was this:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: ruby"&gt;def to_ascii_iconv&lt;br /&gt;    converter = Iconv.new('ASCII//IGNORE//TRANSLIT', 'UTF-8') &lt;br /&gt;    converter.iconv(self).unpack('U*').select{ |cp| cp &lt; 127 }.pack('U*')&lt;br /&gt;  end&lt;br /&gt;&lt;/pre&gt;Essentially, this means that Iconv will convert the characters that it can from Unicode to ASCII. For example, &lt;span class="code"&gt;á&lt;/span&gt; will be converted to &lt;span class="code"&gt;'a&lt;/span&gt;. (I don't know where to find a comprehensive list of these transliterations.) After the transliteration step, any remaining non-ASCII characters - for example, Japanese characters, which have no ASCII equivalent - are discarded using the pack and unpack methods.To see how this works, try this in irb:&lt;pre class="brush: ruby"&gt;"abcdefg".unpack('U*')&lt;br /&gt;&lt;/pre&gt;Your string is converted into an array of numbers representing their Unicode values, as we requested by using "U*". (I'm still a bit fuzzy on exactly how Unicode values work, but let's keep rolling.)Ruby's Array#pack method can do the opposite conversion: numbers to string values.&lt;pre class="brush: ruby"&gt;(1..127).to_a.pack('U*')&lt;br /&gt;&lt;/pre&gt;There you should see a string of all the legal ASCII values, which, apparently, are all in the 1 to 127 range.Knowing this, it's easy to see how you can throw away non-ASCII values in a string:&lt;pre class="brush: ruby"&gt;some_string.unpack('U*').select{|character_value| character_value &lt; 127}.pack('U*')&lt;br /&gt;&lt;/pre&gt;And that's what Obie's code does after its initial conversions with Iconv.Now, if you're curious, you might want to see what Unicode values some other numbers map to. No problem: just change the range value from our earlier example, and write the resulting values to a file (in my case, the Unicode characters don't show correctly in irb):&lt;pre class="brush: ruby"&gt;string = (1..300).to_a.pack('U*')&lt;br /&gt;  File.open('unicodes_hooray.txt','w'){|f| f.write(string)}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Open that up in an editor that can display Unicode to see what you got.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8506302596139279537-8546252303324686206?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/8546252303324686206/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/05/rubys-stringunpack-and-arraypack-and.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/8546252303324686206'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/8546252303324686206'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/05/rubys-stringunpack-and-arraypack-and.html' title='Ruby&apos;s String#unpack and Array#pack and Unicode'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-3122855178773377410</id><published>2011-03-31T12:11:00.001-04:00</published><updated>2011-03-31T12:44:39.829-04:00</updated><title type='text'>Authorized_keys and SCP</title><content type='html'>I have SSH access to a machine, and I want to add my public key to the `authorized_keys` file so I don't have to type a password to log in. A friend taught me a neat command for doing this:&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;% cat ~/.ssh/id_rsa.pub | ssh user@server "cat &gt;&gt; .ssh/authorized_keys"&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;Meaning:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Output the contents of my public key&lt;/li&gt;&lt;li&gt;Send that as standard input to this ssh command&lt;/li&gt;&lt;li&gt;The ssh command runs `cat` on the server&lt;/li&gt;&lt;li&gt;Since `cat` on the server is called without arguments, it uses standard input - in this case, the contents of my public key file&lt;/li&gt;&lt;li&gt;My public key gets added to `authorized_keys`&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Added my key to `authorized_keys` has another benefit, besides not having to type the password when I SSH in: since I'm automatically recognized as a user, &lt;b&gt;I can use tab completion for scp commmands&lt;/b&gt;. (Note: I'm using the zsh shell; remote tab-completion doesn't work for me in bash.)&lt;br /&gt;&lt;br /&gt;In other words, if there's a file called "foo.txt" in my home directory on that server, and I want to copy it to my local machine, I can start type this:&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;% scp user@server:fo &lt;br /&gt;&lt;/div&gt;&lt;br /&gt;... and hit tab, and it will auto-complete "foo.txt". &lt;b&gt;That makes using scp a lot less tedious&lt;/b&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8506302596139279537-3122855178773377410?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/3122855178773377410/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/03/authorizedkeys-and-scp.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/3122855178773377410'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/3122855178773377410'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/03/authorizedkeys-and-scp.html' title='Authorized_keys and SCP'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-3906146879399378912</id><published>2011-03-31T10:15:00.002-04:00</published><updated>2011-03-31T13:20:39.067-04:00</updated><title type='text'>Using git cherry-pick</title><content type='html'>One Git command that I've found to be really useful lately is "git cherry-pick". It lets you take a single commit from one branch and apply it to another.&lt;br /&gt;&lt;br /&gt;For example, say you're working on your "add_blinking_lights" feature branch, and you find a bug in the "rotate_missile_chamber" method. You fix it and commit to the branch between a couple of other feature commits.&lt;br /&gt;&lt;br /&gt;It's going to be a while before you merge this feature back into your master branch, but you do want that bugfix to go live on the next deploy. So, on the feature branch, you do this:&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;cyborg[add_blinking_lights]% git log -3&lt;br /&gt;e9dd159 Added tests for blink rate&lt;br /&gt;a8e358c Fixed bug in rotate_missile_chamber&lt;br /&gt;a7d3302 Added tests for lights turning on&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;OK, so you know the hash for the bugfix. Now you do "git checkout master."&lt;br /&gt;&lt;br /&gt;On the master branch you run "git cherry-pick a8e358c". Here's what that command does:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Look at that commit on the feature branch&lt;/li&gt;&lt;li&gt;See what changes it introduced&lt;/li&gt;&lt;li&gt;Create a new commit on the master branch that introduces the same changes&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Notice that it creates &lt;b&gt;a new commit&lt;/b&gt; on the master branch. If, on master, you run "git log", you'll see &lt;b&gt;a different hash for the same commit message&lt;/b&gt;. Why?&lt;br /&gt;&lt;br /&gt;This is because of how Git models what a commit is. A commit is a complete snapshot of the whole repository, and the hash for a given commit reflects the state of every file in the whole directory - it is a hash of all their hashes.&lt;br /&gt;&lt;br /&gt;So clearly, since master branch doesn't have all of the commits from the feature branch, a complete snapshot of it at the time the bugfix is applied will generate a different hash than a complete snapshot of the feature branch at the time the bugfix is applied there. Thus, different hashes.&lt;br /&gt;&lt;br /&gt;But when you do merge the feature branch into master, that won't matter; the hashes for the individual file where you made the bugfix will be the same, because their contents will be the same, so there will be nothing to update on master for that file.&lt;br /&gt;&lt;br /&gt;Of course, another way of approaching this problem would have been to switch to master before committing the bugfix in the first place, then merge master into the feature branch, or cherry-pick from master to the feature, or rebase the feature on master. But the method above is simple, and if you did already commit the bugfix on your branch, is the best way to get that commit into production.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8506302596139279537-3906146879399378912?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/3906146879399378912/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/03/using-git-cherry-pick.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/3906146879399378912'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/3906146879399378912'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/03/using-git-cherry-pick.html' title='Using git cherry-pick'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-3311263474471153659</id><published>2011-03-10T20:35:00.000-05:00</published><updated>2011-03-10T20:35:32.839-05:00</updated><title type='text'>Where cliche is a mandate</title><content type='html'>A recruiting company that spams me has the slogan "Where Commitment is a Passion."&lt;br /&gt;&lt;br /&gt;What does that even &lt;b&gt;mean&lt;/b&gt;?&lt;br /&gt;&lt;br /&gt;"We're always looking for stuff to commit to. I just committed to eating a whole jar of pickles! Jimmy over there committed to sing everything he says! Linda committed to paying the mortgage for the zoo! We're c-c-crazy for commitment!"&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8506302596139279537-3311263474471153659?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/3311263474471153659/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/03/where-cliche-is-mandate.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/3311263474471153659'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/3311263474471153659'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/03/where-cliche-is-mandate.html' title='Where cliche is a mandate'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-9031058989475640852</id><published>2011-03-07T15:30:00.003-05:00</published><updated>2011-03-12T14:16:23.725-05:00</updated><title type='text'>Short Rubyzip tutorial</title><content type='html'>&lt;a href="http://rubyzip.sourceforge.net/"&gt;Rubyzip&lt;/a&gt; is a handy tool for creating zip files with a Ruby script. But to me, the documentation doesn't make it immediately obvious how to use the thing. This quick tutorial should get you started.&lt;br /&gt;&lt;br /&gt;Say you're submitting some pictures of kitchen gadgets to Strange Cooks Weekly. You need to zip up your images before sending them, and &lt;b&gt;you're contractually obligated to use Ruby to do it&lt;/b&gt;. Here's how.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: ruby"&gt;require 'rubygems'&lt;br /&gt;require 'zip/zip'&lt;br /&gt;&lt;br /&gt;puts "Zipping files!"&lt;br /&gt;&lt;br /&gt;file_path = "Users/me/Pictures/kitchen_implements"&lt;br /&gt;file_list = ['toast_mitten.png', 'gravy_jug.png', 'biscuit_juicer.png']&lt;br /&gt;&lt;br /&gt;zipfile_name = "/Users/me/Desktop/someFile.zip"&lt;br /&gt;&lt;br /&gt;Zip::ZipFile.open(zipfile_name, Zip::ZipFile::CREATE) do |zipfile|&lt;br /&gt;  file_list.each do |filename|&lt;br /&gt;    # Two arguments:&lt;br /&gt;    # - The name of the file as it will appear in the archive&lt;br /&gt;    # - The original file, including the path to find it&lt;br /&gt;    zipfile.add(filename, file_path + '/' + filename)&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Note that if the zip file already exists, this script will try to add stuff to it. If you try to add a file that's already in the archive, it will complain.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8506302596139279537-9031058989475640852?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/9031058989475640852/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/03/short-rubyzip-tutorial.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/9031058989475640852'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/9031058989475640852'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/03/short-rubyzip-tutorial.html' title='Short Rubyzip tutorial'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-1725107881021643313</id><published>2011-03-04T13:43:00.001-05:00</published><updated>2011-03-12T14:17:15.017-05:00</updated><title type='text'>Using Ruby for SCP file transfers</title><content type='html'>Today I needed to write a Ruby script to fetch a file using SCP. I thought it would be nice if it also displayed a "percent complete" counter as it went. This is what I came up with.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: ruby"&gt;require 'rubygems'&lt;br /&gt;require 'net/scp'&lt;br /&gt;puts "Fetching file"&lt;br /&gt;&lt;br /&gt;# Establish the SSH session&lt;br /&gt;ssh = Net::SSH.start("IP Address", "username on server", :password =&gt; "user's password on server", :port =&gt; 12345)&lt;br /&gt;&lt;br /&gt;# Use that session to generate an SCP object&lt;br /&gt;scp = ssh.scp&lt;br /&gt;&lt;br /&gt;# Download the file and run the code block each time a new chuck of data is received&lt;br /&gt;scp.download!("path/to/file/on/server/fileName", "/Users/me/Desktop/") do |ch, name, received, total|&lt;br /&gt;&lt;br /&gt;  # Calculate percentage complete and format as a two-digit percentage&lt;br /&gt;  percentage = format('%.2f', received.to_f / total.to_f * 100) + '%'&lt;br /&gt;&lt;br /&gt;  # Print on top of (replace) the same line in the terminal&lt;br /&gt;  # - Pad with spaces to make sure nothing remains from the previous output&lt;br /&gt;  # - Add a carriage return without a line feed so the line doesn't move down&lt;br /&gt;  print "Saving to #{name}: Received #{received} of #{total} bytes" + " (#{percentage})               \r"&lt;br /&gt;&lt;br /&gt;  # Print the output immediately - don't wait until the buffer fills up&lt;br /&gt;  STDOUT.flush&lt;br /&gt;end&lt;br /&gt;puts "Fetch complete!"&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/8506302596139279537-1725107881021643313?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/1725107881021643313/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/03/using-ruby-for-scp-file-transfers.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/1725107881021643313'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/1725107881021643313'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/03/using-ruby-for-scp-file-transfers.html' title='Using Ruby for SCP file transfers'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-787800206031910857</id><published>2011-02-26T10:34:00.000-05:00</published><updated>2011-02-26T10:34:55.889-05:00</updated><title type='text'>Best error message ever</title><content type='html'>A while back, I sent a text message and got this error back from AT&amp;amp;T.&lt;br /&gt;&lt;a class="leftfloat" href="http://nathanlong.webfactional.com/blogimages/error_i_love_you.jpg"&gt;&lt;img src="http://nathanlong.webfactional.com/blogimages/error_i_love_you.jpg"&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Wow... thanks. I... didn't know you cared?&lt;br /&gt;&lt;br /&gt;Apparently they just append the text of the message (this one was to my wife) to the end of the error, without saying "original message follows" or anything like that.&lt;br /&gt;&lt;br /&gt;In case anyone wants to try getting their own amusing messages, I believe this happened because I mistakenly sent to a land line.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8506302596139279537-787800206031910857?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/787800206031910857/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/02/best-error-message-ever.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/787800206031910857'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/787800206031910857'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/02/best-error-message-ever.html' title='Best error message ever'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-8136303404732841985</id><published>2011-02-23T17:05:00.001-05:00</published><updated>2011-03-12T14:18:29.666-05:00</updated><title type='text'>Watching your log for a specific value</title><content type='html'>If you use Unix, you probably know about piping one command's output through another. You may also know that &lt;pre class="brush: bash"&gt;tail -f somefile&lt;/pre&gt;will show you whatever gets added to the end of a file in real time.&lt;br /&gt;&lt;br /&gt;While working with Rails, I do &lt;pre class="brush: bash"&gt;tail -f log/development.log&lt;/pre&gt;all the time to watch how Rails processes my page requests. Today, I was looking for a specific error message to show up - a MissingTemplate error that a user had experienced**, and I was trying to reproduce it.&lt;br /&gt;&lt;br /&gt;Since I didn't care about anything that was being logged at that moment other than the error I was looking for, it occurred to me to do this:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: bash"&gt;tail -f log/development.log | grep 'MissingTemplate'&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;As I poked around our site, I saw nothing. Until I encountered the error, and BAM - that one line popped into my terminal. Error case located!&lt;br /&gt;&lt;br /&gt;Just one more way that chaining simple tools together can be very powerful.&lt;br /&gt;&lt;br /&gt;*&lt;i&gt;*Actually, the user didn't see the MissingTemplate error; they got a friendly error message, as they should. But we got an email from Hoptoad with the actual error.&lt;/i&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8506302596139279537-8136303404732841985?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/8136303404732841985/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/02/watching-your-log-for-specific-value.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/8136303404732841985'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/8136303404732841985'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/02/watching-your-log-for-specific-value.html' title='Watching your log for a specific value'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-5592256106484444859</id><published>2011-01-11T16:01:00.000-05:00</published><updated>2011-01-11T16:01:51.025-05:00</updated><title type='text'>PHP: now running in the browser!</title><content type='html'>Got this email from a recruiter just now:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;Hope you are doing well. We have an urgent requirement for&lt;br /&gt;&lt;b&gt;PHP Front End Developer&lt;/b&gt; . Please go through the below mentioned job opportunity and let me know your availability for the same.&lt;/blockquote&gt;&lt;br /&gt;PHP Front End Developer!? Wow, those zany Facebook developers must be at it again. First they got PHP to compile, now they've got it running in the browser!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8506302596139279537-5592256106484444859?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/5592256106484444859/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/01/php-now-running-in-browser.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/5592256106484444859'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/5592256106484444859'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/01/php-now-running-in-browser.html' title='PHP: now running in the browser!'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-2521070068398903512</id><published>2011-01-06T12:32:00.000-05:00</published><updated>2011-01-06T12:32:09.644-05:00</updated><title type='text'>IE: Still the biggest yak to shave</title><content type='html'>My conclusion after a day and a half of fixing IE-only Javascript bugs.&lt;br /&gt;&lt;br /&gt;&lt;img src="http://lh4.ggpht.com/_-WP1an04HCc/TSX8FMs-o2I/AAAAAAAACpo/oGZ_ehXuJLU/s800/ie_yak.png"&gt;&lt;br /&gt;&lt;br /&gt;And this is IE8, mind you.&lt;br /&gt;&lt;br /&gt;Come on, Microsoft. Just use Webkit, already.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8506302596139279537-2521070068398903512?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/2521070068398903512/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/01/ie-still-biggest-yak-to-shave.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/2521070068398903512'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/2521070068398903512'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2011/01/ie-still-biggest-yak-to-shave.html' title='IE: Still the biggest yak to shave'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh4.ggpht.com/_-WP1an04HCc/TSX8FMs-o2I/AAAAAAAACpo/oGZ_ehXuJLU/s72-c/ie_yak.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-8221005325119581722</id><published>2010-12-28T22:02:00.000-05:00</published><updated>2010-12-28T22:02:01.850-05:00</updated><title type='text'>Introducing Bindler</title><content type='html'>Rails. Hobos. Bundler. Bindles.&lt;br /&gt;&lt;br /&gt;I awoke one day last week with this comic idea percolating in my brain. Too bad I'm lousy at drawing.&lt;br /&gt;&lt;br /&gt;&lt;img src="http://lh4.ggpht.com/_-WP1an04HCc/TRqkZdgY4yI/AAAAAAAACpQ/RAZc59DTzww/s640/bindler-12-21-2010.png" /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8506302596139279537-8221005325119581722?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/8221005325119581722/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/12/introducing-bindler.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/8221005325119581722'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/8221005325119581722'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/12/introducing-bindler.html' title='Introducing Bindler'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh4.ggpht.com/_-WP1an04HCc/TRqkZdgY4yI/AAAAAAAACpQ/RAZc59DTzww/s72-c/bindler-12-21-2010.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-2684936700799003788</id><published>2010-12-06T09:02:00.001-05:00</published><updated>2011-03-12T14:20:25.200-05:00</updated><title type='text'>Chaining jQuery Pseudoselectors</title><content type='html'>I just finished a very simple Javascript tweak to an app. Here was the requirement: "When the user opens our sign-in modal, we should focus the on the first input." Whatever code I wrote, I would put it a callback when the modal was finished opening.&lt;br /&gt;&lt;br /&gt;This was my first solution: target the id of the first input, and focus on it.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: js"&gt;$('input#user_email').focus();&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;But I didn't like that, because it's not very flexible. What if we add another input to the beginning of the form? We'd have to change the javascript, too.&lt;br /&gt;&lt;br /&gt;After a little tinkering with jQuery's psuedoselectors, I came up with this:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: js"&gt;$('#sign-in-modal-form').find('input:not(:hidden):first').focus();&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Besides being more flexible to future changes, I think this is still pretty readable: select the first input that isn't hidden and focus on it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8506302596139279537-2684936700799003788?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/2684936700799003788/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/12/chaining-jquery-pseudoselectors.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/2684936700799003788'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/2684936700799003788'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/12/chaining-jquery-pseudoselectors.html' title='Chaining jQuery Pseudoselectors'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-50896361140359239</id><published>2010-08-22T12:15:00.002-04:00</published><updated>2011-03-12T14:21:09.404-05:00</updated><title type='text'>Seeing where Ruby methods come from</title><content type='html'>When it comes to reflection - asking an object or class to tell you about itself - Ruby makes a lot of sense. If you want to know what methods an object has, for example, you just ask it: &lt;pre class="brush: ruby"&gt;someobject.methods&lt;/pre&gt;.&lt;br /&gt;&lt;br /&gt;Any array has lots of handy, built-in methods, which it gets from the Kernel module (mixed in by every object), the Enumerable module, and Array itself. (For example, &lt;span class="code"&gt;somearray.shuffle&lt;/span&gt; returns a randomized version of the array.)&lt;br /&gt;&lt;br /&gt;To see what comes from where, we'll make an array, and ask it: "array, please get me an alphabetized list of your methods, go through that list, and print a statement saying where each one was defined." (Thanks to several respondents on Stackoverflow for &lt;a href="http://stackoverflow.com/questions/3492679/ruby-determining-method-origins"&gt;showing me&lt;/a&gt; this.)&lt;br /&gt;&lt;br /&gt;The resulting list presents all kinds of opportunities for exploration. (For example, &lt;a href="http://ruby-doc.org/docs/ProgrammingRuby/html/taint.html"&gt;"tainted?" is very interesting&lt;/a&gt;.)&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: ruby"&gt;irb(main):001:0&gt; a = Array.new&lt;br /&gt;=&gt; []&lt;br /&gt;irb(main):002:0&gt; a.methods.sort.collect {|m| puts "#{m} defined by #{a.method(m).owner}"}&lt;br /&gt;&amp; defined by Array&lt;br /&gt;* defined by Array&lt;br /&gt;+ defined by Array&lt;br /&gt;- defined by Array&lt;br /&gt;&lt;&lt; defined by Array&lt;br /&gt;&lt;=&gt; defined by Array&lt;br /&gt;== defined by Array&lt;br /&gt;=== defined by Kernel&lt;br /&gt;=~ defined by Kernel&lt;br /&gt;[] defined by Array&lt;br /&gt;[]= defined by Array&lt;br /&gt;__id__ defined by Kernel&lt;br /&gt;__send__ defined by Kernel&lt;br /&gt;all? defined by Enumerable&lt;br /&gt;any? defined by Enumerable&lt;br /&gt;assoc defined by Array&lt;br /&gt;at defined by Array&lt;br /&gt;choice defined by Array&lt;br /&gt;class defined by Kernel&lt;br /&gt;clear defined by Array&lt;br /&gt;clone defined by Kernel&lt;br /&gt;collect defined by Array&lt;br /&gt;collect! defined by Array&lt;br /&gt;combination defined by Array&lt;br /&gt;compact defined by Array&lt;br /&gt;compact! defined by Array&lt;br /&gt;concat defined by Array&lt;br /&gt;count defined by Array&lt;br /&gt;cycle defined by Array&lt;br /&gt;delete defined by Array&lt;br /&gt;delete_at defined by Array&lt;br /&gt;delete_if defined by Array&lt;br /&gt;detect defined by Enumerable&lt;br /&gt;display defined by Kernel&lt;br /&gt;drop defined by Array&lt;br /&gt;drop_while defined by Array&lt;br /&gt;dup defined by Kernel&lt;br /&gt;each defined by Array&lt;br /&gt;each_cons defined by Enumerable&lt;br /&gt;each_index defined by Array&lt;br /&gt;each_slice defined by Enumerable&lt;br /&gt;each_with_index defined by Enumerable&lt;br /&gt;empty? defined by Array&lt;br /&gt;entries defined by Enumerable&lt;br /&gt;enum_cons defined by Enumerable&lt;br /&gt;enum_for defined by Kernel&lt;br /&gt;enum_slice defined by Enumerable&lt;br /&gt;enum_with_index defined by Enumerable&lt;br /&gt;eql? defined by Array&lt;br /&gt;equal? defined by Kernel&lt;br /&gt;extend defined by Kernel&lt;br /&gt;fetch defined by Array&lt;br /&gt;fill defined by Array&lt;br /&gt;find defined by Enumerable&lt;br /&gt;find_all defined by Enumerable&lt;br /&gt;find_index defined by Array&lt;br /&gt;first defined by Array&lt;br /&gt;flatten defined by Array&lt;br /&gt;flatten! defined by Array&lt;br /&gt;freeze defined by Kernel&lt;br /&gt;frozen? defined by Array&lt;br /&gt;grep defined by Enumerable&lt;br /&gt;group_by defined by Enumerable&lt;br /&gt;hash defined by Array&lt;br /&gt;id defined by Kernel&lt;br /&gt;include? defined by Array&lt;br /&gt;index defined by Array&lt;br /&gt;indexes defined by Array&lt;br /&gt;indices defined by Array&lt;br /&gt;inject defined by Enumerable&lt;br /&gt;insert defined by Array&lt;br /&gt;inspect defined by Array&lt;br /&gt;instance_eval defined by Kernel&lt;br /&gt;instance_exec defined by Kernel&lt;br /&gt;instance_of? defined by Kernel&lt;br /&gt;instance_variable_defined? defined by Kernel&lt;br /&gt;instance_variable_get defined by Kernel&lt;br /&gt;instance_variable_set defined by Kernel&lt;br /&gt;instance_variables defined by Kernel&lt;br /&gt;is_a? defined by Kernel&lt;br /&gt;join defined by Array&lt;br /&gt;kind_of? defined by Kernel&lt;br /&gt;last defined by Array&lt;br /&gt;length defined by Array&lt;br /&gt;map defined by Array&lt;br /&gt;map! defined by Array&lt;br /&gt;max defined by Enumerable&lt;br /&gt;max_by defined by Enumerable&lt;br /&gt;member? defined by Enumerable&lt;br /&gt;method defined by Kernel&lt;br /&gt;methods defined by Kernel&lt;br /&gt;min defined by Enumerable&lt;br /&gt;min_by defined by Enumerable&lt;br /&gt;minmax defined by Enumerable&lt;br /&gt;minmax_by defined by Enumerable&lt;br /&gt;nil? defined by Kernel&lt;br /&gt;nitems defined by Array&lt;br /&gt;none? defined by Enumerable&lt;br /&gt;object_id defined by Kernel&lt;br /&gt;one? defined by Enumerable&lt;br /&gt;pack defined by Array&lt;br /&gt;partition defined by Enumerable&lt;br /&gt;permutation defined by Array&lt;br /&gt;pop defined by Array&lt;br /&gt;private_methods defined by Kernel&lt;br /&gt;product defined by Array&lt;br /&gt;protected_methods defined by Kernel&lt;br /&gt;public_methods defined by Kernel&lt;br /&gt;push defined by Array&lt;br /&gt;rassoc defined by Array&lt;br /&gt;reduce defined by Enumerable&lt;br /&gt;reject defined by Array&lt;br /&gt;reject! defined by Array&lt;br /&gt;replace defined by Array&lt;br /&gt;respond_to? defined by Kernel&lt;br /&gt;reverse defined by Array&lt;br /&gt;reverse! defined by Array&lt;br /&gt;reverse_each defined by Array&lt;br /&gt;rindex defined by Array&lt;br /&gt;select defined by Array&lt;br /&gt;send defined by Kernel&lt;br /&gt;shift defined by Array&lt;br /&gt;shuffle defined by Array&lt;br /&gt;shuffle! defined by Array&lt;br /&gt;singleton_methods defined by Kernel&lt;br /&gt;size defined by Array&lt;br /&gt;slice defined by Array&lt;br /&gt;slice! defined by Array&lt;br /&gt;sort defined by Array&lt;br /&gt;sort! defined by Array&lt;br /&gt;sort_by defined by Enumerable&lt;br /&gt;taint defined by Kernel&lt;br /&gt;tainted? defined by Kernel&lt;br /&gt;take defined by Array&lt;br /&gt;take_while defined by Array&lt;br /&gt;tap defined by Kernel&lt;br /&gt;to_a defined by Array&lt;br /&gt;to_ary defined by Array&lt;br /&gt;to_enum defined by Kernel&lt;br /&gt;to_s defined by Array&lt;br /&gt;transpose defined by Array&lt;br /&gt;type defined by Kernel&lt;br /&gt;uniq defined by Array&lt;br /&gt;uniq! defined by Array&lt;br /&gt;unshift defined by Array&lt;br /&gt;untaint defined by Kernel&lt;br /&gt;values_at defined by Array&lt;br /&gt;zip defined by Array&lt;br /&gt;| defined by Array&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/8506302596139279537-50896361140359239?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/50896361140359239/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/08/seeing-where-ruby-methods-come-from.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/50896361140359239'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/50896361140359239'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/08/seeing-where-ruby-methods-come-from.html' title='Seeing where Ruby methods come from'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-523911982700490893</id><published>2010-08-15T08:33:00.000-04:00</published><updated>2010-08-15T08:33:40.472-04:00</updated><title type='text'>Visualizing our example setup</title><content type='html'>Some people reading &lt;a href="http://sleeplessgeek.blogspot.com/2010/01/setting-up-apache-php-mysql-phpmyadmin.html"&gt;my post on setting up Apache, PHP, MySQL, Filezilla Server and PHPMyAdmin&lt;/a&gt; have seemed confused about how the whole setup works, or what the pieces are for. I put together this diagram a while back for a coworker and thought it might be helpful to readers here, too. Click the image for a larger version, suitable for zooming or printing out.&lt;br /&gt;&lt;br /&gt;&lt;a class="leftfloat" href="http://nathanlong.webfactional.com/blogimages/http-ftp/Sleeplessgeek_-_FTP_and_HTTP_-_large.png"&gt;&lt;img src="http://nathanlong.webfactional.com/blogimages/http-ftp/Sleeplessgeek_-_FTP_and_HTTP_-_thumb.png"&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The basic idea that I want to convey is that web traffic has two sides: a client and a server. All conversation between them passes over a network. When someone visits a web page, their client, the browser, says "hey, send me your web page, please!" And on the other end of the network, a server gets the request and responds with the web page.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;What's a server?&lt;/h3&gt;&lt;br /&gt;Before we go further, let's clear up some confusion about terms. Specifically, what is a server?&lt;br /&gt;&lt;br /&gt;&lt;b&gt;People use the term "server" to mean different things&lt;/b&gt;. In some cases, we mean "the program that responds to requests." With that meaning in mind, we'd call Apache a web server, MySQL a database server, and Filezilla Server an FTP server. In other cases, we mean "the computer that the server program(s) run on." With that in mind, we might say "our server sits on a rack in the server room."&lt;br /&gt;&lt;br /&gt;In some setups, MySQL might be running on one server machine and Apache on another. In complex setups, both will be running on multiple machines. &lt;br /&gt;&lt;br /&gt;The tutorial was all about setting up the various pieces of server software. If you set them up on your own computer, then visit your site from that same computer, then you refer to the server you're connecting to as "localhost" - local in the sense that it's on the same machine. In that case, the top part (the server) and the bottom part (the client) are really the same computer, and the network request never travels outside the computer you're working on. But of course most web sites you visit are served from elsewhere, and the network request might travel around the world.&lt;br /&gt;&lt;br /&gt;Now, to look at the components we set up. Let's start by looking at the right side of the diagram, the HTTP side. &lt;br /&gt;&lt;br /&gt;&lt;h2&gt;HTTP&lt;/h2&gt;&lt;br /&gt;This is the conversation between a browser and the server program - in our case, Apache. HTTP is the protocol they use to communicate. This just means that each of them expects requests and responses to be formatted a certain way - with headers, a body, and various sub-components of each.&lt;br /&gt;&lt;br /&gt;When a user requests a URL, it's up to Apache to decide what to send back. In basic cases, if the user asks for "http://servername/foo.html", Apache will look for foo.html, read it, and send back its contents. If you ask for "bar.html" and no such file exists, it will tell you so. But you could have Apache do anything you like. For example, it could send back the same message no matter what URL was requested. Or it could map URLs that match certain patterns to certain responses, without there being a one-to-one relationship between requests and files on the server. (Ruby on Rails has a complex "routes" system that does this.)&lt;br /&gt;&lt;br /&gt;Now imagine that the browser has requested a page, and Apache sends it back. What exactly does it send back? Just the HTML (in an HTTP "envelope", of course). The browser then looks at the HTML and says, "oh, I see that this lists some images, and a CSS file, and a Javascript file." It's then up to the browser (and the user's settings) whether it will ask for those other resources or not.&lt;br /&gt;&lt;br /&gt;If the browser requests, say, an image on the page, the server sends it back, too. As far as the server is concerned, each request is totally separate. You can imagine the conversation like this:&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Browser&lt;/b&gt;: I'd like this URL, please.&lt;br /&gt;&lt;b&gt;Server&lt;/b&gt;: Oh, hi, stranger! OK, here's some HTML.&lt;br /&gt;&lt;b&gt;Browser&lt;/b&gt;: (Hmmm, there's an image listed.) Server, please send me the image at this URL.&lt;br /&gt;&lt;b&gt;Server&lt;/b&gt;: Oh, hi, stranger! OK, here's that image.&lt;br /&gt;&lt;br /&gt;Notice that the server doesn't "remember" the browser's previous request. In fact, it might even be a different server responding to the second request. This shows that HTTP is "stateless" - meaning that each request is separate, and doesn't depend on any previous ones. (This gets more complicated when you have logins and cookies and sessions, etc, but at bottom, it's the way HTTP works.)&lt;br /&gt;&lt;br /&gt;In our example setup, when you ask for an HTML file, Apache just finds it and sends back the contents "raw" - without changing them. The same thing is true for CSS and Javascript files. But PHP is different.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Adding PHP&lt;/h3&gt;&lt;br /&gt;In our setup, we configured Apache to treat requests for .php files differently. When it sees that type of request, it asks php.exe to process the request and tell it how to respond.&lt;br /&gt;&lt;br /&gt;PHP.exe will look at the file and execute any of the PHP instructions, like including headers from another file, running functions, pulling information from classes or objects, etc.&lt;br /&gt;&lt;br /&gt;If the script requires it to, PHP will talk with a database server (in our case, MySQL) to get some of the information it needs to complete the request. For example, maybe it has to ask MySQL for a list of products, then output image tags and product descriptions for a catalog. &lt;br /&gt;&lt;br /&gt;When it has finished running the script, PHP.exe gives the complete web page output to Apache, which sends it back to the browser, which can then ask for the images if it pleases.&lt;br /&gt;&lt;br /&gt;One more thing to notice: &lt;b&gt;as far as the browser is concerned, PHP and MySQL don't exist&lt;/b&gt;. It asks for a URL, and it gets back HTML (or an image, or other resource). It doesn't know or care how that gets put together. When it asks for "http://somesite.com/foo.php", it doesn't "know" that "foo.php" is a PHP script (and in fact, depending on how the server is configured, it might not be). The browser could ask for "foo.jpg" and get back HTML and it wouldn't be surprised, as long as the HTTP headers in the server's response said "what I'm sending you back is HTML."&lt;br /&gt;&lt;br /&gt;In our setup, "foo.php" gets run when the browser requests a URL and the URL path is the file path to that script. But this is just a convenience for us, which makes it easy to see how the process works. We could make it more complicated, and as long as the browser could make a request and get a response it understood, it wouldn't know or care.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;FTP&lt;/h2&gt;&lt;br /&gt;So where does Filezilla Server come in? If your server is on localhost, it may not. The basic purpose for Filezilla is to let you move files between the client and server computers. If they're the same computer, you can just copy the files there directly. But if not, you'll have to do it over the network, which is what FTP (File Transfer Protocol) is for.&lt;br /&gt;&lt;br /&gt;Looking at the left side of the diagram, you'll see that there's another client-server relationship. This time, Filezilla Server is the server, and an FTP client (which could be Filezilla Client) is the client. If Filezilla Server is configured to use the same folder of files as Apache, you'll be able to change the content of your web site using this FTP conversation. To add or update a script, you'll upload it via FTP. The next time Apache gets a request for it, it will find it, get PHP to process it and send the results to the browser.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8506302596139279537-523911982700490893?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/523911982700490893/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/08/visualizing-our-example-setup.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/523911982700490893'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/523911982700490893'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/08/visualizing-our-example-setup.html' title='Visualizing our example setup'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-8127745524346548407</id><published>2010-08-07T12:04:00.002-04:00</published><updated>2011-03-12T14:21:53.710-05:00</updated><title type='text'>Quick Ruby Experiment</title><content type='html'>Here's an interesting Ruby experiment:&lt;br /&gt;&lt;pre class="brush: ruby"&gt;Class.ancestors # [Class, Module, Object, Kernel]&lt;br /&gt;&lt;br /&gt;Module.ancestors # [Module, Object, Kernel]&lt;br /&gt;&lt;br /&gt;Object.ancestors # [Object, Kernel]&lt;br /&gt;&lt;br /&gt;Kernel.ancestors # uninitialized constant Kernel&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/8506302596139279537-8127745524346548407?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/8127745524346548407/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/08/quick-ruby-experiment.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/8127745524346548407'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/8127745524346548407'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/08/quick-ruby-experiment.html' title='Quick Ruby Experiment'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-2872543081531825329</id><published>2010-08-07T10:33:00.000-04:00</published><updated>2010-08-07T10:33:51.836-04:00</updated><title type='text'>Ruby is to Esperanto as PHP is to English</title><content type='html'>In 1887, L.L. Zamenhof published a book called "Unua Libro," describing a brand-new language that he hoped would change the world. A language anyone could learn to speak. One without political affiliation, without a messy history. And, I have to imagine he thought: &lt;b&gt;finally&lt;/b&gt;, one that made sense.&lt;br /&gt;&lt;br /&gt;English speakers know that our language is a mess - comedy routines have been built around it.&lt;br /&gt;&lt;br /&gt;Here's &lt;a href="http://www.brianregan.com"&gt;Brian Regan&lt;/a&gt; on learning plurals in school.&lt;br /&gt;&lt;blockquote&gt;So she asked this kid who knew everything. Irwin. “Irwin, what’s the plural for ox?”&lt;br /&gt;&lt;br /&gt;“Ox. Oxen. The farmer used his oxen.”&lt;br /&gt;&lt;br /&gt;“Brian?”&lt;br /&gt;&lt;br /&gt;“What?”&lt;br /&gt;&lt;br /&gt;“Brian, what’s the plural for box?”&lt;br /&gt;&lt;br /&gt;“Boxen. I bought 2 boxen of doughnuts.”&lt;br /&gt;&lt;br /&gt;“No, Brian, no. Let’s try another one. Irwin, what’s the plural for goose?”&lt;br /&gt;&lt;br /&gt;“Geese. I saw a flock of geese.”&lt;br /&gt;&lt;br /&gt;“Brian?”&lt;br /&gt;&lt;br /&gt;[Exasperated laughing]“Wha-a-at?”&lt;br /&gt;&lt;br /&gt;“What’s the plural for moose?”&lt;br /&gt;&lt;br /&gt;“Moosen! I saw a flock of MOOSEN! There were many of ‘em. Many much moosen. Out in the woods…in the wood-es…in the woodsen. The meese want the food in the woodesen…food is the eatenesen…the meese want the food in the woodesenes…food in the woodesenes.”&lt;/blockquote&gt;&lt;br /&gt;We learn English rules, like "to make a past tense, add 'ed' to a verb: sailed, repeated, succeeded, constructed, cleaned." But many common verbs don't follow this. I ate, not eated; I ran, not runned; I went, not goed; I slept, not sleeped. &lt;br /&gt;&lt;br /&gt;Other languages are the same. Spanish, for example, has tons of irregular verbs. Like many languages, it also has genders for all its nouns. Mountains are feminine. Trees are masculine. Why? Nobody knows. Mark Twain had &lt;a href="http://how-to-learn-any-language.com/e/languages/german/the-awful-german-language.html"&gt;hilarious complaints about German&lt;/a&gt;, which adds another gender called "neuter," among other complications.&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;Every noun has a gender, and there is no sense or system in the distribution; so the gender of each must be learned separately and by heart. There is no other way. To do this one has to have a memory like a memorandum-book. In German, a young lady has no sex, while a turnip has. Think what overwrought reverence that shows for the turnip, and what callous disrespect for the girl. See how it looks in print -- I translate this from a conversation in one of the best of the German Sunday-school books:&lt;br /&gt;&lt;br /&gt;Gretchen: "Wilhelm, where is the turnip?"&lt;br /&gt;Wilhelm: "She has gone to the kitchen."&lt;br /&gt;Gretchen: "Where is the accomplished and beautiful English maiden?"&lt;br /&gt;Wilhelm: "It has gone to the opera."&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;The reason for all this messiness is simple: nobody planned this. Languages are organic, evolving things. We forget words, make up new ones, pronounce things lazily, and the mistakes become normal. The rules change, and the exceptions pile up.&lt;br /&gt;&lt;br /&gt;But not in Esperanto. Esperanto was designed &lt;a href="http://www.2-2.se/en/21.html"&gt;to make sense&lt;/a&gt;. &lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;Esperanto is much easier to learn than other languages because:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;The different letters are always pronounced in the same way and every letter in a word is pronounced. Therefore there are no difficulties with spelling and pronunciation, as one knows that the penultimate syllable is always stressed.&lt;/li&gt;&lt;li&gt;The grammar is simple, logical and without exceptions. The grammatical exceptions are often what make it so difficult to learn a new language.&lt;/li&gt;&lt;li&gt;Most of the words in Esperanto are international and are found in languages around the world.&lt;/li&gt;&lt;li&gt;It is easy to make new words with prefixes or suffixes. Thus, if one learns one word, ten or more usually come as part of the package.&lt;/li&gt;&lt;/ul&gt;&lt;/blockquote&gt;&lt;br /&gt;And while I'm sure it has its peculiarities, it's fundamentally different from English or Russian or Chinese or Swahili, which basically became what they are by chance.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;PHP is like English&lt;/h2&gt;&lt;br /&gt;PHP is a very useful language for web development. But it's a bit haphazard, like English. Just look at some of its &lt;a href="http://php.net/manual/en/ref.strings.php"&gt;string functions&lt;/a&gt;:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;count_chars() should have a counterpart called count_words(), right? But it doesn't. The counterpart is str_word_count() .&lt;/li&gt;&lt;li&gt;strip_tags() is named with words separated by underscores. stripslashes() has no underscores.&lt;/li&gt;&lt;li&gt;hebrev() will "convert logical Hebrew text to visual text." I don't know what that means, but really: a whole function for this, in the global namespace? Could something similar be done with Korean or Arabic or Portuguese or Tagalo? If so, would we create korev() and arabiv() and portugv()? Wouldn't it make more sense to have something like langv('languageName') and be done with it? For that matter, why include such a specific function in the base langauge, when 99.9% of users won't need it and those who do could use a library?&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;This haphazard design reflect's &lt;a href="http://en.wikipedia.org/wiki/PHP#History"&gt;PHP's history&lt;/a&gt;: once called "Personal Home Page/Forms Interpreter," it was made with one vision and has been amended and revised and rewritten over and over. I get the impression that somebody said, "hey, I want to add a function for messing with Hebrew." And the PHP team said, "sure, knock yourself out. We'll put it in there."&lt;br /&gt;&lt;br /&gt;Now don't get me wrong. I'm not smart enough to design a language myself. And these days, you can write solid, test-driven, object-oriented code in PHP. But fundamentally, it feels like a language that happened, not one that was made.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Ruby is like Esperanto&lt;/h2&gt;&lt;br /&gt;Ruby, on the other hand, was designed by Yukihiro "Matz" Matsumoto, a Japanese computer scientist who wanted to draw on the strengths of Perl and Python, and above all, to write a language for human beings. As Matz wrote in the foreword to Hal Fulton's &lt;a href="http://rubyhacker.com/"&gt;The Ruby Way&lt;/a&gt;:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;...Programming languages are ways to express human thought... Machines do not care whether programs are structured well; they just execute them bit by bit. Structured programming is not for machines, but for humans... So to design a human-oriented langauge, Ruby, I followed the Principle of Least Surprise. I consider that everything that surprises me less is good. As a result I feel a natural feeling, even a kind of joy, when programming in Ruby.&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;The language feels almost philosophical. It starts with principles: everything is an object. This has deep implications: classes are objects. True and false are objects. "Class" itself is an object, of the class "Class." Odd, yes, but not haphazard. Where PHP seems sloppy, Ruby seems mysterious, like real life. How can light be both a particle and a wave? How can "nil" be an object? We don't know, but we suspect there are satisfying reasons if we dig deep enough.&lt;br /&gt;&lt;br /&gt;This logical consistency generally extends to the particulars of the language. Want to know the number of items in an array, or the number of characters in a string? .length is your method, in either case. (PHP employs count() and strlen(), respectively.) &lt;br /&gt;&lt;br /&gt;If anything can be converted to a string, you can rely on .to_s to do it; to_a and to_i will likewise convert to an array or integer, if possible. There are some unexpected or duplicate methods: str.to_str is the same as str.to_s, but Array doesn't have .to_str. But on the whole, it seems easier to guess what a method will be called than in PHP.&lt;br /&gt;&lt;br /&gt;Ruby, like Esperanto, is quirky. It considers 0 to be true, for example, unlike any other language I know about. But when I encounter its quirks, I assume that they're by design; a necessary condition for some beautiful aspect of the language. In PHP, I think, "somebody didn't think that through."&lt;br /&gt;&lt;br /&gt;But perhaps I should stop there. Who am I to judge? I'm still a beginner in this field, with plenty to learn in both PHP and Ruby. Judgment can wait.&lt;br /&gt;&lt;br /&gt;For now, I should go back to my studies. I should open my copy of "&lt;a href="http://www.manning.com/black2/"&gt;The Well-Grounded Rubyist&lt;/a&gt;" with a curious and open mind. And with the enjoyable expectation that, if I read and think and tinker and practice and &lt;a href="http://stackoverflow.com/questions/3430280/ruby-how-does-object-id-assignment-work"&gt;ask questions&lt;/a&gt;, it will all make sense in the end.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8506302596139279537-2872543081531825329?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/2872543081531825329/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/08/ruby-is-to-esperanto-as-php-is-to.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/2872543081531825329'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/2872543081531825329'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/08/ruby-is-to-esperanto-as-php-is-to.html' title='Ruby is to Esperanto as PHP is to English'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-6889304907260782213</id><published>2010-06-19T15:59:00.002-04:00</published><updated>2010-06-19T16:02:40.702-04:00</updated><title type='text'>AT&amp;T U-Verse</title><content type='html'>I just learned AT&amp;T will probably roll out "&lt;a href="http://www.att.com/u-verse/explore/internet-landing.jsp"&gt;U-Verse&lt;/a&gt;" fiber service in our area soon and went to look at the price and speeds. &lt;br /&gt;&lt;br /&gt;Their naming scheme is pretty funny: Pro, Elite, Max, Max Plus, and Max Turbo. It makes me imagine this conversation.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Me&lt;/b&gt;: I'm not a heavy bandwidth user. What's your base package?&lt;br /&gt;&lt;b&gt;Them&lt;/b&gt;: That would be Pro.&lt;br /&gt;&lt;b&gt;Me&lt;/b&gt;: As in Professional?&lt;br /&gt;&lt;b&gt;Them&lt;/b&gt;: Yes.&lt;br /&gt;&lt;b&gt;Me&lt;/b&gt;: So if I were a professional internet user, I'd want the worst connection.&lt;br /&gt;&lt;b&gt;Them&lt;/b&gt;: No, you'd probably want at least Max.&lt;br /&gt;&lt;b&gt;Me&lt;/b&gt;: There is more than Max?&lt;br /&gt;&lt;b&gt;Them&lt;/b&gt;: Oh yes, there are two more levels past Max. But you probably don't need that. Why don't you try Elite?&lt;br /&gt;&lt;b&gt;Me&lt;/b&gt;: How good is that?&lt;br /&gt;&lt;b&gt;Them&lt;/b&gt;: It's the second-to-slowest one.&lt;br /&gt;&lt;b&gt;Me&lt;/b&gt;: I see. Is your marketing department staffed by native English speakers?&lt;br /&gt;&lt;br /&gt;I don't need high speed, but I like where this is going. Maybe I should hold off until they offer Max Xtreme Warp Nuclear.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8506302596139279537-6889304907260782213?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/6889304907260782213/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/06/at-u-verse.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/6889304907260782213'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/6889304907260782213'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/06/at-u-verse.html' title='AT&amp;T U-Verse'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-661693239426901221</id><published>2010-03-25T21:48:00.006-04:00</published><updated>2011-03-12T14:23:48.128-05:00</updated><title type='text'>Cache busting in PHP: Part 2</title><content type='html'>In &lt;a href="http://sleeplessgeek.blogspot.com/2010/03/rails-caching-and-cache-busting-in-php.html"&gt;my previous post&lt;/a&gt;, I showed how to borrow a technique from Ruby on Rails for busting the browser cache for a particular file. &lt;br /&gt;&lt;br /&gt;If you haven't read that, please check it out and come back. It's OK, I'll wait here. I've got a snack.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Improving on cachedFile()&lt;/h3&gt;&lt;br /&gt;Back? OK, well I've made some improvements to &lt;span class="code"&gt;cachedFile()&lt;/span&gt; and thought I'd share them. Here are the new capabilities:&lt;br /&gt;&lt;br /&gt;1) The function now extracts the file type from the extension&lt;br /&gt;2) It handles images, and specifies their dimensions for faster and smoother rendering by the browser&lt;br /&gt;3) It caches all the information it calculates about a file for faster performance on subsequent requests&lt;br /&gt;&lt;br /&gt;#1 and #2 are pretty straightforward: you can use the function like this:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: php"&gt;cachedFile('foo.png');&lt;br /&gt;cachedFile('subdirectory/bar.png','class="buz"')&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;...and it outputs something like this:&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&amp;lt;img src="/images/foo.png?1241452378" width="16" height="15" /&amp;gt;&lt;br /&gt;&amp;lt;img src="/images/subdirectory/bar.png?1241452378" width="20" height="17" class="buz" /&amp;gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;h3&gt;I put a cache in your cache so you can cache while you cache&lt;/h3&gt;&lt;br /&gt;But what about #3? What's this caching business? How can we add caching to a caching function?&lt;br /&gt;&lt;br /&gt;Let's back up a bit.&lt;br /&gt;&lt;br /&gt;First off, &lt;span class="code"&gt;cachedFile()&lt;/span&gt; was a bit of a misnomer. This function is really for BUSTING a cache. &lt;br /&gt;&lt;br /&gt;1) First, we configured our web server to tell the browser "you can cache these types of files for a whole year - don't ask for them again."&lt;br /&gt;2) Second, we made sure that the browser saw each filename as the combination of the ACTUAL filename, like 'foo.png', and the file's time stamp, resulting in 'foo.png?1241452378' (or something like that). Those numbers represent the last time the file was changed; they're the same time stamp you see on any file on your computer.&lt;br /&gt;3) Third, since the time stamp is automatically pulled from the file, we verified that we can update the file, which will update the time stamp, which will trick the browser into thinking it's never seen that file before, and therefore requesting it again.&lt;br /&gt;&lt;br /&gt;The end result: the browser asks for a file once, then never again (at least for a year) - until the moment you change the file. As soon as it's updated, the browser asks for a new copy; until then, it uses the one it cached.&lt;br /&gt;&lt;br /&gt;So, instead of &lt;span class="code"&gt;cachedFile()&lt;/span&gt;, we could have called the function &lt;span class="code"&gt;browserCacheBuster()&lt;/span&gt;. (But we won't, because I think that sounds cheesy.)&lt;br /&gt;&lt;br /&gt;Now, this is all great, but the server is doing a bit of work for each file. Like before, each time you ask our function for a file, it has to go and determine the time stamp. In addition, my new features mean that for image files, it has to compute the width and height of the image.&lt;br /&gt;&lt;br /&gt;This is all very fast in human terms, but how will it scale? What if you're using &lt;span class="code"&gt;cachedFile()&lt;/span&gt; to spit out the same image tag several hundred times on the same page?&lt;br /&gt;&lt;br /&gt;In that case, it might be nice to remember what you calculated last time. "Foo.png? Oh yeah, I remember him. I wrote down his dimensions and time stamp right here. No need to calculate them again."[1]&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Memoization&lt;/h3&gt;&lt;br /&gt;To make this happen, we're going to use a design pattern called &lt;a href="http://http://en.wikipedia.org/wiki/Memoization"&gt;memoization&lt;/a&gt;. It works like this:&lt;br /&gt;&lt;br /&gt;1) Before you calculate a result or pull it from the database, see if you've already got that result stored in a cache&lt;br /&gt;2) If not, figure out your result and store it in your cache. If so, skip this step.&lt;br /&gt;3) Now you've verified that you've got it in your cache, so return it from there.&lt;br /&gt;&lt;br /&gt;For a given input, the first time the function runs, it will check the cache, find nothing, calculate a result from the input, store the result in cache, and return. Every time after that, it will just check the cache, find a result for that input, and return.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Does it matter?&lt;/h3&gt;&lt;br /&gt;But is there any point in doing this? Are we prematurely optimizing? Maybe. Let's see how much performance gain this really gets us.&lt;br /&gt;&lt;br /&gt;I did a little not-very-scientific testing: added some caching to &lt;span class="code"&gt;cachedFile()&lt;/span&gt;, called it from a loop a few hundred times, and timed the results using PHP's &lt;span class="code"&gt;microtime()&lt;/span&gt;. I tried this with js, css, and image files, and did five or ten iterations of each.&lt;br /&gt;&lt;br /&gt;Not a great sample size, but here's what I found: for .js files, having a cache made the function 2.72 times faster. For .css files, it made it 3.18 times faster. &lt;strong&gt;But for image files, having a cache made the function 119.63 times faster!&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;Clearly, computing those image dimensions is a bit expensive for the server, and we don't want to do it more than necessary.[2] Caching cuts the workload considerably.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Enough talk - code time&lt;/h3&gt;&lt;br /&gt;OK, let's see how our function looks with these changes. (The cache is stored in a global variable so it will persist between function calls. To offset this minor sin, I have labeled it clearly and awkwardly to prevent accidental meddling from elsewhere.)&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: php"&gt;$GLOBAL_cachedFile_cache = null;&lt;br /&gt;function cachedFile($name, $attr=null){&lt;br /&gt;&amp;nbsp;global $GLOBAL_cachedFile_cache;&lt;br /&gt;&amp;nbsp;if (!isset($GLOBAL_cachedFile_cache[$name])){&lt;br /&gt;&amp;nbsp;&amp;nbsp;$root = $_SERVER['DOCUMENT_ROOT'];&lt;br /&gt;&amp;nbsp;&amp;nbsp;$filetype = substr($name,strripos($name,'.')+1);&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&lt;span class="comment"&gt;/* Configuration options */&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;$imgpath = '/images/';&lt;br /&gt;&amp;nbsp;&amp;nbsp;$csspath = '/stylesheets/';&lt;br /&gt;&amp;nbsp;&amp;nbsp;$jspath = '/scripts/';&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;switch ($filetype){&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;case 'css':&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$output = '&amp;lt;link rel="stylesheet" type="text/css" href="/includes/';&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$output .= $name;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$output .= '?' . filemtime($root . $csspath . $name) . '" ';&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if($attr){&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$output .= $attr . ' ';&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$output .= '/&amp;gt;' . "\n";&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;break;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;case 'js':&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$output = '&amp;lt;script type="text/javascript" src="/includes/';&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$output .= $name;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$output .= '?' . filemtime($root . $jspath . $name) . '"';&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$output .= '&amp;lt;/script&amp;gt;' . "\n";&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;break;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;case 'jpg':&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;case 'gif':&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;case 'png':&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="comment"&gt;//This code will get run in any of the three cases above&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$output = '&amp;lt;img src="' . $imgpath . $name;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$output .= '?' . filemtime($root . $imgpath . $name) . '"';&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$imgsize = getimagesize($root . $imgpath . $name);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$output .= ' ' . $imgsize[3];&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if($attr){&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$output .= ' ' . $attr;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$output .= ' /&amp;gt;';&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;break;&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;$GLOBAL_cachedFile_cache[$name] = $output;&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;echo $GLOBAL_cachedFile_cache[$name];&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;Magnanimousness&lt;/h3&gt;&lt;br /&gt;What's that? Want to use this code somewhere? Well, sure. No, you don't have to thank me, or license it, or anything. Just name your kid after me or send me a solid gold pickle.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Humility&lt;/h3&gt;&lt;br /&gt;And of course, perhaps I did something very stupid here. Well, that's what comments are for.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;&lt;br /&gt;[1]You might worry if this will create problems. After all, if we cache the time stamp, won't we miss the fact that the file has been updated and defeat our purpose? No worries: the cache only lasts as long as the page script is running. So if you update a file while a user is loading the page, they won't see it. But on the next reload, they will.&lt;br /&gt;&lt;br /&gt;[2]In fact, it would be reasonable not to do it at all; there are lots of factors in how fast a site performs and seems, but how quickly it renders is certainly one of them. This is meant to help with that, but costs processor speed. You'll have to decide what works best for your site.&lt;br /&gt;&lt;/i&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8506302596139279537-661693239426901221?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/661693239426901221/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/03/cache-busting-in-php-part-2.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/661693239426901221'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/661693239426901221'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/03/cache-busting-in-php-part-2.html' title='Cache busting in PHP: Part 2'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-9075335399016094383</id><published>2010-03-20T13:32:00.009-04:00</published><updated>2010-04-16T07:06:37.555-04:00</updated><title type='text'>Rails caching and cache busting in PHP</title><content type='html'>Ever wondered how to use browser caching to speed up your page loads?&lt;br /&gt;&lt;br /&gt;I was working on a Rails project recently, and noticed &lt;a href="http://api.rubyonrails.org/classes/ActionView/Helpers/AssetTagHelper.html"&gt;something interesting in the documentation&lt;/a&gt;:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;Using asset timestamps&lt;br /&gt;&lt;br /&gt;By default, Rails appends asset‘s timestamps to all asset paths[1]. This allows you to set a cache-expiration date for the asset far into the future, but still be able to instantly invalidate it by simply updating the file (and hence updating the timestamp, which then updates the URL as the timestamp is part of that, which in turn busts the cache).&lt;br /&gt;&lt;br /&gt;It‘s the responsibility of the web server you use to set the far-future expiration date on cache assets that you need to take advantage of this feature. Here‘s an example for Apache:&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;# Asset Expiration&lt;br /&gt;ExpiresActive On&lt;br /&gt;&amp;lt;filesmatch "\.(ico|gif|jpe?g|png|js|css)$"&amp;gt;&lt;br /&gt;ExpiresDefault "access plus 1 year"&lt;br /&gt;&amp;lt;/FilesMatch&amp;gt;&lt;br /&gt;&lt;/div&gt;&lt;/blockquote&gt;&lt;br /&gt;As I explained on Stackoverflow (more on that in a moment):&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;If you look at a the source for a Rails page, you'll see what they mean: the path to a stylesheet might be "/stylesheets/scaffold.css?1268228124", where the numbers at the end are the timestamp when the file was last updated.&lt;br /&gt;&lt;br /&gt;So it should work like this:&lt;br /&gt;&lt;br /&gt;1. The browser says 'give me this page'&lt;br /&gt;2. The server says 'here, and by the way, this stylesheet called scaffold.css?1268228124 can be cached for a year - it's not gonna change.'&lt;br /&gt;3. On reloads, the browser says 'I'm not asking for that css file, because my local copy is still good.'&lt;br /&gt;4. A month later, you edit and save the file, which changes the timestamp, which means that the file is no longer called scaffold.css?1268228124 because the numbers change.&lt;br /&gt;5. When the browser sees that, it says 'I've never seen that file! Give me a copy, please.' The cache is 'busted.'&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;h3&gt;Bringing it to PHP&lt;/h3&gt;&lt;br /&gt;Clever! Now how can we borrow that idea in a PHP app?&lt;br /&gt;&lt;br /&gt;The first step, of course, is to set the server to tell browsers 'cache these files.' The example config above worked for me[2].&lt;br /&gt;&lt;br /&gt;The second step is to append timestamps to your filenames. Here's a first-pass attempt at that:&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&amp;lt;link rel="stylesheet" type="text/css" href="/includes/main.css &amp;lt;?PHP echo '?' . filemtime($root.'/includes/main.css'); ?&amp;gt;" title="default" /&amp;gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;That basically works - the time stamp is appended to the file name. But it's not nearly as streamlined as the Rails way, for a couple of reasons.&lt;br /&gt;&lt;br /&gt;1) You have to type the file name twice - don't repeat yourself!&lt;br /&gt;2) Come to think of it, all your stylesheet links are going to be the same format. Why keep typing in the boilerplate stuff?&lt;br /&gt;&lt;br /&gt;In Rails, you'd just do this:&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&amp;lt;%= stylesheet_link_tag 'main' %&amp;gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;Slick! Helper tags like these take a lot of the drudgery out of HTML when you're using Rails.&lt;br /&gt;&lt;br /&gt;A loose aproximation in PHP could be generalized to handle different file types. For example, you might write a function like this:&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;function cachedFile($type, $name, $attr=null){&lt;br /&gt;&amp;nbsp;$root = $_SERVER['DOCUMENT_ROOT'];&lt;br /&gt;&amp;nbsp;switch ($type){&lt;br /&gt;&amp;nbsp;&amp;nbsp;case 'css':&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;$output = '&amp;lt;link rel="stylesheet" type="text/css" href="/includes/';&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;$output .= $name;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;$output .= '?' . filemtime($root.'/includes/'. $name) . '" ';&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;if($attr){&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$output .= $attr . ' ';&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;$output .= '/&amp;gt;';&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;$output .= "\n";&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo $output;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;break;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;case 'js':&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$output = '&amp;lt;script type="text/javascript" src="/includes/';&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$output .= $name;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$output .= '?' . filemtime($root.'/includes/'. $name) . '"';&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$output .= '&amp;gt;&amp;lt;/script&amp;gt;';&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$output .= "\n";&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo $output;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;break;&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;}&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;...which could then be used like this:&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;cachedFile('css','jquery-ui-1.7.1.custom.css');&lt;br /&gt;cachedFile('css','main.css','title="Default"');&lt;br /&gt;cachedFile('js','jquery-1.4.min.js');&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;Notice that this function assumes something - that your javascript files and stylesheets will always be in a particular folder. That's part of Rails' "convention over configuration" mentality: if you always do something the same way, you only have to specify it once.&lt;br /&gt;&lt;br /&gt;Now, &lt;b&gt;there's still room for improvement&lt;/b&gt;. For example, the type could be extracted from the filename, so that's one less argument to pass in. And more file types could be added. But this function already accomplishes several good things:&lt;br /&gt;&lt;br /&gt;1) It gets your files to be cached by the browser and to bust the cache when necessary&lt;br /&gt;2) It cuts down on code repetition&lt;br /&gt;3) Naming the function &lt;span class="code"&gt;cachedFile&lt;/span&gt; makes its purpose obvious&lt;br /&gt;&lt;br /&gt;Now - how can you verify that this is working? &lt;a href="http://stackoverflow.com/questions/2480447/how-can-i-test-caching-and-cache-busting"&gt;I had the same question myself&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;As Andy on Stackoverflow pointed out, you can load your page in Firefox, use the Firebug add-on, and look in the "Net" panel as you load the page. For any file that's cached, you should see a status message of &lt;span class="code"&gt;304 Not Modified&lt;/span&gt;. For anything that's pulled from the server, you should see &lt;span class="code"&gt;200 OK&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;Try it:&lt;br /&gt;&lt;br /&gt;1) Load the page to request everything once&lt;br /&gt;2) Reload it to verify that things are being cached&lt;br /&gt;3) Make a trivial change to a cached file, so that its timestamp will change&lt;br /&gt;4) Reload the page again and verify that it was requested&lt;br /&gt;5) Reload the page one last time and verify that it's cached again&lt;br /&gt;6) Set up an elaborate Rube Goldberg machine to pat yourself on the back&lt;br /&gt;&lt;br /&gt;(Step 6 is optional.)&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Great ideas are worth borrowing&lt;/h3&gt;&lt;br /&gt;One reason that Rails has become so popular is that it codifies a lot of clever ideas and best practices into easy-to-use shortcuts. You can make a whole app with Rails without ever realizing that it's pulling the trick shown here on your behalf.&lt;br /&gt;&lt;br /&gt;You don't have to use Rails, but if you see a great idea, it's always worth asking: "can I borrow this?"&lt;br /&gt;&lt;br /&gt;Now go bust come caches!&lt;br /&gt;&lt;br /&gt;&lt;i&gt;[1]There's a danger here: notice that the Rails docs say &lt;b&gt;all&lt;/b&gt; asset paths. If you set Apache to tell the browser to cache all images, style sheets and scripts for a year, and you only use a cache busting strategy for some of those things, then your visitors won't see updated versions of the others unless they clear their own browser cache manually or do a hard refresh with Ctrl+F5.&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;i&gt;[2]I put this information into Apache's main config file, &lt;span class="code"&gt;httpd.conf&lt;/span&gt;. If you're using a web host, they probably don't give you access to that, but they may have configured Apache to look for &lt;span class="code"&gt;.htaccess&lt;/span&gt; files in your project folders. If so, you can set caching rules there.&lt;/i&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8506302596139279537-9075335399016094383?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/9075335399016094383/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/03/rails-caching-and-cache-busting-in-php.html#comment-form' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/9075335399016094383'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/9075335399016094383'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/03/rails-caching-and-cache-busting-in-php.html' title='Rails caching and cache busting in PHP'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-5417088842112907379</id><published>2010-03-01T11:42:00.001-05:00</published><updated>2010-03-01T11:44:19.288-05:00</updated><title type='text'>It's not magic</title><content type='html'>A while back, I had a small epiphany. I'd been asked to create a web form that could send emails with attachments. I already had forms that sent email, but attachments? What were they? In my mind, attachments were the little icons above the email. I had no idea how they were created or sent.&lt;br /&gt;&lt;br /&gt;At the same time, I was reading &lt;a href="http://www.simonsingh.net/The_Code_Book.html"&gt;The Code Book&lt;/a&gt;, an entertaining and fascinating look at cryptography - the art of sending scrambled messages, to be unscrambled only by the intended recipient.&lt;br /&gt;&lt;br /&gt;&lt;img src="http://nathanlong.webfactional.com/blogimages/notmagic/codebook.png" class="leftfloat"&gt;&lt;br /&gt;&lt;br /&gt;The simplest, most brain-dead kind of encryption is a &lt;a href="http://en.wikipedia.org/wiki/Caesar_cipher"&gt;Caesar cipher&lt;/a&gt;, where you take all the letters in a message and shift them by the same amount. For example, with a Caesar cipher of 1, this:&lt;br /&gt;&lt;br /&gt;HELLO WORLD&lt;br /&gt;&lt;br /&gt;becomes this:&lt;br /&gt;&lt;br /&gt;IFMMP XPSME&lt;br /&gt;&lt;br /&gt;A modern cryptanalyst would pee his pants laughing if you used this method for anything serious. But one thing about it works: you can encode, and you can decode, as long as you know the rules.&lt;br /&gt;&lt;br /&gt;Now, the Code Book traces the development of encryption methods so complex they'll make your head spin, but all of them are systematic: whatever is encoded can be decoded. You just need to know how.&lt;br /&gt;&lt;br /&gt;This applies to any method of encoding information, even if it's not cryptography. For example, Morse Code encodes letters as electrical pulses - not to hide the message, but just to transmit it. How cool - you can encode actual language as beeps!&lt;br /&gt;&lt;br /&gt;Which shows us another important idea: &lt;b&gt;you can encode anything as anything else.&lt;/b&gt; You can encode the weather forecast with colored socks. You can encode the Constitution with duck calls. &lt;b&gt;As long as there are consistent rules for encoding and decoding, it will work.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Going back to my email attachments, I soon discovered how attachments work: &lt;b&gt;the file data is encoded as text in your email&lt;/b&gt;. It ends up looking like gibberish, but fortunately, no human has to read it, because the email program does that for you.&lt;br /&gt;&lt;br /&gt;How does it know which parts of the text are text and which parts are files? You tell it. Say you want to attach an image. First, you choose some arbitrary string of characters which will probably never occur in an actual email text. Let's say it's "Woo_hamburgers_for_mayor_in_2050_yeeeeeha." Whenever you use that phrase, it means "I'm about to put in some different content." Each section of content also gets a label about what "MIME type" it is, like "image/jpg" or "application/pdf". Your email text goes in one section, and your image data goes in another, after being encoded as text.&lt;br /&gt;&lt;br /&gt;(In PHP, you can encode the data as text like this: &lt;span class="code"&gt;base64_encode($fileContents)&lt;/span&gt;.**)&lt;br /&gt;&lt;br /&gt;On the other end, when someone opens your email, their program is smart enough not to show all the scrambled-looking letters, but instead to say, 'hey, he said this part was an image - let's show it like that.' And it gets decoded. The little icons show up. It works!&lt;br /&gt;&lt;br /&gt;The main thing is, &lt;b&gt;it's not magic.&lt;/b&gt; For me, this turned on a light in my head. I stood next to a fax machine, and pointed my finger at it. "I know what you're doing with your crazy noises," I said. "You're encoding image data as sound!"&lt;br /&gt;&lt;br /&gt;And that's how computers work, all the way down. Image information is encoded as characters, which are encoded as ones and zeros, which are encoded as magnetic charges on a disk platter, which are transmitted as electrical pulses in a circuit board.&lt;br /&gt;&lt;br /&gt;It's hard to understand. It's hard to actually believe sometimes that everything I'm doing on screen can be represented as ones and zeros. &lt;b&gt;But there's no magic. And if there's no magic, there's nothing to be scared of.&lt;/b&gt; If I work hard enough, I can understand a little piece of it - enough to get something done.&lt;br /&gt;&lt;br /&gt;----&lt;br /&gt;**For a detailed walkthrough of my attachment code, see &lt;a href="http://stackoverflow.com/questions/1330626/how-can-i-send-an-email-with-attachments-from-a-php-form"&gt;this Stackoverflow post&lt;/a&gt;.)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8506302596139279537-5417088842112907379?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/5417088842112907379/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/03/its-not-magic.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/5417088842112907379'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/5417088842112907379'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/03/its-not-magic.html' title='It&apos;s not magic'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-1301015268936938733</id><published>2010-03-01T11:17:00.000-05:00</published><updated>2010-03-01T11:17:24.690-05:00</updated><title type='text'>The Bing Button</title><content type='html'>From &lt;a href="http://www.nytimes.com/2010/03/01/technology/01soft.html?ref=technology"&gt;a NY Times article about upcoming Windows 7 phones&lt;/a&gt;:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;In addition, Microsoft is requiring phone makers to keep basic elements of its user interface, including a physical button to start Web searches on Bing.&lt;/blockquote&gt;&lt;br /&gt;Microsoft. Listen. Nobody wants a button for Bing, or Google either, for that matter. This violates three principles about what I want in a smartphone:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;&lt;b&gt;Customizable&lt;/b&gt;. If a button goes to a web site, or opens a calculator, or gives me a voice prompt, and I can't change that, I'm going to be frustrated.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Neutral&lt;/b&gt;. It's my phone, not yours. I'll use Bing or Google or Jeeves or Big Larry's Virus-Laden Search Emporium if I want to. Don't force your product on me.&lt;/li&gt;&lt;li&gt;&lt;b&gt;General purpose&lt;/b&gt;. I don't have a "word processor" key on my computer keyboard. I use on-screen menus for that. There are a million programs I could install, and a billion web sites I could visit. Smartphones are smartphones precisely because they share this characteristic. My flip phone has a single calendar program, take it or leave it. With a smartphone, I could install or write my own, or use one on the web. Having a button that does one thing makes this less like a smartphone and more like a calculator - a single-purpose device. I want a browser, not a Binger.&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;b&gt;If you can't put your customers' desires above your need to cross-brand, you're going to make lousy products&lt;/b&gt;. And your market share will continue to drop.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8506302596139279537-1301015268936938733?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/1301015268936938733/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/03/bing-button.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/1301015268936938733'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/1301015268936938733'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/03/bing-button.html' title='The Bing Button'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-161113045547296565</id><published>2010-02-02T15:05:00.008-05:00</published><updated>2010-02-03T13:20:19.770-05:00</updated><title type='text'>Q&amp;A with Michael Gundlach, creator of AdBlock for Google Chrome</title><content type='html'>On Monday (2-1-2010), &lt;a href="https://chrome.google.com/extensions/detail/gighmmpiobklfepjocnamgkkbiglidom"&gt;AdBlock for Google Chrome&lt;/a&gt; became the most popular extension for the browser, with about 250,000 users as of this writing. That achievement came after nearly two months of frenzied development by its creator, Michael Gundlach, during which he was interviewed by the NY Times, got rave reviews from users, and saved the world from a giant comet. But how?&lt;br /&gt;&lt;br /&gt;&lt;img class="leftfloat" src="http://nathanlong.webfactional.com/blogimages/michael_interview/michael_headshot.png" "/&gt;&lt;br /&gt;&lt;br /&gt;Lucky for me, Michael also happens to be my close friend and coding mentor, so I’ve got him on speed dial. Today during lunch, we chatted about AdBlock, including how agile development and editor mastery helped him achieve so much in so little time.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;How did you get started making AdBlock for Chrome?&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;"I was reading the Google blog at work and noticed that they said extensions were available in the beta channel... When I searched for Adblock on the extensions page, I saw that it didn't exist. I was like, 'oh that's too bad,' and went back to coding. About two hours later, I went, 'what am I doing at work!?' I jumped up and went home and read a tutorial, and posted a dummy version of AdBlock, and immediately started getting user feedback. They were rating it terribly, because it was just a ‘hello world.’ So based on the comments, I updated it so it would do one thing: block ads on ESPN. Then I just went as fast as I could to get a functional version that actually blocked everything that Easylist blocked... and then I kind of didn't stop coding all through December." &lt;br /&gt;&lt;br /&gt;&lt;b&gt;What’s your development background?&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;"I started coding when I was nine, when my brother gave me a computer with BASIC on it and showed me how to use it… I got in trouble in high school for installing the Pascal  compiler on one of the physics computers because I wasn't paying attention. They charged me with hacking into the computer system when I was just trying to make a choose-your-own adventure for my mom for her birthday… I never stopped because I can't stand do anything but program." &lt;i&gt;(Note: Michael went on to work for some well-known companies, including Google.)&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;What's the process for writing an extension? Was it hard to learn?&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;"It was great - the tutorial was really freaking simple, and you just make some Javascript files that you want to run on each page. Chrome’s extensions were based on the GreaseMonkey extension in Firefox, where you just run some Javascript on a random web page... My 20% project at Google was a Greasemonkey script for internal use, so it felt very natural." &lt;i&gt;(Note: Michael recently heard from a current Googler that the update system in Chrome is based on an innovation in Michael’s Greasemonkey script that allowed it to update itself automatically.)&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;img class="rightfloat" src="http://nathanlong.webfactional.com/blogimages/michael_interview/adblock_most_popular.png" "/&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;You and I talk a lot, so I know that you've used agile development throughout this. How do you think of agile development, and why did you choose to use it?&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;"It's better for your psyche to have something working quickly and see that it does work... I wanted to get something out there right then, and the system supported me making tiny changes and shipping them quickly...  If I had done the waterfall method and waited until the extension was complete, somebody else would have already done it and I wouldn’t have been able to catch up… Agile development keeps you focused on the task, and helps you always have something usable for the user so you get feedback."&lt;br /&gt;&lt;br /&gt;&lt;p class="pullquote leftfloat"&gt;"Agile development keeps you focused on the task, and helps you always have something usable for the user so you get feedback."&lt;p&gt;&lt;br /&gt;&lt;b&gt;What are some of the decisions you made that helped you move quickly?&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;"I knew it needed to have filter subscriptions, but at first it was simpler to just copy and past the entire Easylist into a file and use Unix regex tools to turn that into Javascript code [an array]… I didn't have any updating, but that was a quick and dirty way to have something that covered lots of websites at once. The next week, I had people reported ads that weren’t being blocked because I wasn’t updating Easylist, so I had to patch manually while I was trying to get filter subscriptions written.”&lt;br /&gt;&lt;br /&gt;"When I put new features in, I tried to put them as opt-in experiments, using the Google Labs approach, where users that are geeky enough can try it out and give me feedback... Before that, I updated one night and went to bed, and the next morning I found people complaining in my comments that I blocked all images on all web sites. I kept getting those complaints for three days until everybody’s browser had gotten the version that fixed it, even though I had released 10 updates in that time. So now I do stuff in experiments first.”&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Many developers feel like we can't publish something unless it's perfect. But clearly that attitude would have stopped you from succeeding here. How do you decide if something is "good enough for now?"&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;img class="leftfloat" src="http://nathanlong.webfactional.com/blogimages/michael_interview/adblock_homepage.png" "/&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;"It's a balance, but I think the idea that if I put something in there that's not perfect, it will hurt my reputation, that’s false, because that's what Microsoft has been doing forever. If you can put something out that's not perfect and get feedback, you're ahead of the person who hasn't released anything at all… I just look for the absolute simplest thing I can do that adds some functionality, then do that and release it… There are still a lot of things I want to do with AdBlock, but it's been useful for thousands of people even thought it's not perfect." &lt;br /&gt;&lt;br /&gt;&lt;b&gt;What did you learn during this process?&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;"This project has 500 times more users than anything I've ever built personally. I'm seeing how successful Agile can be if I just listen to users and do the thing that people are shouting for most. So I've validated the agile approach... combined with not ever sleeping."&lt;br /&gt;&lt;br /&gt;"Also, I need to weigh how loud the user is with what percentage of the user base they represent. People were clamoring for a long time that I get a change log up and post my source code. I could have spent 4-5 hours doing that, but that would have been time I wasn’t blocking ads and fixing bugs, and most people don’t care about a change log. I actually wrote a priority list down – responding to user requests quickly was first, because people liked my fast customer service, then fixing ad reports, then fixing bug reports, then feature requests, and way down the list was administrative stuff like a change log. The list got more formalized when I finally got some breathing room, when the ads were being blocked and most of the bugs were fixed, so I created a Google Code project, where I can mark bugs with low or high priority and people can see my change log."&lt;br /&gt;&lt;br /&gt;&lt;b&gt;You used object orientation pretty heavily in your Javascript on this project. How did that help you?&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;"What makes it most useful for me in Javascript is that it simplifies the code; it's one level of complexity that's hidden. If an object has two methods exposed, I only have to remember those two methods, where with procedural code there are a bunch of methods in the global namespace that I have to keep up with. It's harder to do in Javascript because the syntax isn't as friendly as in something like Ruby, but once you learn the syntax, it's fine, and it makes your JS feel SO much nicer." &lt;br /&gt;&lt;br /&gt;&lt;b&gt;I had the chance to actually see you working for a bit, and the main thing that stood out to me was your fluid use of vim. It's almost like you're playing an instrument: you don't seem to think about the commands. You're just typing at a thousand miles an hour, and fully formatted code flows out and morphs on the screen, while split windows appear and disappear as needed. How did you attain such editor mastery, and how important is that to your career?&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;p class="pullquote rightfloat"&gt;It's freaking wonderful to be fluent in one editor... I just think ‘I want an IF statement’ and the braces appear.&lt;/p&gt;&lt;br /&gt;"I got it because I've been using Vim for maybe 21 years – my brother got me a Unix account at the University of Georgia and showed me vitutor and said ‘use this.’… I don't know nearly all there is to know about Vi, but I know the bits I need to... It's freaking wonderful to be fluent in one editor, because my thoughts get to flow straight from my brain onto the page, and I don't have to worry about what my fingers are doing in the meantime. I don't have to think at the level of how to put the braces in anymore, I just think ‘I want an IF statement’ and the braces appear. Maybe I'm manually indenting them and getting the formatting right, but it's such lower brain stem work that I can ignore that and think about the code. It's like my fingers are plugged into the computer. It's not 100% - sometimes I get things wrong. But because I've been using it so long, and because Vi rocks so hard, I don't have to think much about it."&lt;br /&gt;&lt;br /&gt;&lt;b&gt;So being really fluent in an editor is crucial?&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;"Absolutely. Vi or Emacs, whichever one, I don't care, learn it. The exception is if you're going to spend all your life doing Microsoft .NET programming, then learn Visual Studio and learn everything about it.”&lt;br /&gt;&lt;br /&gt;“One nice thing about Vi is that it can be used for text editing as well as code, so every time you write a notepad file, you're improving your Vi skills… Get a good .vimrc, that's important... When I try to use Vi on a machine that I don't control, it's painful again. Syntax highlighting and stuff like that don’t work, and I have to stop thinking about my code and think about my editor.” &lt;br /&gt;&lt;br /&gt;&lt;b&gt;Projects like AdBlock for Chrome are not just fun for you – they’re part of your vision for your career. Why?&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;"I've always had the idea that when I can retire from having to work for money, I would still be doing all the same stuff I do now, it would just be for free. Instead, I am fortunate enough to find some hours that I can do this now, and holy crap, I'm the author of a successful open source project, which was one of those goals for retirement... I’m looking for a job now that will let me work from home full time, and possibly less than full time, so I can use some of those hours I would have spent working for pay working on AdBlock."&lt;br /&gt;&lt;br /&gt;&lt;b&gt;How is the AdBlock community coming?&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;"We've already got three official contributors who triage bug reports and figure out how to block ads, and two offers of translation work. So if I ever want to leave, I think can hand it off. That's what open source is so awesome for. If I want to move on to another project, it will be when the community is so strong that AdBlock is running itself. But I don't see that happening anytime soon, because I'm still really excited about making this thing great.”&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8506302596139279537-161113045547296565?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/161113045547296565/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/02/q-with-michael-gundlach-creator-of.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/161113045547296565'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/161113045547296565'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/02/q-with-michael-gundlach-creator-of.html' title='Q&amp;A with Michael Gundlach, creator of AdBlock for Google Chrome'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-2528440862075728437</id><published>2010-02-01T09:57:00.002-05:00</published><updated>2010-02-01T10:18:16.099-05:00</updated><title type='text'>Windows development toolbar</title><content type='html'>At work, I develop on a Windows machine, and need access to various config and log files, as well as my main code trunk and a branch. I got tired of digging through layers of folders to find everything, and set up a quick way to access everything.  Here's how.&lt;br /&gt;&lt;br /&gt;(Note: these screen shots are from Windows XP, but I've confirmed that this works on Windows 7, and it probably works on Windows Vista as well.)&lt;br /&gt;&lt;br /&gt;First, create a folder on your computer (anywhere you like) and create a bunch of shortcuts in it to everything you might need.&lt;br /&gt;&lt;br /&gt;Second, right-click the menu bar and choose Toolbars &gt; New toolbar. Select the folder you just created.&lt;br /&gt;&lt;br /&gt;&lt;img src="http://nathanlong.webfactional.com/blogimages/wintoolbar/dev_toolbar1.png" /&gt;&lt;br /&gt;&lt;br /&gt;Finally, click the little arrows next to the toolbar name. It will pop up with all your shortcuts. Hooray!&lt;br /&gt;&lt;br /&gt;&lt;img src="http://nathanlong.webfactional.com/blogimages/wintoolbar/dev_toolbar2.png" /&gt;&lt;br /&gt;&lt;br /&gt;Final tip - if you'd like to see the full date and time on your menu bar, right-click it again, deselect "lock the toolbar," and drag the toolbar edge up to make it taller. When the full date and time appears there, right-click again and re-lock it.&lt;br /&gt;&lt;br /&gt;&lt;img src="http://nathanlong.webfactional.com/blogimages/wintoolbar/win_clock_with_date.png" /&gt;&lt;br /&gt;&lt;br /&gt;Ta-da! No more double-clicking to see today's date on the calendar.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8506302596139279537-2528440862075728437?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/2528440862075728437/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/02/windows-xp-development-toolbar.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/2528440862075728437'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/2528440862075728437'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/02/windows-xp-development-toolbar.html' title='Windows development toolbar'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-8983079876218708421</id><published>2010-01-18T13:31:00.027-05:00</published><updated>2010-08-15T08:36:52.469-04:00</updated><title type='text'>Setting up Apache, PHP, MySQL, PHPMyAdmin, and Filezilla Server on Windows XP</title><content type='html'>Need to set up a Windows web server? You're in luck! Today we're going to set up more server components than you can shake a frozen iguana at! (Or, for Latin-prejudiced grammar Nazis, "more server components than at which you can shake a frozen iguana!")&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;The Disclaimer&lt;/h4&gt;&lt;br /&gt;I just finished figuring all of this out myself. That's good, because it means I know how to talk to a beginner. But I don't pretend to be an expert. If anybody spots any glaring security holes or other blunders, let me know; otherwise, just be aware that these are only the basics, coming from a relative n00b in all things server-related.&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;The Goal&lt;/h4&gt;&lt;br /&gt;The goal today is to set up a web server on Windows where you can upload your PHP code, serve it with Apache, tie it in to a MySQL database, and administer MySQL with PHPMyAdmin. &lt;br /&gt;&lt;br /&gt;(&lt;b&gt;Update August 15, 2010&lt;/b&gt; - see "&lt;a href="http://sleeplessgeek.blogspot.com/2010/08/visualizing-our-example-setup.html"&gt;Visualizing our example setup&lt;/a&gt;" for help understanding how these pieces work together.)&lt;br /&gt;&lt;br /&gt;Let's start by setting up Apache.&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;A Bunch of Downloads&lt;/h4&gt;&lt;br /&gt;Here's what you need to download (along with the version numbers I'm using):&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Apache Web Server (v 2.2.14) - get the Win32 binary (msi installer) &lt;a href="http://httpd.apache.org/download.cgi"&gt;from the official site&lt;/a&gt;. You can decide between the one that comes with SSL and the one that comes without - either will work for this tutorial.&lt;/li&gt;&lt;li&gt;PHP (v 5.3.1)- we'll download the Windows binary &lt;a href="http://windows.php.net/download/"&gt;here&lt;/a&gt;. This is the PHP interpreter; when Apache needs to serve a PHP script, it will hand it off to the PHP interpreter to deal with the PHP code before it sends HTML to the client. &lt;b&gt;Get the zip file labeled "VC6 x86 Thread Safe"&lt;/b&gt;.&lt;/li&gt;&lt;li&gt;MySQL (5.1.42) - Go &lt;a href="http://dev.mysql.com/downloads/mysql/"&gt;here&lt;/a&gt; and get the "Windows (x86, 32-bit), MSI Installer" - (I got the file named "mysql-5.1.42-win32.msi" - not sure how this is different from the 'mysql-essential' version.)&lt;/li&gt;&lt;li&gt;PHPMyAdmin (3.2.4) - get the zip file &lt;a href="http://www.phpmyadmin.net/home_page/downloads.php"&gt;here&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;&lt;a href="http://nathanlong.webfactional.com/blogimages/winserversetup/phpmyadmin.sql"&gt;phpmyadmin.sql&lt;/a&gt; - tables for some of PHPMyAdmin's advanced features (more on this later)&lt;/li&gt;&lt;li&gt;FileZilla Server (0.9.34) - download exe file &lt;a href="http://filezilla-project.org/download.php?type=server"&gt;here&lt;/a&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h4&gt;Setting up Apache&lt;/h4&gt;&lt;br /&gt;Run the executable you downloaded above. Other than setting your network and server name (I used 'localhost') and your administrator email address, you can just click "Next" through the whole installation. Choose "Typical" when asked for a setup type.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://nathanlong.webfactional.com/blogimages/winserversetup/apache04.png"&gt;&lt;img src="http://nathanlong.webfactional.com/blogimages/winserversetup/apache04.png" height="200"/&gt;&lt;/a&gt; &lt;br /&gt;&lt;br /&gt;When setup is done, Apache is installed as a service on your machine. (If you right-click My Computer and choose Manage, then click &gt; Services and Applications &gt; Services, you can see all the services running on your machine.)&lt;br /&gt;&lt;br /&gt;You can verify that this was successful by opening a browser and going to http://localhost. You should see this:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://nathanlong.webfactional.com/blogimages/winserversetup/apache10.png"&gt;&lt;img src="http://nathanlong.webfactional.com/blogimages/winserversetup/apache10.png" height="200"/&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;...which is the file index.html, located in Apache's htdocs folder.&lt;br /&gt;&lt;br /&gt;Now, if you look in your task bar, you should see an icon that looks like this:&lt;br /&gt;&lt;br /&gt;&lt;img src="http://nathanlong.webfactional.com/blogimages/winserversetup/apache11.png" height="50"/&gt;&lt;br /&gt;&lt;br /&gt;That's a status indicator that says "Apache is running!" You can click it to stop Apache:&lt;br /&gt;&lt;br /&gt;&lt;img src="http://nathanlong.webfactional.com/blogimages/winserversetup/apache12.png" height="100"/&gt;&lt;br /&gt;&lt;br /&gt;...and you should see the status change.&lt;br /&gt;&lt;br /&gt;&lt;img src="http://nathanlong.webfactional.com/blogimages/winserversetup/apache13.png" height="50"/&gt;&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;Installing PHP&lt;/h4&gt;&lt;br /&gt;Extract the PHP zip file to your c:\ drive and rename the folder from "c:\php-5.3.1-Win32-VC6-x86" (or whatever) to simply "c:\php."&lt;br /&gt;&lt;br /&gt;Now, to keep things clean so that you don't have to drop anything PHP-related into the Windows folder, you'll need to add c:\php to the Windows system path. Essentially, that means that when Apache says "I need to run php.exe," Windows will know that c:\php is one of the places where it can look for that.&lt;br /&gt;&lt;br /&gt;To add something to the Windows path, right-click My Computer and choose Properties, then go to the Advanced tab.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://nathanlong.webfactional.com/blogimages/winserversetup/winpath01.png"&gt;&lt;img src="http://nathanlong.webfactional.com/blogimages/winserversetup/winpath01.png" height="200"/&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Click on the "Environment Variables" button near the bottom, then scroll down in the bottom panel until you see "Path."&lt;br /&gt;&lt;br /&gt;&lt;a href="http://nathanlong.webfactional.com/blogimages/winserversetup/winpath02.png"&gt;&lt;img src="http://nathanlong.webfactional.com/blogimages/winserversetup/winpath02.png" height="200"/&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Click to highlight it, then click "Edit."&lt;br /&gt;&lt;br /&gt;&lt;a href="http://nathanlong.webfactional.com/blogimages/winserversetup/winpath03.png"&gt;&lt;img src="http://nathanlong.webfactional.com/blogimages/winserversetup/winpath03.png" height="200"/&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Go to the end of that line and add (without the quotes) "C:\php;", then click OK. Restart your machine so that this change will take effect.&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;Configuring PHP&lt;/h4&gt;&lt;br /&gt;Once you've rebooted, browse to c:\php. Copy either php.ini-production or php.ini-development, depending on the purpose of this server setup[1], to simply c:\php.ini. Then copy that file to c:\php.ini-original before you start making any changes, so you can always go back and see what you did by diffing your current config with the original one. (&lt;a href="http://winmerge.org/"&gt;WinMerge&lt;/a&gt; is a good Windows-based program to do that.)&lt;br /&gt;&lt;br /&gt;Now that you've got a backup, open php.ini in a text editor and make the following changes:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Optionally, set &lt;span class="code"&gt;short_open_tag = On&lt;/span&gt;. This is not considered best practice, but it allows you to start PHP code blocks with just &lt;span class="code"&gt;&amp;lt;?&lt;/span&gt; instead of &lt;span class="code"&gt;&amp;lt;?PHP&lt;/span&gt;. It's convenient, and may be needed if your existing code uses tags like this.&lt;/li&gt;&lt;li&gt;Set &lt;span class="code"&gt;extension_dir = "C:/php/ext"&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Uncomment the 'extension=' line(s) for your preferred database adapter(s), if you've got existing code. For this example, uncomment &lt;span class="code"&gt;extension=php_mysqli.dll&lt;/span&gt; (to match the config we'll use for PHPMyAdmin later on).&lt;/li&gt;&lt;li&gt;Set up your time zone to be used in any PHP date functions. For example, I set &lt;span class="code"&gt;date.timezone = "America/New_York"&lt;/span&gt;. (I know, it's weird to use a string like that. See &lt;a href="http://us3.php.net/manual/en/timezones.php"&gt;the manual&lt;/a&gt; for more.)&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h4&gt;Configuring Apache&lt;/h4&gt;&lt;br /&gt;Now browse to the Apache directory, and go to the "conf" subdirectory. Here, the main configuration file is called "httpd.conf". Just as before, we want to have a backup of it before we make any changes. Fortunately, Apache comes with backup config files, located in the "original" subfolder. Leave those alone for future comparison with your modified versions.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://nathanlong.webfactional.com/blogimages/winserversetup/apache14.png"&gt;&lt;img src="http://nathanlong.webfactional.com/blogimages/winserversetup/apache14.png" height="200"/&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Now, open up httpd.conf in a text editor and let's make some changes.&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Set "DocumentRoot" to point to the folder where you want to keep your web pages and scripts. (The default setting points to Apache's 'htdocs' subfolder.) For example, you could set it to &lt;span class="code"&gt;DocumentRoot 'C:/www'&lt;/span&gt;.&lt;/li&gt;&lt;li&gt;Set &amp;lt;directory&amp;gt; to point to the same folder. Like this: &lt;span class="code"&gt;&amp;lt;directory 'C/www'&amp;gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Set &lt;span class="code"&gt;DirectoryIndex index.php index.html&lt;/span&gt;. This controls which file will be displayed if someone requests a folder. For example, if they browse to www.yoursite.com/foo, with this setting, the server will first look in the foo folder for 'index.php.' If it finds it, it will display it; otherwise, it will proceed to look for 'index.html.' You can add more alternatives to the end of the line or change the order as you like.&lt;/li&gt;&lt;li&gt;Set &lt;span class="code"&gt;EnableSendfile Off&lt;/span&gt; to prevent some page requests from hanging. (This is a Windows-specific configuration that &lt;a href="http://serverfault.com/questions/95620/local-apache-on-windows-xp-not-finishing-page-requests/102080#102080"&gt;took me a few days&lt;/a&gt; to to learn about)&lt;/li&gt;&lt;li&gt;Finally, add the following lines to the end of the file&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;div class="code"&gt;#Enable PHP processing&lt;br /&gt;LoadModule php5_module "c:/php/php5apache2_2.dll"&lt;br /&gt;#.php files should be processed as PHP&lt;br /&gt;AddType application/x-httpd-php .php&lt;br /&gt;#Where to find php.ini&lt;br /&gt;PHPiniDir "C:\php"&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;Whew! That's it for Apache and PHP. Restart Apache, and create a simple page called 'index.html' in your designated web root folder (like "C:\www", or whatever you chose). Now if you visit http://localhost, you should see it.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://nathanlong.webfactional.com/blogimages/winserversetup/new_index_html.png"&gt;&lt;img src="http://nathanlong.webfactional.com/blogimages/winserversetup/new_index_html.png" height="200"/&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Now create a simple php script called 'index.php'. With the DirectoryIndex setting I used above, it will now take precedence over 'index.html' and be displayed when you reload.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://nathanlong.webfactional.com/blogimages/winserversetup/new_index_php.png"&gt;&lt;img src="http://nathanlong.webfactional.com/blogimages/winserversetup/new_index_php.png" height="200"/&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;Installing MySQL&lt;/h4&gt;&lt;br /&gt;This is the easiest part yet. Run the installer and use all the default options.&lt;br /&gt;&lt;br /&gt;Note: The "Typical" installation option puts the database data in "C:\Documents and Settings\All Users\Application Data\MySQL\MySQL Server 5.1". If you ever need to completely remove MySQL and reinstall, if you don't delete this folder along with the program's folder, you'll reinstall and open your database to find that it already has data in it. Spooky.&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;Setting Up PHPMyAdmin&lt;/h4&gt;&lt;br /&gt;This is also pretty simple. For the most basic case, all you need to do is this:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Unzip the phpmyadmin file into your web root folder&lt;/li&gt;&lt;li&gt;Rename its folder to simply 'phpmyadmin' (so that you can browse to 'localhost/phpmyadmin' to see it)&lt;/li&gt;&lt;li&gt;Create a config file in that folder, called config.inc.php&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;A very simple config file (&lt;a href="http://www.phpmyadmin.net/documentation/"&gt;taken from the docs&lt;/a&gt;) looks like this:&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;&amp;lt;?php&lt;br /&gt;$cfg['blowfish_secret'] = 'gtqw0282bg7130jhga7d';  // change to random string of your choice&lt;br /&gt;$i=0;&lt;br /&gt;$i++;&lt;br /&gt;$cfg['Servers'][$i]['auth_type'] = 'cookie';&lt;br /&gt;?&amp;gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;That's it! PHPMyAdmin should work.&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;Making PHPMyAdmin better&lt;/h4&gt;&lt;br /&gt;If you've got previous experience with XAMPP, you'll be accustomed to some nice little configuration features for PHPMyAdmin. For example, you can bookmark queries, and have your foreign keys display as hyperlinks to the associated record.&lt;br /&gt;&lt;br /&gt;Fortunately, we can all get those features working, too. First, add the following lines to config.inc.php:&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;$cfg['Servers'][$i]['pmadb'] = 'phpmyadmin';&lt;br /&gt;$cfg['Servers'][$i]['bookmarktable'] = 'pma_bookmark';&lt;br /&gt;$cfg['Servers'][$i]['relation'] = 'pma_relation';&lt;br /&gt;$cfg['Servers'][$i]['table_info'] = 'pma_table_info';&lt;br /&gt;$cfg['Servers'][$i]['table_coords'] = 'pma_table_coords';&lt;br /&gt;$cfg['Servers'][$i]['pdf_pages'] = 'pma_pdf_pages';&lt;br /&gt;$cfg['Servers'][$i]['column_info'] = 'pma_column_info';&lt;br /&gt;$cfg['Servers'][$i]['history'] = 'pma_history';&lt;br /&gt;$cfg['Servers'][$i]['designer_coords'] = 'pma_designer_coords';&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;All of these little features depend on having a database called 'phpmyadmin', with tables named 'pma_bookmark,' etc.&lt;br /&gt;&lt;br /&gt;Take a look at &lt;a href="http://nathanlong.webfactional.com/blogimages/winserversetup/phpmyadmin.sql"&gt;the sql file I provided&lt;/a&gt;. If you create the database 'phpmyadmin' and then import this file, it will set up the tables you need for the fancy features.&lt;br /&gt;&lt;br /&gt;One last tweak that you can take or leave: I hate having too much pagination on my large tables, so I set PHPMyAdmin to display 500 records at a time by adding this line to the config file:&lt;br /&gt;&lt;br /&gt;&lt;div class="code"&gt;/*Personal Tweaks */&lt;br /&gt;$cfg['MaxRows'] = 500; /* Show 500 rows of a table by default */&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;h4&gt;Setting up FileZilla Server&lt;/h4&gt;&lt;br /&gt;Once again, run through the installation process, accepting all the default settings. When you're done, Filezilla Server should launch, and you'll have a status window / control panel that looks like this:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://nathanlong.webfactional.com/blogimages/winserversetup/filezilla08.png"&gt;&lt;img src="http://nathanlong.webfactional.com/blogimages/winserversetup/filezilla08.png" height="200"/&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Now you need to set up at least one user. Start by clicking "Edit &gt; Users."&lt;br /&gt;&lt;br /&gt;&lt;a href="http://nathanlong.webfactional.com/blogimages/winserversetup/filezilla09.png"&gt;&lt;img src="http://nathanlong.webfactional.com/blogimages/winserversetup/filezilla09.png" height="200"/&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Take a look at this image, then I'll explain it a bit.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://nathanlong.webfactional.com/blogimages/winserversetup/filezilla10.png"&gt;&lt;img src="http://nathanlong.webfactional.com/blogimages/winserversetup/filezilla10.png" height="200"/&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Over on the right, you've got a list of users. Here, I've created two. Everything to the left of there applies to the currently-highlighted user. So your first step is to create one, and after that you can manage settings.&lt;br /&gt;&lt;br /&gt;At a minimum, you need to:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Enable the account&lt;/li&gt;&lt;li&gt;Set a password (well, you COULD skip this, but don't)&lt;/li&gt;&lt;li&gt;Click on "shared folders" to the left and add at least one folder that this user will have access to, as well as what type of access: read-only, write, etc. A developer will probably need full access to the web root folder.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;You can tweak all kinds of other settings, like connection timeout and bandwidth limits, etc etc, but this takes care of the basic setup.&lt;br /&gt;&lt;br /&gt;As long as the machine this is running on is online and doesn't have port 21 blocked in its firewall, you should be able to get on another computer, open an FTP client, put in your new server's IP address and the username and password you just created in Filezilla Server, and connect. You should see the folder you gave that user access to.&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;Done!&lt;/h4&gt;&lt;br /&gt;That's it! If you have any questions, read the documentation for that component, or check on &lt;a href="http://serverfault.com"&gt;serverfault&lt;/a&gt; - it's like &lt;a href="stackoverflow.com"&gt;stackoverflow&lt;/a&gt;, but for system administrators.&lt;br /&gt;&lt;br /&gt;[1] The difference between the development and production versions of this file are mostly related to error reporting and performance profiling (although having error reporting turned on for your production server could expose sensitive security information). Some of the errors and warnings the development version will give you include stuff like "this isn't best practice" or "this code will be deprecated in future versions of PHP." They are great things to learn from your development setup, but you don't want your production server wasting its effort noticing them.&lt;br /&gt;If you're curious to learn more, compare the two files. A diff program like Winmerge (which is free) will let you quickly see the differences between the two, and reading the documentation about those settings will teach you what they're for.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8506302596139279537-8983079876218708421?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/8983079876218708421/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/01/setting-up-apache-php-mysql-phpmyadmin.html#comment-form' title='37 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/8983079876218708421'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/8983079876218708421'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/01/setting-up-apache-php-mysql-phpmyadmin.html' title='Setting up Apache, PHP, MySQL, PHPMyAdmin, and Filezilla Server on Windows XP'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>37</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-369694963225548769</id><published>2010-01-14T12:47:00.000-05:00</published><updated>2010-01-14T12:47:48.899-05:00</updated><title type='text'>Firefox Offline Mode</title><content type='html'>You know what's horrible? Firefox's "Offline Mode."&lt;br /&gt;&lt;br /&gt;I've been tinkering with Apache and with some web pages, and I keep being surprised by weird stuff, like:&lt;br /&gt;&lt;br /&gt; - I don't see my page edits when I reload&lt;br /&gt; - A page displays just fine, even though the server is &lt;b&gt;no longer installed&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Then I realize that Firefox, &lt;b&gt;whose whole job in life is to get online, is offline.&lt;/b&gt; It has, invisibly and without my asking, decided not to reload the page, even though I'm pressing Control+F5, which means "no for real, reload everything on this page, throw away your cache, I want to see the most current stuff possible."&lt;br /&gt;&lt;br /&gt;I don't know what this feature is for, but I need a way to turn it off.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8506302596139279537-369694963225548769?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/369694963225548769/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/01/firefox-offline-mode.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/369694963225548769'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/369694963225548769'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/01/firefox-offline-mode.html' title='Firefox Offline Mode'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-1589951879918758919</id><published>2010-01-14T12:36:00.004-05:00</published><updated>2011-03-12T14:27:22.475-05:00</updated><title type='text'>Adding fields to a web form: a job for closures!</title><content type='html'>In my last post, I explained how closures work. Via their magic, I can make a function to do this:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: js"&gt;nextNumber(); //returns 1&lt;br /&gt;nextNumber(); //returns 2&lt;br /&gt;nextNumber(); //returns 3&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The most concise way to create this in Javascript would be:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: js"&gt;var nextNumber = (function(){var i=1; return function() {return i++;}})();&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Of course, that's not very readable. See &lt;a href="http://sleeplessgeek.blogspot.com/2009/12/so-what-are-these-closure-thingys.html"&gt;my previous post&lt;/a&gt; for a step-by-step introduction to closures.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Putting closures to use&lt;/h2&gt;&lt;br /&gt;One place where this trick is really handy is when you want to dynamically add fields to a web form. You need each new field to have a unique name and ID, but pulling new IDs from a database is overkill.&lt;br /&gt;&lt;br /&gt;Say you've got a form where users can order Viking helmets. For each helmet they order, they need to specify a color and size.&lt;br /&gt;&lt;br /&gt;Suppose your inputs look like this:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: xml"&gt;&amp;lt;label for="helmet1color"&amp;gt;Helmet 1 Color&amp;lt;/label&amp;gt;&lt;br /&gt;&amp;lt;input id="helmet1color" name="helmet[1][color]"/&amp;gt;&lt;br /&gt;&amp;lt;label for="helmet1size"&amp;gt;helmet 1 Size&amp;lt;/label&amp;gt;&lt;br /&gt;&amp;lt;input id="helmet1size" name="helmet[1][size]"/&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ideally, you'd like to add as many new inputs as a customer wants. The more helmets they order, the sooner you can afford to buy that beet farm!&lt;br /&gt;&lt;br /&gt;So you make a Javascript function to generate new inputs:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: js"&gt;function newHelmetInputs(i){&lt;br /&gt;var inputSet = '';            &lt;br /&gt;inputSet += '&amp;lt;label for="helmet'+ i +'color"&amp;gt;Helmet '+ i +' Color&amp;lt;/label&amp;gt;'&lt;br /&gt;inputSet += '&amp;lt;input id="helmet'+ i +'color" name="helmet['+ i +'][color]"/&amp;gt;'&lt;br /&gt;inputSet += '&amp;lt;label for="helmet'+ i +'size"&amp;gt;Helmet '+ i +' Size&amp;lt;/label&amp;gt;'&lt;br /&gt;inputSet += '&amp;lt;input id="helmet'+ i +'size" name="helmet['+ i +'][size]"/&amp;gt;'&lt;br /&gt;return inputSet;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;But of course you've got to tell it whether you need inputs for helmet #2 or #15. Which is where a closure function comes in: it can return a new number every time it's called. Like this:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: js"&gt;i = nextNumber();&lt;br /&gt;newInputs = newHelmetInputs(i);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Slap those inputs onto the page, and you're done! Well, almost.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Completing the Picture&lt;/h2&gt;&lt;br /&gt;To show exactly how this works, I've got to get into some specifics. For my form, I'm doing the following:&lt;br /&gt;&lt;br /&gt;1) Using jQuery on the page&lt;br /&gt;2) Validating the form prior to submission with &lt;a href="http://docs.jquery.com/Plugins/Validation"&gt;jQuery's Validate plugin&lt;/a&gt;&lt;br /&gt;3) Processing the form submission with PHP&lt;br /&gt;&lt;br /&gt;With that in mind, here's how I'd add the new inputs: just after the set of helmet inputs, I'd add button a link or button that says "More Pointy Helmets!", and use the following jQuery to capture the click event:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: js"&gt;$('#addhelmet').click(function(){&lt;br /&gt;var i = nextNumber();&lt;br /&gt;var newInputs = newHelmetInputs(i);&lt;br /&gt;$(this).before(newInputs);&lt;br /&gt;('#helmet' + i + 'size').rules('add', {digits: true});&lt;br /&gt;});&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;That takes care of adding the inputs themselves, and adding a validation rule to make sure the size is given in digits.&lt;br /&gt;&lt;br /&gt;Finally, you may have noticed that the names I gave the inputs are written like arrays. If you're using PHP, you can process those as arrays after the form is submitted. This means that no matter how many fields are added, you'll handle them the same way. For example, if you're generating an email that lists all the helmets in this order, you could do this:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: php"&gt;foreach ($_POST['helmet'] as $key=&gt;$val){&lt;br /&gt;&amp;nbsp;&amp;nbsp;$emailbody .= 'Helmet ' . $key . ' Color: ' . $val['color'];&lt;br /&gt;&amp;nbsp;&amp;nbsp;$emailbody .= 'Helmet ' . $key . ' Size: ' . $val['size'];&lt;br /&gt;&amp;nbsp;&amp;nbsp;$emailbody .= '&lt;br /&gt;"&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Voila! Whether the user wants a solitary helmet or enough to outfit his entire shuffleboard league, this form can handle it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8506302596139279537-1589951879918758919?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/1589951879918758919/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/01/adding-fields-to-web-form-job-for.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/1589951879918758919'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/1589951879918758919'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2010/01/adding-fields-to-web-form-job-for.html' title='Adding fields to a web form: a job for closures!'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-1689091018450540891</id><published>2009-12-21T08:31:00.002-05:00</published><updated>2011-08-05T11:17:36.005-04:00</updated><title type='text'>So what are these closure thingys?</title><content type='html'>A couple of nights ago, I dreamed about closures. I was explaining them to my friend Michael. I think I did an OK job, but since Google still can't search my dreams (glare!), I thought I'd post something here.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;What are closures?&lt;/h2&gt;&lt;br /&gt;Closures are a way to let a function have &lt;b&gt;persistent, private variables&lt;/b&gt; - that is, variables that only one function knows about, where it can keep track of info from previous times that it was run.&lt;br /&gt;&lt;br /&gt;Let's break this down.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Private variables&lt;/h3&gt;&lt;br /&gt;Giving a function a private variable is easy: just make a local variable inside it. For instance, in Javascript:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: js"&gt;function annoyingAlert(){&lt;br /&gt;&amp;nbsp;&amp;nbsp; var x = "I'm private! ";&lt;br /&gt;&amp;nbsp;&amp;nbsp; x += "Nobody can see me outside this function.";&lt;br /&gt;&amp;nbsp;&amp;nbsp; alert(x);&lt;br /&gt;&amp;nbsp;&amp;nbsp; x = "Yep, still private.";&lt;br /&gt;} &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Although x can't be changed from outside our function, it is very short-lived: each time the function finishes, x gets thrown away. When the function runs again, it is re-created. The second string it gets assigned will never be seen in the alert.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Persistent Variables&lt;/h3&gt;&lt;br /&gt;These are also pretty easy. Just make a... (sinister music) global variable.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: js"&gt;nastyGlobalX = 1;&lt;br /&gt;function annoyingAlert(){&lt;br /&gt;&amp;nbsp;&amp;nbsp; var alertString = "I can count! ";&lt;br /&gt;&amp;nbsp;&amp;nbsp; alertString += "I'm currently at " + nastyGlobalX;&lt;br /&gt;&amp;nbsp;&amp;nbsp; alert(alertString);&lt;br /&gt;&amp;nbsp;&amp;nbsp; nastyGlobalX++;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Now you've got persistence - each time you call annoyingAlert(), you'll get a new number. But since we're using a global variable, it could get changed by a different function. Our counting would be ruined!&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Enter closures&lt;/h2&gt;&lt;br /&gt;This is where closures come in. They work like this:&lt;br /&gt;&lt;br /&gt;1. Make an outer, "function maker" function.&lt;br /&gt;2. Declare a local variable inside it.&lt;br /&gt;3. Declare an inner function inside the outer one.&lt;br /&gt;4. Use the outer function's variable inside the inner function.&lt;br /&gt;5. Have the outer function return the inner function&lt;br /&gt;6. Run the function, and assign its return value to a variable&lt;br /&gt;&lt;br /&gt;For example:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: js"&gt;function functionMaker(){&lt;br /&gt;&amp;nbsp;&amp;nbsp; var x = 1;&lt;br /&gt;&amp;nbsp;&amp;nbsp; function innerFunction(){&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; alert(x);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; x++;&lt;br /&gt;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp; return innerFunction;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;var counterFunction = functionMaker();&lt;br /&gt;counterFunction();&lt;br /&gt;counterFunction();&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;See! Magic!&lt;br /&gt;&lt;br /&gt;Wait, you look confused. OK, let's back up.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;A Closer Look&lt;/h2&gt;&lt;br /&gt;OK, look at the example above. What gets assigned to the variable called 'counterFunction?'&lt;br /&gt;&lt;br /&gt;At first glance, you might say it's the functionMaker function. But it's not - we said 'functionMaker()', with parentheses, which means 'run that function and put its return value here.' When functionMaker runs, it ends by returning innerFuction. So counterFunction = innerFunction.&lt;br /&gt;&lt;br /&gt;The magic happens at step 5. When functionMaker returns innerFunction, its life is over. Like a used tissue, it has served its purpose and will be thrown away. That includes any local variables that it contained.&lt;br /&gt;&lt;br /&gt;But wait! Its innerFuction can't be thrown away, because we've assigned it to a variable, counterFunction. We still need it! And counterFunction() uses the local variable that functionMaker() declared (which was called 'x'). So that variable can't be thrown away either.&lt;br /&gt;&lt;br /&gt;But where is that variable? &lt;b&gt;It's in the twilight zone.&lt;/b&gt; From counterFunction()'s perspective, it was declared in the parent scope and persists from one run to the next, just as though it were a global variable.&lt;br /&gt;&lt;br /&gt;But from any other function's perspective, it's a local variable, declared inside functionMaker(). They can't touch it or see it.&lt;br /&gt;&lt;br /&gt;Yeeha! Now we've got a variable that's both private and persistent. It's a place for counterFunction to keep its record, safe from meddling and from garbage collection.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;What's it good for?&lt;/h2&gt;&lt;br /&gt;But... so what? What can we do with that?&lt;br /&gt;&lt;br /&gt;Well, a closure could be used to generate, for example, a function called newUserID(). Every time you call that function, you'd get a new ID. But in most situations, you'd want to take care of something like that in a database.&lt;br /&gt;&lt;br /&gt;A closure would only help if you need a variable to stick around while your program is running, but no longer.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://sleeplessgeek.blogspot.com/2010/01/adding-fields-to-web-form-job-for.html"&gt;Next post&lt;/a&gt;, I'll show an example where that's exactly what you'd need.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8506302596139279537-1689091018450540891?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/1689091018450540891/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2009/12/so-what-are-these-closure-thingys.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/1689091018450540891'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/1689091018450540891'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2009/12/so-what-are-these-closure-thingys.html' title='So what are these closure thingys?'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8506302596139279537.post-7049817915950216697</id><published>2009-12-04T02:52:00.000-05:00</published><updated>2009-12-04T03:00:27.404-05:00</updated><title type='text'>Hello World</title><content type='html'>You have a blog. Your mom has a blog. Your cat has a blog. Various inanimate objects in your house have blogs.&lt;br /&gt;&lt;br /&gt;Me too! I want to have a blog! I like the way it sounds. "Blog." Like a big robot with heavy fists. His job is to crush cars - never people! A trustworthy robot.&lt;br /&gt;&lt;br /&gt;Oh, Blog. You and I will have many adventures together.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8506302596139279537-7049817915950216697?l=sleeplessgeek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sleeplessgeek.blogspot.com/feeds/7049817915950216697/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://sleeplessgeek.blogspot.com/2009/12/hello-world.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/7049817915950216697'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8506302596139279537/posts/default/7049817915950216697'/><link rel='alternate' type='text/html' href='http://sleeplessgeek.blogspot.com/2009/12/hello-world.html' title='Hello World'/><author><name>Nathan Long</name><uri>http://www.blogger.com/profile/02909682437213600859</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>
