Crossing borders: Streamlined, Part 1

Enterprise-strength scaffolding using Ajax, metaprogramming, and the Ruby on Rails framework

Ruby on Rails is a radically productive Web development environment based on the Ruby programming language. Streamlined is a rapidly growing new open source framework based on Ruby on Rails. Streamlined combines the power of Ajax, metaprogramming, code generation, and Ruby on Rails to take Rails productivity to a new level.

Bruce Tate (bruce.tate@j2life.com), President, RapidRed

Bruce TateBruce Tate is a father, mountain biker, and kayaker in Austin, Texas. He's the author of three best-selling Java books, including the Jolt winner Better, Faster, Lighter Java. He recently released From Java to Ruby. He spent 13 years at IBM and is now the founder of the RapidRed consultancy, where he specializes in lightweight development strategies and architectures based on Java technology and Ruby on Rails. His practice now offers a full range of Ruby and Rails education, consulting, and implementation offerings.



05 September 2006

I've been training for a marathon for the first time. The most interesting aspect of marathon training -- really, the only aspect -- is the impact of iterative improvements on a growing foundation. Sometimes I improve my efficiency with short or long runs designed to enhance my conditioning. Other times, within a run, I learn to avoid small mistakes, repeating wasted gestures that don't hurt a single stride much but can waste energy or injure me when added up over 26.2 miles. As I improve each week, the difference in a week isn't pronounced, but over the span of my training schedule, I'll go from my first run of four miles to running 26.2. Software development is similar. If you keep making small improvements and cut out wasted repetition, you'll continually build on a foundation that lets you go much farther with every progressive run.

About this series

In the Crossing borders series, author Bruce Tate advances the notion that today's Java programmers are well served by learning other approaches and languages. The programming landscape has changed since Java technology was the obvious best choice for all development projects. Other frameworks are shaping the way Java frameworks are built, and the concepts you learn from other languages can inform your Java programming. The Python (or Ruby, or Smalltalk, or ... fill in the blank) code you write can change the way that you approach Java coding.

This series introduces you to programming concepts and techniques that are radically different from, but also directly applicable to, Java development. In some cases, you'll need to integrate the technology to take advantage of it. In others, you'll be able to apply the concepts directly. The individual tool isn't as important as the idea that other languages and frameworks can influence developers, frameworks, and even fundamental approaches in the Java community.

In this two-part article, I'll focus on improvements to Ruby on Rails scaffolding, a Rails feature that reduces repetition at early development stages. Part 1 illustrates Rails scaffolding's limitations and introduces Streamlined, a code generator that uses highly effective metaprogramming techniques to eliminate higher-order repetition. Part 2 will delve more deeply into Streamlined's metaprogramming model and customization features.

Low-level versus high-level repetition

Throughout the Crossing borders series, I've addressed languages and frameworks that allow iterative improvement through reduced repetition and improved efficiency:

  • Programming languages with features like duck typing can improve flexibility and reduce repetition by forcing fewer type definitions and reducing code needed purely to support a compiler.
  • Frameworks seek to improve efficiency and eliminate repetition by handling core tasks -- such as persistence or transactions -- so you don't need to code them for each new application.
  • Ruby on Rails eliminates repetitive configuration by leaning on common conventions, letting the framework infer your intent instead of forcing you to configure application features such as database table names and column names specifically within your application.

These all focus on each step, or low-level repetition, as all productive languages and frameworks must. But once you've built an effective foundation, you can aim higher. The Rails scaffolding feature seeks to eliminate the repetition across a common class of applications: database-backed Web applications.

Cutting the fat

Rails cuts out a staggering amount of fat. Repetitive configuration, restating conventions in code, and inefficient idioms in other frameworks don't seem to show their ugly faces in this framework. But a staggering amount of repetition remains. That's true of all frameworks. Remember, Japanese auto makers didn't frighten Mercedes and BMW by cranking out a single car. They simply have a single-minded focus on relentless improvement. If you're seeking improvement in a framework for building traditional Web applications, you'll still find a tremendous amount of fat to pare away.

