Entries from 2008

A Review of Sennheiser HD-555 Headphones

Sennheiser HD-555 A few months back, I got my hands on a pair of Sennheiser HD-555 headphones, and my experience so far has been overwhelmingly positive. Though they may be a little pricy for those who are used to paying $20-30 for headphones, they are more than worth it and make listening to music a joy.

I’ve had some past experience with Sennheiser products: my first pair were the HD 457s, which were followed by a pair of the portable yet powerful PX 100s, after which I got a pair of the HD-280s. Although I really enjoyed the HD-280s, I wanted to try out an open pair of cans (instead of closed), so I went ahead and got the HD-555s.

Closed vs. Open

One of the most important factors to consider when buying a pair of quality headphones is whether you want them to be “open” or “closed”. With closed headphones, your ears are completely sealed off, whereas with open headphones, there’s an opening for sound waves to propagate out from the headphones. Open headphones are generally higher quality, as the waves aren’t resonating off a closed earcup. However, it’s possible for people around you to hear what you’re listening to, which doesn’t happen with closed headphones. Also, the closed design blocks out a lot of external sound, so they’re great if you want to block things out.

Sennheiser HD-555

The HD-555s are open, and I can definitely notice a difference compared to my HD-280s. Bass is punchier, and music is, in general, much crisper. The 555s are also super comfortable: I’ve listened to music for hours at a time without feeling any discomfort, whereas the HD-280s get uncomfortable after about an hour or so.

Some negatives: if someone’s sitting near me while I’m using the HD-555s, they can definitely hear what I’m listening to. For the most part, it’s not an issue, but if I’m somewhere quiet, like the library or something, I usually use different headphones so as not to annoy others. In a similar vein, outside noises are definitely more noticeable with the closed design. With the 280s, I could take them on the plane and not hear the engine at all, with my iPod only at about 50% volume. My 555s, however, require me to turn the volume almost all the way up, and even then, it can be hard to hear quieter songs.

That being said, I definitely would recommend the HD-555s to anyone looking to get a high quality pair of headphones for a reasonable price (they’re right around $100). You could spend hundreds more and get some amazing headphones, but for casual listening, these are a great value.

Posted on December 22, 2008 1 Comment
Tagged with: , , ,

Why I won't be buying Versions

Today, Versions, a Subversion app for OS X, moved to version 1.0, marking the end of its free beta period. While many people will be shelling out the €39 (~$49) for a license, I won’t be doing the same. Having used Versions for a couple months now, it just isn’t for me.

I don’t need a fancy UI.

When I loaded Versions for the first time, I was impressed by the amount of work that went into the UI. It looked nice, and it worked pretty well too. However, I found myself working harder to accomplish simple tasks than when I used the command line. For me, it’s a lot easier to cd to the directory in the Terminal and do a svn stat rather than take the time to load up Versions and do a similar command. Even though it might be nicer to look at, Versions was just never quite as quick or intuitive as the terminal commands I had grown used to. In addition, I almost always have the Terminal open, as I’m constantly running commands during development. Having to open up another app just got in the way of my workflow.

I already use TextMate.

TextMate is the center of my coding world. It is quite simply the greatest text editor I have ever used, and it’s where I spend the vast majority of my time when I’m coding. Built into TextMate is a great SVN plugin that really makes Versions unnecessary. Without changing applications, I just hit CTRL+SHIFT+A and I’m greeted with a list of SVN commands. I tap 5, and I see a dialog where I can set my SVN commit message and choose exactly which items I want to commit from a list of changed files. Why would I need anything more sophisticated than that? If I wanted to go ahead and commit a few files with Versions, I’d have to fire up the application, locate the files I want, and hit commit. The worst part is, at least in the beta version I used, there’s no good way to select multiple files in Versions without resorting to holding the command key and clicking each file. In TextMate, it’s as simple as checking boxes.

Git is the future.

