Tuesday, July 6, 2010

Ruby on Rails decryption - MySQL

Installation is easy! Just make sure you have mysql installed, install the mysql gem, and voila, right?

Well maybe for you, but not for me. I wanted to write to share some of my headaches in case they were also your headaches. I had mysql installed, and I also installed the mysql gem, and now this kept happening on the server log:


[10-07-06:14:54 brian@koolaid /Users/brian/rails/feedtester] script/server start
=> Booting Mongrel
=> Rails 2.3.8 application starting on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
/!\ FAILSAFE /!\ Tue Jul 06 14:55:26 -0700 2010
Status: 500 Internal Server Error
uninitialized constant MysqlCompat::MysqlRes
/Users/brian/.gem/ruby/1.8/gems/activesupport-2.3.8/lib/active_support/dependencies.rb:440:in `load_missing_constant'
/Users/brian/.gem/ruby/1.8/gems/activesupport-2.3.8/lib/active_support/dependencies.rb:80:in `const_missing'
/Users/brian/.gem/ruby/1.8/gems/activerecord-2.3.8/lib/active_record/connection_adapters/mysql_adapter.rb:9:in `define_all_hashes_method!'
/Users/brian/.gem/ruby/1.8/gems/activerecord-2.3.8/lib/active_record/connection_adapters/mysql_adapter.rb:68:in `mysql_connection'
/Users/brian/.gem/ruby/1.8/gems/activerecord-2.3.8/lib/active_record/connection_adapters/abstract/connection_pool.rb:223:in `send'
/Users/brian/.gem/ruby/1.8/gems/activerecord-2.3.8/lib/active_record/connection_adapters/abstract/connection_pool.rb:223:in `new_connection'
/Users/brian/.gem/ruby/1.8/gems/activerecord-2.3.8/lib/active_record/connection_adapters/abstract/connection_pool.rb:245:in `checkout_new_connection'
/Users/brian/.gem/ruby/1.8/gems/activerecord-2.3.8/lib/active_record/connection_adapters/abstract/connection_pool.rb:188:in `checkout'
/Users/brian/.gem/ruby/1.8/gems/activerecord-2.3.8/lib/active_record/connection_adapters/abstract/connection_pool.rb:184:in `loop'
/Users/brian/.gem/ruby/1.8/gems/activerecord-2.3.8/lib/active_record/connection_adapters/abstract/connection_pool.rb:184:in `checkout'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/monitor.rb:242:in `synchronize'
/Users/brian/.gem/ruby/1.8/gems/activerecord-2.3.8/lib/active_record/connection_adapters/abstract/connection_pool.rb:183:in `checkout'
/Users/brian/.gem/ruby/1.8/gems/activerecord-2.3.8/lib/active_record/connection_adapters/abstract/connection_pool.rb:98:in `connection'
/Users/brian/.gem/ruby/1.8/gems/activerecord-2.3.8/lib/active_record/connection_adapters/abstract/connection_pool.rb:326:in `retrieve_connection'
/Users/brian/.gem/ruby/1.8/gems/activerecord-2.3.8/lib/active_record/connection_adapters/abstract/connection_specification.rb:123:in `retrieve_connection'
/Users/brian/.gem/ruby/1.8/gems/activerecord-2.3.8/lib/active_record/connection_adapters/abstract/connection_specification.rb:115:in `connection'
/Users/brian/.gem/ruby/1.8/gems/activerecord-2.3.8/lib/active_record/query_cache.rb:9:in `cache'
/Users/brian/.gem/ruby/1.8/gems/activerecord-2.3.8/lib/active_record/query_cache.rb:28:in `call'
/Users/brian/.gem/ruby/1.8/gems/activerecord-2.3.8/lib/active_record/connection_adapters/abstract/connection_pool.rb:361:in `call'
/Users/brian/.gem/ruby/1.8/gems/actionpack-2.3.8/lib/action_controller/string_coercion.rb:25:in `call'
/Users/brian/.gem/ruby/1.8/gems/rack-1.1.0/lib/rack/head.rb:9:in `call'
/Users/brian/.gem/ruby/1.8/gems/rack-1.1.0/lib/rack/methodoverride.rb:24:in `call'
/Users/brian/.gem/ruby/1.8/gems/actionpack-2.3.8/lib/action_controller/params_parser.rb:15:in `call'
/Users/brian/.gem/ruby/1.8/gems/actionpack-2.3.8/lib/action_controller/session/cookie_store.rb:99:in `call'
/Users/brian/.gem/ruby/1.8/gems/actionpack-2.3.8/lib/action_controller/failsafe.rb:26:in `call'
/Users/brian/.gem/ruby/1.8/gems/rack-1.1.0/lib/rack/lock.rb:11:in `call'
/Users/brian/.gem/ruby/1.8/gems/rack-1.1.0/lib/rack/lock.rb:11:in `synchronize'
/Users/brian/.gem/ruby/1.8/gems/rack-1.1.0/lib/rack/lock.rb:11:in `call'
/Users/brian/.gem/ruby/1.8/gems/actionpack-2.3.8/lib/action_controller/dispatcher.rb:114:in `call'
/Users/brian/.gem/ruby/1.8/gems/actionpack-2.3.8/lib/action_controller/reloader.rb:34:in `run'
/Users/brian/.gem/ruby/1.8/gems/actionpack-2.3.8/lib/action_controller/dispatcher.rb:108:in `call'
/Users/brian/.gem/ruby/1.8/gems/rails-2.3.8/lib/rails/rack/static.rb:31:in `call'
/Users/brian/.gem/ruby/1.8/gems/rack-1.1.0/lib/rack/urlmap.rb:47:in `call'
/Users/brian/.gem/ruby/1.8/gems/rack-1.1.0/lib/rack/urlmap.rb:41:in `each'
/Users/brian/.gem/ruby/1.8/gems/rack-1.1.0/lib/rack/urlmap.rb:41:in `call'
/Users/brian/.gem/ruby/1.8/gems/rails-2.3.8/lib/rails/rack/log_tailer.rb:17:in `call'
/Users/brian/.gem/ruby/1.8/gems/rack-1.1.0/lib/rack/content_length.rb:13:in `call'
/Users/brian/.gem/ruby/1.8/gems/rack-1.1.0/lib/rack/chunked.rb:15:in `call'
/Users/brian/.gem/ruby/1.8/gems/rack-1.1.0/lib/rack/handler/mongrel.rb:67:in `process'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/gems/1.8/gems/mongrel-1.1.5/lib/mongrel.rb:159:in `process_client'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/gems/1.8/gems/mongrel-1.1.5/lib/mongrel.rb:158:in `each'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/gems/1.8/gems/mongrel-1.1.5/lib/mongrel.rb:158:in `process_client'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/gems/1.8/gems/mongrel-1.1.5/lib/mongrel.rb:285:in `run'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/gems/1.8/gems/mongrel-1.1.5/lib/mongrel.rb:285:in `initialize'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/gems/1.8/gems/mongrel-1.1.5/lib/mongrel.rb:285:in `new'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/gems/1.8/gems/mongrel-1.1.5/lib/mongrel.rb:285:in `run'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/gems/1.8/gems/mongrel-1.1.5/lib/mongrel.rb:268:in `initialize'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/gems/1.8/gems/mongrel-1.1.5/lib/mongrel.rb:268:in `new'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/gems/1.8/gems/mongrel-1.1.5/lib/mongrel.rb:268:in `run'
/Users/brian/.gem/ruby/1.8/gems/rack-1.1.0/lib/rack/handler/mongrel.rb:38:in `run'
/Users/brian/.gem/ruby/1.8/gems/rails-2.3.8/lib/commands/server.rb:111
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/rubygems/custom_require.rb:31:in `require'
script/server:3
Argh! Not good. I did some googling and saw suggestions about installing the 2.7.x gem because the 2.8.1 gem wasn't friendly with mysql 5.1 (which I found hard to believe, but the same error kept coming up).

