giant robots smashing into other giant robots

We are thoughtbot. We make web & mobile apps.

Tagged:

Comments (View)

Redis for a flat URL hierarchy

I’m thinking through the URL structure of a new app. I want the routes to have a very flat hierarchy, similar to Quora’s:

I find this intellectually interesting, I’ve heard in some cases it can be good for SEO, and I can think of a few potential objects in this new app that I’d like to share the top-level namespace like this.

Keys and values

It seems to me URLs are to resources as keys are to values. Maybe this is a job for a key-value store. Redis seems cool, so let’s try that.

Set up

Getting Redis on OS X:

brew install redis

Gemfile:

gem "redis"

config/initializers/redis.rb:

REDIS = Redis.connect(:url => ENV["REDISTOGO_URL"])

config/environments/development.rb

ENV["REDISTOGO_URL"] ||= 'redis://localhost:6379'

I’m setting this up so it will work with the “Redis To Go” Heroku add-on if I want to deploy it later.

heroku addons:add redistogo

Set the data

Each time any object I want to share this namespace is saved, I’ll notify Redis. For example, users:

class User < ActiveRecord::Base
  validates_presence_of   :handle
  validates_uniqueness_of :handle

  before_save do
    REDIS.set handle, "User"
  end
end

I’m going to set their handle as the key and the value as the object type. If we also stored the ActiveRecord primary key, we could do the fastest-possible lookup later. That would change this data structure, though… any suggestions on the most Redis-y way to do it that way?

Rack middleware for the request

When a new request comes in, let’s have Redis do what it’s good at (quickly find the key), and if a record is found, mutate the route for my Rack endpoint (a Rails app in this case).

config.ru:

require ::File.expand_path('../config/environment',  __FILE__)
use RedisRouter
run MyRails::Application

app/middleware/redis_router.rb:

class RedisRouter
  def initialize(app)
    @app = app
  end

  def call(env)
    intended_resource = env["REQUEST_PATH"].gsub("/", "")
    if type = REDIS.get(intended_resource)
      new_route = "/#{type.underscore.pluralize}/#{intended_resource}"
      env["REQUEST_PATH"] = env["PATH_INFO"] = env["REQUEST_URI"] = new_route
    end
    @app.call(env)
  end
end

If a record isn’t found, Rack will pass through normally.

Let Rails finish the request

config/routes.rb:

MyRails::Application.routes.draw do
  get "/users/:handle", :to => "users#show"
  get "/places/:handle", :to => "places#show"
  get "/lists/:handle", :to => "lists#show"
end

app/controllers/users_controller.rb:

class UsersController < ApplicationController
  def show
    @user = User.find_by_handle!(params[:handle])
  end
end

Would you do it this way?

This approach introduces a dependency on Redis and makes two requests to two separate databases. We could have had one SQL query that queried multiple tables. And, this is Ruby, land of dynamic typing… what’s with the explicit storing of type in a Redis database?

I like the idea of separating the concern of determining which type of object we want. That query should be super-fast and we can avoid messing with database indexes on our SQL database for this one case.We can separate this concern and if necessary, scale this component of the application independently.

Tagged:

Comments (View)

stupid ruby tricks

Over the past few months of slinging Ruby here at Thoughtbot, I’ve picked up quite a few stupid ruby tricks smart ruby techniques that really help out your code. If you’ve got your own, feel free to leave a comment.

Destructuring yielded arrays

def touch_down
yield [3, 7]
puts "touchdown!"
end

touch_down do |(first_down, second_down)|
puts "#{first_down} yards on the run"
puts "#{second_down} yards passed"
end

=> "3 yards on the run"
=> "7 yards passed"
=> "touchdown!"

At first glance, this barely looks like valid Ruby. But somehow, it just makes sense: it splits up the array. If you’re going to pull out the values of the array inside of the block, why not just do it when you’re defining the block-level variables? This doesn’t seem to work nicely (in 1.8.7 at least) for Hashes, though.

Pulling out elements of an array

>> args = [1, 2, 3]
>> first, *rest = args

>> first
=> 1

>> rest
=> [2, 3]

I knew about splitting up arrays before into individual arguments, but I didn’t know that you could easily get an array of the rest. Perhaps this is Lisp inspired?

Hash#fetch

>> items = { :apples => 2, :oranges => 3 }
=> items = {:apples=>2, :oranges=>3}

>> items.fetch(:apples)
=> 2

>> items.fetch(:bananas) { |key| "We don't carry #{key}!"}
=> We don't carry bananas!

This is just a nice little way to provide some default behavior that might be nicer than checking if the value exists in the hash first.

Hash#new with a block

>> smash = Hash.new { |hash, key| hash[key] = "a #{key} just got SMASHED!" }
=> {}

>> smash[:plum] = "cannot smash."
=> {:plum=>"cannot smash."}

>> smash[:watermelon]
=> {:plum=>"cannot smash.", :watermelon=>"a watermelon just got SMASHED!"}

This is a really neat way to cache unknown values for Hashes (read: memoization!) I also heard it’s awesome for implementing a Fibonacci sequence.

Array#sort_by

>> cars = %w[beetle volt camry]
=> ["beetle", "volt", "camry"]

>> cars.sort_by { |car| car.size }
=> ["volt", "camry", "beetle"]

So, Array#sort_by sorts based on the return value of the block. It’s like a built in #map and #sort that rules even more with some Symbol#to_proc magic.

String#present?

>> "brain".present?
=> true

>> "".present?
=> false

I’m sure most Rails developers know about blank? from ActiveSupport, but what about present?. Yeah, it blew my mind too. I like being as positive as possible in conditionals, so toss out those !something.blank? calls today and start using this.

Tagged:

Comments (View)

Exploring Coding Guidelines

At thoughtbot we’ve recently started to redo our outdated coding standards. We’ve taken a “consider everything” approach. Making the easy decisions by fiat and then opening up some of the decisions to the discussion of the entire group (with one person given ultimate responsibility for making the decision).

Its going well, but slowly, so far. When we did our original coding guidelines a few years ago, it was decided that we would use a different curly brace style than I was used to at the time. It took about two days to get used to the new format, but then after that, I couldn’t see them any other way.

My main point is that when it comes to guidelines, its important to be flexible. What may seem the “wrong” way to do something one day, can easily be the right way the next, if you commit to the change. And as programmers, what we may think are permanently ingrained ways of doing things, can almost certainly be changed.

Our new coding guidelines are pretty much entirely Ruby and Ruby on Rails focused. Anyone out there have any helpful hints or interesting stories about your Ruby and Rails coding guidelines?

Tagged:

Comments (View)

Leaving business in the office

I’ve always had a hard time separating work and home. The problem with being paid to think is that you don’t need your tools in front of you to keep working; you have your brain, you’re good to go. The problem comes in where you can’t stop thinking about a particular difficulty in your job.

I attended Mike’s 30th birthday party on Friday, but was completely unable to get a problem out of my head that had been there since the beginning of November or so (and before you ask, no I didn’t work it out Friday either). Unfortunately, the inability to stop thinking (which is one of my big personal faults), plus the fact that I didn’t know anyone else there, plus the fact that I’m a habitual pacer meant that for the first hour or so I was just walking around a room of people I don’t know, not talking to anyone. I must have looked like a complete loon!

And to think, this could have been avoided if Java would have allowed multiple inheritance. But that’s a post for another time.