My biggest problem with spending close to $50 on a Subversion app is that I really don’t want to use SVN any more. I’ve fallen in love with Git, a distributed version control system that puts Subversion to shame. Just about every new personal project I start will be using Git, so there’s really no point in buying a program just to manage my older projects. If Versions supported Git as well, I’d definitely be considering adding it to my arsenal.

A Grain of Salt

Now, just because Versions hasn’t worked its way into my routine doesn’t mean it can’t be part of yours. I personally know several people that really enjoy Versions, and it has really improved their workflow. It’s definitely a well-made product, it just doesn’t work for me.

Also, if you’re in the market for an OS X client for SVN, definitely check out Cornerstone as well. While I haven’t used it personally, I’ve heard good things about it.

Posted on November 17, 2008 5 Comments
Tagged with: , , , , , , ,

Resizing Rounded Rectangles in Photoshop

In the past, I’ve always dreaded working with rounded rectangles in Photoshop, because they’re just so annoying to resize. If you try to change the object’s height or width, it distorts the corners, making things look pretty bad. Today, I found a way to resize them using the direct selection tool, and it has already made my life a whole lot easier. Check out the screencast I put together to find out how.

Posted on November 11, 2008 9 Comments
Tagged with: , , ,

Finding Related Entries Using Tags with Ruby on Rails

One of the cool features that I built into my new site is the “related” sidebar box on every entry and link. Using a not-so-sophisticated algorithm, my site automatically picks out other entries that seem to be related to the current entry, which hopefully helps readers navigate to my other content. It really wasn’t too difficult to implement, so I figured I’d go through my thought process and the code that makes it happen.

The Not So Fancy Algorithm

It took me a little while to come up with a way to determine if an entry is “related” that was both accurate and relatively efficient–I could have used some complicated tool that parses the content of my entries, but instead I decided use something that’s a little simpler: tags. To understand how it works, pretend I have three entries:

  • Entry A - Tagged with: Turkey, Roast Beef, Cheese, Bread
  • Entry B - Tagged with: Bread, Baking
  • Entry C - Tagged with: Turkey, Cheese, Bread, Lettuce

Let’s say we’re looking for entries related to Entry A. From just looking at B and C, it’s clear that C should be closest, as they A and C both have something to do with sandwiches, whereas B only talks about bread. To rank the entriesprogramatically, I first do a query to find entries with any of the tags from Entry A. Then, once I have that list, I sort the entries by how many of Entry A’s tags are used. So, in the above example, Entry C would have 3 matched tags, and Entry B would have 2. It’s not a perfect system, but so far, it seems to be working pretty well.

The Code

So, here’s the code that’s performing all the magic:

class Post < ActiveRecord::Base
  def related(limit=5)
    @related ||=
      returning self.class.find_tagged_with(tag_list, :conditions => ['posts.id != ?', self.id], :limit => limit) do |posts|
        posts.sort_by do |p|
          matched_tags = p.tags.find_all {|t| self.tags.include?(t)}
          matched_tags.size
        end.reverse
      end
  end
end

The real meat of the method is in the returning block, so let’s take a look at that:

returning self.class.find_tagged_with(tag_list, :conditions => ['posts.id != ?', self.id], :limit => limit) do |posts|
  posts.sort_by do |p|
    matched_tags = p.tags.find_all {|t| self.tags.include?(t)}
    matched_tags.size
  end.reverse
end

What’s happening here is I’m first searching for any entries tagged with the current entry’s tags (I’m using acts_as_taggable_on_steroids), then, with the data that’s returned, I use Ruby to sort the entries by the number of “matched” tags, which then gets returned from the method. Conventional wisdom suggests moving the matched tags part into SQL, since MySQL is more efficient than Ruby at handling data. However, I’m using relatively small sets of information, and I haven’t run into any performance issues yet.

Overall, this method’s working pretty well for me, but I’m sure as I accumulate more posts, I’ll need to refine it some. I’d really like to incorporate some sort of popularity ranking, based on number of comments and views, but that’s not something I’m too worried about at the present.

