Saturday 12 July 2008

Primary Keys that mean something in Rails

I like Rails, I really do, but you like me may need to migrate a data-model that doesn't use locally generated integers as the primary keys (PKs) of entities.

Rails, by default, seeds model (entity) data with unique identifiers it uses as PKs from database specific mechanisms (say serial columns in PostgreSQL).

Personally, I think locally generated integers used as identifiers for "things", are most poor for two main reasons:

  1. They don't scale.  Your local postgreSQL instance may just be part of a wider data architecture.  If you use locally generated PKs as Rails will get you to, you'll clash with other locally (but possibly the same value) PKs from other local DBs when synchronization with the "master" takes place.  If you don't know what I'm talking about then go look at scaling databases, its not just a postgreSQL issue.  If you really really have/want/need to use an identifier, then there is a good article of using Universal Unique Identifiers (UUID) for IDs at GUID-as-Primary-in-Rails although the article doesn't follow through on how in precise detail, so we'll cover that in a later blog.
  2. They don't mean anything.  Say for example you have an entity type HillType, whose PK should really be a unique and meaningful name.   If you really wanted to find out about the details of a HillType of name gnarly you'd want to enter a RESTful-like URL of http://localhost:3000/hilltypes/gnarly.  You really wouldn't want to know the mapping between "gnarly" and some internal ID that Rails generated for you via a DB sequence.  Yes, I know there are ways of RESTfulizing the internal ID to some other attribute on the model/table, but avoid the complexity in the first place, and under-populate your database with data it actually needs to be readable by people not frameworks.

So ..........  After those contentious points, how do we do it?

Three steps to RAILS CRUD with meaningful PKS

1) Unfortunately, I've not found a way round of avoiding an integer PK in Rails, unless either you specify your migration without a PK, or you avoid the Rails meta-DDL completely and go native in your migration as in here:-

class CreateHillTypes < ActiveRecord::Migration
  def self.up
      # -------------------------------
      # This is postgreSQL specific DDL
      # -------------------------------
      execute <<-EOF
        create table public.hill_types (
            typename varchar(255) not null unique,
            description varchar(2000),
            primary key (typename)
        );
      EOF
  end

  def self.down
    drop_table "hill_types"
  end
end

2) We also need to ensure our Rails "knows" we've provided a non-standard PK, so we need to amend our model:-

class HillType < ActiveRecord::Base
    set_primary_key "typename"
end

3) Next we need to amend the templated controller that script/generate scaffold HillType generated for us to avoid attempting to mass-assign what is now a protected attribute (typename) on our model.  So, on the create method Rails makes for you by default in your model controller, you'll need something like:

def create
      @hill_type = HillType.new
      got_details = params[:hill_type]
      @hill_type.typename = got_details["typename"]
      @hill_type.description = got_details["description"]
      respond_to do |format|
          if @hill_type.save
              flash[:notice] = 'HillType was successfully created.'
              format.html { redirect_to(@hill_type) }
              format.xml  { render :xml => @hill_type, :status => :created, :location => @hill_type }
          else
              format.html { render :action => "new" }
              format.xml  { render :xml => @hill_type.errors, :status => :unprocessable_entity }
          end
      end
  end

That is it.  Go gambol in the fields of  meaningful URLs, and leave out meaningless framework convenience identifiers from your models, they are embarrassing.

Thursday 3 July 2008

Rails, XHTML and using your own CSS styles

Got to migrate your CSS and your standard layouts to rails? Read on ......

A Ruby on Rails (RoR) app holds three key locations you'll need to know about to re-use your imagery, your Cascading Style Sheet (CSS) look and feel, and your standard layouts.

  1. <project_name>/public/images: Put your logos and your bits and pieces in terms of iconography in here.
  2. <project_name>/public/stylesheets: Put your CSS files (holding all your standard page styling) in here.
  3. <project_name>/app/views/layout/application.html.erb: This is where we can define our standard layout to apply for our pages.

Standard Layout

So, for example, my application.html.erb looks like this:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
    <html>
    <!-- This is a standard wrapper for all view content -->
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>Sample Rails common layout wrapper</title>
        <!-- Include all our styles as pulled from styles.css -->
        <%= stylesheet_link_tag 'styles' -%>
        <!-- Include all the standard Rails javascript includes -->
        <%= javascript_include_tag :defaults -%>
    </head>
    <body>
        <div id="wrapper">
            <!-- The page content -->
            <div id="content">
                <!-- Standard header to page -->
                <div id="header">   
                    <div style="float:left; margin-top:15px;">
                        <a href="http://timepoorprogrammer.blogspot.com">
                            <img alt="" src="images/author.png" style="border-style: none"/>
                        </a>
                    </div>
                </div>
                <!-- The content that can we'll change dynamically later -->
                <div id="dynamicPanel">
                    <!-- Yield to whatever local content Rails expects -->
                    <%= yield -%>
                </div>
                <!-- Standard footer to page -->
                <div id="footer">Copyright &copy; 2008 me</div>
            </div>
        </div>
    </body>
