Thursday, December 20, 2007

Rails v2.0 with IIS MSSQL ISAPI_Rewrite3

Intro

This is an update to my blog on running rails with IIS and Mondrel using MSSQL, This update details the steps for a rails v2.0 application (rather than 1.8)

My Rails app is not heavely used (2 full time, several casual users), so a proxy/redirect from IIS to single Mongrel seemed ideal.

IIS will still service up the static/public content, and all other requests are forwarded to Mongrel(s). Using ISAPI_Rewrite3 from Helicon Tech

ISAPI_Rewrite3 is cool it mimics Apache mod_rewrite.

Also I needed to re-document the install/set-up so here it is.

Install Software

Overview

  • Ruby is installed in C:\ruby
  • Rails applications are under C:\rails
  • Rails application is called "rapp" (Rails application). And in C:\rails\rapp
  • SQL Express is the database.
  • A install CD with all relevant software/gems is available on R:\downloads

Install MSSQL

Install MSSQL with a named instance SQL$RAPP this will allow you to shutdown one rails application DB, without affecting others.

> cd R:\downloads
> dotnetfx.exe  # Install .Net Frame work 2.0 
> SQLEXPR32-SP2.EXE # MS SQl Server 2005 Express with SP2,
Untick "Hide Advanced Options" to allow a named instance to be insalled  
# Used Named Instance "RAPP" based on name of rails application
# Used mixed authentication, "sa" passsword "*********"
> SQLServer2005_SSMSEE-SP2.msi # MS Sql Server Managment Studio  with SP2

Configure MSSQL

Rails connects to MSSQL using tcp/ip. This is not enabled by default

> SQLServerManager.msc # Start "SQL Server Configeration Manager"

SQL Server Network Configuration -> Protocols for RAPP -> Enable TCP/IP
SQL Native Client Configuration -> Client Protocols -> Enable TCP/IP 

Install Ruby

This is included for completeness

> cd R:\downloads
> ruby186-25.exe # Install ruby # source: ruby 
# Select Install "Ruby" and "Enable RubyGems"
# Install to "C:\ruby 
# Check C:\ruby\bin to your everyones path !!
# If not added, My Computer -> Properties -> 
        Advanced ->  Environment  Variables -> System Variables

> cd R:\downloads\rubygems-0.9.4  # source: rubygems   
# NOTE: Use 0.9.4 as mongrel does not install correctly with 0.9.5 
> ruby setup.rb # Install ruby gems

Install Rails/Mogrel

> cd C:\downloads
> gem install rake # http://rubyforge.org/projects/rake/
Successfully installed rake-0.7.3

> gem install rails # "http://rubyforge.org/projects/rails/
Successfully installed activesupport-2.0.1
Successfully installed activerecord-2.0.1
Successfully installed actionpack-2.0.1
Successfully installed actionmailer-2.0.1
Successfully installed activeresource-2.0.1
Successfully installed rails-2.0.1

> gem install mongrel # pick the win32, # http://mongrel.rubyforge.org/
Successfully installed gem_plugin-0.2.3
Successfully installed cgi_multipart_eof_fix-2.5.0
Successfully installed mongrel-1.1.1-mswin32

> gem install mongrel_service --source http://gems.rubyforge.org  # pick the win32
Successfully installed win32-service-0.5.2-mswin32
Successfully installed mongrel_service-0.3.3-mswin32

Install MSSQL Drivers

Install MSSQL Drivers (DBI/ADO drivers)

> cd R:\downloads\ruby-dbi-0.1.1 # http://rubyforge.org/projects/ruby-dbi/
> ruby setup.rb config --with=dbd_ado
> ruby setup.rb setup
> ruby setup.rb install

Install sqlserver adapter (as now packaged seperately) - this is available from http://gems.rubyonrails.org

> gem install activerecord-sqlserver-adapter --source http://gems.rubyonrails.org

Install RMagick / Faster CSV

NOTE: When including rmagic in your rails app. Use the full name rmagic.rb, as depending on your paths rgagic.so may be included rather than rmagic.rb

> cd R:\downloads\rmagick-1.15.9 # http://rubyforge.org/projects/rmagick/ win32
> ImageMagick-6.3.5-8-Q8-windows-dll.exe
# Install to C:\ruby\ImageMagick
# Tick [x] Update executable search path.
> gem install rmagick-1.15.9-x86-mswin32-60.gem --local
# Note: Documentation is in 
# C:\ruby\lib\ruby\gems\1.8\gems\rmagick-x.x.x-win32\doc\index.html
> cd R:\downloads
> gem install fastercsv-1.2.0 --local

Install Iaspi Rewriter

> cd R:\downloads
> ISAPI_Rewrite3_00xx.msi # http://www.helicontech.com/isapi_rewrite/
# Install to C:\Inetpub\ISAPI_Rewrite3\
> Run C:\Inetpub\ISAPI_Rewrite3\Helicon Manager.exe
# And Register product # See: serialno.txt for the serial number

Patch and Patch

Patch Active Record - SQL Server Adapter

SQL 2005 differs from previous versions, it reports null columns as uppercase NULL and you need to patch the sqlserver_adapter.rb

# Patch  sqlserver_adapter.rb (See Rails Ticket #7733) for server 2005
> cd C:\ruby\lib\ruby\gems\1.8\gems\activerecord-sqlserver-adapter-1.0.0\
              lib\active_record\connection_adapters
> edit sqlserver_adapter.rb
# Line ~304: default = ...... "=~ /null/ ? nil"   ==> 
      "=~ /null/i ? nil"  # Case insensitive match on the word

Patch Action Controller - CGI Process

When preforming a re-direct the header is inspected for the real host when request has been forward, however rails does not strip any extra white space.

> cd C:\ruby\lib\ruby\gems\1.8\gems\actionpack-2.0.1\lib\action_controller
> edit cgi_process.rb

    def host_with_port_without_standard_port_handling
      if forwarded = env["HTTP_X_FORWARDED_HOST"]
        forwarded.split(/,\s?/).last
      etc ...

# Line ~87: forwarded.split(/,\s?/).last  ==> 
#    forwarded.split(/,\s?/).last.strip # strip extra spaces/tabs 

Patch SQL Server Adapter - to handel dates prior to 1970

This is detailed in a seperate post, Check out SOLVED: Rails + MSSQL dates prior to 1970

Create/Configure Rails Application

