Posts Tagged ‘developers’

Basic many-to-many Associations in Rails

January 29, 2010

View the Source Code

Many-to-many relationships

Data modeling is the science (and art) of creating the database schema that most purely matches the real world objects involved in your project. Part of this is defining how the objects relate to one another. Let’s say your application tracks Items and Categories. If each item can only belong to one category, then you have a one-to-many relationship; categories have many items. But if an item can appear in more than one category, you have a many-to-many relationship.

There are two ways to handle many-to-many relationships in Ruby on Rails, and this article will cover both.

has_and_belongs_to_many

The simplest approach is if you don’t need to store any information about the relationship itself. You just want to know what items are in each category, and what categories each item belongs to. This is called “has_and_belongs_to_many”. We use has_and_belongs_to_many associations in our models, and create a join table in our database. Here are your models:

# app/models/category.rb
class Category < ActiveRecord::Base
  has_and_belongs_to_many :items
end

# app/models/item.rb
class Item < ActiveRecord::Base
  has_and_belongs_to_many :categories
end

Next, let’s create the join table by generating a new migration. From the command line:

script/generate migration AddCategoriesItemsJoinTable

Now we’ll edit the migration file it creates:

class AddCategoriesItemsJoinTable < ActiveRecord::Migration
  def self.up
    create_table :categories_items, :id => false do |t|
      t.integer :category_id
      t.integer :item_id
    end
  end

  def self.down
    drop_table :categories_items
  end
end

Notice the :id => false, which keeps the migration from generating a primary key. The name of the table is a combination of the two table names we’re joining, in alphabetical order. This is how Rails knows how to find the join table automatically.

has_many :through

The other way to setup a many-to-many relationship between objects is used if you do, or think you will, need to track info on the relationship itself. When was item X added to category Y? That’s info you can’t store in the category or item tables, because it’s info about the relationship. In Rails, this is called a has_many :through association, and it’s really just as easy as the first way.

First, we’re going to create a new model, that defines the relationship between items and categories. For back of a better name, let’s call it a Categorization. Setup your models like this:

# app/models/category.rb
class Category < ActiveRecord::Base
  has_many :categorizations
  has_many :items, :through => :categorizations
end

# app/models/item.rb
class Item < ActiveRecord::Base
  has_many :categorizations
  has_many :categories, :through => :categorizations
end

# app/models/categorization.rb
class Categorization < ActiveRecord::Base
  belongs_to :category
  belongs_to :item
end

We’re connecting both original models to :categorizations, and then connecting the them to each other via the intermediary Categorization model. Now, instead of a join table whose only function is connecting the others, we add a full-fledged table to manage our new model:

class CreateCategorizations < ActiveRecord::Migration
  def self.up
    create_table :categorizations do |t|
      t.integer :category_id
      t.integer :item_id

      t.timestamps
    end
  end

  def self.down
    drop_table :categorizations
  end
end

We still have the two foreign key integer columns, but we’ve removed :id => false so this table will have an id column of its own. We also added timestamps, so we’ll be able to tell when an item was added to a specific category. I also created a migration that removes the old categories_items table, but it’s not shown here.

Which is Better?

The simpler has_and_belongs_to_many approach has a small advantage when you *know* you’re not going to need to track info about the relationship itself. If this is the case, there’s a very slight performance gain because you’re not loading an extra model class at runtime.

More often than not, however, you’re going to eventually want to track relationship-specific data. We used the example of tracking when a relationship was created. Another would be if you want to track, over time, how many times a visitor clicks on an item under each category. That counter needs to be stored in the Categorization model, and that’s a reason not to use the simpler has_and_belongs_to_many approach.

I’ve created an example application (get it here) with tags for each version – has_and_belongs_to_many, and has_many :through.

Nesting your has_many :through relationships

January 28, 2010

View the Source Code

Let’s say you’re creating a site where people can track their memberships in various store clubs – from grocery store loyalty cards, to memberships to Sam’s or CostCo. People and Stores have a many-to-many relationship called a Membership. Stores also have sales, and you want people to be able to manage all sales at all of their stores easily.

The Problem

You’d like to be able to say @member.sales, but there’s a problem – Rails doesn’t support daisy-chaining associations the way we’d like. Here’s how we want to setup our associations:

# app/models/member.rb
class Member < ActiveRecord::Base
  has_many :memberships
  has_many :clubs, :through => :memberships
  has_many :sales, :through => :clubs
end
# app/models/club.rb
class Club < ActiveRecord::Base
  has_many :memberships
  has_many :members, :through => :memberships
  
  has_many :sales
end
# app/models/membership.rb
class Membership < ActiveRecord::Base
  belongs_to :member
  belongs_to :club
end
# app/models/sale.rb
class Sale < ActiveRecord::Base
  belongs_to :club
end

And for reference, here’s the full schema:

# db/schema.rb
ActiveRecord::Schema.define(:version => 20100129152803) do
  create_table "clubs", :force => true do |t|
    t.string   "name"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  create_table "members", :force => true do |t|
    t.string   "name"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  create_table "memberships", :force => true do |t|
    t.integer  "member_id"
    t.integer  "club_id"
    t.date     "expires"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  create_table "sales", :force => true do |t|
    t.string   "name"
    t.text     "description"
    t.date     "start"
    t.date     "end"
    t.integer  "club_id"
    t.datetime "created_at"
    t.datetime "updated_at"
  end
end

While everything looks good from a data modeling perspective, there’s one issue – our member model isn’t allowed to daisy-chain assocations, so we can’t get to our sales easily. A call to @member.sales gives us this:

ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: clubs.member_id: SELECT "sales".* FROM "sales"  INNER JOIN "clubs" ON "sales".club_id = "clubs".id    WHERE (("clubs".member_id = 1)) 

The Solution

In comes Ian White’s nested_has_many_through plugin, which does exactly what you’d think Rails does already. Without changing the way you create associations, this plugin “just works” out of the box. Nothing to include in models or config files. Here’s how you install it:

script/plugin install git://github.com/ianwhite/nested_has_many_through.git

Now run @member.sales and you get what you’d expect – a list of all sales that a member is entitled to attend. You can nest even deeper if you like, but I offer this word of caution. Nested has_many :through associations are like the most precious liquid on earth: Captain Morgan’s spiced rum. Enjoy in moderation :)

Epilogue

I’ve created a full rails app on GitHub (View the Source Code) so you can download and play around with it. There are “before” and “after” tags, so you can see how the app reacts with and without the plugin. It also has Shoulda tests.

Route Testing with Shoulda

January 27, 2010

I received a comment in a recent article, by someone who saw the default routes in my config/routes.rb example and suggested I remove them. Yes! This makes total sense from several angles.

Between RESTful routes and named routes, there’s no good reason not to explicitly name all of your routes. Also, you don’t get the goodness of say, “pets_path” or “pet_path(@pet)” if you just let the default routes handle PetsController for you. Finally, if you’re doing TDD (test-driven development) then why aren’t you testing your routes as well?

Route testing is easy, and the tests themselves run super fast. One app of mine has almost 600 routing assertions that run in just 2 seconds. It’s fun to watch the dots for your routing tests zoom across the screen! Let’s start by adding a test file to our system with the standard boilerplate:

# test/unit/routing_test.rb
require 'test_helper'

class RoutingTest < ActionController::TestCase

end

We’ll start with some TestUnit basics before we get into how Shoulda can make life easier. There are three assertions to learn:

def test_generates_user_index
  assert_generates '/users', :controller => 'users', :action => 'index'
end

def test_recognizes_user_index
  assert_recognizes {:controller => 'users', :action => 'index'}, '/users'
end

def test_routes_user_index
  assert_routing '/users', :controller => 'users', :action => 'index'
end

The first assertion, assert_generates, verifies that if url generators like url_for are passed this hash of controllers, actions and whatever else, the correct route (/users in this case) is generated. The next assertion, assert_recognizes, does just the opposite, ensuring that a route of /users end up calling the index action of the users controller. The third version (assert_routing) does both! It combines the two previous assertions into one, and this is what you’ll want most of the time.

Using Shoulda to DRY up your tests

Here are the above tests, in Shoulda form:

require 'test_helper'

class RoutingTest < ActiveSupport::TestCase
  context "routing users" do
    should "generate /users" do
      assert_generates '/users', :controller => 'users', :action => 'index'
    end

    should "recognize users" do
      assert_recognizes {:controller => 'users', :action => 'index'}, '/users'
    end
  
    should "generate and recognize users" do
      assert_routing '/users', :controller => 'users', :action => 'index'
    end
  end
