Tuesday, January 22, 2008

read_multipart fault with "bad boundary end of body part"

Intro

This one has been bugging be for ages, My application would fault at random times with, "bad boundary end of body part" from the read_multipart in ActionController. I finally took a very close look.

I have a form, with a quite few fields, uses can also add attachments so the post is via multipart.

I worked out the odds of this happening (once I found a solution) are approx 1 in 120, and was suprised this has not cropped up before. There cant be many rails apps, with large data entry screens using multipart out there

What happens in read_multipart

  • If your post content is greater than 10240 bytes, read_multipart fills up buff with 10240 bytes
  • The code to extract params, will see the "--boundary[CR][LF]" and extract the param and delete up to the bounday, and then loop for the next param
  • But if the buff ends with "--boundary[CR][LF]", on extracing the param the buff is zero length
  • But for some reason, If buff is zero a break is issued and then a fault happens

What wrong

  • A check for zero bytes read, is performed previously in the code, why do it again??
  • I cant figure out why you would check the buff for zero length??, when it is valid to be zero.

The fix

  • Dont break if buff is zero length !!

Edit /ruby/lib/ruby/gems/1.8/gems/actionpack-2.x.x/lib/action_controller/request.rb


def read_multipart(body, boundary, content_length, env)

  etc ...

 buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{quoted_boundary}([\r\n]{1,2}|--)/n) do
    content.print $1
    if "--" == $2
      content_length = -1
    end
    boundary_end = $2.dup
    ""
  end
  
  etc ...

  # break if buf.size == 0 # Dont break on zero, as buf could end on a boundary !!! resulting in empty buff
  break if content_length == -1
  
  etc...

Rails Ticket

Checkout rails ticket 10886 just lodged for updates.

Finally

If some one knows why the buff zero length is/was needed, I would like to know

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

How to E-Mail rails/ms-sql log files with Windows XP/2003 using CDO.Message

Intro

I needed to monitor my rails app admin tasks, this is hosted on Windows Server 2003. The most convienient way was to run a batch (.cmd) script to perform various tasks and e-mail myself the outcome. Windows does not have a built in utility to do this, So Wrote one.

Since Im a rails developer, the obvious choice of language (apart from ruby) was JavaScript, And use windows cscript utility. This worked a treat

How it works

Windows 2000+, and XP provides CDO.Message ActiveX controls to send e-mails, All you need is to give it a remote smtp server (that allows forwarding), Set the body/attachments and you are away.

What it does is simply send a file as an attachment or as the body of the e-mail, given an sever

Usage

  usage: sendmail.js to-email "subject" [ "body" ]

options:  /smtp smtp.xxx.co.nz - Smtp address of server 
          /port nn - Smtp port defaults to 25  
          /cc e-mailaddress 
          /from email - defaults to username@computername.local
          /attach filepath - attach the file  
          /body filepath  - use file as body of the mesasge 
          
   note: Note filepath must be fully qualified          

Example Usage

C:> cscript /nologo sendmail.js /smtp smtp.isp.com
                        /body "fullpath\admin.log" 
                        toemail@somewhere.com 
                        "[RAILS-ADMIN] Log of admin tasks"

The Code

Click here sendmail.js to view the code
Google