Dr Nic

[BTS] Dr Nic’s Civilizations 2.0

I’m not promising there ever will be a Dr Nic’s Online Civilizations game, but it has seemed like a good idea for a few weeks, and today I went from ASCII map through to beautiful CSS rendering of the map.

6am
ASCII to HTML mapping
8am
Base tiles
6pm
Correct edges
9pm
Convert table to div structure
Simple layout of map Basic tiles Correct edges Removed table structure

I miss home.

TilesheetThis was a technically interesting process. The images used are actually all on the one base tile sheet [1], and each panel of the map is allocated a separate CSS class relating to the specific tile required.

So for example, to show the base grasslands tile, you’d need some CSS like:

.tiles {height: 30px; width: 30px; background: url(tiles.png) top left no-repeat; ...}
.grasslands {background-position: 0px -30px;}

For a desert tile, with some grasslands to the left, you might use:

.desert-left {background-position: -60px -60px;}

This really gives a nice tiling effect without you needing to do any fancy image manipulations or the user loading up lots of small images. You just assign the appropriate CSS class to the panel (a DIV or TD, for example). Want to change a tile? Change the class (e.g. using Prototype/jQuery libraries)

Yes, this solution requires lots of individual CSS class definitions; but you’re clever – you’ll generate them won’t you :)

Reader assistance required

The one technical trick that I don’t have a solution for at the moment is a Google Maps-like interface for scrolling map around. I’d love to hear from anyone with some bright thoughts on how they implement that (I don’t want to have to try to read their compact Javascript code).

Any thoughts?

[1] The source images came from the FreeCiv GPL game

[BTS] Magic Models vs ActiveRecords – Efficiency

Dr Nic’s Magic Models are magical, that goes without saying. But are they efficient at being magical? Do you get the same speed in your application as if you explicitly defined your classes and your associations will your application execute faster?

Yes. The cost of accessing a Magic Model and a normal ActiveRecord is exactly the same – once the Magic Model has been initially generated. Its just a normal ActiveRecord like all your other ActiveRecords: they are loaded into memory when you first ask for them. Normal ActiveRecords are loaded into memory from your /app/models/.rb file; Magic Models are generated classes. The Magic Models would in fact be quicker to load as there is no file access required.

The cost of accessing Automatic Associations versus normal Active Record associations is also exactly the same. To understand the inner workings of associations, see err.the_blog’s latest article.

The point to take away from the discussion is that associations are a generated method on your class. Once you’ve generated it once (at the time you call has_many for example), you have it permanently. So there is only a small, one-time cost for the Automatic Associations – to see which association to generate for the incoming method call. But once you’ve generated the association, its yours permanently.

[BTS] Magic Models – Class creation

[BTS] = Behind the Scenes; also a news-like TV show we used to watch as school kids (in Australia) that explored world news events and then our teachers would make us write reports about it. (UPDATE: this is a lie; it was BTN – Behind the News) Anyway….

Class creation – the magical way

As an educational exercise, it is definitely interesting to look at how the Magic Model’s Invisible Classes [1] are generated. Inside the gem [2], please turn to the file /lib/module.rb I’ve broken it up below too.

class Module
  alias :normal_const_missing :const_missing

  def const_missing(class_id)
    begin; return normal_const_missing(class_id); rescue; end

If you open the Irb/Console and type Foo, you’ll get an “unitialized constant Foo” error. This is raised by the const_missing method in the Module class. Yes, there’s a core class called Module. Type Module.methods.sort to see all the fun things it can do, or look up the API.

Like so many things in Ruby you can override this method, just like you might override the method_missing on a class (Rails does this on its ActiveRecord to support the find_by_... magical methods.)

When you load Magic Models (via the require 'dr_nic_magic_models' statement) it renames/aliases the current const_missing method and then overrides it.

The first thing it does it call the original const_missing to see if it returns anything useful. Why? Rails also overrides this method. Rails doesn’t preload all your model classes at start up. Instead when you first ask for a model class, it loads it via the const_missing method call. So Magic Models won’t do anything until Rails has had a chance to try an load the class from a app/models/foo.rb file, for example.

    unless table_name = DrNicMagicModels::Schema.models[class_id]
      raise NameError.new("uninitialized constant #{class_id}") if
                    DrNicMagicModels::Schema.models.enquired? class_id
    end

The DNMM::Schema class is in charge of mapping an unknown class name (class_id) to one of your database’s tables. To prevent nasty infinite recursion, we raise the “uninitialized constant” error if another const_missing request comes in for the same class_id.

    klass = Class.new ActiveRecord::Base
    const_set class_id, klass

The Class API takes a superclass as a parameter. Here, we want our Invisible Model to be an ActiveRecord.

By setting the anonymous class object to a constant value, e.g. Foo, it automatically names the class. E.g. klass.name == 'Foo'

    klass.set_table_name table_name

Finally, we force the new ActiveRecord class to use the table name that DNMM::Scheme found. You just can’t trust that the class will know what its table name is.

If you find yourself using this sort of code, then here’s some bonus code. When you create your class, you can install some code at the same time.

    klass_code = %q{def to_s; 'I''m magically generated'; end }
    klass_code = lambda {klass_code}
    klass = Class.new ActiveRecord::Base, &klass_code

Feedback

Was this easy to read? Too compact/too long? Let me know below.

If you go on and build something magically cool after reading this, definitely let me know.

Moral of the Story

You can live as a programmer without ever knowing any meta-programming features of Ruby. Heck, we all did it in Java/C++/C# and never knew we needed them. A lot of powerful API features exist in Rails that you’d never be able to build in many other languages; the ActiveRecord library is a tribute to meta-programming magic.

UPDATE: I had previously called Module a module, when its a class. Fixed.

[1] I just like calling them Invisible Models.

[2] Gems are installed inside your Ruby installation folder. Try something like ....\lib\ruby\gems\1.8\gems\ and you'll see all your favourite gems, like rails-1.1.5, active-record-1.14.4, etc.