Riding the Rails with WebSphere: Part 1: Combine Ruby on Rails with WebSphere Application Server and get rapid development with security, scalability, and reliability

This primer shows how Ruby on Rails applications can be deployed inside of IBM® WebSphere® Application Server V6.1 and integrated with WebSphere Application Server's existing features -- and why you might want to consider bringing these two worlds together. This content is part of the IBM WebSphere Developer Technical Journal.

Ryan Shillington (shillington@us.ibm.com), Software Engineer, IBM

Author photoRyan Shillington is a software engineer at IBM in Austin, Texas. He recently worked full time creating a Rails e-commerce Web site before joining IBM in May of 2007. Ryan currently works on WebSphere Business Services Fabric.



23 January 2008

Introduction

Many organizations have standardized their IBM WebSphere Application Server environments with Java™ because of its excellent scalability, security, and support. However, developing dynamic Web pages in Java can often be tedious and time intensive. Ruby on Rails simplifies the creation of dynamic Web pages with rapid development and testing features that justify the excitement it has generated throughout the programming community.

This article is the first of two that demonstrates how a Ruby on Rails application can be deployed inside of WebSphere Application Server V6.1, and how Ruby on Rails applications can be integrated with WebSphere Application Server's existing features. This marriage provides the best of both worlds: rapid development and testing while leveraging your investment in WebSphere Application Server.


What is Ruby on Rails?

Ruby on Rails is sometimes difficult to describe succinctly because it includes so many things. Ruby on Rails (hereafter referred to as Rails) is:

  • An MVC (model, view, controller) framework, split into two parts: ActionController for the controller and ActionView for the model and view.
  • An ORM (Object Relational Mapping) framework, called ActiveRecord.
  • A set of command line tools that do the dirty work of setting up directory structures and creating initial production and test code.

Rails is surprisingly dynamic and, therefore, was built on a programming language (Ruby) that does not require rigid constructs and type safety. For this reason, other frameworks that expect developers to code in Java have a hard time competing with Rails in terms of number of lines of code. The main advantage of using Ruby on Rails is that it is fascinatingly simple.

For example, Ruby is interpreted, which means that you can iteratively write code, hit the Refresh button in the browser to see your changes, write more code, refresh again, and so on. Using the built-in WEBrick Web server that comes with Rails, you do not need to wait for WAR and EAR files to redeploy. Yes, it is possible to set up WebSphere Application Server to do this for specific WARs and EARs for long term Java development, but for quick, simple iterative development, the basic WEBrick Web server does this without any extra setup cost -- plus, it's really quite convenient.

As a seasoned Java developer, after completing a few Rails tutorials, I was astounded to learn that a fully functional Web application could be developed with only a few lines of code and without copious amounts of XML configuration. In fact, this article relies on the command line tools supplied with Rails to help you create an entire sample wish list application. (The only exception to this is the code in the migration to create a database table, but that can also be done in straight SQL in the database.)

This article guides you through making the most out of Rails applications in WebSphere Ap-plication Server. This information will help you if you have an existing Rails application that you want to migrate and run on WebSphere Application Server, or if you want to start using Rails as a front end for your existing Java EE applications, or if you are interested in using Rails but aren't sure how this new technology will integrate into your current environment. The goal here isn't simply to provide steps to accomplish essential tasks, but also to explain both the reasons for taking this approach, and the value that the combination of Ruby on Rails and WebSphere Application Server provides.

Prerequisites and setup

To follow the steps outlined in this article, you need to have this software installed:

  • IBM WebSphere Application Server V6.1.0.11, installed to C:\WAS.
  • Sun™ JRuby 1.0.2, unzipped to C:\jruby-1.0.2, with these environment variables set:

    set JAVA_HOME=C:\WAS\java
    set JRUBY_HOME=C:\jruby-1.0.2
    set PATH=%JAVA_HOME%\bin;%JRUBY_HOME%\bin;%PATH%

    Earlier versions of JRuby have problems running with the IBM JDK.

Ruby contains a handy package management tool, called gem, which is akin to Perl's CPAN. It's very useful for adding libraries to your Ruby installation, and you can use it to install Rails and ActiveRecord-JDBC:

Listing 1
C:\>gem install rails -y --no-rdoc --no-ri
Bulk updating Gem source index for: http://gems.rubyforge.org
Successfully installed rails-1.2.5
Successfully installed activesupport-1.4.4
Successfully installed activerecord-1.15.5
Successfully installed actionpack-1.13.5
Successfully installed actionmailer-1.3.5
Successfully installed actionwebservice-1.2.5

C:\projects\wishlist\WEB-INF\gems\specifications>gem install 
activerecord-jdbc-adapter  
--no-rdoc --no-ri
Successfully installed activerecord-jdbc-adapter-0.6

Rails, which contains ActiveRecord, supports a number of databases but does not support JDBC directly. The ActiveRecord-JDBC gem provides a connection adapter to make ActiveRecord look like it supports JDBC out of the box. ActiveRecord-JDBC supports IBM DB2®, MySQL, Oracle, Microsoft® SQL Server, and other databases, but Derby is used in the examples in this article primarily because it is free to use, and can be easily migrated to DB2, which is convenient if you later need something heavy duty.


Ruby and JRuby: what's the difference?

JRuby is a pure Java implementation of the original Ruby interpreter that was originally written in C. At first, it seems as though the main advantage over Ruby is that it enables you to call Java code from Ruby code and vice versa. Although this is very useful, this is not the only reason to consider using JRuby over Ruby.

