Ruby on Rails is a Web application framework that aims to provide an easy path to application development. In fact, the framework's proponents claim that Ruby on Rails developers can be up to ten times more productive than they would be when using traditional J2EE frameworks. (Read the article titled "Rolling with Ruby on Rails" for more on this claim; see Resources). While this statement has been the source of considerable debate in the Rails and J2EE communities, little has actually been said about how Rails and J2EE architectures compare. This article will contrast the Rails framework against a typical J2EE implementation using common open source tools that are regularly found in enterprise applications.
To find a simple, one-sentence description of Rails, you need look no further than the project's home page:
Rails is a full-stack, open-source Web framework in Ruby for writing real-world applications with joy and less code than most frameworks spend doing XML sit-ups.
Although I can't guarantee that the framework will deliver on its promise of joy, the statement does a good job of summing up Rails' qualities. The full stack consists of a Web server, a framework for processing HTTP requests and responses, and a framework for easily persisting data to a relational database. Rails strives for development ease by eliminating complicated XML configuration files and using the very dynamic nature of the Ruby language to help minimize much of the repeating code often found in static typed languages.
Rails and a typical J2EE Web stack
Figure 1 compares the Rails stack to a typical J2EE Web stack comprised of the Tomcat servlet container, the Struts Web application framework, and the Hibernate persistence framework.
Figure 1. Comparison of Rails and J2EE stacks
As you can see, the fundamental difference between the Rails stack and the components that make up a common J2EE-based Web application is small. Both have a container in which the application code will execute; an MVC framework that helps to separate the application's model, view, and control; and a mechanism to persist data.
Struts' ActionServlet and Rails' DispatchServlet are both examples of the Front Controller pattern; as such, they both provide the same functionality. They accept HTTP requests, parse the URL, and forward processing of the request to an appropriate action. In the case of Struts, an action is a class that extends Action; for Rails, it is a class that extends ActionController. The main difference between the two front controllers is how they determine the action that processes a particular request.
With Struts, a developer needs to externalize the mappings of specific requests to Action classes in an XML configuration file. When the ActionServlet is first loaded, it parses this file and prepares to accept requests. By convention, HTTP requests that end in .do get redirected to ActionServlet for dispatching to the appropriate Action. The XML in Figure 2 is a typical mapping. It tells the ActionServlet to forward a request called deleteOrder.do to controllers.order.DeleteOrderAction for further processing.
Rails takes a different approach. Instead of relying upon a configuration file to map requests to actions, it discovers the appropriate action based on the URL requested. As you can see in Figure 2, the URL http://localhost/order/delete/4 indicates for Rails to invoke the delete method on an instance of OrderController and make 4 available as an instance variable. Rails is smart enough to know that /order maps to a controller class defined in a file named order_controller.rb. If there is a find method defined in the controller, that method can be invoked by simply replacing delete with find in the URL.
Figure 2. URL mappings in Rails and Struts
In both Rails and Struts, the action acts as a bridge between the front controller and the model. The developer provides an implementation of an action in order to provide application-specific processing of a request. The front controller is responsible for accepting the request and passing it off to a specific action. Figure 3 illustrates a basic action hierarchy for Rails and Struts.
Figure 3. Rails and Struts action hierarchy
Struts requires that the developer extend Action and override execute() in order to process the request. Generally, each Action class provides a very specific unit of work. Figure 3 illustrates three specific actions: SaveOrderAction, DeleteOrderAction, and ListOrdersAction. The front controller calls the execute() method and passes it a number of useful objects, including the HTTP request and response objects. ActionForm is a class that conveniently transfers and validates form-related input to and from the view, and ActionMapping contains the configuration information for the mapping as described in the XML in Figure 2.
The execute() method returns an ActionForward object that Struts uses to determine the component that continues processing the request. Generally, this component is a JSP, but ActionForward can also point in other actions. Developers must be aware that Struts will create a single instance of the Action and allow multiple threads to invoke execute(). This allows for faster request processing, as the framework is not continually creating new Action instances to handle each request. But because a single object is shared between multiple threads, you must observe proper threading considerations, as other threads are likely to pummel instance variables that hold state in the action.
In Rails, you must extend ActionController::Base for the model to participate in the processing of a request. Rails doesn't pool the instance of the ActionController; instead, it creates a new instance for each request. While this might have a negative impact on performance, it makes development easier. Developers need not be concerned with the threading issues that are present in Struts, and as a result, the session, request, header, and parameters are all accessible as instance members of the ActionController. ActionControllers are also a logical place to group all processing of specific domain logic. While Struts Action classes are fine-grained and provide very specific units of work, Rails ActionControllers are coarse-grained and model discreet units of work as methods.
Listing 1 and Listing 2 illustrate a typical Struts action and a typical Rails action, respectively.
Table 1 offers a comparison of the logic flow of the two methods, and illustrates what happens on specific lines in Listings 1 and 2. When you examine the execute() method of DeleteOrderAction and the delete method of OrderController, you can see that they are basically the same.
Table 1. execute() and delete methods compared
| Step | Struts | Rails |
| Framework calls action | Line 03: execute() | Line 07: delete |
| ID retrieved from request | Lines 06-07: Pulled from request object | Line 08: Pulled from an instance hash of all parameters |
| Order record is deleted from database | Lines 09, 14-24: delete() method called, deleting the record using Hibernate | Line 09: Deletes the record using ActiveRecord |
| Redirecting to list the remaining orders | Line 11: The ActionMapping object is used to look up the next component to forward processing to. The XML mapping in Figure 2 shows that success maps to /listOrders, which is another Action that is responsible for looking up the remaining orders and presenting them as a JSP. | Line 10: The redirect_to method is called with a hash of the next action to invoke; in this case, it simply calls the list method of the same controller. |
A persistence framework is used to move data to and from the database in the application layer. Both Hibernate and Rails persistence frameworks can be classified as object/relational mapping (ORM) tools, meaning that they take an object view of the data and map it to tables in a relational database. Both frameworks aim to reduce the development time associated with working with relational databases. However, Figure 4 illustrates some fundamental differences in how each is designed and configured.
Figure 4. Comparison of Active Record and Hibernate persistence frameworks
Hibernate is based on the Data Mapper pattern, where a specific mapper class, the Session, is responsible for persisting and retrieving data to and from the database. Hibernate can persist any Java object as long as it conforms to JavaBean specifications. XML mapping files describe how a class maps to a particular table in the database, along with any relationships that the class has with other classes.
Listing 3 shows an example of a Hibernate mapping file. The class tag maps the Order object to the ORDERS table and has a number of sub tags that describe its properties, the ID and the order name, and a one-to-many relationship to models.Item. Listing 4 shows the Order class itself.
Listing 3. Order.hbm.xml
... 01 <hibernate-mapping> 02 <class name="models.Order" table="ORDERS" 03 dynamic-update="true" dynamic-insert="false" 04 discriminator-value="null"> 05 06 <id name="id" column="id" type="java.lang.Long" 07 unsaved-value="null"> 08 <generator class="identity"/> 09 </id> 10 11 <set name="items" lazy="false" inverse="false" 12 cascade="none" sort="unsorted"> 13 <key column="id"/> 14 <one-to-many class="models.Item"/> 15 </set> 16 17 <property name="name" type="java.lang.String" 18 update="true" insert="true" 19 access="property" column="name"/> 20 </class> 21 </hibernate-mapping> |
Listing 4. Order.java
01 public class Order {
02 private Set items;
03 private String name;
04 private Long id;
05
06 public Long getId() { return id;}
07
08 public void setId(Long id) { this.id = id;}
09
10 public Set getItems() { return items;}
11
12 public void setItems(Set items) { this.items = items; }
13
14 public String getName() { return name; }
15
16 public void setName(String name) { this.name = name; }
17 }
|
Rails' ORM framework is called Active Record and is based upon the design pattern of the same name. Martin Fowler describes an Active Record as "an object that wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data." In Rails, each domain object extends ActiveRecord::Base, which provides the CRUD operations.
Active Record doesn't require a mapping file, as Hibernate does; in fact, a developer working with Active Record doesn't need to code getters or setters, or even the properties of the class. Through some nifty lexical analysis, Active Record is able to determine that the Order class will map to the ORDERS table in the database. Using a combination of Ruby reflection and metaprogramming, the columns of the table become properties of the object. Accessors and mutators are also added.
Listing 5 shows the completed code for the Order class. The one line of code in the class body of Order defines its relationship to the Item object. has_many is a static method call for which the symbol :items is a parameter. ActiveRecord uses :items to discover the Item domain object and in turn maps the Item object back to the ITEMS table in the database.
Listing 5. order.rb
01 class Order < ActiveRecord::Base 02 has_many :items 03 end |
The Order class as coded in Listing 5 provides dozens of class and instance methods at runtime. Table 2 offers a partial list of operations and attributes available on Order:
Table 2. Attributes and operations available on Order
| Class methods | Instance methods | Attributes |
|
|
|
While Ruby on Rails is a very new and exciting framework that has generated considerable interest in the Web development community, the core architecture follows the basic patterns found in J2EE. It's the philosophical approach to development of Web applications that sets the two frameworks apart. Rails prefers explicit code instead of configuration files, and the dynamic nature of the Ruby language generates much of the plumbing code at runtime. Most of the Rails framework has been created as a single project and application development benefits from a set of homogeneous components. In contrast, the typical J2EE stack tends to be built from best-of-breed components that are usually developed independently of one another, and XML is often used for configuration and gluing the components together.
So, should you consider Rails for your next Web application? Well, why shouldn't you? It's a well-written stack of components that work well with each other and are based upon industry accepted enterprise patterns. The Ruby language allows for fast development and adds to the framework by generating much of the application plumbing. Those who are familiar with MVC and ORM frameworks available in the Java world will have no difficulty wrapping their minds around Rails.
Should you dispense with J2EE altogether in favor of Rails? Absolutely not. J2EE is a well-established standard with several solid implementations and, most importantly, is a proven technology. But I do suggest that you download a copy of Rails and start hacking away. Many of the tutorials that are available are introductory and will get you up and running in no time. Again, I don't guaranteeing that you'll experience joy as a result of working with Rails, but I do bet you'll find contentment.
- Check out the Ruby on Rails Web site.
- Pick up most of what you need to know about Ruby from ruby-lang.org.
- Find more information on Struts and the Jakarta project.
- Learn more about Hibernate.
- Get an excellent introduction to creating Rails applications in "Rolling with Ruby on Rails" by Curt Hibbs (ONLamp.com, January 2005).
- Find detailed explanations of the Model-View-Controller and Front Controller patterns in Sun's Blueprints catalog.
- Explore abridged explanations of the Active Record and Data Mapper patterns at Martin Fowler's Web site.
- Catch Fast-track your Web apps with Ruby on Rails so you can quickly build and customize applications with this Ruby-based framework and its model-view-controller design pattern (developerWorks, June 2005).
- Discover how Struts can help you control change in your Web project and promote specialization in Struts, an open-source MVC implementation (developerWorks, February 2001).
- In Object-relation mapping without the container, learn to combine Hibernate and Spring to build a transactional persistence tier for your enterprise applications (developerWorks, April 2004).
- Check out Wikipedia's definitions of reflection and metaprogramming.
- Visit the developerWorks Web Architecture zone for articles covering various Web-based solutions.
- Get involved in the developerWorks community by participating in
developerWorks blogs.
Comments (Undergoing maintenance)