Most database-backed Web applications must provide a user interface to do CRUD operations -- create, read, update, and delete -- for nearly every major table in the system. Building these user interfaces should be automated, not repeated. Rails begins to eliminate some of that repetition through scaffolding, a feature that builds default CRUD screens based on the contents of a set of database tables. With Rails, you can build a fully scaffolded application from scratch in just a few simple operations. If you've been following the Crossing borders series, you've seen the steps before. I'll condense them for you this time around:

  1. Type rails trails to generate a Rails application to catalog mountain biking trails.
  2. Create a database called trails_development using your chosen database engine and modify trails/config/database.yml to reflect your chosen configuration.
  3. Change to the trails directory and generate your model and controller by typing ruby script/generate model Trail (you can omit the ruby if you're running on UNIX) to generate a model called Trail and script/generate controller Trails to generate a controller called TrailsController.
  4. Edit the file db/migrate/001_create_trail.rb to look like Listing 1:
    Listing 1. Initial migration
    class CreateTrails < ActiveRecord::Migration
      def self.up
        create_table :trails do |t|
          t.column :name, :string
          t.column :difficulty, :string
          t.column :description, :text
        end
      end
    
      def self.down
        drop_table :trails
      end
    end
  5. Edit the file app/controllers/trails_controller.rb to look like Listing 2:
    Listing 2. Scaffolding within the TrailsController
    class TrailsController < ApplicationController
      scaffold :trail
    end
  6. Run the migration by typing rake migrate.
  7. Start a server with the command script/server and point a browser to localhost:3000/trails/list.

And presto: You've got a simple working database-backed Web application that does each CRUD-based operation, as shown in Figure 1. You can see the main screen listing each item and the associated graphics offer Ajax windows for creating, reading, updating, and deleting items.

Figure 1. A simple Rails application
Rails default scaffolding

You've expended very little effort to get to the point where you can kick your application development into high gear. A Rails presenter almost always shows scaffolding because it's so flashy, and it's extremely useful for debugging and showing your customer something in a hurry. You can generate scaffolding via a code generator -- you'd type script/generate scaffold Trail Trails in this example -- or through specifying the scaffold metaprogramming tag in a controller. Each approach has its place.


Adding relationships

Scaffolding does have some significant limitations: it doesn't handle relationships, and it doesn't take advantage of the excellent Rails Web services or Ajax support. To illustrate these limitations, you'll create a Location with model, view, and controller. Location has a one-to-many relationship with Trail. Scaffolding does not help you manage the relationship at all.

Create a model (script/generate model Location) and controller (script/generate controller Location Locations) for a location. As you did with the TrailsController, add scaffold :location to location_controller.rb. To weave Location and Trail together, you want a many-to-one relationship between the two, so add belongs_to :location to Trail and has_many :trails to Location, as in Listing 3:

Listing 3. Relationships in trail.rb and location.rb
class Trail < ActiveRecord::Base
  belongs_to :location
end

class Location < ActiveRecord::Base
  has_many :trails
end

Edit db/migrate/002_create_locations.rb to look like Listing 4:

Listing 4. The migration for the locations table
class CreateLocations < ActiveRecord::Migration
  def self.up
    create_table :locations do |t|
      t.column :city, :string
      t.column :state, :string
    end
    add_column "trails", "location_id", :integer
  end

  def self.down
    drop_table :locations
    remove_column "trails", "location_id"
  end
end

Run the migration by typing rake migrate. (To see more about migrations, see Crossing borders: Rails migrations.)

Whew. That's a lot of setup in a hurry. It's time to take a deep breath and talk about what you've built so far:

  • You have a database table for trails and another one for locations.
  • You have Ruby model objects with Rails relationships between them.
  • The model now has a many-to-one relationship between trails and locations.
  • You have a strategy for dealing with changes in your schema, and you can back out either of the two major schema changes you've made so far.
  • You have a primitive user interface.

The model object is a first-class Rails object that is suitable for production uses, though you might want to add some validation. Many Rails model objects are simple because the attributes are all added dynamically with metaprogramming. To illustrate the existing relationship, let's add some data through the console. Start the console by typing script/console and type the commands in Listing 5:

Listing 5. Adding data to trails and locations
>> trail = Trail.new
=> #<Trail:0x2446168 @attributes={"name"=>nil, "location_id"=>nil, 
   "description"=>nil, "difficulty"=>nil}, @new_record=true>