end

While these tests are simple, they’re not very DRY. Just imagine, you’ll need to have seven of these tests *just* for the most basic RESTful route. Instead, install my shoulda routing macros:

script/plugin install git@github.com:bellmyer/shoulda_routing_macros.git

Now here’s an example routing file:

# config/routes.rb
ActionController::Routing::Routes.draw do |map|
  # a simple resource
  map.resources :chickens

  # a resource with extra actions
  map.resources :users, :collection => {:thankyou => :get}, :member => {:profile => :get}

  # nested resources
  map.resources :owners do |owners|
    owners.resources :pets
  end

  # singleton resource
  map.resource :session
  map.ltp_contact_link '/ltp/:code', :controller => 'ltp_contacts', :action => 'new'
end

And this is how easy they are to test:

# test/unit/routing_test.rb
require 'test_helper'

class RoutingTest < ActionController::TestCase
  # simple
  should_map_resources :chickens

  # with extra actions
  should_map_resources :users, :collection => {:thankyou => :get}, :member => {:profile => :get}

  # nested
  should_map_resources :owners
  should_map_nested_resources :owners, :pets

  # singleton
  should_map_resource :session
end

There’s no longer a good excuse to use default routes, or not test your routes in detail. Enjoy!

Single-Table Inheritance with Tests in Ruby on Rails

January 26, 2010

Single-table inheritance was a difficult concept for me to grasp when I first started using Ruby on Rails. Part of this might stem from the fact that it’s not a perfectly modeled concept in Rails itself. Relational databases like MySQL, PostgreSQL, and sqlite don’t have any concept of (inherited) sub-tables.

Let’s say you want to track pets. Cats and dogs need to be treated differently, but they basically have the same set of attributes, and share some methods as well. Here’s how we setup our models:

# app/models/pet.rb
class Pet < ActiveRecord::Base
  validates_attributes :name

  def speak
    ''
  end
end
# app/models/dog.rb
class Dog < Pet
  def speak
    'Woof!'
  end
end
# app/models/cat.rb
class Cat < Pet
  def speak
    'Meow!'
  end
end

Notice we include a default speak method in the parent class. This isn’t required, but it will help with any other pet types we may create in the future. This will be the default, so if we add a Fish category, we don’t need to define its own speak method separately.

All three models will share a common database table. In our migration, we’ll need to add a string column called “type”, that will contain the class name of the individual record:

class CreatePets < ActiveRecord::Migration
  def self.up
    create_table :pets do |t|
      t.string :name
      t.string :type

      t.timestamps
    end
  end

  def self.down
    drop_table :pets
  end
end

This is how Rails will know which records are cats, and which are dogs. You’ll usually never have to work with this column directly. Now calls to Dog.all will return only dog records from the pets table. Calling speak on a pet record will return “Woof!” for a dog record, and “Meow!” for a cat record.

Now, how do we test this? We should create a test file for each model class. We’ll test everything in that is included in the parent model in its test file, then only test the things that change in the child model tests:

# test/unit/pet_test.rb
require 'test_helper'

class PetTest < ActiveSupport::TestCase
  def test_should_validate_presence_of_name
    pet = Pet.new()
    assert !pet.valid?
    assert pet.errors.on(:name).include?("can't be blank")
  end

  def test_should_speak_blank
    pet = Pet.new
    assert_equal '', pet.speak
  end
end
class DogTest < ActiveSupport::TestCase
  def test_should_bark
    dog = Dog.new
    assert_equal 'Woof!', dog.speak
  end
end
class CatTest < ActiveSupport::TestCase
  def test_should_meow
    cat = Cat.new
    assert_equal 'Meow!', cat.speak
  end
end

Single-table inheritance is powerful, and only slightly tricky to grasp. It’s well worth learning.

Vanity URLs in Ruby on Rails Routes

January 25, 2010

Many social networking sites like Twitter have “pretty urls” that get you to a user’s profile in the fewest possible keystrokes. Their format is usually http://example.com/username, and it’s easy to add this to your own Rails application.

Let’s say you have a restful profiles controller that we use for the public-facing aspects of a user. Put this in your routes file:

# config/routes.rb
ActionController::Routing::Routes.draw do |map|
  map.resources :profiles
  map.resources :users

  map.profile_link '/:login', :controller => 'profiles', :action => 'show'
  
  map.connect ':controller/:action/:id'
  map.connect ':controller/:action/:id.:format'
end

Notice we have our special named route “profile_link” after our resources. The Rails routing file works top-to-bottom, meaning Rails will grab the first route that matches the incoming url. If we put our profile_link route in front of the resources, this would confuse the routing engine and some of our restful routes would fail.

Now we need to setup our show action to use the login parameter to find the correct user:

# app/controllers/profiles_controller.rb
class ProfilesController < ApplicationController
  def show
    @user = User.find_by_login(params[:login])
  end
end

Of course, you’ll need corresponding view code in app/views/profiles/show.html.erb as well.

That’s all it takes to get short, sweet vanity urls in your rails application.

Adding Columns and Default Data to Existing Models

January 25, 2010

If you have an existing model in your Ruby on Rails application and you’ve already run the migration to create the table in the database, you may want to add columns later on. This is easy. Let’s say we have an ActiveRecord user model, and we want to add a rating for each user, to allow others to vote a user up or down. First, create the migration:

script/generate migration AddRatingToUsers

Or if you want to be fancy, we can use a little magic and specify our new column along with it:

script/generate migration AddRatingToUsers rating:integer

Rails is smart enough to figure out the table name from the migration name, and you’ll see this in the migration file that is created:

class AddRatingToUsers < ActiveRecord::Migration
  def self.up
    add_column :users, :rating, :integer
  end

  def self.down
    remove_column :users, :rating
  end
end

If you run the migration right away, you’ll run into problems because all the users that exist will have a rating of nil, and this will probably break any calculations you have in your code. Let’s update it with a default value:

    add_column :users, :rating, :integer, :default => 0

Now the database will default all existing and new users to a rating of zero.

More Complex Default Values

But what if you need a more complex default? Let’s say you want to add a unique API key for each user. Maybe we have a user instance method called “generate_api_key”, and it’s called whenever a new user is created:

class User < ActiveRecord::Base
  after_create :generate_api_key

  def generate_api_key
    self.update_attribute(:api_key, self.username + '123')
  end
end

But what about the users that were already in the database when we added the column? Should we put that in the migration, too? Absolutely not. It can be done, but migrations are not meant for data loading – only creating and changing the structure of your underlying database.

A better way is a rake task. These belong in the lib/tasks folder of your application, and they’re easy to create:

# lib/tasks/invitation_tokens.rake
namespace :seed do
  desc "generate api keys for users that don't have one already"
  task :api_keys => :environment do
    User.all(:conditions => {:api_key => nil}).each do |user|
      user.generate_api_key
    end
  end
end

Then, you can call this rake task from the command line like this:

rake seed:api_keys

This has a couple benefits. First, if you setup a new instance of your rails app on a different server (or even somewhere else on the same server) you can call this rake task to jumpstart your users. Even better, since you’re relying on a model method, you can unit test that method to make sure it performs as expected.

Restful Controller Tests with Shoulda – Stubbing

January 20, 2010

View the Source Code

This is part 2 of a 5 part series on restful controller tests, using Shoulda as the foundation. Here are all of them so far:

  1. The Basics
  2. Stubbing for Speed

Stubbing

My initial set of controller tests are a great foundation. They lay out exactly how to take advantage of Shoulda to create tests for three different roles of user: anonymous visitor, member, and admin. Now we’ll use the Mocha ruby gem to speed up our tests, by eliminating database calls.

Here’s my (amateurish) video of the changes, followed by a more detailed explanation:

We’ll start by ensuring that rails will require the gem. Add this to config/environments/test.rb:

config.gem 'mocha'

Then on the commandline:

rake gems:install RAILS_ENV=test

Mocha allows us to stub (or “fake”) methods on an object, and track how often they’re called during a test. For example, if you stub an object’s save method in a create action, you can test what happens when saving succeeds (returns true) or fails (returns false), with no messy database interaction.