Posted on November 11, 2008 2 Comments
Tagged with: , , , , , ,

Thoughts on Affordable Web Hosting

I’ve created my share of websites, and in doing so, I’ve had experiences with a whole lot of web hosts, some of which I’ve loved, others I’ve really disliked. Below, I compiled a list of four of my favorite hosts, two that offer shared hosting and two VPS hosts. Being a college student, all four are affordable, and I’ve had a good experience with all of them.

Dreamhost

The Basics: Super-cheap, feature-rich shared hosting.

Pricing: $9.95/mo. for 1 domain registration, 500GB disk space, 5TB bandwidth, unlimited emails and databases. If you use the code KS50 when signing up, you’ll get $50 off your first year.

My Thoughts: I love Dreamhost. I hate Dreamhost. I’ve gone back and forth between these two emotions numerous times, but ultimately I’ve been really satisfied with my Dreamhost experience. It’s impossible not to like all the developer friendly features, like Subversion hosting and full SSH access. However, they don’t have the the greatest history of downtime, and they even accidently overcharged customers by over $7 million. All that being said, I’m still a happy customer, because, well, they’re really, really cheap, and, for the most part, I haven’t had any issues with them. I wouldn’t host anything mission critical with them, but for the average blog or personal site, they more than suffice.

EngineHosting

The Basics: No frills, high quality shared and dedicated hosting, aimed mostly at ExpressionEngine users.

Pricing: $10/mo. for their cheapest plan (400MB disk space, 10GB bandwidth, 100MB database, 15 emails)

My Thoughts: If you’re looking for a feature-rich host with gigabytes upon gigabytes of storage space, EngineHosting definitely isn’t it. On their lowest plan, you get 400MB diskspace, 10GB bandwidth, and only 15 email addresses, which means if you compare them solely on numbers to hosts like Dreamhost, they will likely always come out on bottom. However, their customer service and quality is excellent, especially if you’re building an ExpressionEngine site. I’ve used them for a client site for about 10 months now, and they’ve been absolutely stellar: the site hasn’t been down once, and I haven’t had to contact support once. I won’t think twice about using them for any future ExpressionEngine sites.

RailsPlayground

The Basics: Affordable VPS hosting, specializing in Ruby on Rails.

Pricing: $14.95/mo. for the cheapest VPS plan (10GB diskspace, 256MB RAM, 100GB bandwidth)

My Thoughts: Although I only used RailsPlayground for a couple months, I really enjoyed their service. I had never run a VPS (virtual private server) before, and I was able to pick it up pretty quickly, thanks to their excellent support: I would come across an issue, email them, and they would respond right immediately. Even though I no longer use them, it’s not due to a poor experience, but rather due to my desire to try out Slicehost. I wouldn’t hesitate recommending them to anyone looking for an affordable VPS.

Slicehost

The Basics: Cost-effective VPS hosting, with an amazing community.

Pricing: $20/mo. for the cheapest plan (10GB diskspace, 256MB RAM, 100GB bandwidth)

My Thoughts: Slicehost is currently hosting this site, and I couldn’t be happier. I’m no sysadmin, but following their excellent articles has helped me to build a fast, secure, and stable VPS. I was further amazed by their community when I mentioned an issue at 1AM on their IRC channel, and within 20 minutes, it was fixed. They seem to be completely focused on providing really reliable hosting, and it really shows. The result is rock-solid hosting, and I highly recommend them for anyone looking for VPS hosting.

Posted on September 21, 2008 Leave a Comment
Tagged with: , , , , , , ,

Simple PHP Caching Using Output Buffering

I’ve worked on quite a few PHP projects recently, and all of them have required some form of caching. From working with each, I’ve come up with a pretty efficient method for caching code using PHP’s output buffering. It ends up being really quick and super flexible.

Output Buffering Basics