Create Rails Application

> mkdir C:\rails
> cd C:\rails
> rails rapp # Create rails app called rapp

Add in some addins

Active record "acts_as_list" is now a plugin add it in, I also recommend annotate_models from "Agile Developemnt With Rails"

> cd C:\rails\rapp
> gem install acts_as_list  --source http://gems.rubyforge.org
> ruby script/plugin install file://C:\downloads\annotate_models 
> ruby script/plugin install http:#svn.pragprog.com/Public/plugins/annotate_models

Create Rails "Hello World" Application

> ruby script/generate controller hello

Edit C:\rails\rapp\app\controllers\hello_controller.rb. And past in this code.

class HelloController < ApplicationController
   $render_count ||= 0
   def world
     $render_count += 1
     render :text =>  "<html><body><h1>Hello World</h1>
               Page rendered #{$render_count} times.</body</html>"
   end
end

Woops: We dont have a database yet, so remove the requirement

  Edit \rails\rapp\config\environment.rb
  # No database support, remove active_record etc ..
  config.frameworks -= [ :active_record, :active_resource, :action_mailer ]

Test basic application works with WebBrick

> cd C:\rails\rapp
> ruby script/server # Run WebBrick to test 
> iexplore.exe http://localhost:3000/hello/world 
# Should get hello world page
> Ctrl+break # Close WebBrick

Host using mongrel service

Perform the following steps to host rapp using mongrel

NOTE: We will be hosting this rails application under the relative url "/rapp"

NOTE: We only bind to localhost (127.0.0.1), since IIS will eventually forward locally to mongrel

The port chosen is 4004

> cd C:\rails\rapp
> mongrel_rails service::install -N rails_rapp -a 127.0.0.1 
         -c C:\rails\rapp -p 4004 -e production --prefix /rapp
# NOTE: adding --prefix /rapp, sets 
#      ActionController::AbstractRequest.relative_url_root = "/rapp"
> sc config rails_rapp start= auto
> net start rails_rapp
# To remove the service use: mongrel_rails service::remove -N rails_rapp

Permissions

Mongrel service will be running as SYSTEM or NETWORK SERVICE, You need to check/give the following permissions.

  • Give FULL permission for C:\rails\rapp\temp and C:\rails\rapp\log
  • Give FULL permission to TEMP (C:\WINDOWS\TEMP)
  • Give FULL permission to any other folders your rails app1 writes to (e.g C:\rails\app1\public\attachments )
  • Check Read/Execute permissions for C:\ruby\* and C:\ruby\ImageMagick
  • Check Read permission Under HKEY_CLASSES_ROOT\ADODB.Connection for both the ADODB.Connection and ADODB.Connection.X.X entries # mssql only

Test using mongrel service

Browse to http://localhost:4004/rapp/hello/world, Note all url's are now relative to rapp, This allows multiple rails apps to be hosted behind iis

> iexplore.exe http://localhost:4004/rapp/hello/world 
# Should get hello world page, Rendered for the 1st time 
# Press refresh a few times, See the counter grow.

Now to host behind IIS

Create a virtial directory, so IIS services up the public/static portion of the site, (i.e. C:\rails\rapp\public)

> mkdir C:\Inetpub\Logfiles
> inetmgr.exe # Change log file folder
  # Goto -> Default Web Site -> Properties -> Web Site Tab -> Logging properties
    Log file directory -> C:\Inetpub\Logfiles

> inetmgr.exe # Add in your application
  # Goto -> Default Web Site -> New -> Virtual Directory -> 
    Alias/Name -> rapp
    Directory/Path -> C:\rails\rapp\public
    Permissions -> Read + Scripts + Executables
    
    # Note: Need Execute otherwise proxy/forwarding does not work !!!! 

Test static portion

Browse to http://localhost/rapp/index.html - You should see the standard welcome to rails page

> iexplore.exe http://localhost/rapp/index.html 
# Should get the "Rails Welcome aboard" 

Configure ISAPI_Rewrite3 to forward to Mongrel

Confiure ISAPI_Rewrite to forward all http://host/rapp/* to mongrel on http://127.0.0.1:4004/rapp/*. NOTE: We do not forward requests contining a full stop (.), So files like .js and .css etc will be serviced up by iis

> C:\Inetpub\ISAPI_Rewrite3\Helicon Manager.exe
    Click -> Edit
    Enter your new rule
        RewriteProxy rapp/([^.]+)$ http://127.0.0.1:4004/rapp/$1

Your "C:\Inetpub\ISAPI_Rewrite3\httpd.conf" file should look something like

# Helicon ISAPI_Rewrite configuration file
# Version 3.0.0.23
RewriteEngine on
RewriteBase /
RewriteProxy rapp/([^.]+)$ http://127.0.0.1:4004/rapp/$1

Rails .htaccess legacy
Remove/Rename the c:\rails\rapp\public\.htaccess file, as this is read by ISAPI_Rewrite but is designed for Apache !!!

> rename c:\rails\rapp\public\.htaccess c:\rails\rapp\public\apache.htaccess

Test mongrel/dynamic portion

Browse to http://localhost/rapp/hello/world - You should see the Hello World with counter from last test

> iisreset # restart iis
> iexplore.exe http://localhost/rapp/hello/world
# Should get hello world page, Rendered for the nth time 

Finished

You should now have a working rails application, behind IIS using Mongrel.
To add another application simply nominate a new relative url and repeat the above steps

Multiple Applications

When running multiple rails applications, Set the session key to be unique for each instance, otherwise you will be trying to share session keys across multiple apps.
> Edit \rails\rapp\controllers\application.rb
  config.action_controller.session = {
    :session_key => '_rapp_session',
    :secret      => '571 etc... a0263'
  }

Notes on: SSL/HTTPS and ISAPI_Rewrite3 with RewriteProxy

If you are running IIS with SSL, You need to pass to the backend application than fact you are running https://

The header needs to contain "X-Forwarded-Proto: https", ISAPI_Rewrite3 does not add this for you as it does for X-Forward-Host etc. So we need to do it ourselves if using https

Edit your "C:\Inetpub\ISAPI_Rewrite3\httpd.conf" to contain the following, Before any RewriteProxy

# Add "X-Forwarded-Proto: https" to the header so back end knows if running with ssl/https
RewriteCond %{HTTPS} ^on$
RewriteHeader X-Forwarded-Proto: .* https

