In this second part of a two-part series, we'll finish creating reports that can be accessed via the Internet. This article is excerpted from chapter 5 of the book Practical Reporting with Ruby and Rails, written by David Berube (Apress; ISBN: 1590599330).
Building Reports Accessible from the Internet - Examining the We b Report Application (Page 2 of 5 )
Let's launch the application using the built-in Rails test web server. You can do that by using the following command:
ruby script/server
-------------------------------------------- => Booting Mongrel (use 'script/server webrick' to force WEBrick) => Rails application starting on http://0.0.0.0:3000 => Call with -d to detach => Ctrl-C to shutdown server ** Starting Mongrel listening at 0.0.0.0:3000 ** Starting Rails with development environment ... ** Rails loaded. ** Loading any Rails specific GemPlugins ** Signals ready. INT => stop (no restart). ** Mongrel available at 0.0.0.0:3000 ** Use CTRLC to stop. --------------------------------------------
Open a web browser and enter the address http://localhost:3000/home to see the application. You should see a result similar to Figure 5-1.
Note The Rails test server can use one of two web servers: WEBrick, which is installed by default, and Mongrel, which is faster. It automatically uses Mongrel if it's installed. If Mongrel is not installed, it will use WEBrick, and your output will be slightly different than what is shown in the book. However, it won't make a difference for this example.
Figure 5-1.Ruby on Rails actor schedule
To view the report on a cell phone, you would need to run this application on a public server. If you did so, and then used your cell phone to navigate to port 3000 on the appropriate URL, you would see something similar to Figure 5-2 (of course, the exact view depends on the cell phone model). Additionally, you would likely add authentication and other real-world features before actually deploying this application. Nonetheless, as you can see, the approach of using simple semantic HTML can work well even on cell phones.
Figure 5-2.Ruby on Rails actor schedule on a Samsung A900 cell phone
Dissecting the Code
The bulk of the application is in the controller and the view, but before we get to that, let's take a brief look at the migration (Listing 5-2), which defines the database schema for the application:
class InitialSchema < ActiveRecord::Migration def self.up create_table :actors do |t| t.column :name, :text, :length=>45 t.column :phone, :text, :length=>13 end create_table :projects do |t| t.column :name, :text, :length=>25 end create_table :rooms do |t| t.column :name, :text, :length=>25 end create_table :bookings do |t| t.column :actor_id, :integer t.column :room_id, :integer t.column :project_id, :integer t.column :booked_at, :datetime end end
def self.down drop_table :actors drop_table :projects drop_table :rooms drop_table :bookings end end
This migration has two methods: up and down. The up method creates several table s using the Rails built-in column types, which are automatically mapped to database-specific types. The down method drops the tables and is used to undo a migration. Notice that these tables automatically have an artificial primary key column, id, added by default. If that is undesirable, you need to explicitly state you don't want an id column, by using the ;id=>false option in your create_table statement.
Next, let's look at the controller (Listing 5-6):
def index @actors_today = [] @actors_tomorrow = [] Actor.find(:all).each do |actor| @actors_today << {:actor=>actor, :bookings=>actor.booking.find(:all, :conditions=>[ 'TO_DAYS(booked_at)=' << TO_DAYS(NOW())'])} @actors_tomorrow << {:actor=>actor, :bookings=>actor.booking.find(:all, :conditions=>[ 'TO_DAYS(booked_at)=' << TO_DAYS(NOW())+1'])} end end
The controller has just one action, which represents the main page people will see when they visit your page. This action prepares two lists of actors for the view: one for today's schedule and another for tomorrow's schedule. Each item in the list has two parts: an actor object representing the actor and a list of bookings for the appropriate time period.
After this controller is called, the view of the same name is automatically rendered to the screen by Rails. Specifically, it implicitly calls the render method for you; you can override this with your own call to the render method, which lets you render a view with a different name or that is associated with a different controller.
Let's take a look at that view (Listing 5-7) next. First, it prints out all of the actors and their schedule for the day, as follows:
<h1>Today's Schedule:</h1>
<% @actors_today.each do |actor_today| %> <h2><%= actor_today[:actor].name %></h2> <p><%if actor_today[:bookings].length > 0 %> actor_today[:bookings].each do |b| <%=b.booked_at.strftime('%I:%m%p') %>, <%=b.room.name %>, <%=b.project.name %><br> <%end%> <%else%> Nothing for today! <%end%> </p>
<%end %>
It loops through each of the actors, prints their name as an h2 element, and then prints the bookings. It formats the booked_at time for each and prints it, along with the room name and the project name. The stftime function formats the date into a nice, human-readable form (see http://ruby-doc.org/core/classes /Time.src/M000297.html for details).
Then you do a very similar loop for tomorrow's schedule. In fact, it's identical, except for the references to @actors_today being replaced with @actors_tomorrow and similar changes.
Note that you could easily display this data in a table, but using paragraphs and headers gives the browser more control over wrapping, which makes it display better on small screens, such as the cell phone display you saw in Figure 5-2.
Now that you've seen how easy it is to create a web application with Rails, let's take a look at a slightly more complicated example.