While I won’t get into a primer on Mocha itself, I will say that almost every database interaction can be removed from functional tests, with the exception of user authentication. I usually leave that in, and that’s my one and only use for fixtures. Fixtures are fast, but cumbersome if you begin adding multiple records for every model in your application. I use factories instead, but they’re not as fast for loading the database. Factories are another chapter in the Functional Tests saga, however.

That said, let’s get on to reviewing the stubbed versions of my tests. Starting with the admin context, our setup changes from this:

    setup do
      @valid = Factory.build(:setting).attributes
      @setting = Factory :setting
      login_as :admin
    end

to this:

    setup do
      @setting = Factory.build :setting
      @setting.id = 1001

      Setting.stubs(:find).returns(@setting)
      Setting.stubs(:find).with(:all, anything).returns([@setting])
      
      login_as :admin
    end

Instead of creating a new setting, we’re using our factory to build an unsaved one. We’re giving it an id since saving would have normally done this. Finally, we’re stubbing out the find method, both for an individual and for all settings, so they return the one we’ve built. Loggin in as admin will be our only database hit.

Now for the actions. The index, show, and new action tests stay exactly the same, except the first two are no longer hitting the database – we’ve stubbed out the find method to automatically return our fake setting. On to the create method, in the “with valid data” context. Here’s the original:

context "with valid data" do
  setup do
    post :create, :setting => @valid
  end
        
  should_assign_to :setting, :class => Setting
  should_redirect_to("setting page"){setting_path(assigns(:setting))}
  should_set_the_flash_to "Setting was successfully created."
        
  should "create the record" do
    assert Setting.find_by_name(@valid['name'])
  end
end

And here’s the stubbed version:

context "with valid data" do
  setup do
    Setting.any_instance.expects(:save).returns(true).once
    Setting.any_instance.stubs(:id).returns(1001)
          
    post :create, :setting => {}
  end

  should_assign_to :setting, :class => Setting
  should_redirect_to("setting page"){setting_path(1001)}
  should_set_the_flash_to "Setting was successfully created."
end

We’ve stubbed any instance of Setting to return true upon save, without actually saving. No database hit, and we still get to test everything. We’ve even dropped our “create the record” test, and stopped passing in valid data, because the expectation we set in the setup handles this. To be more paranoid, we could pass in valid data and check that those params end up in the Setting.new call, but I think that’s overkill since it’s such a basic step.

Next, the “with invalid data” context. First the original:

context "with invalid data" do
  setup do
    post :create, :setting => {}
  end
  
  should_assign_to :setting, :class => Setting
  should_respond_with :success
  should_render_with_layout :settings
  should_render_template :new
  should_not_set_the_flash
end

And the new version:

context "with invalid data" do
  setup do
    Setting.any_instance.expects(:save).returns(false).once
    post :create, :setting => {}
  end
  
  should_assign_to :setting, :class => Setting
  should_respond_with :success
  should_render_with_layout :settings
  should_render_template :new
  should_not_set_the_flash
end

The only changes here are that we’re setting a stubbed expectation of a call to save, forcing a return value of false, and again we don’t need to pass in valid data. Now we can run all the same tests, because we’ve forced the code down the failure branch.

Our edit tests stay the same, with the exception again of no database hit thanks to our stubbed finders. Next up is the update action, and we’ll start with the valid data context. Here’s the original:

context "with valid data" do
  setup do
    put :update, :id => @setting.id, :setting => {:name => 'Bob'}
  end
  
  should_assign_to(:setting){@setting}
  should_redirect_to("setting page"){setting_path(assigns(:setting))}
  should_set_the_flash_to "Setting was successfully updated."
  
  should "update the record" do
    @setting.reload
    assert_equal 'Bob', @setting.name
  end
end

And here’s the new version:

context "with invalid data" do
  setup do
    @setting.expects(:update_attributes).returns(false).once
    put :update, :id => @setting.id, :setting => {}
  end
  
  should_assign_to :setting, :class => Setting
  should_respond_with :success
  should_render_with_layout :settings
  should_render_template :edit
  should_not_set_the_flash
end

Much like a successful create, we stub out our update to succeed and run all the same tests except that the record was actually changed. Now the invalid data context. Here’s the original:

context "with invalid data" do
  setup do
    put :update, :id => @setting.id, :setting => {:name => nil}
  end
  
  should_assign_to :setting, :class => Setting
  should_respond_with :success
  should_render_with_layout :settings
  should_render_template :edit
  should_not_set_the_flash