RewriteProxy etc ....

Monday, December 17, 2007

SOLVED: Rails + MSSQL dates prior to 1970

Intro

MSSQL Microsft SQL Server only stores dates as datetime, As a consequence this is interperted by rails as a Time class, however a Time class cannot have dates prior to 1970, So effectively a date_select etc will raise an exception, for dates prior to 1970. If the column is in reality just a date, why not make it a date.

A have tested hoping rails v2.0 solves this but to no avail, So I needed to go back to my original solution.

My solution relies on manually changing the base type of such fields back to date. Hope this helps others.

What to patch/change

(1) We need to allow a model to change the type of a column. So we patch SQLServerColumn to allow the type to be changed

(2) Edit each model to change, the appropriate columns datatype after_initialize.

(3) Patch "SQLServerColumn" to return a date for a date.

Allow column type to be change

Create lib/sql_server_column_ext.rb

# Patch SqlServerColumn as may need to change column type from datetime to a date, as time cannot be -ve (< 1970) !!!
class ActiveRecord::ConnectionAdapters::SQLServerColumn
  def type=(val)
    @type=val
  end
end 

Edit config/environment.rb

require 'sql_server_column_ext.rb'

Change data type on column, after initalized

Edit model (e.g app/models/patient.rb)

  @@patched_date_of_birth_type=false
  def after_initialize
    if ( !@@patched_date_of_birth_type )
       date_of_birth_column = Patient.columns_hash['date_of_birth']
       date_of_birth_column.type= :date
       @@patched_date_of_birth_type=true
    end
  end

Fix up SQLServerColumn

Edit C:\ruby\lib\ruby\gems\1.8\gems\activerecord-sqlserver-adapter-1.0.0\lib\active_record\connection_adapters\sqlserver_adapter.rb

Implement code to return a date not DateTime when a column has a type of :date

Change "type_cast" method and introduce new method "cast_to_date". The code almost works without this change, but weird things (dates becomming null) because of issues related to converting between Date and Time and vise versa.

def type_cast(value)
   etc...
   when :date      then cast_to_date(value)  # was cast_to_datetime(value)
   etc...
end

def cast_to_date(value)
  return value.to_date if value.is_a?(Date) || value.is_a?(Time) || value.is_a?(DBI::Timestamp) # DateTime is_a? Date
  if value.is_a?(String) 
    date_array = ParseDate.parsedate(value)
    return Date.new(*date_array[0..2]) rescue nil
  end
  return nil
end

Finally

If there is a more elegant solution I would very much like to know

Tuesday, October 9, 2007

HOW TO: Rails under IIS with Mongrel and ISAPI_Rewrite3

Update - Dec - 2007

I have updated this entry for rails 2.0
Check out Rails v2.0 with IIS, ISAPI_Rewrite3, Mongrel

Intro

I have been running multiple rails apps under IIS using fast cgi, I was gettting very frustrated (No hair left), It seems to fault one a week. (Think this is related to the size of a post)

The Rails apps are not heavily used each with (~3 full time, several casual users), so a proxy/redirect from IIS to single Mongrel seemed ideal.

IIS will still service up the static/public content, and all other requests are forwarded to Mongrel(s). Using ISAPI_Rewrite3 from Helicon Tech

ISAPI_Rewrite3 is cool it mimics Apache mod_rewrite.

Also I needed to document the install/set-up so here it is.

Install Software

Overview

  • Ruby is installed in C:\ruby
  • Rails applications are under C:\rails
  • Rails application is called "rapp" (Rails application). And in C:\rails\rapp
  • SQL Express is the database.
  • A install CD with all relevant software/gems is available on R:\

Install SQL/Express

Install IIS with a named instance SQL$RAPP this will allow you to shutdown one rails application DB, without affecting others.

C:> cd R:\mssql
C:> dotnetfx.exe  # Install .Net Frame work 2.0 
C:> SQLEXPR32-SP2.EXE # MS SQl Server 2005 Express with SP2, 
# Used Named Instance "RAPP" based on name of rails application
# Used mixed authentication, "sa" password "you-choose"
C:> SQLServer2005_SSMSEE-SP2.msi # MS Sql Server Managment Studio  with SP2

Install Ruby

This is included for completeness

C:> cd R:\ruby
C:> ruby186-25.exe # Install ruby 
# Select Install "Ruby" and "Enable RubyGems"
# Install to "C:\ruby 
# Check C:\ruby\bin to your everyones path !!
# If not added, My Computer -> Properties -> Advanced ->  Environment  Variables -> System Variables
C:> cd R:\ruby\rubygems-0.9.4
C:> ruby setup.rb # Install ruby gems

Install Rails/Mogrel

C:> cd R:\ruby
C:> gem install activesupport-1.4.2 --local
C:> gem install activerecord-1.15.3 --local
C:> gem install actionpack-1.13.3 --localexit

C:> gem install actionmailer-1.3.3 --local
C:> gem install actionwebservice-1.2.3 --local
C:> gem install rails-1.2.3 --local
C:> gem install win32-service-0.5.2-mswin32 --local
C:> gem install gem_plugin-0.2.2 --local
C:> gem install cgi_multipart_eof_fix-2.3 --local
C:> gem install mongrel-1.0.1-mswin32 --local
C:> gem install mongrel_service  --local

Install MSSQL Drivers

C:> cd R:\ruby\ruby-dbi-0.1.1
C:> ruby setup.rb config --with=dbd_ado
C:> ruby setup.rb setup
C:> ruby setup.rb install

Patch Active Record - SQL Server Adapter

SQL 2005 differs from previous versions, it reports null columns as uppercase NULL and you need to patch the sqlserver_adapter.rb

# Patch  sqlserver_adapter.rb (See Rails Ticket #7733) for server 2005
C:> cd C:\ruby\lib\ruby\gems\1.8\gems\activerecord-1.15.3\lib\active_record\connection_adapters
C:> edit sqlserver_adapter.rb
# Line ~282: "=~ /null/ ? nil"   ==> "=~ /null/i ? nil"  # Case insensitive match on the word 

Install RMagick / Faster CSV

NOTE: When require RMagick in your rails app. Use the full name require 'RMagick.rb', as depending on your paths RMagick.so may be included rather than RMagick.rb

