 | Level: Intermediate Bruce Tate (bruce.tate@j2life.com), President, RapidRed
03 Oct 2006 Part 1 of this two-article series introduced Streamlined, a Rails-based open source framework that combines the power of Ajax, metaprogramming, and code generation to take Rails productivity to a new level. Part 2 explores how the metamodel behind Streamlined enables customizations.
In Part 1 of this two-part series, I demonstrated a Ruby on Rails feature called scaffolding, illustrated its limitations, and then showed how a framework called Streamlined can improve scaffolding to the point of building a functional user interface. But a user interface almost always requires some customization, even when you use the most sophisticated generators. If you can't effectively extend what you've built, no matter how quickly you've built it, you don't have an effective application development framework. This article shows the foundation for the customization model behind Streamlined: the Streamlined metamodel.
A quick Streamlined review
 |
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 Part 1, I built a simple Rails application to store mountain-biking trails and organize them by their closest city. I started by creating a model containing two model objects and a relationship between them. I then ran the Streamlined application generator, which produced a surprisingly robust application, with the following features:
- A full user interface contains a layout, with links to the managing page for each model.
- Each model has a list page that can be sorted by any column and filtered based on the contents of all columns.
- The Ajax-enabled list lets you show, edit, delete, or create entries.
- An exporter can generate XML or CSV exports for the view.
As the young Streamlined framework grows, enhancements to the base application template will be available to all Streamlined applications. Currently planned enhancements include Web services based on REST and SOAP and canned style templates called skins.
Customizing the default application
It's time to customize the code for the Trails application. If you want to code along with me, you need to install Ruby, Rails, and Streamlined (see Resources) and build the application in Part 1. You'll add onto that foundation to customize the Trails application.
If you haven't already done so, start your Web server by typing script/server (ruby script/server on Windows) from your project directory. Bring up the view for Trails by pointing your browser to the URL http://localhost:3000/Trails/list. Figure 1 shows the base Streamlined view. If you've added any data, you'll notice that unlike Rails scaffolding, Streamlined manages relationships for you. You'll also notice that the relationships depend on the id field.
Figure 1. A default Streamlined application
Streamlined customization strategies
This implementation works, but it's not the best one for users. You'd prefer to see the name of the city for the trail location instead of a simple ID. In fact, this application's users should never need to see an ID. Before I solve this problem, I'll dive into the customization philosophies for Streamlined.
Some application-generation frameworks would force you at this point to modify generated code. Rails scaffolding certainly works that way, but that's just one of at least three approaches to extending a generated application:
- You can modify generated code.
- You can use programming idioms (such as inheritance) and customization hooks (such as Visual Basic scripts) to write new code extending the application.
- You can increase the available metadata so the framework can generate a smarter application.
Each of these approaches has a place. The first strategy gets progressively more limited as code complexity increases because it saddles the developers with potentially complex code that they didn't write. You should restrict this strategy to extending very simple code. Streamlined makes it easy to modify the existing controllers by adding new actions.
The second approach -- extending an application with new custom code -- is a common approach taken by most rapid application development environments. Usually, this strategy works best when the framework designers consider places to add customizations. Rails, combined with the dynamic nature of Ruby, makes this strategy appealing by providing many customization points that the developer might not expect. In Streamlined, you'll often create a new template for your application's views. You can also layer on your own crosscutting concerns, such as security, by using the generic Rails architecture.
The third approach, an improved metamodel, is the foundation of Streamlined. In Part 1, you saw how scaffolding used the metadata provided by Active Record -- types, column names, and relationships -- to build a sophisticated user interface. Within Java applications, you'd often implement this strategy by using XML configuration. That is certainly one way to provide metadata, but I see too many sharp corners and angles, and I'm concerned that one day, I'll cut my cornea.
Modifying display contents through the metamodel
 |
