Section 19.5. An Introduction to Wee


19.4. Web Development with Nitro

Nitro is a web application development toolkit. Although Nitro, along with its ORM companion Og, is well-suited for constructing conventional MVC-style applications, its intended design affords a variety of architectures.

The simplest way to install Nitro is to use rubygems. The Nitro gem has a small number of dependencies that you will also need to install (og, redcloth, and a few others).

gem install nitro  --include-dependencies


At the time of writing, the latest release was Nitro 0.31.0. As you might expect, the API and libraries are still shifting. Also know that the overview given here just scratches the surface of what Nitro can do.

19.4.1. Creating a Basic Nitro Application

Nitro is often used with Og, an ORM library that handles the persistence of Ruby objects. But Og is not required; in fact, one of the nice features of Og/Nitro is that you need not decide in advance whether your application requires a database or object persistence. If your design evolves and such a need arises, modifying plain Ruby objects into persistent objects is mostly a matter of changing a few lines of code in your models. There is talk of modifying the use of the attr family of methods, so this may get even simpler in the future.

Og is covered in section 10.4.7, "Object-Relational Mappers (ORMs)." The Nitro examples will not make much use of it.

Although Nitro offers a wide range of features, the simplest Nitro application does not require much. Creating a basic Nitro app is not much different from creating a conventional static HTML site.

First, create an application directory:

/home/jbritt/demo


Then add a public folder with index.html:

/home/jbritt/demo/public/index.html


To start, make index.html simple:

<html>   <head>     <title>Nitro!</title>   </head>   <body>     <h1>The Ruby Way</h1>     <h2>Hal Fulton</h2>   </body> </html>


Now create run.rb in your application root directory:

require 'nitro' Nitro.run


To see this new application in action, execute the file run.rb (found in the root of the demo/ directory). Then launch a web browser and navigate to http://127.0.0.1:9999 (where 9999 is the default Nitro port).

If all goes well, you will be greeted by our simple page. Congratulations; you've written your first Nitro application. Nitro offers much more than this, of course, so let's look at how we can expand the application.

The main file of interest is run.rb. Depending on how you deploy your code, the execution of run.rb might be handled by a dispatcher script in the public folder. For demonstration and testing purposes, though, you can execute run.rb directly and use the built-in WEBrick dispatcher. In production, though, you would want to take advantage of Nitro's built-in support for Mongrel, SCGI, or FastCGI.

Nitro supports a variety of application architectures and patterns, and web development typically follows MVC (model-view-controller). But the choice is yours, and Nitro makes it easy to evolve from simple view-only sites to fully refactored, database-backed applications.

By default, Nitro will handle page requests by first looking in the public folder for a matching file. Nitro assumes that, absent an explicit page name, the request refers to index.html. In this regard it behaves much like any static web environment. We can, if needed, add additional static HTML pages to the public folder and use subdirectories of public that can hold images and CSS files.

More useful, though, is what Nitro does when it cannot find a direct match for a resource request. Change the file extension on index.html to .xhtml:

public/index.xhtml


Then restart run.rb. Return to http://127.0.0.1:9999, and you should see the page again. Nitro, not finding index.html, looks for index.xhtml and loads that. xhtml is the default Nitro file extension for dynamic content. In general, when given a page request, Nitro first looks for the .html file, then the .xhtml version.

Using a Nitro xhtml file allows you to embed template variables and logic. Change index.xhtml to look like this:

<html>   <head>     <title>Nitro!</title>   </head>   <body>     <h1>The Ruby Way</h1>     <h2>Hal Fulton</h2>     <p>Page last updated:  #{Time.now}</p>   </body> </html>


Reload the page, and you should see the current date and time displayed. Nitro also supports an XML processing instruction syntax:

<?r curr_date = Time.new.strftime( "%a, %b %d, %Y")  ?> <html>   <head>     <title>Nitro!</title>   </head>   <body>     <h1>The Ruby Way</h1>     <h2>Hal Fulton</h2>     <p>Page last updated:  #{curr_date}</p>   </body> </html>


Note that the <?r ... ?> syntax does not require your template to be XML. Nitro provides ways for processing templates as XML, and this syntax allows for templates that are also valid XML.

19.4.2. Nitro and the MVC Pattern

Embedding code directly into templates is handy for trying out ideas and migrating from static pages to dynamic content. However, before long, you may find that testing and maintaining the application is increasingly difficult. The Model-View-Controller pattern will improve things by moving code into Ruby classes.

We'll start by creating a controller class, main.rb. If your application directory has an src folder, Nitro will add it to the load path. Following the Nitro convention, we'll create <app_root>/src/controller/book.rb:

class BookController   def index     @author = "Hal Fulton"     @title = "The Ruby Way"     @last_update = Time.new   end end


The index.xhtml will have to change to use these instance variables:

<html>   <head>     <title>Nitro!</title>   </head>   <body>     <h1>#{@title}</h1>     <h2>#{@author}</h2>     <p>Page last updated:  #{@last_update}</p>   </body> </html>


We'll need some adjustments to run.rb as well:

require 'nitro' require 'controller/book' Nitro.run(BookController)


Restart the WEBrick server and reload the page to see the results.

Some things to note: Template files can remain in the public folder; the controller class does not need to extend from any special base class; the class passed to Nitro.run is automatically mapped to the root URL of the application. Each of these is configurable.

By default, Nitro will look for templates in the template and public directories. If you do not want templates in public (perhaps preferring to reserve that for static HTML files), you can create a template folder and store them there. Template paths are assumed to follow URL paths, relative to the root template folder. Our index.xhtml file may be either public/index.xhtml or template/index.xhtml. The use of the public directory for template files eases the transition of a static site to a dynamic one, but you will do better to organize them separately.

Controller classes may be mapped URL paths using Server.map. For example, our demo application may have a static main page with books (well, the one book we have so far) listed under a different path. We can arrange this mapping by changing run.rb:

require 'nitro' require 'controller/book' Nitro::Server.map = { '/books' => BookController } Nitro.run()


We'll need to move the corresponding template to match the new path (template/books/index.xhtml).

Restart the server, but this time browse to the new path:

http://127.0.0.1:9999/books


At this point we'd want to create a proper main page for the site, but we'll omit that here to save space. A more interesting addition would be the capability to request details for more than one book (however terrific that one book may be). We can add a Book model in src/model/book.rb:

class Book   @@items = {}   attr_accessor :title   attr_accessor :author   attr_accessor :update_time   def initialize( values = {} )     @title, @author = values[:title], values[:author]     @update_time = Time.now   end   def save     @@items[author] =  self   end   def self.find(author)     @@items[author]   end end


The controller can now fetch data from the Book class. We'll change index to this:

def index   book = Book.find_by_author("Mark Twain")   @author = book.author   @title = book.title   @last_update = book.update_time end


And we'll change run.rb to reference the Book model and load some data by adding these lines:

require 'model/book' Book.new(:title => "Life on the Mississippi",          :author =>  "Mark Twain" ).save


Restarting the application and reloading the page should show us the book details. Now suppose that we have other books we'd like to see. Instead of using a hard-coded reference to one book, we can tell the controller to search by author name. Here's a new controller method, find:

def find(author)   book = Book.find_by_author(author)   @author = book.author   @title = book.title   @last_update = book.update_time end


It's the same code as index (and the matching template can be created by renaming index.xhtml to find.xhtml), but it takes a single argument, which will be used to retrieve an item by the name of the author. Note that, although we are developing a web application, the controller class is not much different from a class in any other Ruby application. The most notable difference is that the methods are not returning any values, but there is no reliance on special environment values or objects. (Be aware, though, that Nitro does supply various niceties for coding for a web app, and they are available to your controller code simply by having the class inherit from Nitro::Controller. For example, Nitro provides scaffolding, flash message transfer among requests, and a sophisticated rendering pipeline if and when your application needs them.)

We need to make one additional observation about controller methods. Methods designed for use as page request handlers will typically be paired with like-named templates. Nitro combines the method and template to render the final result. We've seen how a Nitro app can work without controllers by using only views. But the complement is true, too. A controller method can generate its complete output without any template. Nitro page requests are processed as actions. An action is the combination of view and controller methods. Under the hood, Nitro dynamically creates action methods that merge the two. But if one or the other is missing, no problem. If an action does not have a template, the return value of its controller method becomes the output.

For example, you could have a URL that responds to a search by returning only the book title with this sparse BookController method:

def sparse(author)   @context.content_type =   'text/plain'   book = Book.find_by_author(author)   book.title end


If your controller method is not returning HTML, you should change the content-type header sent in the response using @context.content_type=. (Incidentally, if a matching template exists, you can override its use by calling render_text.)

But where does the argument to find or sparse come from? By default, Nitro follows the familiar web development pattern of mapping URL path segments to controllers, methods, and arguments. Restart the application, and try the URL http://127.0.0.1:9999/books/find/Hal%20Fulton.

Note the use of %20 to encode the whitespace in the author name. This is removed by Nitro before reaching our find method.

In general, then URL paths map to controllers and methods as /controller/method/arg1. Additional arguments can be passed as more URL path segments. Nitro provides a routing mechanism as well so that you are not obligated to couple your URLs to implementation details.