I saw another suggestion about adding a compile flag to tell gem to compile the 64-bit version of the gem. That made sense to me, because I was running the whole kit and kaboodle on my macbook Intel Core Duo which, to my surprise, was a 64-bit processor. Hello apple! Okay, so I tried reinstalling the gem with some fitting compile flags:

[10-07-06:14:57 brian@koolaid /Users/brian/rails/feedtester] gem uninstall mysql
Successfully uninstalled mysql-2.8.1
[10-07-06:14:57 brian@koolaid /Users/brian/rails/feedtester] sudo env ARCHFLAGS="-arch x86_64" gem install mysql -- --with-mysql-dir=/usr/local --with-mysql-config=/usr/local/mysql/bin/mysql_config
Password:
Building native extensions. This could take a while...
Successfully installed mysql-2.8.1
1 gem installed
Installing ri documentation for mysql-2.8.1...

... snip ...

No definition for time_equal

No definition for error_errno

No definition for error_sqlstate
[10-07-06:14:58 brian@koolaid /Users/brian/rails/feedtester]
No errors... looking good. Now to kick up mongrel and try again:

dyld: lazy symbol binding failed: Symbol not found: _mysql_init
Referenced from: /Library/Ruby/Gems/1.8/gems/mysql-2.8.1/lib/mysql_api.bundle
Expected in: flat namespace