Output buffering is a pretty simple concept: instead of letting PHP return data to your user’s browser, you capture it and store it in a “buffer,” and you can decide what to do with it. Here’s a simple example:

ob_start();
echo "Hello!  This is buffered.";
$buffer = ob_get_clean();

Let’s go through the code line-by-line. The first line calls ob_start() (docs), which starts output buffering. The next line normally would be sent to the browser. Instead, since I called ob_start(), it gets stored in our buffer. The third line takes the current buffer and assigns it to the $buffer variable and stops the current buffer, all using the ob_get_clean() function (docs). It’s really simple stuff, and it becomes very powerful when used correctly.

How Caching Will Work

For this post, I’m going to be caching a simple API, and the general process will work like this:

  1. A user makes an API call, something like http://mysite.com/api/?method=myapp.search&type=people&query=Kyle
  2. If a cache file exists for the call, and it is younger than 15 minutes, skip to #6.
  3. Start buffering PHP’s output.
  4. Run the code to process the request.
  5. Save the contents of the buffer to a file, with a unique filename.
  6. Return result to the user.

Where Cached Output Will Be Saved

To save the output, I’m going to be creating a file for each unique request. For this application, the request will be unique based on the GET parameters passed. To do this, I’ll be creating an MD5 hash of an alphabetical list of GET keys and values. Here’s the function:

function cache_key() {
  $keys = array();
  foreach($_GET as $key => $value) {
    $keys[] = $key . "=" . $value;
  }
  sort($keys);
  return md5(implode('&', $keys));
}

function cache_filename() {
  globals $cache_dir;
  return $cache_dir . '/' . cache_key() . '.cache';
}

Please note that this will have to be customized based on what exactly you’re caching. For instance, if you’re caching individual pages, you may want to create the key using the path to the page. Whatever it is you’re using, just make sure it is unique and consistent for each page.

Checking The Cache

When a request is made, it’s necessary to first check to see if it has already been cached, and, if it has, whether the cache hasn’t expired. I’ll be using the filesystem to achieve this:

$cache_time = 15*60; // 15 minutes in seconds

function cache_exists() {
  globals $cache_time;

  if(@file_exists(cache_filename()) && time() - $cache_time < @filemtime(cache_filename())) {
    return true;
  } else {
    return false;
  }
}

So what exactly is going on here? If you take a close look, we’re using the file_exists() (docs) and filemtime() (docs) functions to see if the cache file already exists and, if it does, whether it’s recent enough to serve (in this case, if it’s less than 15 minutes old, the function returns true). I’m placing @ signs before these two functions so that, if they fail, it doesn’t return an error. Instead, the function will just return false and the code will run as if no cache file exists.

Putting It All Together

Now, it’s time to get everything working together. First, a couple of necessary functions for saving and reading the cache:

function read_cache() {
  return file_get_contents(cache_filename());
}

function save_cache($value) {
  $fp = @fopen(cache_filename(), 'w');
  @fwrite($fp, $value);
  @fclose($fp);
}

Now, a few calls to wrap around your code:

function start_cache() {
  if(cache_exists()) {
    echo read_cache();
    exit();
  } else {
    ob_start();
  }
}

function stop_cache() {
  $data = ob_get_clean();
  save_cache($data);
  echo $data;
}

And to implement it, this is all you need to do:

start_cache();
// Your code that needs to be cached
stop_cache();

And you’re done! All-in-all, it’s a very simple way to achieve a very powerful result.

Drawbacks to This Method

The first thing you want to keep in mind when using this caching method is that it caches the entire page. This can be good: if everyone visiting the page sees the same content anyways, why not cache it for everyone? However, if you’re serving a page that appears different to different users, it can be a bad idea. For instance, what if an administrator visits the page, and it gets cached? When the next non-administrator visits, they’re going to see all the administration information. Bad news.