>> trail.name = "Hermosa Creek"
=> "Hermosa Creek"
>> trail.difficulty = "easy"
=> "easy"
>> trail.description = "22 miles of mostly downhill singletrack."
=> "22 miles of mostly downhill singletrack."
>> trail.save
=> true
>> location = Location.new
=> #<Location:0x240d1c4 @attributes={"city"=>nil, "state"=>nil}, @new_record=true>
>> location.city = "Durango"
=> "Durango"
>> location.state = "Co"
=> "Co"
>> location.trails << trail
=> [#<Trail:0x2446168 @errors=#<ActiveRecord::Errors:0x2411c9c @errors={}, 
   @base=#<Trail:0x2446168 ...>>, @attributes={"name"=>"Hermosa Creek", "id"=>1, 
   "location_id"=>nil, "description"=>"22 miles of mostly downhill singletrack.", 
   "difficulty"=>"easy"}, @new_record=false>]
>> location.save
=> true
>> hc = Trail.find 1
=> #<Trail:0x147c588 @attributes={"name"=>"Hermosa Creek", "location_id"=>"1", 
   "id"=>"1", "description"=>"22 miles of mostly downhill singletrack.", 
   "difficulty"=>"easy"}>
>> hc.location
=> #<Location:0x6cc2f8 @attributes={"city"=>"Durango", "id"=>"1", "state"=>"Co"}

Listing 5 adds a trail and a location to the database, managed with a foreign key pointing from the location_id column in trails to the id column in locations. The model objects are robust enough to form a reasonable foundation for an application. The views, however, are another story.

Relationship issues

Point your browser to http://localhost:3000/trails/show/1 to see the screen shown in Figure 2:

Figure 2. Rails scaffolding has no relationship fields
Rails default scaffolding for show

You see no hint of the relationship between trail and location here. You'll also notice that the scaffolding is very primitive: it has no graphics, no Ajax, no common headers or sidebars, and none of the embellishments you've come to expect from modern Web pages. Now, you're probably willing to give Rails a free pass because you've spent only a few minutes building a relatively sophisticated application through scaffolding. It's likely that you don't expect that this simple feature can generate robust code. It's time to raise your expectations.

Scaffolding represents a significant improvement over the state of the art in most Web development frameworks, but it can and should be improved. But by building on this particular foundation, you'll find that the leverage you can get is extreme. It's like starting your marathon training at 13 miles instead of 4.

Scaffolding -- like many metaprogramming techniques -- is nothing but a run-time code generator. Some in the Rails community think scaffolding is a limited idea, believing that the scaffold is not rich enough to handle most applications. Others think scaffolding is a great idea and that the quality of the scaffold is the fundamental issue. It all depends on the nature of your application. If you're building a repetitive pattern, you'll get great mileage out of the metaprogramming techniques that form the foundation for scaffolding. If your template is sufficiently adjustable and rich, you'll be able to reduce repetition at much higher levels in the framework. Enter Streamlined.


Streamlined: Scaffolding on steroids

Since Rails came into existence, Rails plug-ins of various shapes and sizes have been raising the level of abstraction for all application development. Components such as the login generator let you generate security. Other plug-ins make it easier to handle Web services within Rails. Streamlined goes beyond mere scaffolding with a production-quality application generator. As with scaffolding, you may need to extend the resulting code, but the initial application is surprisingly functional in its own right.

Created by Relevance LLC, Streamlined grew out of a set of open source tools used to automate training called Code Site and was improved each time Relevance built a commercial Rails application. Eventually, the team produced the generalized Streamlined framework to solve the common problems across many projects.