dyld: Symbol not found: _mysql_init
Referenced from: /Library/Ruby/Gems/1.8/gems/mysql-2.8.1/lib/mysql_api.bundle
Expected in: flat namespace

zsh: trace trap script/server start
And then mongrel dies right there after the first page load! WHA?? Okay, so the 32-bit compile of the gem throws an exception in ruby, and the 64-bit compile throws a dyld error that the system can't find _mysql_init. I was able to successfully reproduce the errors in irb as well, which at least tells me that it's not really a rails issue. Oh this is fun.

It then hit me... what if my install of mysql is what's wrong? Maybe rails can't find _mysql_init because it's a pointer that's not in the expected place or format? I did some checking out and found my version of mysql was 32-bit:

[10-07-02:14:00 brian@koolaid /Users/brian/rails/feedtester] mysql --version
mysql Ver 14.14 Distrib 5.1.43, for apple-darwin10.2.0 (i386) using readline 5.1
[10-07-02:14:00 brian@koolaid /Users/brian/rails/feedtester] file `which ruby` `which mysql`
/usr/bin/ruby: Mach-O universal binary with 3 architectures
/usr/bin/ruby (for architecture x86_64): Mach-O 64-bit executable x86_64
/usr/bin/ruby (for architecture i386): Mach-O executable i386
/usr/bin/ruby (for architecture ppc7400): Mach-O executable ppc
/usr/local/mysql/bin/mysql: Mach-O executable i386

Time to upgrade peoples! I won't get into the details of the upgrade, since the method of mysql installation is entirely up to you, and I know you'll have your own reasons for choosing why you installed mysql in the way you did. Anyway, I did the mysqldump to reserve my database content, installed the 64-bit version, did some clean-up, re-upped the database from the dump, and now my mongrel log looks like this:

[10-07-06:14:58 brian@koolaid /Users/brian/rails/feedtester] script/server start
=> Booting Mongrel
=> Rails 2.3.8 application starting on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
SQL (0.1ms) SET NAMES 'utf8'
SQL (0.1ms) SET SQL_AUTO_IS_NULL=0


Processing MybooksController#index (for 127.0.0.1 at 2010-07-06 14:58:41) [GET]
Mybook Load (0.0ms) Mysql::Error: Table 'hb.mybooks' doesn't exist: SELECT * FROM `mybooks`