Also, due to the simplicity of this method, there’s no way to easily expire the cache of a single page. Let’s go back to the blog entry example. If you decide to make a change to the entry, you’ll have to wait at least 15 minutes before the cache is cleared, or you have to go in and delete all the cache files (since it’s difficult to determine which file goes with which page). For many applications, this probably won’t an issue, but it’s something to keep in mind.

Download the Source

Hopefully, this was helpful. If you’d like to download the entire source, you can grab it here.

Posted on September 16, 2008 11 Comments
Tagged with: , , , , , ,

RESTfully Forward Users to FeedBurner in Rails

After launching my new site, I realized I had forgotten to use FeedBurner to track my RSS feed subscribers. Because FeedBurner requires that you forward users to your feed on their servers, I needed a way to forward users along, but I also wanted to make sure that if FeedBurner’s spider came to the same feed URL, they would be served the source RSS instead of being forwarded. My solution ended up like this:

class NotebooksController < ApplicationController
  def show
    respond_to do |format|
      format.html
      format.rss do
        unless request.env['HTTP_USER_AGENT'] =~ /feedburner/i
          redirect_to 'http://feeds.feedburner.com/KyleSlattery'
        end
      end
    end
  end
end

If you take a close look at the, all I’m doing is checking the user agent of the request, and unless it’s FeedBurner, I redirect the user to the “burned” feed. If it is FeedBurner, things go ahead normally, and the RSS feed gets rendered.

This means that instead of creating separate actions, one that forwards to FeedBurner, and one that renders the RSS, I just have one action/URL that does both: http://kyleslattery.com/notebook.rss. So far, it’s worked great, and doing it this way really helped to keep my code clean and RESTful.

Posted on August 26, 2008 3 Comments
Tagged with: , , , ,

Posting to Brightkite using ActiveResource and REST

The other day, I came across Brightkite’s REST API. After taking a look at it, I decided it was the perfect opportunity to try out ActiveResource, the dead simple way to consume RESTful resources. In 20 lines, I was able to put together a simple script to find your most recent check-in on Brightkite and then post to that place.

A Quick Intro To Active Resource

First off, here’s an idea of just how easy it is to connect to a REST API using ActiveResource. Take, for instance, the “places” resource in Brightkite; these URLs all have the base http://brightkite.com/places. To interface with places, all it takes is this:

class Place < ActiveResource::Base
  self.site = 'http://brightkite.com'
end

That’s it. To get all the places, just do Place.find(:all). To create a new place, all it takes is Place.new. Amazing, to say the least. There’s a lot more that’s possible, so I recommend you check out the ActiveResource page on the Rails wiki.

The code

Below are the 20 lines necessary to post a note to Brightkite through the API. Just change USERNAME and PASSWORD to your username and password and change the note text at the bottom, and you’re ready to go. While this is a pretty simple example, it shows just how powerful a well constructed REST API can be.

Posted on August 11, 2008 14 Comments
Tagged with: , , , , ,

Getting Brightkite and Verizon to Play Nicely

After hearing all about Brightkite from Brandice and Colin, I decided to take the plunge and try out the service a few weeks ago. In the time since, I’ve become addicted to the service, and I’m constantly amazed at how well thought out the site is. One thing that really bugged me, though, is that Brightkite doesn’t work with Verizon text messages. After being initially disheartened, I found getting around the issue to be pretty easy: instead of text messages, I used PIX messages.

If you take a look at Brightkite’s SMS Guide when logged into the service, you’ll notice a email under “Post a photo to a place.” This is the email you’ll be sending your updates to. To use any of the mobile commands (PDF) with Verizon, just send a PIX message to the email address without a photo attached. Put your command in the “text” field, send the message, and you should be good to go.

A word of caution: if you don’t have unlimited picture messaging, this method may end up being very costly. I’m not sure if Verizon still charges their PIX rate for messages without pictures, but if they do, make sure you’re not going over your allotted number of messages.

Hopefully, Verizon will fix whatever issue is breaking the Brightkite integration soon, so we don’t have to continue to hack around the problem. In the meantime, however, this method should continue to work; I haven’t had any issues with it thus far.