Download the Streamlined .gem file for the initial alpha version (see Resources). Change into the directory where you saved the .gem and type gem install streamlined. Everything you need is installed automatically. If you have problems, you can get excellent support through the streamlined blog, and commercial support options are also available.

It's time to put Streamlined into practice. First, run the Streamlined generator by typing script/generate streamlined location trail. When prompted whether to replace the locations and trails controllers, respond y. Next, restart the server by killing the webrick server process and typing script/server.

Point your browser to http://localhost:3000/locations/list to see the result in Figure 3:

Figure 3. Default Streamlined application
Streamlined default scaffolding for list

You can immediately tell Streamlined produces a more complete application. Compare the Streamlined list with the one in Figure 1. The differences are striking:

  • The default application handles relationships, which you can see by clicking on one of the Edit links. You'll see more about relationships in the next section and even more in Part 2 of this article.
  • The application makes better use of stylesheets and generates a more sophisticated stylesheet. Streamlined uses techniques, such as preferring <div>s over tables, to make each page's elements easier to style. This alpha version of Streamlined has limited styling, but expect future versions to lift that limitation.
  • The application has a default navigation sidebar on the left and a menu and header on the top. These menus have a more complete default behavior and can be customized.
  • Each row of the table has graphics for editing, showing, or deleting the row, and the application has additional graphics for creating a new entry, exporting CSV, and exporting XML for the whole table.

This page looks more like a default application and less like an incomplete scaffold. That's precisely the point of Streamlined. Before I go much further, I should give you a brief description of how Streamlined works.

Prerequisites

To use Streamlined, you start with a working database schema and a model using the classic Rails tools and conventions you've seen in this article. Then, you use script/generate streamlined model1, model2, and so on to generate the Streamlined interface. Streamlined observes Rails naming conventions and reloads application objects frequently when in development mode, so you can simply reload your browser to see most coding changes.

Like Rails scaffolding, Streamlined is a metaprogramming framework that uses metadata to build a default application, which you can then customize in a variety of ways. The framework queries two sources of metadata: the Active Record model and a custom metadata file for each model object. By default, Rails captures enough metadata within Active Record to build a surprisingly sophisticated default user interface. Active Record queries your database tables for information that goes far beyond the data in the table and maintains other information you provide, such as primary keys, relationships, columns, column types, and column sizes. Streamlined uses all of these things to give you a default application, but to tailor the application, the framework needs more data. Streamlined provides an additional source of metadata.

A quick query of the directories beneath trails/app reveals the usual Rails directories: models, controllers, views, and helpers. But a fifth directory is available as well: streamlined. This is where you specify additional metadata. A quick listing of the four files in the streamlined directory tells the story:

  • location.rb and trail.rb contain the detailed customization information for the models of the same name.
  • streamlined_relationships.rb contains more information about relationships specified through Active Record, such as how Streamlined will render the relationships.
  • streamlined_ui.rb contains configuration information for global user interface issues, such as whether to create headers, footers, and the left navigation bar.

Right now, Streamlined works with a combination of code generation (which generates code you can modify) and true metaprogramming (which uses the Ruby language to add code dynamically to an application at run time). Streamlined generates static content and pages that you will probably want to modify later. For example, the generator copies stylesheets and graphics directly to your project. You can create views, which you may or may not need to modify, with true metaprogramming or code generation.

Features

By playing with this default application, you can get a sense of just how many features it provides. The left-hand navigation sidebar has a link for each model you specify -- just trails and locations for your model. Clicking a link takes you to the main page for each one. The header has a default set of links to manage objects in the domain, context-sensitive help, and an About page.

As you move into the area for tabular data, you can see increasingly sophisticated capability. A text box serves as a filter for records. To see how it works, click the + link to add a new trail and type in some data. Next, type Her in the main window. You'll see the list trimmed to only entries containing the given text in any field. You can also click any of the column names to sort columns by that heading.

Along the way, you've no doubt noticed the excellent Ajax capability. One of the big benefits of Ajax in a CRUD setting like this is that you can provide everything you need for managing a table on one major screen, with a few smaller pop-ups (for edit, show, and delete). Ajax enables a richer user experience, a more concise application footprint, and better user feedback.