ActiveRecord::StatementInvalid (Mysql::Error: Table 'hb.mybooks' doesn't exist: SELECT * FROM `mybooks` ):
app/controllers/mybooks_controller.rb:5:in `index'

Rendered rescues/_trace (99.0ms)
Rendered rescues/_request_and_response (1.2ms)
Rendering rescues/layout (internal_server_error)

Well I didn't expect it to find the table, but at least it's not crashing and the exceptions are expected.

In conclusion, if you find your mysql gem isn't connecting, make sure you have the right versions and distributions for your system.

Thursday, July 1, 2010

Nostalgic spam

Weird! Today I got an email containing the "Microsoft/AOL Giveaway" note. In case you don't recall, the best write-up is here on snopes. I haven't seen this in, well, jeez... dunno how many years it's been. I was about to hit the delete button, but then I thought "wow, this is a part of history, kinda like walking the Internet equivalent of the John Muir trail." My mind boggles as I think of how much ancillary spam had likely been created from this email circling the globe so many times. I can't help but feel a bit of guilt for the young, naive, teenage version of myself who actually believed in and hoped for that check from nowhere that had no logical reason of existing. Besides, there's nothing more convincing than the words "I AM A LAWYER" in bold, caps, and H2 font size.

[delete]

Monday, June 28, 2010

Ruby on Rails decryption - MVC

Okay, I don't know about the rest of you, but I'm frustrated at documentation from the ruby and from the ruby-on-rails community that enforce committing to a whole new way of thought in order to produce applications. As an experienced perl developer, I'm just looking for some basic equivalents and paths to conversion. So, as I figure things out and discover some new things, I'm going to publish them here.

This is not in any way meant to disrespect or disregard the "ruby way" as much as it is intended to just share techniques that I've learned. Anyway, on with the show!

Model-View-Controller was one of the hardest concepts to wrap my mind around when I started looking at rails. So, rather than try to understand MVC directly, I'm going to look at how the app functions and later relate to MVC. This may seem backwards to those who know, but sometimes the easiest way in is through the back door.

With most static applications, the path in the URL is the path to the file being processed. Apache does have some fanciness using mod_rewrite to alter the appearance of the path, but the concept is still the same in the end. MVC apps, such as ruby-on-rails or perl's catalyst, use the path to give instruction to the application. For example, in a rails app, a URL path that begins with domain.tld/foo/bar looks for the "bar" class in the "foo" controller. By default, the "bar" class loads the "bar.erb" view, but this can be altered in the class as needed (e.g., loading an error or rickroll page)

Didn't understand any of that? Let's break it up a bit further.

Let's say you created your first rails app and just wanted to try a simple "Hello World" page to see if things were working (or even further, let's say you wanted to create a rails app that didn't require a database, so using scaffold wasn't an option). You'd first create your rails app:

[10-06-28:13:57 brian@koolaid /Users/brian/rails] rails HelloWorld
create
create app/controllers
create app/helpers
create app/models
create app/views/layouts

... snip ...

create doc/README_FOR_APP
create log/server.log
create log/production.log
create log/development.log
create log/test.log
[10-06-28:13:57 brian@koolaid /Users/brian/rails] cd HelloWorld


Then, you'd dilligently run your generate script to create your first controller:

[10-06-28:13:57 brian@koolaid /Users/brian/rails/HelloWorld] script/generate controller hello
exists app/controllers/
exists app/helpers/
create app/views/hello
exists test/functional/
create test/unit/helpers/
create app/controllers/hello_controller.rb
create test/functional/hello_controller_test.rb
create app/helpers/hello_helper.rb
create test/unit/helpers/hello_helper_test.rb
[10-06-28:13:57 brian@koolaid /Users/brian/rails/HelloWorld]

Wow, that's a bunch of files for an app that doesn't even really work yet. Theoretically, you should be able to start your server and see something:

[10-06-28:13:57 brian@koolaid /Users/brian/rails/HelloWorld] script/server start -d
=> Booting Mongrel
=> Rails 2.3.8 application starting on http://0.0.0.0:3000
[10-06-28:14:00 brian@koolaid /Users/brian/rails/HelloWorld]

Visiting this place in the browser returns the sexy default home page for your rails app. But, what about your controller?

Routing Error

No route matches "/hello" with {:method=>:get}


(pardon the inaccurate styling... blogspot is being challenging right now... soon I will have my own stuff hosted somewhere) So, I have to create more to produce a workable page? Understandable, but still confused since there's no here-is-what-you-do-next doc.

The default action that rails will execute when there is no action is "index" (sound familiar, apache fans?). So, in your hello controller, you'll want to create an "index" object.

Oh, and for those who don't know ruby (which I barely know at this point), you will also want to create an "initialize" function. This is, in my perspective, analogous to the "new" construct used in perl, or the "__construct" function in php. The path in your rails app to the controller is always "app/controllers/[name]_controller.rb" for easy locating.

Here's the stock hello_controller we created just a bit ago:

class HelloController < ApplicationController
end

Pretty dull, huh? Oh, more ruby language syntax for you... the left angle bracket means that HelloController inherits the ApplicationController features. Anything you put into ApplicationController becomes available to all your other controllers. Fun, eh? This also means we don't have to fuss with an initialize function since we're inheriting it from ApplicationController.

Ok, so here goes with a simple index action:

class HelloController < ApplicationController
def index
end
end

Not too scary. Let's check out the response when we visit the page:

Unknown action

No action responded to index. Actions:


The error changed, which is actually good. This means what we did had an impact in the system. It's recognized the index action, but nothing happened. Why? Because there's no view!

So, let's give it a view.

The path to the views for a controller is "app/views/[controller_name]/[action_name].erb". In this case, we're using the "hello" controller and the "index" action, so let's rock it!
<html>
<head>
<title>Hello World!</title>
</head>
<body>
<h2>Hello World</h2>
<p>Hello from Ruby on Rails!</p>
</body>

Engage app/views/hello/index.erb

Hello World

Hello from Ruby on Rails!


And load up 0.0.0.0.:3000/hello

Hello World

Hello from Ruby on Rails!

Success! Now that seemed like a lot for just publishing a plain static page. We'll get more into some fun stuff in the next post, but I wanted to get the basics out. Have fun getting started!

Wednesday, October 7, 2009

Mexican-style Granola - recipe from Rick Bayless

Part of my Sunday TV-watching experience includes a fun show called "Mexico - One Plate at a Time." The chef on the show, Rick Bayless, is this crazy hippie who finds all kinds of amazing gourmet Mexican recipes. He owns a couple famous restaurants in Chicago, so it makes one wonder "how can a Chicagoan know anything aboug good Mexican cuisine?" Well, I've been impressed so far.

The latest recipe was, of all things, granola! It was so simple, I had to share. The recipe calls for puffed amaranth, but I'm sure any old puffed rice cereal would do the trick. I didn't have either of those, but I did have some unsweetened shredded coconut, which was a key ingredient that I had used in another granola recipe. That recipe took five hours in a 250 degree oven. This recipe took half an hour in a 300 degree oven. With the temperature dropping, I'm all for using the oven these days.

So, I suggest visiting the website and following the recipe to the letter (although the peanuts were a bit odd in the mix, next time I'm leaving them out and increasing the rest of the nuts & seeds to compensate)

Monday, October 5, 2009

Slow-cooked pulled pork sandwiches

A while ago, my girlfriend found this recipe for pulled pork sandwiches that just really captured my attention. I've made this recipe several times now, but last night became the topper. I've made a few modifications to the original, but now that I've got a winning version, I'm sticking to it.

The key alterations involved:
  • Replace half the coriander with cumin
  • Replace the paprika with chili powder
  • Use all apple juice, or half apple juice & half cider vinegar for the sauce
  • Crock pot with a steaming basket does a great job with this dish
So, for your eating delight, here's my winner recipe for slow-cooked pulled pork (edits highlighted):

Ingredients

  • 1/4 cup dark brown sugar, lightly packed
  • 2 tbsp kosher or coarse salt
  • 2 tbsp chili powder
  • 1 tbsp ground black pepper
  • 1 tsp ground coriander
  • 1 tsp ground cumin
  • 1/2 tsp dry mustard (found in spice section)
  • 1 tsp onion powder
  • 1 boneless pork butt, about 3 pounds (also called pork shoulder)
  • 1 cup apple juice
  • 1 cup cider vinegar
  • 1 package plain soft white rolls or other bread

Tip: You can buy bone-in or boneless pork butts. Both have their benefits: Cooking bone-in will contribute some flavor (and increase the cooking time slightly). But if you have your butcher take out the bone, you can rub the spice mix into the incisions where the bone was removed — a great way to get the flavor deep inside the meat.

Instructions

Mix brown sugar and dry spices together in a small bowl. Rub all over pork, cover, and let sit in the refrigerator for as long as you have time for (as little as 1 hour or up to overnight - time in fridge not necessary when using crock pot). Pour the juice and vinegar into your crock pot. Set a vegetable steaming basket inside the crock pot and set the roast on top. Cover and cook on low for at least 8 hours or overnight, or on high for 5 hours (crock pots may have different temperatures - your mileage may vary), until pork is brown outside and meat is very tender, basically falling apart.

Remove steamer basket from crock pot, letting the meat fall into the juice. While still warm, shred pork into small pieces using 2 forks or 10 fingers. Transfer to bowl for serving, or cover and refrigerate for up to 2 days. To reheat, just transfer to shallow baking dish, bring to room temperature, and place in preheated 350 degree oven for 15 minutes.

Best served on toasted kaiser rolls with a little mustard and mayonnaise. Spicy wasabi or chipotle mayo works great too.

Saturday, September 26, 2009

Peach jam

The mess began small, just some tools sprawled out across a table. But it was much more than a mess than we were after. My GF and I worked on making jars of peach jam for everyone as Christmas gifts. The experiments with blackberries and blueberreis turned out so well, it was only natural to make the peach jam next, since they're in season and all over the stores right now. I love a good peach, and I love being able to package it up for a later date.

The process seemed simple enough. Boil the jars to sterilize them, and keep them boiling while prepping everything. Peel the peaches, measure the sugar and lemon juice, crush the peaches with the potato masher, mix it all together, and keep it boiling over high heat until it begins to set. Then, we ladle it into the hot jars, put the lid on, set the jars back in the boiling water to process, and pray that we don't give all our friends and family botulism.

The first stage of the procedure is peeling the peaches. We had some conflict of opinion on proper methods, and I turned out wrong. So, in case you're wondering, here's what to do: Start a large pot of covered boiling water. When the water is at a rolling boil, drop in all your peaches that you intend to peel. Keep it in there for about a minute. Riper peaches will peel easier than unripe ones. Lift a peach out and feel the skin to see if it slides on the flesh at all. If it does, it's probably ready to go. Drop it into an ice water bath, roll it around for a few seconds, then use your thumbs to slide the peel off.

Crushing fruit is surprisingly theraputic. It's also one of the few times I really get to use my gigantic potato masher. I really don't know what I was thinking when I bought that gigantic thing, considering the fact that I probably spent more on it than all of my spatulas combined. But, it got its mileage when we turned this pile of unsuspecting fruit into pulp.

The fruit is then mixed with sugar, lemon juice, and the pectin. Some fruits are high enough in pectin where all they need is to be reduced. This was certainly the case with the blackberries and blueberries. Peaches are just not so. We made two batches, one with full sugar, and one using a "no sugar added" pectin that ended up tasting too tart to enjoy. I added some sugar to it anyway, which helped the flavor. We'll give them out as 'low-sugar' instead of sugar-free.

The hot peach jam goes into the hot jars, which are then sealed and submerged in boiling water for ten minutes. This is to make sure we got out all the nasty nasties to keep the food safe for human consumption. And I must say, this stuff is tasty, so we ultimately want some human consumption involved.

I'm glad I got the canner, but I should've read the label. This canner is designed for quart-size jars, and we had pint-size jars. They were falling over sideways in the pot and required a little babysitting. One of the lids came off as I was lifting it out. We could not trust it to be shelf stable anymore. (Darn, I have to keep one!)

Tuesday, September 22, 2009

Fun with Telemarketers #1

So, before getting into this, I think it's safe to state that there is no real good time for a telemarketer to call. However, there are worse times than others, such as 8:15pm while my son is reading me his bedtime story. However, I've come to see telemarketer calls a free license opportunity to screw around with people.

Tonight, however, I couldn't be bothered with it at all. I got the call and was ready to give them hell when I answered the call... only there wasn't a human on the line, I was greeted with hold music.

So, I thought I'd return the favor. I ran across the room to my clock radio, quickly found the first station with semi-clear music, set the phone down by the speaker, and returned to finish my story.

I was anticipating a chance to pick up the phone, hear a voice, and put them "on hold" again. Let's see when the next call comes in. I have an older and more interestering technique which I'll share next time...

Quick update: Repeated the procedure again on the following night. Can't these guys take a hint? All they need to do is wait for me to finish and I'll talk to them.