19.4.3. Nitro and Og

Although Og, Nitro's object-relation manager, is covered elsewhere, you would miss the full effect if we didn't show how simple it is to make our application database-backed. First we change run.rb to set up Og:

# Just before the call to Book.new : require 'og' Og.setup(:store => 'mysql',          :name => 'demo',          :user => 'root',          :destroy => true,          :port => 3316)


We next change our Book model:

require 'glue/timestamped' class Book   is Timestamped   property :title, String   property  :author, String   def initialize( values = {} )     @title, @author = values[:title], values[:author]   end end


The class-variable storage of Book instances is deleted. Calls to attr_accessor are replaced with the Og/Nitro method property, which serves multiple purposes. It too creates variable accessor methods, but it also tells Og that this class is marked for persistence. initialize stays almost the same, but by requiring the timestamped file and indicating that the class is Timestamped, we get the update_time attribute automatically.

The remaining methods can be deleted; they are now implemented by Og. Restarting the application will now have Nitro creating a MySQL database for the application, along with a table for our Book objects. This is "all Ruby, no SQL" database-backed web application development.

19.4.4. Common Web Development Tasks in Nitro

Nitro has a remarkably rich rendering subsystem. A proper description would be beyond the scope of this book. However, there are various tasks that web developers commonly need to do, so let's take a look at how we might perform these tasks in Nitro.