C:> cd R:\ruby\RMagick-1.14.1_IM-6.3.0-7-Q8
C:> ImageMagick-6.3.0-7-Q8-windows-dll.exe 
# Install to C:\ruby\ImageMagick-6.3.0-Q8
# Tick [x] Update executable search path.
C:> gem install rmagick-1.14.1-win32 --local
# Note: Documentation is in C:\ruby\lib\ruby\gems\1.8\gems\rmagick-1.14.1-win32\doc\index.html

Install Faster CSV

C:> cd R:\ruby
C:> gem install fastercsv-1.2.0 --local

Install Iaspi Rewriter

UPDATE 30-Oct-2006: You need to install build 29 or above, Prior versions doubled up on cookie data from other sessions. This effectively gave a new session, the session from the last person accessing the site !!! (A bit scary!!)
C:> cd R:\ISAPI_Rewrite3
C:> ISAPI_Rewrite3_00xx.msi
# Install to C:\Inetpub\ISAPI_Rewrite3\

Patch Action Controller - CGI Process

When preforming a re-direct the header is inspected for the real host when request has been forward, however rails does not strip any extra white space.

# Patch cgi_process.rb
C:> cd C:\ruby\lib\ruby\gems\1.8\gems\actionpack-1.13.3\lib\action_controller
C:> edit cgi_process.rb
# Line ~83: forwarded.split(/,\s?/).last  ==> forwarded.split(/,\s?/).last.strip # strip extra spaces/tabs 

Create/Configure Rails Application

Create Rails "Hello World" Application

C:> mkdir C:\rails
C:> cd C:\rails
C:> rails rapp # Create rails app called rapp
C:> cd rapp
C:> ruby script/generate controller hello

Edit C:\rails\rapp\app\controllers\hello_controller.rb. And past in this code.

class HelloController < ApplicationController
   $render_count ||= 0
   def world
     $render_count += 1
     render :text => "<html><body><h1>Hello World</h1>Page rendered #{$render_count} times.</body</html>"
   end
end

Test basic application works with WebBrick

C:> cd C:\rails\rapp
C:> ruby script/server # Run WebBrick to test 
C:> iexplore.exe http://localhost:3000/hello/world 
# Should get hello world page
C:> Ctrl+break # Close WebBrick

Host using mongrel service

Perform the following steps to host rapp using mongrel

NOTE: We will be hosting this rails application under the relative url "/rapp"

NOTE: We only bind to localhost (127.0.0.1), since IIS will eventually forward locally to mongrel. The port chosen is 4004

C:> cd C:\rails\rapp
C:> mongrel_rails service::install -N rails_rapp -a 127.0.0.1 -c C:\rails\rapp -p 4004 -e production --prefix /rapp
# NOTE: adding --prefix /rapp, sets ActionController::AbstractRequest.relative_url_root = "/rapp"
C:> sc config rails_rapp start= auto
C:> net start rails_rapp
# To remove the service use: mongrel_rails service::remove -N rails_rapp

Permissions

Mongrel service will be running as SYSTEM or NETWORK SERVICE, You need to check/give the following permissions.

  • Give FULL permission for C:\rails\rapp\temp and C:\rails\rapp\log
  • Give FULL permission to TEMP (C:\WINDOWS\TEMP)
  • Give FULL permission to any other folders your rails app1 writes to (e.g C:\rails\app1\public\attachments )
  • Check Read/Execute permissions for C:\ruby\* and C:\ruby\ImageMagick-6.3.0-Q8
  • Check Read permission Under HKEY_CLASSES_ROOT\ADODB.Connection for both the ADODB.Connection and ADODB.Connection.X.X entries # mssql only

Test using mongrel service

Browse to http://localhost:4004/rapp/hello/world, Note all url's are now relative to rapp, This allows multiple rails apps to be hosted behind iis

C:> iexplore.exe http://localhost:4004/rapp/hello/world 
# Should get hello world page, Rendered for the 1st time 
# Press refresh a few times, See the counter grow.

Now to host behind IIS

Create a virtial directory, so IIS services up the public/static portion of the site, (i.e. C:\rails\rapp\public)

C:> mkdir C:\Inetpub\Logfiles
C:> inetmgr.exe # Change log file folder
  # Goto -> Default Web Site -> Properties -> Web Site Tab -> Logging properties
    Log file directory -> C:\Inetpub\Logfiles

C:> inetmgr.exe # Add in your application
  # Goto -> Default Web Site -> New -> Virtual Directory -> 
    Alias/Name -> rapp
    Directory/Path -> C:\rails\rapp\public
    Permissions -> Read + Scripts + Executables
    
    # Note: Need Execute otherwise proxy/forwarding does not work !!!! 

Test static portion

Browse to http://localhost/rapp/index.html - You should see the standard welcome to rails page

C:> iexplore.exe http://localhost/rapp/index.html 
# Should get the "Rails Welcome aboard" 

Configure ISAPI_Rewrite3 to forward to Mongrel

Confiure ISAPI_Rewrite to forward all http://host/rapp/* to mongrel on http://127.0.0.1:4004/rapp/*. NOTE: We do not forward requests contining a full stop (.), So files like .js and .css etc will be serviced up by iis

C:> C:\Inetpub\ISAPI_Rewrite3\Helicon Manager.exe
    Click -> Edit
    Enter your new rule
        RewriteProxy rapp/([^.]+)$ http://127.0.0.1:4004/rapp/$1

Your "C:\Inetpub\ISAPI_Rewrite3\httpd.conf" file should look something like

# Helicon ISAPI_Rewrite configuration file
# Version 3.0.0.23
RewriteEngine on
RewriteBase /
RewriteProxy rapp/([^.]+)$ http://127.0.0.1:4004/rapp/$1

Rails .htaccess legacy
Remove/Rename the c:\rails\rapp\public\.htaccess file, as this is read by ISAPI_Rewrite but is designed for Apache !!!

C:> rename c:\rails\rapp\public\.htaccess c:\rails\rapp\public\apache.htaccess

Test mongrel/dynamic portion

Browse to http://localhost/rapp/hello/world - You should see the Hello World with counter from last test

C:> iisreset # restart iis
C:> iexplore.exe http://localhost/rapp/hello/world
# Should get hello world page, Rendered for the nth time 

Fininshed

You should now have a working rails application, behind IIS using Mongrel.
To add another application simply nominate a new relative url.

Multiple Applications