The standard C implementation of Ruby uses green threads, meaning that it is possible for one user's long running process to block all of the other users. One user who is requesting an extensive report that takes, say, a minute to complete can block all other users, who might just be trying to view a Web page that might normally take less than a second to display. Although this might change when Ruby 2.0 is released, it is possible in the standard Ruby implementation for all users in this situation to be blocked for over a minute, waiting on another user's long spanning request.

This is not the case with JRuby, which implements Ruby threads based on Java threads that use native OS threads. JRuby can switch context at almost any time. The result is that a Rails application running on JRuby will usually be more responsive under heavy load than when running with the standard Ruby implementation.


Deploying a Rails project

The first big step you will perform will be to walk through the fundamentals of deploying a WAR that contains a Rails project on WebSphere Application Server. In this section, you will begin by deploying a blank Rails application that includes only the Rails welcome page. Specifically, you will set up the directory structure, package it into a WAR file, and then deploy it on a running WebSphere Application Server.

  1. Create a Rails project

    Rails makes setting up the base directory structure for your project very easy. Run the rails command to create all of the necessary starter files:

    Listing 2
    C:\>mkdir projects
    
    C:\>cd \projects
    
    C:\projects>jruby -S rails wishlist
          create
          create  app/controllers
          create  app/helpers
          create  app/models
          create  app/views/layouts
      <continued...>

    You might notice that you didn't call the standard Ruby rails in the above commands, but rather jruby -S rails. The -S flag signals to JRuby to find the script that follows in JRuby's bin directory. Otherwise, it looks in the user's current directory.

  2. Install Goldspike

    Goldspike is a Rails plug-in that provides the tasks and templates needed to augment your build system to create a WAR file out of your newly created Rails project. Rake is Ruby's build tool that is used to automate shell-like commands, such as packaging and deploying Ruby code, and performing database migrations. Java developers can consider it roughly equivalent to Ant, and C/C++ developers can consider it roughly equivalent to make. It is oddly similar to both, with the exception that Rake actually is Ruby code. For the purpose of this article, you need only understand that Rake is used to create a build system.

    1. Run the command shown in Listing 3 to install Goldspike.
      Listing 3
      C:\projects\wishlist>jruby script\plugin install 
      http://jruby-extras.rubyforge.org/svn/
      trunk/rails-integration/plugins/goldspike
      + ./goldspike/README
      + ./goldspike/Rakefile
      + ./goldspike/generators/goldspike/goldspike_generator.rb
      + ./goldspike/generators/goldspike/templates/war.rb
      + ./goldspike/generators/goldspike/templates/web.xml.erb
      + ./goldspike/init.rb
      + ./goldspike/install.rb
      + ./goldspike/lib/create_war.rb
      + ./goldspike/lib/java_library.rb
      + ./goldspike/lib/packer.rb
      + ./goldspike/lib/run.rb
      + ./goldspike/lib/util.rb
      + ./goldspike/lib/war_config.rb
      + ./goldspike/tasks/war.rake
      + ./goldspike/test/test_create_war.rb
      + ./goldspike/test/test_java_library.rb
      + ./goldspike/test/test_maven_library.rb
      + ./goldspike/test/war_config_test_config.rb
            exists  config
            create  config/war.rb
            create  WEB-INF
            create  WEB-INF/web.xml.erb

      Goldspike created two new files in the source tree:
      • config/war.rb: This file enables you to peg which versions of JRuby, Rails, and other plug-ins will be packaged inside of your WAR file.
      • WEB-INF/web.xml.erb: This is a template for the generated WEB-INF/web.xml file that will end up in your WAR.
    2. Create a lib\java directory and use it to hold any JAR files that will be referenced in your JRuby code. Any classes inside of JARs in lib\java will be packaged inside of theWAR created by Goldspike. These new directories will be created the first time you run the Rake task to create the WAR:
      • WEB-INF/classes: Add any Java classes here that will be referenced by the Ruby code.
      • WEB-INF/gems: Goldspike will automatically bundle all of the gems that you have installed into this directory so that they are available inside of WebSphere Application Server.
      Do not add new JARs here; they will be automatically copied over from lib\java.
  3. Create a WAR file

    To make sure that the basics are working, use JRuby and Goldspike to create your first WAR file:

    Listing 4
    C:\projects\wishlist>jruby -S rake war:standalone:create
    	(in C:/projects/wishlist)
    	info: Assembling web application
    	info: Packing needed Java libraries ...
    	info: Packing needed Ruby gems ...
    	info:   adding Ruby gem ActiveRecord-JDBC version 0.5
    	info: Packing needed files ...
    	info: Creating web archive

    The size of the newly created WAR file should be a little over 7 MB. Don't be alarmed that an empty Rails project is so large: Goldspike also packaged up JRuby and Rails into the WAR file. (Alternatively, you can have Goldspike tell WebSphere Application Server, via web.xml, where JRuby and Rails are installed to prevent them from being included in these packages, but this is beyond the scope of this article.)

  4. Deploy the WAR file

    To deploy the new WAR:

    1. Open the WebSphere administrative console by navigating to http://localhost:9060/admin.
    2. In the left navigation menu, expand Applications and click on Install New Application.
    3. Click Browse to select the recently created WAR file: C:\projects\wishlist\wishlist.war (Figure 1).
      Figure 1. Preparing for the application installation
      Figure 1. Preparing for the application installation
    4. Set the Context root to wishlist, then click Next.
    5. In the Deployment wizard, accept the defaults and click Next until you get to Step 4, then click Finish.
    6. Scan the messages on the resulting Installing... panel to verify that there are no warnings or errors, and then Save.
    7. In the navigation pane, under Applications, select Enterprise Applications.
    8. Check the box next to wishlist_war, then click on the Start button.
    9. Alternatively, if you plan on redeploying throughout your development cycle, you'll want to consider setting up a Jython wsadmin script. Although the details of scripting is beyond the scope of this article, an example is below. Know, however, that only the first six lines following the # Customize these: comments need to be modified for your environment.
      Listing 5
      import re
      import os
      
      # Customize these:
      WAR_FILE = "C:/projects/wishlist/wishlist.war"
      WAR_NAME = "wishlist_war"
      WAR_CONTEXT_ROOT = "/wishlist"
      SERVER_NAME = "server1";
      NODE_NAME = "rshillington-ltNode01"
      CELL_NAME = "rshillington-ltNode01Cell"
      
      # No need to customize the rest.
      def log(text):
          print "---- " + text;
      
      appList = AdminApp.list()
      log("Apps installed before uninstall: \n" + appList + "\n\n")
      
      log("Find the appManager...")
      appManager = AdminControl.queryNames
      ("cell=" + CELL_NAME + ",node=" + NODE_NAME +
      ",type=ApplicationManager,process=" + SERVER_NAME + ",*")
      
      containsApp = re.compile("\\b" + WAR_NAME 
      + "\\b", 0).search(appList) != None
      
      if containsApp:
          log("Stop the application...")
          try:
              AdminControl.invoke(appManager, 
              "stopApplication", WAR_NAME)
          except:
              # This is okay - it just wasn't started.
              log("\tApplication not started.");
      
          log("Uninstall the application...")
          AdminApp.uninstall(WAR_NAME)
      
          log("Save the changes...")
          AdminConfig.save()
      
      
      log("Install the application...")
      AdminApp.install(WAR_FILE, "-appname " 
      + WAR_NAME + " -server " + SERVER_NAME + 
      " -contextroot " + WAR_CONTEXT_ROOT + " -usedefaultbindings")
      AdminConfig.save()
       
      log("Start the application...")
      AdminControl.invoke(appManager, "startApplication", WAR_NAME)
       
      log("Save the changes...");
      AdminConfig.save()
      
      appList = AdminApp.list()
      log("Apps installed after install: \n" + appList + "\n\n")
    10. Save the script as deployWishlist.py and run it using the wsadmin tool. You should see something like this:
      Listing 6
      C:\WAS\bin>wsadmin.bat -f c:\moveit\deployWishlist.py 
      -user admin -password <your password>
      WASX7209I: Connected to process "server1" on node 
      rshillington-ltNode01 using SOAP 
      connector;  The type of process is: UnManagedProcess
      ---- Apps installed before uninstall:
      DefaultApplication
      ivtApp
      
      ---- Find the appManager...
      ---- Install the application...
      WASX7327I: Contents of was.policy file:
       //
      // Template policy file for enterprise application.
      // Extra permissions can be added if required by the 
      // enterprise application.
      // NOTE: Syntax errors in the policy files will cause the enterprise 
      //   application FAIL to start. Extreme care should be taken when editing
      //   these policy files. It is advised to use the policytool provided by the 
      //   JDK for editing the policy files (WAS_HOME/java/jre/bin/policytool).
      //
      
      grant codeBase "file:${application}" {
      };
      
      grant codeBase "file:${jars}" {
      };
      
      grant codeBase "file:${connectorComponent}" {
      };
      
      grant codeBase "file:${webComponent}" {
      };
      
      grant codeBase "file:${ejbComponent}" {
      };
      
      ADMA5016I: Installation of wishlist_war started.
      ADMA5058I: Application and module versions are validated
      with versions  of deployment targets.
      ADMA5062W: An error occurred while reusing the existing 
      deployment.xml file. The original deployment settings 
      are not reused."
      ADMA5005I: The application wishlist_war is configured in 
      the WebSphere Application Server repository.
      ADMA5053I: The library references for the installed optional
       package are  created.
      ADMA5005I: The application wishlist_war is configured in 
      the WebSphere Application Server repository.
      ADMA5001I: The application binaries are saved in 
      C:\WAS\profiles\AppSrv01\wstemp
      \Script116c5288827\workspace\cells
      \rshillington-ltNode01Cell\applications
      \wishlist_war.ear\wishlist_war.ear
      ADMA5005I: The application wishlist_war is configured
       in the WebSphere Application Server repository.
      SECJ0400I: Successfuly updated the application
      wishlist_war with the appContextIDForSecurity 
      information.
      ADMA5011I: The cleanup of the temp directory for 
      application wishlist_war is complete.
      ADMA5013I: Application wishlist_war installed 
      successfully.
      ---- Start the application...
      ---- Save the changes...
      ---- Apps installed after install:
      DefaultApplication
      ivtApp
      wishlist_war
    11. Now point your browser at http://locahost:9080/wishlist. You should see a Web page similar to Figure 2.
      Figure 2. Initial application Welcome page
      Figure 2. Initial application Welcome page

