In this second part to a two-part series on creating reports on the desktop with Ruby-on-Rails, you'll learn how to create GUIs with Ruby and take a look at a sample report. It is excerpted from chapter four of the book Practical Reporting with Ruby and Rails, written by David Berube (Apress; ISBN: 1590599330).
GUIs and More for Desktop Reports - Dissecting the Code for the Program (Page 4 of 4 )
The first part of Listing 4-3 sets up the connection to MySQL, creates the various models, and so forth--nothing new there.
The bulk of the program is controlled by one class, TransmegtechGraphWindow, which is divided into two parts: update_display and initialize methods. update_display creates a graph for the selected player, writes it to a file, and then displays it in your application. Much of this graphing code is the same as the code from Listing 3-4 in Chapter 3. The initialize method creates a window and the user interface elements required: the drop-down lists to select the game and player to graph, the labels for those two drop-down lists, and the large display area that will be used to view the graph.
Whenever either of the drop-down entries is changed--that is, when the user selects a new game or a new player to graph--the update_display method will be called again.
The first two lines of this method call the getItemData method of the two combo boxes. ItemData is where you can store an integer for each item in the list. Here, ItemData holds the game IDs and the player IDs. By calling the getItemData method with the currently selected item as the parameter, you can get the appropriate player and game IDs.
After that, you create a chart, just as in Listing 3-4. The chart is written to a file, and then loaded and displayed; however, unlike in the Chapter 3 example, you immediately unlink--or delete--the file after displaying it.
Note You could use a Tempfile object, which automates the unlinking behavior, but you can't specify a Tempfile's extension, and FXRuby uses file extensions to determine the file format of a given image. This is an unfortunate approach, yet apparently fairly common--the Gruff and PDF writer gems both have a similar issue.
In order for the update_display method to do anything, you need a user interface element to call it. So, first you must create a window to contain that user interface:
The first line creates a new FXApp object, which represents the entire application. FXApp handles application-wide tasks such as starting messaging loops, event timers, quitting the application, and so forth. The second line creates the main window as an instance of the FXMainWindow class. Note that you are using the default icon for this application, which is the nil, nil part of the call. The first of those two nil parameters sets the normal icon, and the second sets the minimized icon for your window. The third line sets the width and height. (You can actually set the height and width via optional parameters to the constructor, but this method is clearer, since the constructor already has a large number of parameters.)
Next, you need to place a strip of controls at the top of the window. These controls will be used to select the player and game for the report. Whenever these settings are changed, the graph should automatically update. Before you add the controls, you need to create a space to put them:
This line creates a new FXMatrix called control_matrix, which is a FOX container in which you can place other controls. As noted earlier, by default, an FXMainWindow object places controls vertically. The MATRIX_BY_COLUMNS flag in the FXMatrix constructor makes it stack controls horizontally. The other alternative is MATRIX_BY_ROWS, which stacks controls vertically. The second parameter, 4, specifies how many controls should be placed inside the FXMatrix control before starting a new row. For example, you could set MATRIX_BY_ROWS with a parameter of 2 to make a long vertical row of labels next to a vertical row of text boxes.
After you have a place to put your report controls, you create them:
This code creates two controls: an FXLabel, which is a visual indicator of the purpose of the next control, and an FXComboBox, which is a list of elements that can be accessed by clicking a drop-down arrow. The second parameter to the FXComboBox constructor is the width. The third parameter is the message target. This parameter is a relic from the original FOX implementation in C, and you won't typically use it in an FXRuby application. (If you're interested, you can find out more at http://www.fxruby.org/doc/events.html.) The fourth parameter, COMBOBOX_STATIC | FRAME_SUNKEN, is a bit field consisting of various style bits. Specifically, it's two constants OR'd together: the COMBOBOX_STATIC constant makes the box static, so that users must pick from the list, and the FRAME_SUNKEN constant makes the box have a three-dimensional sunken effect.
Additionally, the numVisible attribute selects how many options are visible in the drop-down list at one time. Here, you set numVisible to 5 to let the user see all of the available elements without scrolling.
Next, you fill the FXComboBox with options:
Game.find(:all).each do |game| @game_combobox.appendItem(game.name, game.id) end
This loop calls appendItem for each game in the database. The first parameter to the appendItem method is the text that represents the option in the drop-down list. The second parameter is the value the item has, which is stored in the itemData array in the @game_combobox object. The update_display method uses this value later in the code to retrieve the selected game ID.
Of course, you need to actually update the display when the user changes the FXComboBox. You use the connect method to do just that:
@game_combobox.connect(SEL_COMMAND) do update_display end
The connect method attaches a block of code to a given FOX message on a given FOX object. In this case, you're specifying that whenever SEL_COMMAND is received by the object, the update_display method will be called. SEL_COMMAND is sent to FXComboBox objects when they change, so your update_display method will be called whenever someone selects a new game to analyze.
Tip Along with SEL_COMMAND, FOX has quite a few other trappable messages. You can use these to customize your users' experience with fine touches. You can get a complete list of all the FOX messages at http://www.fox-toolkit.org/ftp/FoxMessages.pdf.
After this, the script creates a player label and drop-down list in the same way as the game label and list.
Next, you need to create a place for the graphs to be displayed:
@graph_picture_viewer.connect( SEL_CONFIGURE ) do update_display end
The first line creates an FXImageView object, which you will use to display the graphs. Note the use of the LAYOUT_FILL_X and LAYOUT_FILL_Y flags, which mean that the graph viewer will use all available space in the window.
Finally, outside your class, you need to actually display your window and let it run. The following three lines do just that:
FXApp.instance().create
main_window.show( PLACEMENT_SCREEN )
FXApp.instance().run
The first line creates the necessary objects, which are specific to the operating system (and, happily, the details are hidden from us). The second line displays your window on the screen, and the PLACEMENT_SCREEN flag means it will be centered. A call to FXApp.instance() returns your previously defined FXApp object, since FXApp is a singleton.
PACKAGING THE REPORTER DESKTOP APPLICATION
Simply creating a desktop application is not enough--users need to have the application running on their machine. If your application will be run by only a few users, or if your code will run only server side, this may not be an issue. However, if you need to install Ruby and associated libraries on users’ machines for a wider distribution of your application, that can be a challenge.
Fortunately, a gem called RubyScript2Exe can help. This gem creates executable programs, and you can target Mac OS X, Windows, or Linux. (You’ll need to be running the appropriate operating system to create an executable for it, however.) In fact, RubyScript2Exe can even copy Ruby library dependencies for you. It will run your program once as a "test run," and from there, determine which gems and other source code you’ve used. It will then copy the source into your finished executable, which will be transparently available to your finished program. Additionally, you can package binary or configuration files with your script, so that the entire program is one convenient package.
For the example in this chapter, you can create an executable with the following command:
rubyscript2exe desktop_team_performance_graph.rb
As you can see, this gem is very easy to use. Using various options, you can modify the output. You can find out more about RubyScript2Exe in my Apress book, Practical Ruby Gems, as well as from the RubyScript2Exe home page: http://www.erikveenstra.nl/rubyscript2exe /index.html.
Summary
This chapter covered how you can extend your applications directly onto users' desktops. You saw how to use the spreadsheet-excel gem to create an Excel spreadsheet report, which can be extremely helpful to end users. Then you created a desktop application that displays beautiful graphs using Gruff and Active Record.
As you can see, Ruby isn't a web-only or console-only language. It offers a great deal of flexibility for creating applications that extend directly to the user's desktop in familar formats, including thick-client and common office applications. However, there's no question that Ruby is best known for its usage on the Web. In the next chapter, you'll discover how all of the techniques you've learned so far can be used with Ruby on Rails, the popular web development framework.
DISCLAIMER: The content provided in this article is not warranted or guaranteed by Developer Shed, Inc. The content provided is intended for entertainment and/or educational purposes in order to introduce to the reader key ideas, concepts, and/or product reviews. As such it is incumbent upon the reader to employ real-world tactics for security and implementation of best practices. We are not liable for any negative consequences that may result from implementing any information covered in our articles or tutorials. If this is a hardware review, it is not recommended to open and/or modify your hardware.