When running multiple rails applications, set the session key to be unique for each instance, otherwise you will be trying to share session keys across multiple apps.
C:> Edit \rails\app1\controllers\application.rb
class ApplicationController < ActionController::Base
  # Pick a unique cookie name to distinguish our session data from others'
  session :session_key => '_rapp_session_id'

Wednesday, July 4, 2007

A auto selector for a select "Autoselector.Local", like Autocompleter.Local

Background

I needed a text field to automatically select items from a select list, To assist the users when coding on a form.

Within "script.aculo" there is a similar function Autocompleter that autocompletes, given a list of possible choices, this is done client/browser side

The Solution

To develop a autoselector, this would take the text field to monitor, a list of keywords to match, and input select to update

The JavaScript Code

Place the below code into say "application.js"

NOTE: This code required prototype.js, effects.js and controls.js to be included

//========================================
// Autoselector.local 
// Contributors:
//  Murray Speight (http://mspeight.blogspot.com/)
//========================================

// Define a client side Autoselector.Local, like "Autocompleter.Local"
// Requires, prototype.js, effects.js and controls.js
//
// Parameters: id of element to watch, id to update, and array of [keyword,value] pairs 
// The keyword can be a regular expression e.g. "deep.*vein"

var Autoselector = {};
Autoselector.Local = Class.create();
Autoselector.Local.prototype = {
  initialize: function(element, update, keyvalue ) {
    this.element     = $(element); 
    if ( this.element ) {
        this.update      = $(update);  
        this.options = {};
        this.keyvalue = keyvalue;
        this.observer = null;
        this.element.setAttribute('autocomplete','off');
        observer=this.onKeyUp.bindAsEventListener(this);
        Event.observe(this.element, "keyup", observer );
     }
  },
  onKeyUp: function(event) {
    // Scan the text for keywords, using regular expressions 
    var text=this.element.value.toLowerCase();
    for (i=0;i<this.keyvalue.length;i++) {
      var re=new RegExp('\\b'+this.keyvalue[i][0]);
      if ( text.search(re) >= 0 ) {
        var newvalue = this.keyvalue[i][1];
        if ( this.update.value != newvalue ) {
          this.update.value = newvalue;
          // If has an onchange event, Call it
          if ( this.update.onchange )
            this.update.onchange.call(this);
        } 
        break;
      }
    } 
  }
}

Example HTML Usage

Below is an example usage. Typically the keywords/choices will be generated by the back end application. This is a tiedied up example of the generated html

NOTE:Ensure the call to new Autoselector.Local is called after the controls are defined

<input id="case_subject" maxlength="78" name="case[subject]" size="46" type="text" />
 
<select id="case_coding_id" name="case[coding_id]" onchange="my_on_change_coding(); void 0;">
  <option value=""></option>
  <option value="1">Cellulitis</option>
  <option value="2">Deep vein thrombosis</option>
  <option value="3">Respiratory Infections</option>
</select>       

<script type="text/javascript">
//<![CDATA[
  new Autoselector.Local('case_subject','case_coding_id',
       [["absces", 1], ["abces", 1], ["cellulit", 1], 
        ["dvt", 2], ["deep.*vein", 2], ["thrombo", 2], 
        ["pneum", 3], ["bromo", 3]]);
//]]>
</script>

Friday, June 22, 2007

A better group_by "in_groups_by" for ActiveRecord

Background

Active record's find returns an array, you can use group_by to put the result into a groups. However group_by returns a hash, a hash does not have any order, When you iterate thru the hash it comes out in any order.

Better Approach

Write a function to return a array of records grouped by some critiera (Like in_groups_of). Now you can simply iterate thru using each and each. In my implementation I have assumed the data is already ordered

The Code

class Array
  def in_groups_by
    # Group elements into individual array's by the result of a block
    # Similar to the in_groups_of function.
    # NOTE: assumes array is already ordered/sorted by group !!
    curr=nil.class 
    result=[]
    each do |element|
       group=yield(element) # Get grouping value
       result << [] if curr != group # if not same, start a new array
       curr = group
       result[-1] << element
    end
    result
  end
end

Go on Give it a go, Copy and paste the below code into say the bottom of "environment.rb", re-cycle the server, and try

An Example

def customers_grouped_by_country
  ds=Customer.find(:all, :order => :order=>"county_id")
  ds.in_groups_by(&:county_id)
  # or the alternative syntax 
  # ds.in_groups_by { |r| r.country_id }
end

<% customers_grouped_by_country.each do |group| %>
  <h1>Country <%= group[0].country_id></h1>
  <% group.each do |e| %>
    <p><%= e.name %></p>
  <% end %>
<% end >

Thursday, June 21, 2007

Tuesday, June 19, 2007

Disable Browsers Autocomplete on a Rails Form

Intro

Autocomplete on a form can cause the observe_field not to fire (I was using on => keyup, i am going to change to on => blur and test)

Also the users get confused if too many things on a form, start autocompeting

The Solutuion

Simply disable autocomplete, Tested on IE/Firefox. This can be done at the form level

<% form_tag( {:action => 'post'}, { :autocomplete => :off } ) do %>

Running Rails Apps More Reliability under IIS

Intro

I have been attempting to get the new FastCGI for IIS preview to run on IIS5/6 as detailed previously.

Yes it runs just, But is unrelaible when you perform http posts. However the exersise had one good side effect.

The Good News

The blog at 10 steps to get Ruby on Rails running on Windows with IIS FastCGI detailed a change you need to make to the cgi.rb. because IIS does not support No Parsed Headers (NPH)

Once this change was in place, My web site using the older fastcgi runs much more reliably

# The changes is to lib\ruby\1.8\cgi.rb, 
# is to comment out the refereence to IIS
 
Line 559: if options.delete("nph") # or /IIS/n.match(env_table['SERVER_SOFTWARE'])

The Bad News

I would perferr to use the new FastCGI for IIS when it comes out, My problem issue is detailed on the IIS FastCGI Handler Forum hopefully some one will details a solution.

Friday, June 8, 2007

ADO Explorer A sql tool for Access/MS-SQL/VFP

I just spent the last day converting data from a legacy access application to My Rails app (back end "dictated" db ms-sql), It was a tedious exercuise using Access and MS's Managment Studio.

Years ago developed an application that could access any ADO database using a common interface. So I developed it again, but made it better. Conversion was a breeze. This tool affectionately called ADOExploer is available here ADO Explorer Page. Feedback so-far has been good, enjoy

