Nate Wiger in San Diego, CA Musings on software

Jekyll Plus Twitter Bootstrap on S3

I’ve decided that modern web development is largely a test of your personal pain threshold. My blog redesign goals seemed reasonable: A responsive, mobile-friendly page layout, easily searchable by Google, that could be edited locally and then pushed to a hosting service. At times it felt like I was building the Curiosity Rover II.

I’ve decided that modern web development is largely a test of your personal pain threshold. My blog redesign goals seemed reasonable: A responsive, mobile-friendly page layout, easily searchable by Google, that could be edited locally and then pushed to a hosting service. At times it felt like I was building the Curiosity Rover II. My main goal was to create a responsive page design that would work equally well on laptops, iPhones, iPads, Kindles, and so on. When I’m reading on an iWhatever, it drives me nuts when I hit a blog with 10pt font that I have to pinch in and swipe back and forth to read. But creating a fluid layout isn’t as easy as just shrinking things; you need to reshuffle the layout, as this excellent slide deck highlights.

So after a good amount of brain stuffing on responsive design, I settled on the following packages to make this all happen:

Some of these worked great. Others made me want to club a baby seal.

Laid Out

Here’s a comparison of my finished blog layout on a laptop:

Laptop layout - full page

And on a mobile device:

Mobile layout - top
Mobile layout - bottom

On smaller screens, notice how the search bar remains up-top, under the title. My bio, on the other hand, shifts all the way to the bottom, after the article. I wanted people to hit my content first, and then my smiling face after they had read the article, so they could look at it with either adoration or disgust.

If we breakdown

Here’s my core page structure:

<div class="row-fluid" id="topbar">
  <div class="span6" id="blogname">
    <a class="brand" href="/">Nate Wiger in San Diego, CA</a>
    <em>Musings on software</em>
  </div>
  <div class="span6" id="searchbox">
    <!-- Google custom search box -->
    <gcse:searchbox></gcse:searchbox>
  </div>
</div>
<div id="searchresults">
  <!-- Google custom search result -->
  <gcse:searchresults></gcse:searchresults>
</div>
<div class="row-fluid">
  <div class="span9">
    <div class="index-content">
      <!-- Article or list of posts -->
      <p>With the launch of <a href="http://aws.typepad.com/aws/2013/09/amazon-elasticache-now-with-a-dash-of-redis.html">AWS ElastiCache for Redis this week</a>, I realized my <a href="http://github.com/nateware/redis-objects">redis-objects gem</a> could use a few more examples.  Paste this code into your game’s Ruby backend for real-time leaderboards with Redis. <!--more--> <a href="http://redis.io/topics/data-types">Redis Sorted Sets</a> are the ideal data type for leaderboards. This is a data structure that guarantees uniqueness of members, plus keeps members sorted in real time. Yep that’s pretty much exactly what we want.  The Redis sorted set commands to populate a leaderboard would be:</p>
ZADD leaderboard 556  "Andy"
ZADD leaderboard 819  "Barry"
ZADD leaderboard 105  "Carl"
ZADD leaderboard 1312 "Derek"

This would create a leaderboard set with members auto-sorted based on their score. To get a leaderboard sorted with highest score as highest ranked, do:

ZREVRANGE leaderboard 0 -1
1) "Derek"
2) "Barry"
3) "Andy"
4) "Carl"

This returns the set’s members sorted in reverse (descending) order. Refer to the Redis docs for ZREVRANGE for more details.

Wasn’t this a Ruby post?

Back to redis-objects. Let’s start with a direct Ruby translation of the above:

require 'redis-objects'
Redis.current = Redis.new(host: 'localhost')

lb = Redis::SortedSet.new('leaderboard')
lb["Andy"]  = 556
lb["Barry"] = 819
lb["Carl"]  = 105
lb["Derek"] = 1312

puts lb.revrange(0, -1)  # ["Derek", "Barry", "Andy", "Carl"]

And… we’re done. Ship it.

Throw that on Rails

Ok, so our game probably has a bit more too it. Let’s assume there’s a User database table, with a score column, created like so:

class CreateUsers < ActiveRecord::Migration
  def up
    create_table :users do |t|
      t.string  :name
      t.integer :score
    end
  end
end

We can integrate a sorted set leaderboard with our User model in two lines:

class User < ActiveRecord::Base
  include Redis::Objects
  sorted_set :leaderboard, global: true
end

Since we’re going to have just a single leaderboard (rather than one per user), we use the global flag. This will create a User.leaderboard sorted set that we can then access anywhere:

puts User.leaderboard.members

(Important: This doesn’t have to be ActiveRecord — you could use Mongoid or DataMapper or Sequel or Dynamoid or any other DB model.)

We’ll add a hook to update our leaderboard when we get a new high score. Since we now have a database table, we’ll index our sorted set by our ID, since it’s guaranteed to be unique:

class User < ActiveRecord::Base
  include Redis::Objects
  sorted_set :leaderboard, global: true

  after_update :update_leaderboard
  def update_leaderboard
    self.class.leaderboard[id] = score
  end
end

Save a few records:

User.create!(name: "Andy",  score: 556)
User.create!(name: "Barry", score: 819)
User.create!(name: "Carl",  score: 105)
User.create!(name: "Derek", score: 1312)

Fetch the leaderboard:

@user_ids = User.leaderboard.revrange(0, -1)
puts @user_ids  # [4, 2, 1, 3]

And now we have a Redis leaderboard sorted in real time, auto-updated any time we get a new high score.

But MySQL has ORDER BY

The skeptical reader may wonder why not just sort in MySQL, or whatever the kewl new database flavor of the week is. Outside of offloading our main database, things get more interesting when we want to know our own rank:

class User < ActiveRecord::Base
  # ... other stuff remains ...

  def my_rank
    self.class.leaderboard.revrank(id) + 1
  end
end

Then:

@user = User.find(1) # Andy
puts @user.my_rank   # 3

Getting a numeric rank for a row in MySQL would require adding a new “rank” column, and then running a job that re-ranks the entire table. Doing this in real time means clobbering MySQL with a global re-rank every time anyone’s score changes. This makes MySQL unhappy, especially with lots of users.

Kids are calling so that’s all for now. Enjoy!

    </div>
  </div>
  <div class="span3">
    {\% include sidebar.html \%}
  </div>
</div>

http://blog.teamtreehouse.com/beginners-guide-to-responsive-web-design

DNS

$ dig +short allthingsdistributed.com
174.129.25.170
$ dig +short www.allthingsdistributed.com
s3-website-us-east-1.amazonaws.com.

Twitter Bootstrap was the real enabling tech here

Other Cool Resources