K.R.C. LogoThe Book of Kara

Setting up A Rails Application for EVE Online

Published 6 February 2008

Hi! You've stumbled upon a blog post by a guy named Ryan. I'm not that guy anymore, but I've left his posts around because cool URIs don't change and to remind me how much I've learned and grown over time.

Ryan was a well-meaning but naïve and priviledged person. His views don't necessarily represent the views of anyone.

As I mentioned before, I've be having a lot of fun with EVE Online. The attraction, however, hasn't been just to the ability to fly around in a ship and blast pirates (as if that weren't enough!). EVE has a pretty excellent API, an browser that provides in-game information, and even dumps of art and data for developers to use.

I've already got a few ideas on fun things to do with all of this data. The trick is trying to make opinionated Rails play nice with the browser, the API and the Data. To that end, here are the strategies I'm invoking. Comments are, of course, welcome.

Dealing with Data

I can't even begin to wrap my brain around the cool things you can do with EVE data. A number of desktop applications exist for character tracking and skill planning, but that's only a very shallow bit of what's possible. Getting the data into manageable form, however, is a bit tricky.

I wanted to keep my EVE data separate from my game data. Presumably EVE's data will change over time. Ideally a new dump will come out and I can just swap it with my current one--no fuss, no muss. I downloaded the Trinity 1.0 SQLite3 dump. And dropped it into db/ of my Rails application. Then I had to make sure to point all EVE data to the correct database.

config/datbase.yml:

eve_development:
  adapter: sqlite3
  database: db/trinity_1.0_sqlite3.db
  timeout: 5000
eve_test:
  adapter: sqlite3
  database: db/trinity_1.0_sqlite3.db
  timeout: 5000
eve_production
  adapter: mysql
  database: *******
  username: ********
  password: ********
  host: *******

and

app/models/eve_data.rb:

class EveData < ActiveRecord::Base
  establish_connection "eve_#{RAILS_ENV}"
end

Now any model that subclasses EveData will point to the correct database and key:

app/models/race.rb:

class Race < EveData
  set_table_name :chrRaces
  set_primary_key :raceID
  has_many :careers, :foreign_key => :raceID
end

The tedious part of this is setting up all of the associations, as EVE data does not conform to rails column naming conventions (although it's not unimaginable that one could fix this with an elegant script).

One last note: if you generate any sub-classes of EveData using the built-in generators, be sure to remove test/fixtures/<subclass>.yml as it will cause you tests to die horrible deaths.

API Wrangling

Rails has a lot of built-in API magic, all of which is absolutely worthless with EVE. Consuming the EVE API requires a lot of anonymous XML parsing. Boooooring! Luckily, Lisa Seelye has an excellent wrapper module for the EVE API. So long as you're okay with her clear-as-mud license, Lisa's work will be a great benefit.

Managing the EVE Browser

Update: I've written up a slightly better way to target the EVE Browser in Rails 2.0.

The EVE Online browser stinks. I got only halfway through the HTML 4.01 test suite before quitting out of frustration. I'm pretty sure it can't even render GIF images. I'd love to see them bundle WebKit instead. One thing the browser does, however, is provide custom HTTP headers (without the required X- in front, of course) for in-game EVE data. Custom headers are available to the controller in the request.env collection. I made them available to all controllers and views with the following:

app/controllers/application.rb:

class ApplicationController < ActionController::Base
  ...
  # Provides access to views
  helper_method :eve?
  helper_method :trusted?
  helper_method :eve_data

  private
    def eve?
      !! request.env['HTTP_EVE.TRUSTED']
    end
    def trusted?
      request.env['HTTP_EVE.TRUSTED'] == "yes"
    end
    def eve_data
      if trusted?
        {
          "name" => request.env['HTTP_EVE.CHARNAME'],
          "id" =>  request.env['HTTP_EVE.CHARID'],
          "alliance" => request.env['HTTP_EVE.ALLIANCENAME'],
          "region" => request.env['HTTP_EVE.REGIONNAME'],
          "constellation" => request.env['HTTP_EVE.CONSTELLATIONNAME'],
          "solar system" => request.env['HTTP_EVE.SOLARSYSTEMNAME'],
          "station" => request.env['HTTP_EVE.STATIONNAME'],
          "corporation" => request.env['HTTP_EVE.CORPNAME'],
          "role" => request.env['HTTP_EVE.CORPROLE']
        }
      end
    end
end

The eve? and trusted? methods return booleans and eve_data a hash of the character's current location in-game. They allow me to write helpers such as

app/helpers/application_helper.rb:

def character_image_tag(id, size, options = nil)
  if eve?
    o = { :src => "#{src}:#{id}", :size => "#{size}" }
    o.merge!(options) if options.is_a(Hash)
    tag(:img, o)
  else
    o = { :width => "#{size}", :height => "#{size}" }
    o.merge!(options) if options.is_a(Hash)
    image_tag("http://img.eve.is/serv.asp?s=#{size}&c=#{id}", o)
  end
end

Guessing by the speed of the browser, using in-game resources instead of external will save the user a lot of time.

As players fly around EVE, the application has the ability to detect a location change and refresh the page, updating the location information. Applications could work in EVE from only one URL, even without JavaScript. In order to do so, however, the application must do some header-gymnastics as well. My take on this is something akin to:

app/controllers/foo_controller.rb:

class FooController < ApplicationController
  def index
    # Tell the client your content will change
    # Not necessary, but correct and future-proof
	response.headers["Vary"] = "eve.trustme"
    if ! eve?
      # Do something to the regular browser
      render :action => "external"
    elsif ! trusted?
      trust_me
      render :action => "trust_me"
    else
      lookup
      render :action => "lookup"
    end
  end

  def external
    ...
  end

  def trust_me
    # Send the trust header
    response.headers["eve.trustme"] = "http://#{request.env['HTTP_HOST']}/::Trust me."
    ...
  end

  def play
    # Send the auto-refresh header
    response.headers["refresh"] = "sessionchange;URL=#{url_for(:controller => 'foo')}"
    ...
  end

end

Each action will then have its own view file and its own logic, but the same URL. Be careful where you send the auto-refresh header: create, update and delete actions should not get them. Note too that the trust_me action should end with render => :nothing or similar. This does not work. The EVE browser requires a text/html file to trigger the trust prompt.

And that's it! You're on your way to a Rails-powered EVE application. Please send me any questions, critiques or issues. I'll post more often with other quirks I pick up.

UPDATE: Modified the database set-up to use my database.yml for EVE data and connect EVE data to my environments. Also moved establish_connection into the model where it should be. I also added a note on fixtures that took me a silly amount of time to work around.

Notes

  1. I haven't been able to find a license for this data. Although I'm guessing it's Creative Commons Attribution-DontDoAnythingStupid.
K.R.C. LogoPrevious: Football company seeks the moderately webbyNext: EVE on Rails - Creating an EVE in-game-optimised version of your Rails site