Thursday, May 31, 2007

How to: Disable Backspace key in IE and Firefox

The problem

When a user presses the Backspace key on a page in Internet Explorer (IE) presses the back button, this is very frustrating when you have a form on the screen and accidently press backspace. And loose your page.

I watched one of my users enter data on a form, but was correcting some data in a text box and pressed backspace once too many times, and IE when BACK!!

Firefox is much better (at least it does not do a BACK on a text input), but it does do a back on other fields (e.g. a select)

The fix

Trap the onkeydown to ignore backspace. The code below also disables Enter unless on the Submit button

The Code
<script type="text/javascript">

// Trap Backspace(8) and Enter(13) - 
// Except bksp on text/textareas, enter on textarea/submit

if (typeof window.event != 'undefined') // IE
  document.onkeydown = function() // IE
    {
    var t=event.srcElement.type;
    var kc=event.keyCode;
    return ((kc != 8 && kc != 13) || ( t == 'text' &&  kc != 13 ) ||
             (t == 'textarea') || ( t == 'submit' &&  kc == 13))
    }
else
  document.onkeypress = function(e)  // FireFox/Others 
    {
    var t=e.target.type;
    var kc=e.keyCode;
    if ((kc != 8 && kc != 13) || ( t == 'text' &&  kc != 13 ) ||
        (t == 'textarea') || ( t == 'submit' &&  kc == 13))
        return true
    else {
        alert('Sorry Backspace/Enter is not allowed here'); // Demo code
        return false
    }
   }
</script>
FireFox Fix

You can disable the backspace action in firefox by going to "about:config" and changing "browser.backspace_action" to something other than "0" or "1", e.g. "2"

Tuesday, May 29, 2007

How to: Run Multiple Rails Apps under IIS using fast Cgi

I have found iis/aspi/fast cgi to be un-reliable, seems to crash once a week, I think it is related to a specific size of a post, But have not had the time to track it down

Update - Oct - 2007

Thanks this page seems to be quite popular

A much better/reliable solution is to use IIS with Mongrel behind it,
Check out HOW TO DEPLOY: Rails under IIS with ISAPI_Rewrite3/Mongrel

Update - Dec - 2007

I have updated this entry for rails 2.0
Check out Rails v2.0 with IIS, ISAPI_Rewrite3, Mongrel

Disclaimer

First off I dont recommend it, But if you must as dictated by an outside source here are the steps.

There are severl source's of information around but none that give and how/who with step-step details.

After accessing various sources, I finally got IIS to work "reasonably reliably", PS: Included are tips for trouble shooting permissions issues.

Background - How do you get IIS to talk to rails

When IIS sees an url like http://host/something.asp?search=abc, It looks up and sees if there is an application (dll/exe) associated with the extension .asp, if there is then it passes the request to the application associated with the extension.

So in order to run a ruby application behind iss, we need to associate a extension with "rubyw.exe" for each rails app, the extension can be anything, lets call this ".rap1" (Ruby Application 1).

Below details that steps to do this

STEP 1 - Rewrite The Url

We need to give IIS a url that looks something like http://host/something.rap1?information-for-rails-app,

But Rails generates nice looking urls, e.g. http://host/app1/controller/method, IIS allows you to re-write URL's,

So we take http://host/app1/controller/method?etc=1 and convert it to http://host/something.rap1?opnq=/controller/method?etc=1

To do this we grap "Ionic's Isapi Rewrite for IIS" (details below)

STEP 2 - Rewrite it back for Rails

But the Rails app is expecting "/controller/method?etc=1" not "opnq=/controller/method?etc=1", So you need to re-write it back in to a nice url inside rails.

To do this we patch ActionController::AbstractRequest.request_uri (see app1/lib/action_controller_request_ext.rb)

STEP 3 - app prefix the Url

Since we will be running multiple apps, we need to prefix the urls rails generates with /app1, to distinguish each application (see app1/lib/environment.rb, ActionController::AbstractRequest.relative_url_root = "/app1")

Background - Running Ruby from IIS

OK lets assume we have the url's ok, Next step is to actually run ruby (rubyw.exe) to get the app to work.

We could run rubyw directly as a cgi, but this would mean for each request, rails is started run and closed, VERY VERY Slow.

So we take "Shane Careveo's Fast CGI" dll which keeps the rails app running and simply feeds it new requests

STEP 4 - Map your extension

Using IIS Map the .rap1 extension to call the isapi_fcgi.dll. (which will call rubyw)

STEP 5 - Configure fcgi to run Ruby

Tell the isapi_fcgi.dll what ruby application to run when it gets called with a .rap1 extension, (details below)

You will also need to install ruby code, to deal with how isapi_fcgi disptaches. (see dispatch.fcgi)

STEP 6 - Permission

Fix up permissions as ruby will be run as a limited user.

Misc Notes

You can replace "opnq" in the url, with anything you like, I kept it the same as used by RORIIS, also the "something" can be any name you want, I called it fastcgi, but all we need is for IIS to look at the extension .rap1 and call isapi_fcgi.dll that in turn runs ruby!!!

Install Ruby/Rails MSSQL Software

The boring bit, This section you can skip, But was included for completeness

Install MSSQL
> dotnetfx.exe  # Install .Net Frame work 2.0 
> SQLEXPR32-SP2.EXE # MS SQl Server 2005 EXPRESS 
  # Install to "C:\Program Files\Microsoft SQL Server 2005 EXPRESS"
  # Used Named Instance "SQLEXPRESS"
  # Used mixed authentication, "sa" passsword "yourchoice"
> SQLServer2005_SSMSEE.msi # Install MS Sql Server Managment Studio
Install Ruby + Gems
> mkdir C:\ruby
> run ruby185-21.exe # Install ruby 
  # Select Install "Ruby" and "Enable RubyGems"
  # Install to "C:\ruby 
  # Check C:\ruby\bin to your everyones path !!
  
  # download rubygems, un-zip and run setup.rb
> ruby setup.rb # Install ruby gems
Install Rails
> cd C:\ruby
> gem install --include-dependencies rails # Wait may take a while !!!
> gem install --include-dependencies win32-service # pick the most recent mswin32 !!
# gem install --include-dependencies mongrel # pick the win32 pre-built
# gem install --include-dependencies mongrel_service  
Install MSSQL Drivers
# download ruby-dbi, un-zip and run setup.rb
> ruby setup.rb config --with=dbd_ado
> ruby setup.rb setup
> ruby setup.rb install
# Patch  sqlserver_adapter.rb (See Rails Ticket #7733)
> cd C:\ruby\lib\ruby\gems\1.8\gems\activerecord-1.15.3\
                  lib\active_record\connection_adapters