end

And the new version:

context "with invalid data" do
  setup do
    @setting.expects(:update_attributes).returns(false).once
    put :update, :id => @setting.id, :setting => {}
  end
  
  should_assign_to :setting, :class => Setting
  should_respond_with :success
  should_render_with_layout :settings
  should_render_template :edit
  should_not_set_the_flash
end

And much like our unsuccessful create, we stub out the update to return false, and run the same tests! The finaly action that changes is update, and it’s very easy. First the original:

context "destroying" do
  setup do
    delete :destroy, :id => @setting.id
  end
      
  should_assign_to(:setting){@setting}
  should_redirect_to("index"){settings_path}
  should_not_set_the_flash
      
  should "delete the record" do
    assert !Setting.find_by_id(@setting.id)
  end
end

And the new version:

context "destroying" do
  setup do
    @setting.expects(:destroy).once
    delete :destroy, :id => @setting.id
  end
    
  should_assign_to(:setting){@setting}
  should_redirect_to("index"){settings_path}
  should_not_set_the_flash
end

This time we’re only stubbing out the call to destroy, expecting it to happen once. All the tests stay the same, except we no longer test that a record has actually been removed. No record was actually in the database, so we can’t. Plus, a failing call to destroy is so rare, it doesn’t even let you know in the return value.

The admin context is the only one that changes with these upgrades, members and visitors never get this far in our examples. This will generally make your functional tests over twice as fast, which is huge when you’re faced with mounting test times. I believe I was able to cut testing time down from 90 seconds to just under 30, implementing stubbing across an application’s test suite.

If you view the source code for this part of the series, you’ll see a lot of repetition in the tests. We’ll DRY (Don’t Repeat Yourself) that code in the next chapter in the series.

Shoulda Macro for a Cleaner Uniqueness Test

January 19, 2010

Shoulda macros are so neat and tidy, aren’t they? I love kicking off my unit tests with quick-and-deadly validation and association tests. Here’s an example:

require 'test_helper'

class CouponTest < ActiveSupport::TestCase
  should_validate_presence_of :code, :name, :description

  context "validating uniqueness" do
    setup do
      Factory :coupon
    end

    should_validate_uniqueness_of :code
  end

  should_belong_to :user
end

Do you see anything wrong with this picture? should_validate_uniqueness_of necessarily requires that you already have a record in the database. As much as I hate database interaction in my tests, it’s a necessary evil here. The macro works by copying the attributes of an existing record, and validating it to see if you get any uniqueness errors. So, for this one test, I have to setup a context with a Factory call, because I’m not about to create a record for all the other tests that don’t need it.

Wouldn’t it be nice if you could call should_validate_uniqueness_of and it would create the record for you, if one didn’t already exist? It could use the Factory you’ve already setup. And it would still work the old way if you don’t have factories, or don’t enjoy DRY coding.

Add this macro to your application:

# test/shoulda_macros/validation_macros.rb
module Test
  module Unit
    class TestCase
      class << self
        alias_method :svuo_original, :should_validate_uniqueness_of
        
        def should_validate_uniqueness_of(*attributes)
          class_name = self.name.gsub(/Test$/, '')
          klass = class_name.constantize
          model_sym = class_name.underscore.to_sym
          context "with a record in the database" do
            setup do
              Factory model_sym unless klass.count > 1
            end
            
            svuo_original *attributes
          end
        end
      end
    end
  end
end

Here, we’re overriding the default Shoulda macro. Before we call the original, we’ll setup a context and create the record using our Factory, unless a record is already created. That means it will always “just work”, with the exact same syntax, and all of the options of the original should_validate_uniqueness_of

Thoughtbot can’t create the macro like this by default, because it assumes you have factory_girl installed. But if you do use factories, this upgrade will take some of the ugly out of your tests:

require 'test_helper'

class CouponTest &lt; ActiveSupport::TestCase
  should_validate_presence_of :code, :name, :description
  should_validate_uniqueness_of :code

  should_belong_to :user
end

Nice, eh? As usual, all you need to do to install/create a new shoulda macro is drop a ruby file into the test/shoulda_macros folder, with macro methods defined. You don’t even need to reopen TestCase the way I did, unless you’re overriding an existing macro.

