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
- I haven't been able to find a license for this data. Although I'm guessing it's Creative Commons Attribution-DontDoAnythingStupid. ↩