The mail-merge metaprogramming metaphor
In previous articles in the Crossing borders series, you've seen both templates and metaprogramming techniques in Ruby. Think of the Ruby templates as mail-merge documents. Then, Rails uses metaprogramming to produce and consume a metamodel, such as the database schema or column definitions, for a list of names in the mail-merge application. Streamlined introduces two foundational pieces of code to simulate this approach. View templates let Streamlined generate not one "mail-merge" document, but all similar views throughout an application. And Streamlined uses metadata through Active Record and also through a custom metamodel. These pieces, combined with the Streamlined generators and libraries, form an impressive platform for generating applications.
|
|
Rather than deal with that pain, Streamlined allows effective customization through metaprogramming plus templates. To understand this approach, think of a mail-merge application. A mail-merge application takes a list of names and a template that contains a message. The template has some custom places to plug in details such as names, addresses, and phone numbers. Think of the list of names as metadata that you use to customize a template.
Streamlined is like a mail merge -- one that uses metadata for each model object to create not mail letters, but whole applications. You provide a view template or use the default one Streamlined supplies. First, you ask Streamlined to generate an application template and a holder for the metadata for each model class you specify. Next, you specify custom user interface options through the metamodel, such as how you want Streamlined to display relationships. Then, you tell Streamlined how to render each view. Streamlined applies the data in the metamodel to the template, combining them to form a new view for each model you specify, as in Figure 2:
Figure 2. Streamlined metadata and templates
First, I'll show you the metamodel. You'll first see how the metadata impacts the application. Then, I'll show you how to customize applications using Streamlined templates. In particular, look at the app/Streamlined/trail.rb file. You'll see an empty class. This is where you can customize certain aspects of the trails/list view. Edit the file to look like Listing 1:
Listing 1. The Streamlined metamodel for trail.rb
require File.dirname(__FILE__) + '/streamlined_ui'
class TrailUI < Streamlined::UI
relationship :location, :fields => [:city, :state]
end
|
Listing 1 represents the metadata for the Trails views. Streamlined can substitute these values into view templates, which you'll see later, to form full user interface screens. Now, reload your browser. You'll see a refreshed view, as in Figure 3:
Figure 3. Revising the relationship content
Notice the change in the representation of the relationships. The Location column now shows two fields: city and state.
The Streamlined creators could have easily added customization data to Active Record. That strategy would keep all configuration in the same place. But they didn't, and I'm grateful. Their approach, a metamodel in a separate directory, has several key advantages:
- The separate metadata doesn't mix view data with model data. Clean separation between model and view is important to reduced coupling, making radical view changes less likely to ripple into the model and vice versa.
- The streamlined directory allows additional metadata without pollution of Active Record.
- The Streamlined approach lets a single place consolidate common view characteristics, much as Active Record does for persistence concerns.
So far, I've changed the way Streamlined presents a relationship in the Trails view. Often, metaprogramming frameworks need to allow aesthetic decisions, such as deciding which columns or internal fields are important for the users. By default, Streamlined excludes special Rails fields such as foreign keys, primary keys, and timestamps, and includes the rest. You can easily override these defaults with the user_columns method, which has include and exclude options. For example, I need to make another change. The Description field will likely usually have whole paragraphs of text. Typically, a user interface will not want to list all fields in a summary list like this one. I can use the metamodel to exclude certain fields in the list view. Edit app/streamlined/trail.rb to include code that excludes the Description column, as in Listing 2:
Listing 2. Excluding the description column
require File.dirname(__FILE__) + '/streamlined_ui'
class TrailUI < Streamlined::UI
relationship :location, :fields => [:city, :state]
user_columns :exclude => [:description]
end
|
Then, reload your browser to get the result in Figure 4:
Figure 4. Removing a custom column
Modifying display structure
One of Streamlined's more powerful aspects is the way it helps you manage relationships. Load the view for http://localhost:3000/locations/list. Take a close look at the relationship user interface. You'll see a number, which is the count of trails associated with the location. Next, click an Edit link beneath the location column. You'll see a list of check boxes, as in Figure 5. These are all of the trails in the database, with the checked ones belonging to the given location. This window is a view of the relationship.
Figure 5. The default relationship view
The Streamlined framework offers you three different relationship views:
- The default
:membership view represents relationships as check boxes, as in Figure 5. A checked box denotes membership. Streamlined guesses which field to use for the check box, but you can override the guess with an option called view_fields.
The :inset_table view in Figure 6 shows related fields in an inset table, in line. In this case, you can immediately see related data in a nested table.
Figure 6. The :inset_table relationship view
Listing 3 shows the code for the view in Figure 6:
Listing 3. Inset table views for relationships
require File.dirname(__FILE__) + '/streamlined_ui'
class LocationUI < Streamlined::UI
relationship :trails, :view => :inset_table
end
|
Figure 7 also shows a table but uses the :window view to put it in an on-demand popup instead of an inset:
Figure 7. The :window relationship view
The code for the :window relationship view is in Listing 4:
Listing 4. Window views for relationships
require File.dirname(__FILE__) + '/streamlined_ui'
class LocationUI < Streamlined::UI
relationship :trails, :view => :window
end
|
Each of these views is Ajax-enabled. When you check a box in the membership view, the view issues an Ajax call to update the associated model on the server. Changes are instantaneous, with immediate user feedback. Streamlined effectively uses Ajax to blur the lines between Web user interfaces and rich clients. Full Ajax-enabled views are not what you'd typically think of as metaprogramming features, but once you have the right foundations in place, you can work at increasingly higher levels of abstraction.
The Streamlined framework also lets you decide what to present within the relationship summary fields. In Figure 3, you see a count of all of the related trails for a given location. You could also configure your summary to show totals, the fields of all related cities, or other mathematical features such as averages as they are enabled. For example, you could specify relationship :trails, :summary => :list, :fields => [:name, :difficulty] to see the result in Figure 8:
Figure 8. Lists as a summary
Ruby features enable this syntax. The summary line again is relationship :trails, :summary => :list, :fields => [:name, :difficulty]. This code calls a method called relationship and passes in some parameters. You can dig a little deeper to find out exactly what's going on. In Listing 4, you saw that each Streamlined configuration inherits from Streamlined::UI. (Streamlined is a module, and UI is a class in that module.) You can see the method definition for relationship by opening the app/streamlined/streamlined_ui.rb file. You can see the method definition in Listing 5:
Listing 5. The relationship method from the StreamlinedUI.rb file
def relationship(rel, options = {})
ensure_options_parity(options)
initialize_relationships unless @relationships
@relationships[rel] = {} unless @relationships[rel]
@relationships[rel].merge!(options)
end
|
You see two parameters: rel and options. You probably expected to see more, because the method call expresses parameters for :trails, :summary, and :fields. Actually, :trails is the rel parameter, and :summary => :list, :fields => [:name, difficulty] are name-value pairs for a single hash map for options. This syntax is a little confusing at first, but it gives Ruby the ability to simulate named, optional parameters with minimal additional overhead to the language.
Extending the view templates
So far, I haven't needed to change the code in the existing application, but Streamlined does provide several mechanisms that make customizing a view easier. You can easily imagine scenarios requiring you to modify one view or even all of the views within an application. First, I'll show you how Streamlined deals with a single view. Then, I'll expand the discussion to include all of the views in an application.
By default, Streamlined creates a view for each model in the application and also a view template for all application views. To override a single view, you can just change the implementation. For example, replace all of the code in app/views/trails/list.rhtml with the single word Testing. Then, click on Trails on the left sidebar. You'll see the view in Figure 9. You can customize any single view in the system in this way.
Figure 9. Modifying a Streamlined view
Changing views independently is nice, but if you want to get the full power out of metaprogramming, you'd probably prefer to use a single template to build all views in an application. That approach gives you the best possible leverage because you're effectively writing one template for your entire application. Delete all of the files in app/views/trails. This will prompt Streamlined, at run time, to invoke the common template views. Streamlined overrides a method called render to accomplish this purpose. Stop and start the Web server (because you've changed the underlying file structure) and reload your browser. You'll see the original Trails view from Figure 1 restored. If I had chosen, I could have created my initial application with a single view template by invoking the Streamlined generator with the no_views option.
You can find all of the view code in app/views. The sidebar, headers, footers, menu, and common files are in the streamlined directory. The views to manage relationships are in streamlined/relationships, and the partials and view for the main content are in streamlined/generic_views.
Listing 6 shows part of the app/views/streamlined/generic_views/_list.rhtml partial for the master Streamlined template:
Listing 6. Part of a Streamlined template (line breaks added for readability)
<% for item in @streamlined_items %>
<% alt = 1 - alt %>
<tr <%= "class='odd'" if alt == 0 %>>
<% for column in @model_ui.user_columns_for_display %>
<td><%=h item.send(column.name) %></td>
<% end %>
<% for relationship in @model.reflect_on_all_associations %>
<td>
<div id="<%= relationship_div_id(relationship, item) %>">
<%= render(:partial => "#{@model_ui.summary_def(relationship.name).partial}",
:locals=> {:item=> item, :relationship=> relationship,
:fields=> @model_ui.summary_def(relationship.name).fields})%>
</div>
<%=link_to_function("Edit", "Streamlined.Relationships.open_relationship('#{
relationship_div_id(relationship, item)}', this,
'/#{params[:controller]}')")%>
</td>
<% end %>
...deleted code to render links...
<% end %>
|
Notice the two for loops. Each one reflects on metadata from Active Record and Streamlined. The first loop iterates through each Streamlined column. Streamlined determines which columns participate by taking all columns from the Active Record metadata, removing certain Rails-specific columns such as id, and excluding or including each of the columns you specify in the Streamlined metadata, as you saw in Listing 2. The second loop iterates through all relationships in the Active Record model and renders the appropriate partial for each relationship.
I can also combine approaches. I can code an application with the single Streamlined template for most views -- one with site-wide programming conventions -- and then override single views as needed when I want to break the common site-wide conventions. The result is an extremely powerful development paradigm that lets you start with a good default implementation and edit or completely replace the standard implementations with custom ones on a site-wide or single-page basis.
The big picture revisited
Streamlined, then, is a framework with a rich metamodel that enables application generation. Your metamodel becomes your higher-level programming language, increasing your productivity. In some ways, such a framework was inevitable in Rails because the same metaprogramming facilities that enable scaffolding are available to all users of the Rails framework. It was just a matter of time before someone built a more effective scaffolding system.
In Streamlined, you have a combination of a framework with higher language abstractions, code generation, and customization hooks for extension. A common metamodel drives everything. Using this technique, the framework has a focus that's primarily on the user interface, but you can start to see it push beyond mere Web pages. Streamlined provides, or will soon provide, all of the following:
- Atom feeds for content syndication: This will let other applications on the Web consume an automated XML feed, produced by Streamlined applications, and conforming to a common standard.
- XML and CSV exports: Streamlined allows exports in common data formats.
- Queries and filters: Streamlined lets you filter content with simple queries, and then use the results.
- REST-based Web services: Streamlined initially had Web services support but removed it because Rails architects are redesigning the Web services support to be based on the Simply RESTful plug-in system (see Resources).
Streamlined is striving to become, in the near future, a metaprogramming framework that lets you create an arbitrary template and plug in existing Rails components, generators, and plug-ins. This framework will go beyond look and feel to create a common architecture, potentially creating an extremely powerful corporate application generator. Says Justin Gehtland of Relevance, LLC:
"The next several releases of Streamlined are intended to solidify its position as a meta-framework. It will subsume and re-use the standard Rails generators, readily available plugins and other open source components to quickly assemble an entire application, above and beyond the CRUD support currently provided. We'll take the same approach to configuration as we've used before: rely on good conventions, but provide a declarative DSL for modifying the defaults."
A higher abstraction level with excellent customization is the holy grail for any development language, including the Java language. In the next Crossing borders article, I'll look at the benefits of delayed binding. Until then, look for ways to increase your power by raising your own abstractions, and keep crossing borders.
Resources Learn
-
Java To Ruby: Things Your Manager Should Know (Bruce Tate, 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 (Bruce Tate, 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.
-
Simply RESTful: Provides a sneak peak at the next model for Rails REST Web services.
-
"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.
-
Ruby on Rails: Download the open source Ruby on Rails Web framework.
-
Ruby: Get Ruby from the project Web site.
Discuss
About the author  | 
|  | Bruce 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. |
Rate this page
|  |