Organic Search Engine Optimization

January 18, 2010

I don’t make my living as an search engine optimization expert, but I’ve been a web developer since 2000. I’ve watched with morbid curiosity as the SEO trainwreck has unfolded and evolved over the last decade. If that sounds boring, understand that shows like Desperate Housewives were not yet available for people who enjoy watching the self-indulgent suffering of others. I’ve gotten off-topic, slightly.

Bad Search Engine Optimization

A client recently brought in other contractors who professed to have the keys to SEO gold: keyword meta tags, and url redirecting. To be fair, they weren’t hired as SEO experts either – they’re good at their primary jobs, and were trying to help out with the information they had. Unfortunately, that information was about 10 years out of date.

I have good news and bad news. The bad news is that there are no shortcuts to effective search engine optimization. None of the big three search engines (Google, Yahoo, and MSN/Bing) use them, and they make up 93% of the search engine market share.1 Google2 and Yahoo3 have both stated that they stopped using them, and MSN never did.

Why? Because if you give people an easy way to tell you what their site is about, and how relevant it is, everybody is going to say their site is the source for a thousand different things. In fact, that’s exactly what happened, which is why keyword meta tags stopped being effective long before search engines officially pulled the plug.

Good Search Engine Optimization

Good SEO is organic. Most of it just happens naturally if you’re doing what a good site should – providing quality content that people would be interested in reading. It happens a little better if you’re aware of a few things first. If there are certain key phrases everyone is searching to get to sites like yours, you should mention those key phrases in your content. Don’t overdo it, because Google and visitors know the difference. Also, you do get a boost in rankings if other sites link to yours. Finally, search engines will list you higher if searchers actually click the link to your site in their search results.

Here is my cost-effective guide to good search engine rankings. I’m sure professional SEO experts (the rare good ones) have a few more tricks up their sleeves, but these basics will get you farther than you’d think, for free.

  1. Research. Signup for Google AdWords and build out a campaign as if you were going to advertise. When you pick the keywords you’d like to sponsor, they’ll show you how popular those terms are. You want to gear your site toward the most popular search terms that are relevant to your site.
  2. Description Meta Tags. Use the description meta tag. It won’t help your search engine ranking, but it will tell people why they should click your link after searching.
  3. Add Content. Make a regular habit of adding content to your site. Strive for 1-2 pages of relevant content per week. This is why blogs do so well in search engines – they’re constantly growing in relevant content. If you do nothing else, add to your site’s content frequently!
  4. Encourage Links to Your Site. You can ask the webmasters of other related (noncompeting) sites if they’d be interested in exchanging links to each other’s sites. This really only works between legitimate sites. “Link farms”, networks of sites setup just to link to each other, are ignored by search engines at best, and penalized at worst. Also, if your content is relevant and worthy, other sites will begin linking to you without asking.
  5. Be Creative. My client and I realized they’d been sitting on a goldmine of content. They’d built a 100+ page reference guide for their industry, but it was a members-only resource. Soon it will be public, drawing in people who would have never heard of their site otherwise.

That’s it. Nothing beats having a rich site, full of content people actually want to read. There are no shortcuts, because any “trick” is abused to the point of nullifying the effect. It’s organic search engine optimization, and it works.

References

  1. ComScore – reports on search engine market share
  2. Google Can’t Be Gamed – an article and video of a Google engineer explaining for the record that keyword meta tags don’t matter.
  3. Yahoo Search No Longer Uses Keywords Tag

Kansas City Ruby User Group’s January Meeting

January 17, 2010

Last Tuesday, the Kansas City Ruby User Group (KCRUG) had its monthly meeting. There were 15-20 of us in attendence, with Nate Bunnyfield checking in from the Lawrence on Rails group in nearby Lawrence, Kansas. I’ve been meaning to go to one of their meetings, which are every other Thursday.

Shashank Date gave the main presentation, about Ruby blocks, procs, and lambdas. His presentation is here:

Ryan Smith also presented his Decorator pattern for Rails:

We don’t have our video setup exactly as we’d like it yet, but we’re getting there. Thanks to the presenters, along with Wes Garrison who recorded and processed the video!


Follow

Get every new post delivered to your Inbox.