Posted on August 8, 2008 3 Comments
Tagged with: , ,

A Fresh Start

I never thought I’d actually get to this point, but, today, I am releasing a complete ground-up reworking of my site. With Ruby on Rails as a starting point, I was able to develop my very own content management system, and it is now powering everything here (with the help of a few plugins). I’m also excited to have a brand new design up, and it’s one I really have grown to enjoy. Read on for more details on the specifics of the new site.

The Design

Ever since I started using Twitter, I always used a stunning picture of the Crab Nebula as my background. When I started redesigning my site, I used the photo as a placeholder, but after a while it stopped being a placeholder and started driving the rest of the design, so I decided to keep it. The colors in the layout were directly influenced by the image: nearly every color comes directly from the Crab Nebula photo.

One thing I attempted with this was to not hold myself to the notion that there has to be a sidebar on every page. If you take a look at my About and Work pages, you can see where I switched things up. Personally, I think this helps to make it feel a little less like a blog, something I was definitely aiming for.

Even though I’m launching the new design today, there’s still quite a bit of refining for me to do. For one, the notebook page is pretty cluttered, so I really need to go through and organize that a lot better. In addition, several people I’ve shown the site to have mentioned that the header is a bit awkward; having the navigation attached and the title detached throws them off. I’m definitely going to need to find a better way to have that set up.

The Development

Developing the site is something I really enjoyed, and it allowed me to try a bunch of things I’ve been thinking of for a while. The site is built using Ruby on Rails, several plugins, and a whole lot of my own code.

One thing I’m proud of on the site is that every single page is cached. This way, things stay speedy, and the load on the server is lessened. Adding caching in Rails is really easy, and if you’re looking to do it yourself, you’ll probably want to check out this Rails Envy tutorial as well some Railscasts episodes.

For the server setup, I originally was planning to use Dreamhost and Phusion Passenger, but after using it for a bit, I felt like I needed a little more freedom, so I ended up buying a VPS from Slicehost. So far, I’ve been thrilled with Slicehost’s service and performance, and I’m definitely considering moving more projects over to them. As far as the technical side of things, I’m still using Passenger to run the site, though I might test out using Mongrel to see if that’s any faster.

Next Steps

I’m really excited about the new site, but I’m not done yet. Like I mentioned before, I still have some design tweaking to do, but I also have a lot I want to implement under the hood. There are bound to be issues that come up, and I’ll be hopefully fixing them as fast as possible, but if you run into something, please let me know.

I would love to hear any feedback (negative or positive) you have about the new site, so please leave a comment below!

Posted on August 8, 2008 2 Comments
Tagged with: , , , ,

Pass Blocks to Your Markdown Helper

For the new site, I’m going to be using Markdown to handle the formatting of all of my content. I created a simple helper to make things easy:

def markdown(str)
  RDiscount.new(str).to_html
end

(Note: I’m using the RDiscount library instead of BlueCloth, for reasons discussed here)

However, what if I wanted to add a “Read more” link at the end of the entry excerpt? I’d have to do something like this:

<%= markdown entry.excerpt + link_to("Read more", entry_path(entry)) %>

This way didn’t really appeal to me. What I really wanted to do was just pass a Ruby block, like this:

<% markdown do %>
  <%= entry.excerpt %> <%= link_to "Read more", entry_path(entry) %>
<% end %>

In order to do this, I just had to modify my markdown helper a bit:

def markdown(str='', &block)
  str    = capture(&block) if block_given?
  result = RDiscount.new(str).to_html

  block_given? ? concat(result, block.binding) : result
end

And, just like that, I can now pass blocks to my Markdown helper.

Posted on July 15, 2008 Leave a Comment
Tagged with: , ,

Redevelopment Update

I put together a quick video showing where I’m at with the new design and backend for KyleSlattery.com. Take a look, and let me know what you think!

Posted on July 12, 2008 3 Comments
Tagged with: , , ,