To wrap up, take a look at the relationship management. Click the Locations link on the left-hand sidebar. Then click the + graphic and add a new location (try adding Moab, Utah). Click Edit underneath the trails and select the trails that should belong to this location. Notice that Streamlined keeps a count by default of the number of trails belonging to each location. This default behavior is already quite rich, but I'll customize it in Part 2 with increasingly sophisticated optimizations.


Comparing to Java frameworks

To date, the most popular Java™ frameworks do not generate scaffolding, much less applications. Part of the reason is a fundamental lack of competition driving innovation in this space. Ruby on Rails is changing that. Still, you'd assume that after eight years of Web frameworks, someone would have built something similar.

Application generators have not been successful in the Java environment. There's one important problem with most of them: They rely too much on code generators and not enough on a metamodel with a solid metaprogramming framework to supplement code generation. Such frameworks can give you a short productivity boost, but they can't maintain improvements over time. Generated code is usually too brittle and often complex. Unless you have sufficient capability to customize code between generation passes, you'll lose your productivity engine over time. Streamlined does allow code generation, but only for those parts of the application that will not change, or for simple and volatile sections of an application -- such as views and stylesheets -- that developers can easily change and maintain.

Two Java frameworks that seem to get the mix of code generation and metaprogramming right are RIFE and JMatter (see Resources). I've discussed RIFE several times in this series, but JMatter is new. The JMatter framework has an open source license and commercial pricing as well. Based on Hibernate with Swing, JMatter allows rapid development of quite sophisticated applications based on a metaprogramming model. Eitan Suez, a prominent speaker on the Java circuit, built JMatter to help rapidly jump-start Java development for a two-tier client/server application for a medical practice. After nearly two years of specialization, JMatter is surprisingly robust, and its features easily rival Rails and Streamlined. It remains to be seen if the pace of change in JMatter can keep up with the state of the art in the Ruby community.


Looking ahead

In this article, I presented Rails scaffolding, its limitations, and an alternative called Streamlined. Streamlined's scaffolding is more complete, but so far it's still scaffolding. In Part 2, you'll read a more detailed discussion of the metaprogramming model around Streamlined, and you'll learn how to customize key pieces of the application. Until then, open up your mind and your toolbox. Keep crossing borders.

Resources

Learn

  • Java To Ruby: Things Your Manager Should Know (Pragmatic Bookshelf, 2006): The author's book about when and where it makes sense to make a switch from Java programming to Ruby on Rails, and how to make it.
  • Beyond Java (O'Reilly, 2005): The author's book about the Java language's rise and plateau and the technologies that could challenge the Java platform in some niches.
  • "Book review: Agile Web Development with Rails" (Darren Torpey, developerWorks, May 2005): Get the scoop on a book that deepens readers' understanding of Rails and the rationale behind agile development approaches.
  • Streamlined: Visit the Streamlined Web site/blog.
  • The Rails API: The Rails Framework documentation is the best way to learn about scaffolding from the inside out and the metaprogramming techniques that make it tick. See the scaffolding.rb class for more details.
  • Programming Ruby (Dave Thomas et al., Pragmatic Bookshelf, 2005): A popular book on Ruby programming.
  • The Java technology zone: Hundreds of articles about every aspect of Java programming.

Get products and technologies

  • Streamlined: Download the Streamlined application generator and try it out. Streamlined is moving rapidly, so you'll want to download the initial alpha version.
  • RIFE: A Java-based metaprogramming framework, providing very good scaffolding for CRUD-based applications based on a strong metamodel.
  • JMatter: Download the new JMatter framework, the best framework the author has seen for two-tier Swing- and Hibernate-based applications.
  • Ruby on Rails: Download the open source Ruby on Rails Web framework.
  • Ruby: Get Ruby from the project Web site.

Discuss

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 Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=157618
ArticleTitle=Crossing borders: Streamlined, Part 1
publish-date=09052006