> edit sqlserver_adapter.rb
# Line 28x: "=~ /null/ ? nil"   ==> "=~ /null/i> ? nil"  
#    Case insensitive match on the word null
Install other gems (e.g. RMagick / Faster CSV)
# download ImageMagick and run setup.rb
> ImageMagick-6.3.0-7-Q8-windows-dll.exe 
# Tick [x] Update executable search path.

# download rmagick gem and install
> gem install rmagick --local
# Note: Documentation is in C:\ruby\lib\ruby\gems\
#    1.8\gems\rmagick-1.14.1-win32\doc\index.html

# download fastercsv gem and install
> gem install fastercsv --local
Create Rails Application and check operation
Create a basic rails app, for testing. I assume c:\rails is where your rails apps will be placed
> mkdir C:\rails
> cd rails
> rails app1 # Create rails app called app1
> ruby script/server # Run WebBrick to test 
> iexplore.exe http://localhost:3000 
  # Should get welcome page
> iexplore.exe http://localhost:3000/controller/etc
  # You should see routine error, no route found... 
  # exception from the from the rails app  
> Ctrl+break
> exit 

The Details

The original source for set-up is as follows. Thanks to all.

"Ruby On Rails For IIS Fast-CGI" (This contains all the files menthoned)

NOTE: If did not install "ROR4IISFCGI_1.0.5.exe", As the install is for a single rails application, Simply Open the file with say 7-Zip and extract. I used this file simply as the source. For each step I have detailed only the final destionation folder

The set-up process detailed below, assumes the folllowing.

  • Ruby is installed in c:\ruby
  • Your application is called "app1" and resides in c:\rails\app1, The extension nominated for is .rap1
  • Iis has C:\Inetpub as its base
  • Windows is C:\Windows

Install/Configure IaspiRewriter

The following files are installed/edited
Inetpub
Inetpub\IsapiRewrite4.dll
Inetpub\IsapiRewrite4.ini # Copied and Edited
rails
rails\app1
rails\app1\lib
rails\app1\lib\action_controller_request_ext.rb
rails\app1\config\environment.rb # Edited  
rails\app1\config\routes.rb # Edited  
Rewrite The Url
# copy/install Inetpub\IsapiRewrite4.dll, Inetpub\IsapiRewrite4.ini
> inetmgr.exe # Add url rewriter 
  # Goto:  Default Web Site -> Properties -> ASPI Filters -> Add 
    Name: IsapiRewrite4
    Exe: C:\Inetpub\IsapiRewrite4.dll  
> cd C:\Inetpub
> edit IsapiRewrite4.ini
  # Add the following rule to take urls with /app1 to call fastcgi.rap1 
  RewriteRule ^/app1(/[^.]+)$ /fastcgi.rap1?opnq=$1
  # Note: urls, with a '.' in them are not mapped. 
  # This is cool, because iis will handel serving up the the 
  # public images/css etc (see later)
> edit routes.rb  # If using Web Services. Change the .wsdl to _wsdl 
  as now cannot have a "." in a url destined for rails
  # change ':controller/service.wsdl' => ':controller/service_wsdl'

Rewrite it back for Rails
# copy/install rails\app1\lib\action_controller_request_ext.rb
> cd C:\rails\app1\config
> edit environment.rb
  # Add the following line to the section titled 
  # "Include your application configuration below"
  require 'action_controller_request_ext.rb'
app1 prefix the Url
> cd C:\rails\app1\config
> edit environment.rb
  # Add the following line to the section titled 
  # "Include your application configuration below"
  ActionController::AbstractRequest.relative_url_root = "/app1" 
      if ENV['RAILS_ENV'] == 'production'

Install/Configure FastCGI

The following files are installed/edited
Inetpub
Inetpub\isapi_fcgi.dll
Inetpub\wwwroot
Inetpub\wwwroot\fastcgi.rap1 # Created manually
rails
rails\app1
rails\app1\FastCGI.reg # Created manually 
rails\app1\public
rails\app1\public\dispatch.fcgi
ruby
ruby\lib
ruby\lib\ruby
ruby\lib\ruby\site_ruby
ruby\lib\ruby\site_ruby\1.8
ruby\lib\ruby\site_ruby\1.8\fcgi.rb
ruby\lib\ruby\site_ruby\1.8\i386-msvcrt
ruby\lib\ruby\site_ruby\1.8\i386-msvcrt\fcgi.so
Windows
Windows\System32
Windows\System32\libfcgi.dll
Windows\System32\msvcp71.dll # if not already present
Windows\System32\msvcr71.dll # if not already present
Configure fastcgi to run Ruby

Place settings in the register to tell isapi_fcgi which ruby app to run based on the file extension it is called as

> copy files as indicated above ...
> echo "# fastcgi" > C:\Inetpub\wwwroot\fastcgi.rap1 # Simply a dummy file
> edit/create C:\rails\app1\FastCGI.reg
# Place the following in the file. 

# These registry settings tell isapi_fcgi.dll what rails 
# app to run based on what file extension it is called with

[HKEY_LOCAL_MACHINE\SOFTWARE\FastCGI]
[HKEY_LOCAL_MACHINE\SOFTWARE\FastCGI\.rap1]
@=""
"BindPath"="Rails-FCGI-app1"
"AppPath"="C:\\ruby\\bin\\rubyw.exe"
"Args"="C:\\rails\\app1\\public\\dispatch.fcgi"
"Environment"=hex:52,41,49,4c,53,5f,45,4e,56,3d,70,72,6f,64,75,63,74,69,6f,6e,0d,0a,00
"StartServers"=dword:00000003
"IncrementServers"=dword:00000001
"MaxServers"=dword:00000006
"MaxProcMem"=dword:00000064
"Timeout"=dword:00000258

# The key "Environment" contains "RAILS_ENV=production\r\n\0" 

> cd C:\rails\app1\
> FastGGI.reg # Load details into the widows registry
Configure/Map your extension IIS

We will Map a Virtual directory to our application, Map the extension .rap 1 to our application