If you are building a website of one or two pages, it may not matter much whether you are repeating the same markup and text. But keeping all your pages up-to-date becomes an error-prone chore when you have to maintain the repeated pieces by hand. Nitro helps you adhere to the DRY principle (Don't Repeat Yourself) by providing a number of ways to reuse such pieces of text.

The simplest way is to use include files. For example, suppose we decide that all our pages should have a common footer. We can stick that footer markup into a template file and render it into our pages in this way:

<?include href='/footer' ?>


The template file footer.xinc might look like this:

<div id='footer'>Read More Ruby Books</div>


If you use a relative path as the HRef value, Nitro will search the template folders defined for the current controller. If you use an absolute path, only that path under the application's template root is searched.

This preceding method is best for static content. There is also the following syntax for file inclusion, which inserts the specified file into the calling template just prior to template compilation:

<include href='/footer' />


The results are as if the include text were simply part of the calling template.

A more sophisticated form of content inclusion uses the render element:

<render href='/controller/action' />


where the HRef indicates some application URL path.

The compilation process for including these partial views with <render /> is essentially the same as when calling a full view. You may have methods in your controller that correspond to the included file and use these methods to set instance variables to be used in the included template.

Nitro blurs the line between template source code and Ruby code. One example is how a controller action may be spread between a method and a template file, or just one or the other. Another example is Nitro Elements, which are a way of encapsulating code and markup in a custom markup element available in your views.

Rather than have each view define the HTML for a full page, we can define a common HTML layout and reuse it across actions. We'll create the file element/layout.xhtml:

<html>   <head>     <title>#{@title}</title>     <style>       body {         background-color: white;         font-family: sans-serif;       }     </style>   </head>   #{content} </html>


We the change template/books/find.xhtml to use this new element:

<Layout title='Details for #{@title}'>   <h1>#{@title}</h1>   <h2>#{@author}</h2>   <p>Page last updated: #{@last_update}</p> </Layout>


What's inside the Layout element is inserted into the content variable in layout.xhtml. Elements can accept parameters; the title attribute in the Layout start tag populates the @title instance variable in layout.xhtml.

If this seems similar to calling a method and passing arguments, it is. We can also define the layout as a Ruby class (src/element/layout2.rb):

require 'nitro/element' class Layout2 < Nitro::Element   def render     %^<html>     <head>       <title>#{@title}</title>       <style>         body {           background-color: white;           font-family: sans-serif;         }       </style>     </head>     #{content}     </html>^   end end


We can then change find.xhtml to use the Layout2 element (we'll also have to change run.rb to require this new element class). Elements may contain other elements, allowing you to assemble your views from a set of reusable components.

Often you have large chunks for code that embody logic common to multiple applications. For example, many web apps have a notion of user account and authorization. Rather than rewrite the code for each program, the capability to mix in existing code saves time and reduces maintenance.

This form of application reuse is called parts. A part is essentially a mini-Nitro site focused on some specific function. (Nitro includes one out of the box, called Admin.) The code for this subsite does not need to have a run.rb, though it is useful to include one so that the part may be executed on its own to demonstrate its behavior.

Parts go, naturally enough, under a part folder. Suppose that we have some user authentication code to reuse. Our application parts directory tree would look something like this:

<app_root>/part/users <app_root>/part/users.rb <app_root>/part/users/public/ <app_root>/part/users/controller.rb <app_root>/part/users/model/user.rb <app_root>/part/users/model/acl.rb <app_root>/part/users/template/login.xhtml <app_root>/part/users/template/form.xinc <app_root>/part/users/run.rb


Our main run.rb would then include the part by doing a simple require:

require 'part/users'


Nitro then treats all the code in the part/users directory as if it were located under the main application source code directory. Template file searches begin with the main application template folders and continue down into parts directories. If you want to override a template included with a part, you just need to put your preferred version in a corresponding folder under the main application template path.

Applications often need to display repeated data; this is typically rendered as an HTML table in a template. If you do not know the number of data rows in advance you'll want a way to loop over a collection.

You can use the syntax to embed Ruby code in your template, but Nitro offers a special compiler pipeline class that simplifies common logic constructs.

The compiler pipeline is a sequence of transformations acting on your templates as they are combined into actions. There are transformation classes for various tasks, such as static file inclusion, XSLT transformation, and localization. The Morphing class examines template markup, watching for special attributes in the XHTML indicating assorted transformations.

Our demo application is sparse on data, but if we imagine there being many authors and many books for each author, we might want to show a list of an author's books. Our controller method would populate an @books variable with a list of books; the section of a template to loop over this list would look like the following code:

<h4>Books by #{@author}</h4> <ul> <li each="book in @books" > #{book.title}</li> </ul>


The Morphing compiler spots the each attribute on the li element and converts this into the following code:

<?r for book in @books ?> <li>#{book.title} </li> <?r end ?>


This new Nitro markup is then passed on down the compiler pipeline.

Similarly, you can use the times attribute to repeat an element. Look at this fragment:

<img src='/books/2/491/1/html/2//img/ruby.png' alt='*' times='@book.rating' />


The preceding code has the following result:

<?r 3.times do ?> <img src='/books/2/491/1/html/2//img/ruby.png' alt='*' /> <?r end ?>


19.4.5. Other Important Details

Nitro has numerous niceties, too many to cover here. Some of the more notable goodies are discussed briefly in this section.

Nitro comes with helper code to use a number of JavaScript libraries that support various forms of DHTML and Ajax. Nitro defines a high-level syntax for easy integration. For example, the Nitro distribution includes an example of searching Flickr and rendering thumbnails of images. The text field for entering search tags is Ajax-enabled with this markup:

<input type="text"  name="tags" auto_complete="true" />


The controller implements tags_auto_complete to return an XML string based on field contents.

Nitro allows for caching of actions (that is, rendered pages), method results, and generated text fragments. For example, to cache the rendered results of the index page, a controller would call

cache_output :index


Caching may be added in code segments as well:

<?r cache(:book_list_cache_key) do ?>  <ul>   <li each="book in Books.all">#{book.title}</li>  </ul> <?r end ?>


Nitro has a built-in transformation class for localization that allows for automatic template content substitution. This class is not part of the default compiler pipeline; you must add it in run.rb:

require 'nitro/compiler/localization' include Nitro Compiler.transformation_pipeline = [   StaticInclude,   Elements,   Morphing,   Markup,   Localization,   Cleanup ]


Note that you can also arrange the pipeline sequence to omit standard transformations or to define your own. You then define the locales:

Localization.locales = {   :en => 'conf/locales/en.yml',   :de => 'conf/locales/de.yml' }


A locale is just a YAML file that maps strings:

--- :author: Autor :language: Sprache :book_rank: Buchrank


Template files then use a special syntax to denote text from substitution:

<div class='detail'>[[:author]]: #{@book.author}</div> <div class='detail'>[[:language]]: #{@book.language}</div> <div class='detail'>[[:book_rank]]: #{@book.rank}</div>


The selection of locale file is based on the value of session[:LOCALE]. The value of the current local hash is also available in controller methods from the special variable @lc.

@language = @lc[:language]


More extensive language changes may also be done by using multiple template directories, one for each language. The application template root would then be derived from the assigned locale.

For more information on Nitro, consult the following resources:

  • http://www.nitroproject.org/ (the Nitro home page)

  • http://rubyforge.org/forum/forum.php?forum_id=5921 (Nitro project page on RubyForge)

  • http://oxyliquit.de/ (Nitro user help and tutorial site)




The Ruby Way(c) Solutions and Techniques in Ruby Programming
The Ruby Way, Second Edition: Solutions and Techniques in Ruby Programming (2nd Edition)
ISBN: 0672328844
EAN: 2147483647
Year: 2004
Pages: 269
Authors: Hal Fulton

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net