</html>

There are a few things here:

  1. The doc type is xhtml.  If you open this up in Firebug or in HTML Tidy from your Firefox browser (get it if you haven't already done so), they'll both tell you this is a full on dynamic HTML page.
  2. The javascript_include_tag is embedded Ruby that ensures your pages include the standard JS files that come with RoR 2.1
  3. The style_sheet_link_tag points off to the public/stylesheets location where you put your CSS.
  4. The example img tag points off to the public/images location, yet doesn't need the public in the actual definition
  5. The <%= yield -%> marker tells RoR to put the content of your views you may have defined or will be defining (under  app/views/<controller_name> within the standard RoR structure) .

Now go restart your rails, and have a look at your views now, and bask in the fact that they'll be using your styles and common layout now.

Note: If you are puzzling over what app/views/controller_name means and aren't sure what the names of RoR files should be to express, say controllers, views, models, and how they relate to your RoR file naming conventions and URLs, go look at http://peepcode.com/products/rails-from-scratch-part-i for a particularly good introduction.

Rails and PostgreSQL for beginners

I've read a few blogs out there on using Ruby on Rails with the very excellent database postgreSQL.  But, I'd not found more than a do the installations and off you go kind of thing.  Given most of the online tutorials assume you click on "About your application's environment" to ensure all your rails malarky is working okay, I thought it sensible to tell you how.

Note: This blog assumes you have the free Aptana studio for RadRails installed, ruby installed on windows, both the rails and the postgres-pr gems installed for ruby, and you've got Postgresql up and running on your local box.  Honestly, there are
a load of online examples of installing Ruby, Rails, Aptana studio, and postgresql for Windows.  I'll not cover them here.

1) Start your postgresql server.

2) Create a directory.

3) Use Aptana Studio to point to this directory location, choosing postgresql as the DB of choice, and Aptana will create the basic sub-directory setup for rails you'll need. 

4) It will also start the rails server. 

5) About this time you'll choosing to view "About your application's environment" to find out what you've got.  This will fail.

Why?, well when you create a default rails project in Aptana Studio, or any other tool that creates the rails structures you get a default database.yml in your top level directory, that you can use to setup your DBs with this project.

But, its default content.

So ................

a) Shutdown your rails server from the Aptana Studio server's tab, or however else you do it.

b) Create a file called newdb.bat in your top level directory of your new project

Its contents should look alot like:-

    psql -h localhost -U postgres <db\create.sql
call rake db:migrate

This allows you to logon to postgres from the windows command line to create the databases the project requires as defined in an SQL batch file.

c) This SQL batch file is called create.sql in your local project db directory, which should look alot (substitute the your_project and your_username, and your_password markers with your own details) like this (in Postgresql format):-

    /* Drop and re-create the development database */
    drop database if exists <your_project>_development;
    create database <your_project>_development
        with owner = postgres
        encoding = 'UTF8'
        tablespace = pg_default;

    /* Drop and re-create the test database */
    drop database if exists <your_project>_test;
    create database <your_project>_test
        with owner = postgres
        encoding = 'UTF8'
        tablespace = pg_default;

    /* Drop and re-create the production database */
    drop database if exists rails_from_scratch_part_one_production;
    create database <your_project>_production
        with owner = postgres
        encoding = 'UTF8'
        tablespace = pg_default;

    /* Drop the user if they exist, and re-create them */
    drop user if exists <your_username>;
    create user <your_username> with password '<your_password>';

    /* Grant the user all privileges on the databases */
    grant all privileges on database <your_project>_development to <your_username>;
    grant all privileges on database <your_project>_test to <your_username>;
    grant all privileges on database <your_project>_production to <your_username>;

5) Go to the windows command line.  Navigate to where your newdb.bat file lives, and run it from there.  This will setup three project databases for you in Postgresql, one for development, one for test, and one for production.

6) If you have properly installed Postgresql properly, you can check the DB content from your PgAdmin console.  With Rails
2.1.0 this will create a table called schema_migrations in each of your three databases.  We'll come back to this soon once we've got tables to make.

6) Refresh your project in Aptana studio or whatever.  Now restart your server from the server list tab, or however you do it
and THEN click (or in my case double click) on "About your Application's environment". 

Hey Presto, you will now be presented with the environment, including the development database in which your project resides.

You are now up and running with postgresql and rails.