> inetmgr.exe # Add in your application to Virtual dir 
  # Goto:  Default Web Site -> New Virtual Directory -> 
    Name -> app1
    Path -> C:\rails\app1\public
    Permissions -> Read
>  netmgr.exe # Add in your application extension .rap1
  # Goto:  Default Web Site -> Properties -> Home Directory 
         -> Configration -> Mapping -> Add
    Executable -> C:\Inetpub\isapi_fcgi.dll
    Extension -> ".rap1"  # with the "."
    Script Engine -> Ticked
    Check the File Exists -> Ticked
  # If the OK button is gray, (bug in IIS 5.1)
      click on the Executable File Name a 2nd time 
  # Ensure Cache ISAP Extensions is Ticked 
Configure Permissions (the messy bit)

Your app wont run yet, as rubyw will be running as a restricted user. Perform the following steps. We will set-up a group called "Rails" and assign the IIS process user "IWAM_computername" to this group.

> compmgmt.msc
  # Goto: Local Users and Groups -> New Group -> Rails
  # Add new Rails group (with no members)   
> compmgmt.msc
  # Goto: Local Users and Groups -> IWAM_computername -> 
  #       Properties -> Member Of -> Add -> Rails

Configure iis to run application's as the local user "IWAM_computername". This makes managing permissions simpler.

> inetmgr.exe 
  # Under Server 2003+ 
    # Goto:  Application Pools -> 
       DefaultAppPool -> Properties -> Identiry 
    # Change to "IWAM_computername" 
  # Under XP 
    # Goto:  Default Web Site  ->  Properties ->
         Home Directory -> Identiry 
    # Application protection to "Medium (Pooled)"" 

Server 2003+ only, Configure IIS to accept the dll's and rubyw.exe application

# Under Server 2003+ (Only)
> inetmgr.exe  # Set permissions on applications
  # Goto:  Web Server Extension -> Add Web Server Extension 
    Name: Rails
    Files: Inetpub\IsapiRewrite4.dll, 
           Inetpub\isapi_fcgi.dll, 
           Windows\System32\libfcgi.dll, 
           C:\ruby\bin\rubyw.exe
    Status: Allowed
Set premissions on the required files/dirs for the "Rails" group

Dont forget this step otherwise your application will not run !!!

  • Give FULL permission for C:\rails\app1\temp and C:\rails\app1\log
  • Give FULL permission to TEMP (C:\WINDOWS\TEMP)
  • Give FULL permission to any other folders your rails app1 writes to (e.g C:\rails\app1\public\attachments )
  • Check Read/Execute permissions for all the .dll's menthoned above and rubyw.exe
  • Check Read permission Under HKEY_CLASSES_ROOT\ADODB.Connection for both the ADODB.Connection and ADODB.Connection.X.X entries # mssql only
  • Check Read permission Under HKEY_LOCAL_MACHINE\SOFTWARE\FastCGI # this should be ok

Test It

Perform the following steps to test it works.

Ist check your app still works with WebBrick (see above Installing Ruby etc)

> iisreset.exe # Re-start iis
> iexplore.exe http://localhost/app1/controller/etc
# You should see routine error, no route found... 
# If so you have an exception Message from the Rails app, cool !!! 
# If you get fast cgi error or page not found, 
#        check out trouble shooting. 

Configure Your Second Application

Simply repeat the steps above (that relate to a specific application) for your application. Assgning your own name and extension.

Simply change "app1" to your application name and".rap1" to your nominated extension

I would not recommend using .asp/.aspx extension, quite yet, mind you with ruby/rails going so well, it wont be long before the .aspx extension will become un-used. :-)

NOTE: Do make sure you change the "BindPath"="Rails-FCGI-app1" in the registry for fast CGI for each application, otherwise you may end up with one app's url going to another.

Trouble shooting

Some trouble shooting tips.

First off, Does rubyw start within iis.
  • Grab "Process explorer" from sysinternals
  • run "procexp.exe"
  • Right click headings and choose select columns, Add "User Name"
  • Reset iis. (iisreset.exe)
  • navigate to http://localhost/app1/controller/etc
  • watch the dllhost.exe within svchost.exe,
    • does rubyw.exe appear then disappear, If so possibly a permissions thing, does the rubyw.exe user, have permissions to all the files.
    • It does not appeara Check the fcgi set-up.
Try running your app as a restricted user.

Try the following to run your app as a restricted user. It might highlight some things

  • Create a user called IWAM_TEST (give it a password)
  • Remove IWAM_TEST from the Users group
  • Add IWAM_TEST to the Rails group
  • Now Run your app as IWAM_TEST using WebBrick
  • > runas /user:IWAM_TEST cmd 
    # You should get another cmd window as this user, 
    > env 
    # Look at the env, look ok. 
    > cd \rails\app1
    > ruby script/server
    # Look at the output, Does it say "unable to access log file" 
    # If so fix permissions
    
    Navigate to http://localhost:3000/controller/etc
    
    # Any more messages
    > Ctrl+break # break out of WebBrick
    > exit  
    
    

Conclusion

I hope this write up helps some. It helped me.

I know there is not much documentation on multiple apps and IIS.

I have done it, but think IIS -> Apache -> Mongrel would be a better choice.

All comments welcome

An Update

There is one small change you need to make to cgi.rb to make fastcgi more reliable. Check out Running Rails Apps More Reliability under IIS

How to: Fix up Blogger Template To Utilize The Full Screen

Intro

Blogger.com's template's (well the one I choose) uses floating divs of fixed width, This may be ok for some, but for those of us with screens larger the 640x480 a lot of space is wasted.

So I removed the floating divs and placed in a table, nice and simple and looks great

Changes to html template are as follow:-

#outer-wrapper {
  width: 660px;
  etc ...
  }


#main-wrapper {
  float:left;
  overflow:hidden;
  width:410px;
}

#sidebar-wrapper {
  float:right;
  overflow:hidden;
  width: 220px; 240px;
}

etc ...

<table>
<tr>
<td valign="top">
<div id="main-wrapper">

etc ...

</td>
<td valign="top">
<div id="sidebar-wrapper">

etc ...

<div class="clear">
</td>
</tr>
</table>
</div> <!-- end content-wrapper -->
<div id="footer-wrapper">

Additional Changes

I also added my own .body h4, h5 and pre styles to the ccs, to gives a much nicer look

Google