Write a wish list application

Next, you will develop a quick birthday-style wish list application, into which you can enter the names of gifts that you want so that others can buy them for you:

  1. Rails needs to know how to connect to your database. For this article, you will use a file-based Derby database that is hardcoded to a specific path. Change config\database.yml to contain:
    Listing 7
    development:
      adapter: jdbc
      driver: org.apache.derby.jdbc.EmbeddedDriver
      url: jdbc:derby:c:\projects\wishlist\db\wishlist_dev;create=true
    
    test:
      adapter: jdbc
      driver: org.apache.derby.jdbc.EmbeddedDriver
      url: jdbc:derby:c:\projects\wishlist\db\wishlist_test;create=true
    
    production:
      adapter: jdbc
      driver: org.apache.derby.jdbc.EmbeddedDriver
      url: jdbc:derby:c:\projects\wishlist\db\wishlist_prod;create=true

    The test and production sections are identical to the development section, except they are configured to use the databases wishlist_test and wishlist_prod in the URL instead of wishlist_dev. The test database will be used for running your tests to avoid affecting your development environment. The production environment ideally points to where your application is being hosted. For the purpose of this article, the production environment would be used for deploying the application to WebSphere Application Server. For now, it's okay for it to be an empty database. The create=true flag on the Derby URL tells Derby to create the directory and the database if it doesn't already exist.
  2. To make ActiveRecord JDBC aware, load the activerecord-jdbc-adapter gem by the adding the five statements in the if RUBY_PLATFORM block in Listing 8 to environment.rb before the Rails::Initializer.run block. Your config\environment.rb will then look like this:
    Listing 8
    # Be sure to restart your web server when you modify this file.
    # Uncomment below to force Rails into production mode when
    # you don't control web/app server and can't set it the proper 
    # way ENV['RAILS_ENV'] ||= 'production'
    
    # Specifies gem version of Rails to use when vendor/rails is not present
    RAILS_GEM_VERSION = '1.2.5' 
    unless defined? RAILS_GEM_VERSION
    
    # Bootstrap the Rails environment, frameworks, 
    # and default configuration
    # require File.join(File.dirname(__FILE__), 'boot')
    
    if RUBY_PLATFORM =~ /java/
        require 'rubygems'
        gem 'activerecord-jdbc-adapter'
        require 'jdbc_adapter'
    end
    
    Rails::Initializer.run do |config|
    # Settings in config/environments/* 
    #take precedence over those
    # specified here
    ...
  3. To include the Derby JDBC JAR in the JRuby classpath and the WAR, add it to the system CLASSPATH. To include it into the WAR, copy derby.jar into lib\java:
    Listing 9
    C:\projects\wishlist>cd lib\java
    
    C:\projects\wishlist\lib\java>copy C:\WAS\derby\lib\derby.jar 
     derby-10.1.3.jar 
    1 file(s) copied.
    
    C:\projects\wishlist\lib\java>set 
    CLASSPATH=C:\projects\wishlist\lib\java\derby-10.1.3.jar
  4. Pay special attention to the added version number. Goldspike only recognizes Maven-style dependencies and therefore won't recognize the JAR files without version numbers. Unfortunately, Goldspike doesn't scan the lib\java directory and pick up all of the JARs in the directory, so you need to modify config\war.rb to look like this:
    Listing 10
    # Goldspike configuration
    
    # Set the version of JRuby and GoldSpike to use:
    maven_library 'org.jruby', 'jruby-complete', '1.0.2'
    
    # Add the Derby library to the WAR
    include_library 'derby', '10.1.3';
    
    # Add the ActiveDirectory-JDBC adapter to the WAR
    add_gem 'activerecord-jdbc-adapter', '0.6';
    
    # Uncomment this to have the war run in the 
    # rails 'development' environment 
    # (against wishlist_dev) when run in WebSphere
    #@result.rails_env= 'development';
  5. Goldspike permits a number of largely undocumented options, which can be found in vendor\plugins\goldspike\lib\war_config.rb. For example, to change the path and name of the generated WAR file, add this line to the end of config/war.rb:
    Listing 11
    @result.war_file= 'c:/temp/funnyName.war';
  6. Run the Rails generated scripts to create the code for your wish list editor. Answer Y (for yes) whenever Rails asks a question.
    Listing 12
    C:\projects\wishlist>jruby script\generate controller Editor
          exists  app/controllers/
          exists  app/helpers/
          create  app/views/editor
          create  test/functional/
          create  app/controllers/editor_controller.rb
          create  test/functional/editor_controller_test.rb
          create  app/helpers/editor_helper.rb
    
    C:\projects\wishlist>jruby script\generate model Wish
          exists  app/models/
          exists  test/unit/
          exists  test/fixtures/
          create  app/models/wish.rb
          create  test/unit/wish_test.rb
          create  test/fixtures/wishes.yml
          exists  db/migrate
          create  db/migrate/001_create_wishes.rb
    
    C:\projects\wishlist>jruby script
    \generate scaffold Wish Editor
          exists  app/controllers/
          exists  app/helpers/
          exists  app/views/editor
          exists  app/views/layouts/
          exists  test/functional/
      dependency  model
          exists    app/models/
          exists    test/unit/
          exists    test/fixtures/
       identical    app/models/wish.rb
       identical    test/unit/wish_test.rb
       identical    test/fixtures/wishes.yml
          create  app/views/editor/_form.rhtml
          create  app/views/editor/list.rhtml
          create  app/views/editor/show.rhtml
          create  app/views/editor/new.rhtml
          create  app/views/editor/edit.rhtml
    overwrite app/controllers/editor_controller.rb? [Ynaqd]
           force  app/controllers/editor_controller.rb
    overwrite test/functional/editor_controller_test.rb? [Ynaqd]
           force  test/functional/editor_controller_test.rb
       identical  app/helpers/editor_helper.rb
          create  app/views/layouts/editor.rhtml
          create  public/stylesheets/scaffold.css
    • The first command in the script above, jruby script\generate controller Editor, creates the controller that will contain the back end code for the editor actions.
    • The second command, jruby script\generate model Wish) creates the model that will take care of writing data to the database.
    • The third command, jruby script\generate scaffold Wish Editor, scaffolds the basic list, create, edit, and delete actions in the editor controller for the wish objects stored in the database.
  7. One of the files created when generating the model was a migration, in db/migrate/001_create_wishes.rb. A migration is used to migrate a database from its existing state (in this case, empty) to a new state that usually has a new table, new columns, and so on. The migration here will be used to create a table in your database, called wishes, to hold the individual items in your wish list. Edit the new migration, db/migrate/001_create_wishes.rb, to add the columns you think are necessary. Whatever columns are added to the wishes table will automatically be present in your application, in the same order.
    Listing 13
    class CreateWishes < ActiveRecord::Migration
      def self.up
        create_table :wishes do |t|
           t.column :name, :string, :limit => 255, :null => false
           t.column :url, :string, :limit => 1024, :null => true
        end
      end
    
      def self.down
        drop_table :wishes
      end
    end
  8. Run rake to have Rails create the table in your wishlist_dev development database.
    Listing 14
     C:\projects\wishlist>jruby -S rake db:migrate
    (in C:/projects/wishlist)
    == CreateWishesTable: migrating =======================
    -- create_table(:wishes)
       -> 0.8440s
    == CreateWishesTable: migrated (0.8440s) ==============
  9. Do the same thing to the production database, which is the one to which WebSphere Application Server will be pointing:
    Listing 15
    C:\projects\wishlist>jruby -S rake db:migrate RAILS_ENV=production
    (in C:/projects/wishlist)
    == CreateWishes: migrating ============================
    -- create_table(:wishes)
       -> 0.1400s
    == CreateWishes: migrated (0.1400s) ==================
  10. Finally, package up the WAR again, making sure that it includes the Derby JAR.
    Listing 16
    C:\projects\wishlist>jruby -S rake war:standalone:create
    (in C:/projects/wishlist)
    (in C:/projects/wishlist)
    info: Assembling web application
    info: Packing needed Java libraries ...
    info:   adding Java library jruby-complete-1.0.2
    info:   adding Java library derby-10.1.3
    info: Packing needed Ruby gems ...
    info: Packing needed files ...
    info: Creating web archive
  11. Uninstall the old WAR from the WebSphere admin console and reinstall the new WAR that was generated in C:\projects\wishlist\wishlist.war, following the same directions as before. When you first hit http://localhost:9080/wishlist/editor/, you might receive this error:
    Listing 17
    Error 503: The server is currently overloaded, please try again later.

    This is expected. Although WebSphere Application Server thinks that the WAR has been started, JRuby still needs time to startup its own interpreter, and then start Rails. In your server's SystemOut.log file, you should eventually see something like this:
    Listing 18
    WebApp  A   SRVE0180I: [wishlist.war1165c5b3058#wishlist.war] [/wishlist] 
    [Servlet.LOG]: JRuby init time: 6344ms
    WebApp  A   SRVE0180I: [wishlist.war1165c5b3058#wishlist.war] [/wishlist] 
    [Servlet.LOG]: Rails init time: 14531ms
    WebApp  A   SRVE0180I: [wishlist.war1165c5b3058#wishlist.war] [/wishlist] 
    [Servlet.LOG]: Runtime 0 loaded
    WebApp  A   SRVE0180I: [wishlist.war1165c5b3058#wishlist.war] [/wishlist] 
    [Servlet.LOG]: Requests can now be processed
  12. Once you see the final "Requests can now be processed" message, you can start adding and editing wishes in your new wish list application. If you see an error in the WebSphere Application Server SystemOut.log that looks like this:
    Listing 19
    WebApp A SRVE0180I: [wishlist_war#wishlist.war] [/wishlist] [Servlet.LOG]: 
    Failed to load Rails: No such file or directory - C:/WAS/profiles/AppSrv01/C:

    then make sure that you are using JRuby 1.0.2 and nothing earlier. Since you created the initial WAR before you setup the war.rb file, make sure that the only file in WEB-INF\lib that begins with "jruby" is jruby-complete-1.0.2.jar. If all is well, when you go to http://localhost:9080/wishlist/editor/list you should see a panel similar to Figure 3.
    Figure 3. Wish list application panel
    Figure 3. Wish list application panel

Test cases

Developing software rapidly is useless if you cannot ensure the quality. Rails comes with a powerful framework for automating unit tests (for the model), functional test cases (for the controllers), and integration tests (end to end across multiple controllers). The true power of Rails is that you can write tests as fast as you can write code.

When you generated the code for the editor controller, a test was created in test\functional\editor_controller_test.rb. Remember how you set up three databases at the beginning of this exercise? The Rails framework will duplicate the structure of the tables in the development, and populate those tables with data from your fixtures, which reside in test\fixtures. Since you marked the Name column as NOT NULL in the database in the migration earlier, you need to edit the test data that Rails created for you in test\fixtures\wishes.yml to include names for your wishes. Your wishes.yml fixtures file might now looks like this:

Listing 20
first:
  id: 1
  name: A new bike
second:
  id: 2
  name: A great programming language
  url: http://www.rubyonrails.org

You also need to modify line 55 in editor_controller_test.rb in the test_create method, because it tries to create a new wish without a name -- which shouldn't work and will cause the test to fail. Change that line to something like this:

Listing 21
post :create, :wish => 
{:name => "Gift certificate to my favorite store."}

Now the test cases will run smoothly:

Listing 22
C:\projects\wishlist>jruby -S rake
(in C:/projects/wishlist)
C:/jruby-1.0.2/bin/jruby.bat -Ilib;test 
"C:/jruby-1.0.2/lib/ruby/gems/1.8/gems
/rake-0.7.3/lib/rake/rake_test_loader.rb" 
"test/unit/wish_test.rb"
Loaded suite 
C:/jruby-1.0.2/lib/ruby/gems/1.8/gems/rake-0.7.3/lib/rake/rake_test_loader
Started
.
Finished in 0.375 seconds.
1 tests, 1 assertions, 0 failures, 0 errors
C:/jruby-1.0.2/bin/jruby.bat -Ilib;test 
"C:/jruby-1.0.2/lib/ruby/gems/1.8/gems
/rake-0.7.3/lib/rake/rake_test_loader.rb" 
"test/functional/editor_controller_test.rb"
Loaded suite 
C:/jruby-1.0.2/lib/ruby/gems/1.8/gems/rake-0.7.3/lib/rake/rake_test_loader
Started
.....DEPRECATION WARNING: paginate is deprecated and will be removed
from Rails 2.0 (Pagination is moving to a plugin in Rails 2.0: script/plugin
install svn://errtheblog.com/svn/plugins/classic_pagination)  See 
http://www.rubyonrails.org/deprecation for details. (called from list at 
C:/projects/wishlist/app/controllers/editor_controller.rb:12)
DEPRECATION WARNING: paginate is deprecated and will be removed 
from Rails 2.0 (Pagination is moving to a plugin in Rails 2.0: script/plugin
install svn://errtheblog.com/svn/plugins/classic_pagination)  See 
http://www.rubyonrails.org/deprecation for details. (called from list at 
C:/projects/wishlist/app/controllers/editor_controller.rb:12)
...
Finished in 1.344 seconds.

8 tests, 26 assertions, 0 failures, 0 errors
C:/jruby-1.0.2/bin/jruby.bat -Ilib;test 
"C:/jruby-1.0.2/lib/ruby/gems/1.8/gems
/rake-0.7.3/lib/rake/rake_test_loader.rb"

Any deprecation warnings you get are telling you that your code won't be valid when Rails 2.0 is released. Unlike with Java, the Rails community removes deprecated functionality from future versions.

Now that the tests have succeeded, you can develop your Rails application by first writing your tests, and then add Ruby code to make your test cases work -- without having to regularly deploy them WebSphere Application Server. If you call any Java methods in your Ruby code, remember to add the supporting JARs to both your system CLASSPATH variable and lib\java. You can also run the WEBrick server to quickly test your interim changes as you continue to add to your application:

Listing 23
C:\projects\wishlist>jruby script/server
=> Booting WEBrick...
=> Rails application started on http://0.0.0.0:3000
=> Ctrl-C to shutdown server; call with --help for options
[2007-11-20 01:05:25] INFO  WEBrick 1.3.1
[2007-11-20 01:05:25] INFO  ruby 1.8.5 (2007-11-01) [java]
[2007-11-20 01:05:25] INFO  
 WEBrick::HTTPServer#start: pid=1881829418 port=3000

WEBrick and WebSphere Application Server can run concurrently. They are in different JVMs listening to different ports and shouldn't interfere with each other.


Security

Although it might be acceptable for the public to view your wishlist, you want to be able to control who can edit it. In the standard C version of Ruby on Rails, you would handle user authentication by either installing one of over ten different user authentication packages, or try to create your own by creating a user table, or something similar. In contrast, of course, WebSphere Application Server is trusted to handle LDAP, ActiveDirectory, Kerberos, and many third party authentication providers. Of course, it also supports standard Java EE security, and so you will find plenty of documentation available (see Resources).

To restrict functional access to your wishlist using WebSphere Application Security:

  1. Create a viewer controller, parallel to the editor that displays the wish list, and then restrict editor access to one specific user:
    Listing 24
    C:\projects\wishlist>jruby script\generate controller Viewer
          exists  app/controllers/
          exists  app/helpers/
          create  app/views/viewer
          exists  test/functional/
          create  app/controllers/viewer_controller.rb
          create  test/functional/viewer_controller_test.rb
          create  app/helpers/viewer_helper.rb
  2. Copy the code out of the EditorController for the ViewerController's only method. After this, app\controllers\viewer_controller.rb should look like:
    Listing 25
    class ViewerController < ApplicationController
      def list
          @wish_pages, 
          @wishes = paginate :wishes, :per_page => 10
      end
    end
  3. From the editor, copy the template for listing the wishes to app\views\viewer\list.rhtml, then remove the links to Show, Edit, Destroy and New Wish. The purpose here is to set up two separate controllers so that you can demonstrate enabling Java EE security on one of them.
    Listing 26
    <h1>Listing wishes</h1>
    
    <table>
      <tr>
      <% for column in Wish.content_columns %>
        <th><%= column.human_name %></th>
      <% end %>
      </tr>
      
    <% for wish in @wishes %>
      <tr>
      <% for column in Wish.content_columns %>
        <td><%=h wish.send(column.name) %>
        </td>
      <% end %>
      </tr>
    <% end %>
    </table>
    
    <%= link_to 'Previous page', 
     { :page => @wish_pages.current.previous } 
    if @wish_pages.current.previous %>
    <%= link_to 'Next page', { :page => 
    @wish_pages.current.next } 
    if @wish_pages.current.next %> 
    
    <br />
  4. Create a simple login page at public\login.html:
    Listing 27
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 
    Transitional//EN"
    "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
        <title>Login</title>
    </head>
    <body>
    <h1>Login</h1>
    <form action="j_security_check" method="post">
        Username: <input type="text" name="j_username">
        <br>
        Password: <input type="password" 
        name="j_password"><br>
        <br>
        <input type="submit"><br>
    </form>
    </body>
    </html>
  5. Create a simple login error page at public\login-error.html:
    Listing 28
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML
     4.01 Transitional//EN"
      "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
        <title>Login Failed</title>
    </head>
    <body>
    <h1>Login Failed.</h1><br>
    <br>
    <a href="login.html">Please try again.</a>
    </body>
    </html>
  6. To restrict everything under the editor controller (that is, under the relative URL editor/*), add the security section below to web.xml.erb. This is all WebSphere Application Server needs to restrict everything in /editor/* to users in the Administrator group. Remember that the Goldspike war:standalone:create Rake task generates the web.xml file using web.xml.erb as a template. Below the <servlet-mapping> element in WEB-INF/web.xml.erb, add:
    Listing 29
    <security-constraint>
        <display-name>Editor</display-name>
        <web-resource-collection>
            <web-resource-name>
              Editor
            </web-resource-name>
            <url-pattern>/editor/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>Administrator</role-name>
        </auth-constraint>
    </security-constraint>
    
    <login-config>
        <auth-method>FORM</auth-method>
        <form-login-config>
            <form-login-page>/login.html</form-login-page>
            <form-error-page>/login-error.html</form-error-page>
        </form-login-config>
    </login-config>
    
    <security-role>
        <role-name>Administrator</role-name>
    </security-role>
  7. Finally, you need to turn on application security in WebSphere Application Server and add your user to the wish list's Administrator group:
    1. Login to the admin console at http://localhost:9060/admin.
    2. In the left menu, expand Security and select Secure administration, applications, and infrastructure.
    3. Check the Enable application security checkbox and then click Apply.
    4. Save directly to the master configuration.
    5. In the left menu, expand Applications, and select Enterprise Applications.
    6. Select wishlist_war.
    7. Under the Detail Properties section, select Security role to user/group mapping.
    8. Click the checkbox beside Administrator and then the Look up users button.
    9. Click Search.
    10. Select your user and click > > to move the user to the Selected: column. (If there are no users to select in the Available: column, you can create a new user by expanding Users and Groups in the left menu and select Manage Users).
    11. Click OK.
    12. Restart WebSphere Application Server. (If application security had already been enabled, you would not need to restart WebSphere Application Server.)
  8. After restarting WebSphere Application Server (if that step was necessary), view the list action in the viewer controller (http://localhost:9080/wishlist/viewer/list). You should be prompted with the login challenge when you try to navigate to pages from the editor controller (such as http://localhost:9080/wishlist/editor/).

Adding security for future controllers or specific URLs is as simple as modifying the WEB-INF/web.xml.erb template.


Specifying default users and groups

Right now, each time you uninstall and reinstall the WAR file, you need to add your user back into the WAR's Administrator group. To avoid having to do this, you can package your WAR file inside an EAR and specify default groups and users inside of the EAR's META-INF configuration files. To do this:

  1. Create a META-INF directory in your root rails directory next to WEB-INF.
  2. Create an application.xml file in META-INF that describes the WAR file and the roles:
    Listing 30
    <?xml version="1.0" encoding="UTF-8"?>
    <application id="Application_ID" version="1.4" 
    xmlns="http://java.sun.com/xml/ns/j2ee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
    http://java.sun.com/xml/ns/j2ee/application_1_4.xsd">
    	<display-name>Wishlist_EAR</display-name>
    	
    	<module id="wishlist">
    	   <web>
    	    <web-uri>wishlist.war</web-uri>
                       <context-root>/wishlist</context-root>
    	   </web>
    	</module>
    	
    	<security-role id="Administrator">
    	  <role-name>Administrator</role-name>
    	</security-role>
    </application>
  3. The purpose of the file above is to automate the context root of the Web application so that you don't need to manually enter it. If you were using the Jython script mentioned earlier, then you will need to modify the AdminApp.install command on line 40 to no longer include the context root. If you're deploying it manually, then you don't need to type the context root on the initial installation page. Even if you aren't interested in providing the default context root, this step is required for the next step to work.
  4. Create an ibm-application-bnd.xml file in META-INF that details which WebSphere Application Server users and groups are mapped to the application's Administrator group:
    Listing 31
    <?xml version="1.0" encoding="UTF-8"?>
    <applicationbnd:ApplicationBinding xmi:version="2.0" 
    xmlns:xmi="http://www.omg.org/XMI" 
    xmlns:applicationbnd="applicationbnd.xmi" 
    xmi:id="ApplicationBinding">
      <authorizationTable xmi:id="AuthorizationTable">
        <authorizations xmi:id="RoleAssignment">
          <users xmi:id="admin_user" name="admin"/>
          <role href="META-INF/application.xml#Administrator"/>
          <groups xmi:id="Administrators_Group" 
                name="Administrators"/>
        </authorizations>
      </authorizationTable>
      <application href="META-INF/application.xml#Application_ID"/>
    </applicationbnd:ApplicationBinding>

    The affect of this file is that the user with id "admin" and any users in the group "Administrators" will be automatically include in the "Administrators" role, as defined in web.xml and application.xml. The interesting lines in this file are the users, role, and groups elements on lines 4, 5 and 6 respectively:
    • The user element says to put the user "admin" into this role.
    • The role element describes which role in web.xml the users and groups elements are referring to.
    • The groups element dictates which groups are going to be in this role.
    There can be as many users and groups elements as you want, but only one role element per authorizations element.
  5. Create a custom rails task to package the WAR file and the files in META-INF into the EAR:
    Listing 32
    namespace :ear do
     desc 'Create an ear file to wrap the war created by goldspike'
      task :create do
       unless system("jar -cf wishlist.ear META-INF\* wishlist.war")
        raise "Error: failed to create archive, error code #{$?}"
       end
      puts "Ear was successfully created."
    end
    end

    This final step essentially executes jar -cf wishlist.ear META-INF\* wishlist.war on the command line. Remember that Rails tasks are just Ruby code; the first three lines create a task "create" in the "ear" namespace. The sole purpose of putting tasks into namespaces is to ease maintenance. The next line says to execute your JAR command and raise an error if unsuccessful. Finally, a statement is issued to provide feedback to the user, saying that the command was successful. Here is what the output should look like:
    Listing 33
    C:\projects\wishlist>jruby -S rake ear:create
    (in C:/projects/wishlist)
    Ear was successfully created.

    The result is that the root rails directory will now contain wishlist.ear.

Using data sources

So far, this example has relied on ActiveRecord-JDBC to create and manage the pool of JDBC database connections. In production systems, it's often preferable to use a Java EE JNDI data source that can be controlled through the application server's admin console. That way, the database connections can be monitored through the application server without the help of a developer.

The beauty of ActiveRecord-JDBC is that, when configured correctly, it will attempt to use a JNDI data source if it can find one, and fall back on plain old JDBC if it cannot. The purpose of this to enable you to still use the Rake commands to migrate your database, run your tests, and so on.

To create a new data source (if you don't have one created already):

  1. Login to the WebSphere Application Server admin console.
  2. Select Resources => JDBC => Data sources. => New.
  3. Give the data source a meaningful name, such as Derby Wishlist.
  4. The JNDI name should look like a directory path, such as jdbc/wishlist.
  5. Assuming that you are using Derby, leave the Component-managed authentication alias and XA recovery authentication alias set to none.
  6. Click Next.
  7. Choose the Select an existing JDBC provider radio button and then select Derby JDBC Provider from the drop down list.
  8. Click Next.
  9. Set your database name to the directory path where the Derby database should reside on the file system; for example: C:\projects\wishlist\db\wishlist_prod. (If your database does not exist prior to this step, be sure to append create=true to the end of the command.
  10. Click Next, then Finish, then Save.
  11. You now have a data source that points to your wishlist_prod database and has the JNDI named jdbc/wishlist. Modify config/database.yml to add the JNDI name, and comment out the actual database url parameter:
    Listing 34
    adapter: jdbc
    driver: org.apache.derby.jdbc.EmbeddedDriver
    jndi: jdbc/wishlist
    #url: jdbc:derby:c:/projects/wishlist/db/wishlist_prod;create=true

    As mentioned earlier, the test and db:migrate Rake tasks won't work with the JDBC url property commented out. You are only commenting out the url line to show that the data source is being used. You will uncomment the url property again once you are convinced that the application server is using the JNDI name to lookup the data source.
  12. Redeploy the wish list application. This time, after the editor controller, you should see something like this in the WebSphere Application Server SystemOut.log:
    Listing 35
    InternalGener I   DSRA8203I: Database product name : Apache Derby
    InternalGener I   DSRA8204I: Database product version : 10.1.3.2
    InternalGener I   DSRA8205I: JDBC driver name  : Apache Derby 
     Embedded JDBC Driver
    InternalGener I   DSRA8206I: JDBC driver version  : 10.1.3.2

To perform further migrations using the Rails Rake command line tasks:

  1. Uncomment the URL property in config\database.yml.
  2. Stop WebSphere Application Server, since it contains a lock on the database.
  3. Run jruby -S rake db:migrate RAILS_ENV=production to verify that your database is up to date.

You might see warnings like this when running Rake or a script from the command line:

Listing 36
JNDI data source unavailable: javax.naming.NoInitialContextException: 
Need to specify class name in environment or system property, or as an 
applet parameter, or in an application resource file: 
java.naming.factory.initial; trying straight JDBC

This is normal and expected. Rails is warning you that there is nowhere to look up the JNDI information and that it is falling back onto JDBC, as expected.


Conclusion

New programming languages, regardless of their cutting edge features and capabilities, always require time to mature. Enterprise applications, by their nature, require a set of mundane-by-comparison essentials: top notch security, a strong administrative interface, and the ability to be maintained at run time by IT personnel who are not programmers, to name a few. By combining the maturity of WebSphere Application Server with the rapid development possibilities of Ruby on Rails, you can get the competitive advantages of both platforms.

This article was a primer on getting started developing Rails applications on WebSphere Application Server. Part 2 will explain how you can leverage existing custom Java APIs and EJB components with Rails, if Rails on WebSphere Application Server can be used to create SOAP and REST Web services, what the differences are in performance and memory utilization between Rails running on Ruby/JRuby and regular Java servlets and JSPs, and more.

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into WebSphere on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=WebSphere, Open source
ArticleID=282857
ArticleTitle=Riding the Rails with WebSphere: Part 1: Combine Ruby on Rails with WebSphere Application Server and get rapid development with security, scalability, and reliability
publish-date=01232008