Ruby/Tk Primer: Creating a cron GUI Interface with Ruby/Tk
by Christopher Roach06/25/2004
This article is the first in a three-part series that teaches you how to use
Ruby and Tk on your Mac OS X system. During the course of this tutorial, I hope
to convince you of the advantages of using Ruby and the Tk toolkit by creating
a program that will act as a GUI-based front end to the cron
daemon.
In the first half of the series, we'll create the back end of our program. This
will be an array-based class that allows us to load/save cron jobs from/to a
text file whose contents will be added to the cron schedule. By doing so, I
hope to provide a primer to Ruby for those of you who are unfamiliar with the
language.
The next article will concentrate on creating the GUI for the back end we create in this article. The GUI we create will be implemented with the Tk GUI toolkit, developed by John Ousterhout in the late 80s as a complement to his Tcl scripting language. Both of the technologies we're using here have been ported to nearly every platform imaginable, so the skills you develop in these articles, as well as the program itself, will be portable to nearly any system you can imagine.
Regardless of whether you're interested in programming with Ruby or Tk or both,
or even if your just interested in getting a free program that makes cron a
bit more user-friendly, I hope you'll find these articles useful and entertaining.
So, without any further ado, let's get down to the real nitty-gritty and learn
a little bit about Ruby.
What Is Ruby?
|
Related Reading
Ruby in a Nutshell |
Ruby is a dynamic object-oriented language created by Yukihiro "Matz" Matsumoto in 1993 as a replacement for Perl. The Ruby language incorporates many of the best features of other existing languages to make it a very powerful and easy-to-use language.
Like Smalltalk, Ruby is a pure object-oriented language, meaning that everything
in Ruby is an object. Thus, messages can be sent to a static string like so:
"Hello World!".length(). Also, Ruby is dynamically typed, just like Smalltalk,
so it is not necessary to declare the variables in your program. Another feature
shared by Smalltalk and Ruby (and other languages, such as Java) is the use of
garbage collection to return memory that was previously allocated back to the
system. This essentially gets rid of the infamous memory leaks associated with
other languages such as C and C++.
Similar to Perl and Python, Ruby is interpreted and can be used to develop short and powerful programs that automate tasks on your system. Because of this last feature and a few others, such as dynamic typing, Ruby is often referred to as a scripting language. This is a bit of a misnomer, since scripting languages are usually considered to be pseudo-languages and not quite as powerful as their full-fledged siblings.
However, unlike most scripting languages, including Perl, Ruby was designed from the ground up to be object-oriented. It is for this reason that Matsumoto has preferred to call his language a "dynamic object-oriented language" rather than describe it as a scripting language. Regardless of whether you refer to Ruby as a programming language or as a scripting language, it still remains that Ruby can be used to develop quick, short, powerful scripts as well as easily maintainable, purely object-oriented, full-fledged applications. I like to think of Ruby as an extremely portable, all-in-one, Swiss army knife for any of your computing tasks. With all of these features leaving you drooling to learn more, why are we wasting time talking? Let's get our hands dirty and write some code.
Hello, Ruby!
Since I'm not one to go against tradition, its only fitting that our first program be the ubiquitous "Hello World" program. Start by opening up your favorite editor (I tend to be rather partial to Emacs) and typing in the following code:
puts "Hello, World!"
Save the file with whatever name you like and type ruby filename into
the command prompt of your terminal application. Remember that filename
should be the name you chose for your file. Press Return, and voilà! You have
written your very first Ruby program.
Let's dissect it, shall we? What we have done here is call the puts
method and pass in the string "Hello, World!" as an argument (feel free to surround
the string with parentheses if it makes you feel better; however, in Ruby they
are not necessary). The puts method just prints a given string to the
standard output (in this case, the command line) and tacks a new line onto the
end.
Another method, print, does just the same, but it excludes the new line.
If you noticed in that explanation I referred to puts as a method, but
doesn't it look like its just a function call? After all, I don't see an object
reference in front of the puts method, and of course you remember that I claimed
that Ruby was a pure object-oriented language earlier, don't you? Well, I didn't
lie to you. What is essentially happening here is the same as calling self.puts("Hello,
World!").
Self is a reference to the object in which the method resides. It's
similar to the this reference used in C++. So what's the reference self
referring to? In all Ruby programs, everything executes within the context
of a top-level object. Since all objects inherit from the main Object class,
which includes methods from the Kernel module (which, incidentally, is where
the puts method can be found), every object has access to methods such
as puts and print. The call to puts is really a call to
the puts method associated with the top-level object in which our program
is executing. So even though it looks like a function call in a procedural language,
we are really calling a method from an object.
We've now written and executed our first Ruby program. Let's move on to something
a little tougher and begin developing our cron back end. We'll start by learning
how to create a class and all of its attributes and actions.
Creating Classes In Ruby
Classes in Ruby are created by using the keywords class and end.
Let's begin our program by creating a new file called CronJobMgr.rb and typing
in the following code to begin our class:
class CronJobMgr
end
Take notice of the character case in the code above, Ruby is a case-sensitive
language. Also, it uses the first characters of names to identify their usage.
In Ruby, class names and constants begin with a capital letter, instance variables
begin with an "at" symbol (@), class variables with two of them (@@), global variables
with a dollar sign ($), and local variables and method names with a lowercase
letter. In this sense, Ruby seems to be a little more like Perl. However, Perl's
identifiers are used more for data-typing, whereas Ruby's are more for scope.
With a class now defined, shall we move onto adding some attributes and actions
to it, so that it can accomplish something more than just being created? Let's
kill two birds with one stone and create a method and all of the class's attributes
at the same time. Since Ruby is dynamically typed, we can do this all in one
step; otherwise, we would have to declare our variables before using them as
we have to in languages such as Java. This is truly one of Ruby's strengths
since it allows the user to write powerful object-oriented scripts in a short
amount of time and with very little code. Open up that file again and add the
following method to the CronJobMgr class:
def initialize(minute, hour, day, month, weekday,
command)
@minute = minute
@hour = hour
@day = day
@month = month
@weekday = weekday
@command = command
end
It's easy to see that we define a method by using the keywords def and
end. We named our method initialize and its signature tells us
that it accepts six arguments when it is called. The initialize method
is important to Ruby. The initialize method is automatically called after
a new object is created. Notice that what this method does is initialize six
of the object's instance variables to the values passed into the method. Also,
remember that Ruby is a dynamic language; thus, variables do not need to be statically
typed, and as a result, do not have to be defined before being used. What this
means is that we have created a method that initializes the CronJobMgr object's
six instance variables and created those variables at the very same time.
Congratulations, you now have a full class with state and actions. However,
it still doesn't do very much outside of initializing itself with a few values.
Why don't we add one more method and take it out for a spin? Copy the following
method into your CronJobMgr class:
def to_s
"#{@minute}\t#{@hour}\t#{@day}\t#{@month}\t" +
"#{@weekday}\t#{@command}"
end
What this method does is return a string representing the state of the object
as a tabbed list of attributes. In Ruby, the value of the final expression is
returned from the method, but if it makes you feel better, you can place the
keyword return before the string. The #{. . .} construct just evaluates
the expression within the braces; in this case, it returns the value of an instance
variable. The plus sign (+) is used to concatenate two strings.
We used double quotes to surround our string rather than single quotes because
in Ruby, double quotes are used when we want to use expression evaluations within
the string. Otherwise, we can use single quotes to create a literal string.
One more thing we want to notice is that to_s is another important
Ruby method. All classes in Ruby supply a to_s method that essentially
returns a string representation of the object.
Since we now have a class that can possibly do something, let's create a test file and try out our new class. Do so by creating a new file called test.rb and typing in the following code:
require 'CronJobMgr.rb'
cronJobMgr = CronJobMgr.new("50", "11", "*", "*",
"*", "/usr/bin/date")
puts cronJobMgr
What we have done here is tell the program to use the code in the CronJobMgr.rb
by requiring it, and then we created an instance of the CronJobMgr by calling the
new method found in all Ruby classes and passing in the arguments that we wish
to go to the initialize method. Finally, we printed the state of our object
to the screen. Try it out -- call ruby test.rb at the command line and
make sure that you see a tabbed list of the attributes you passed into the new
method.
Now you know the basics of creating classes, methods, and attributes in Ruby.
We need to apply this knowledge to finishing our CronJob class and developing
our CronJobMgr class. For these tasks, we will need to pick up a little more
Ruby knowledge. In this next section, we'll learn about a quick way to write
accessor methods, how to inherit from another class, what code blocks and iterators
are, and how to use Ruby's File class, and as a few other interesting little
tidbits. So don't stop now, you're doing great, go grab yourself a cup of coffee
or tea and read on.
A Little More Ruby
Take a look at the code for the completed CronJob
class. You'll notice in the finished class we have added a second initialize
method, a method named commandName, and some weird things at the top
named attr_reader and attr_writer. Let's start with the most obvious
of the three: the new initialize method.
This method is just a default initialize that allows us to create a
new CronJob instance without passing in any parameters. It basically sets the
state of the object to the default state. The next thing you'll notice is a
method named commandName. In this method, we've used the split
method provided by the string class to parse out the name of the command we've
chosen for the object and return it as a string. This method will be used later
in the second article to display the cron job's data in the GUI.
The final additions to the class are the attr_reader and attr_writer
methods (yes, these are methods, also). These methods are just shortcuts provided
by Ruby for creating reader and writer methods for a class. The only difference
between these and normal accessor methods are that they are used just as if
they were regular instance variables. If you've done any C# programming, you
can think of them as being similar to properties. One final thing to notice
in the CronJob class is that the arguments passed into the methods have a colon (:)
in front of them. That means that your passing in a symbol, or instead of passing
in the value of the variable, you're actually passing in the name of the variable.
We've finally finished our CronJob class. Now we need to create a class
whose instance will hold several instances of CronJobs. This class needs to
also provide a way to load/save CronJob objects from/to a text file. For these
tasks, we will learn how to use inheritance to extend the built-in Array class
and we'll also learn how to do text I/O using Ruby's File class. First things
first; let's create a class skeleton and add it our CronJobMgr.rb file.
class CronJobMgr < Array
def loadCronJobs(filename)
end
def saveCronJobs(filename)
end
end
In the code above we have created a class that defines two new methods, named
loadCronJobs and saveCronJobs. Both of these take an argument named
filename. You should also notice that our class declaration is followed by a
less-than sign (<) and the class name Array. This is how we show inheritance
in Ruby. In essence, we are saying that the class CronJobMgr is a child of Ruby's
Array class. Essentially, we have created an array of our own and added two methods
to it. Next, we need to flesh out those methods.
Go ahead and take a look at the loadCronJobs method in the CronJobMgr.rb
file. The first thing we need to do in our loadCronJobs method is open
the file from which we are to extract the jobs. We do this by calling the open
method of the File class. The open method uses another nice feature of
Ruby known as code blocks.
A code block is just what it suggests, a block of code. The strength of these
blocks comes in using them in conjunction with methods, as we see the open
method doing here. The open method allows the user to pass in the block
of code they wish to have executed. The open method then invokes the
block of code and passes to it the argument file. When the block is finished
executing, the file is automatically closed.
You'll notice that the rest of the code is littered with code blocks and the
methods that use them. Code blocks are a very important feature of Ruby. Combined
with methods, code blocks provide a way for users to create iterators. Iterators
are basically methods that invoke a block of code repeatedly. Doesn't sound
like much, but believe me, start using them and you'll love them. The each_line
and upto methods (and the each method in the saveCronJobs
method) are examples of iterators in use. The each_line method simply
executes the block of code passed into it for each of the lines in the file.
The upto and each methods are just as obvious, and I leave it up
to you to look into what each does on your own.
Now that you understand the concepts of blocks and iterators, it should
be easy to understand the rest of the code. The loadCronJobs method just
opens a file and parses each line in the file into the set of attributes that are
used to initialize a new CronJob object, which is then pushed into the CronJobMgr
object (remember, it is essentially an array with a couple of extra methods).
The saveCronJobs method is even easier to understand. This method opens
a file for writing and loops through each CronJob in itself and writes
each to the file. The only surprise left is the call to the system method before
the close of the saveCronJobs method. This method just allows the user
to make calls to commands provided by the operating system.
In this instance, we use the system method to call the crontab command on the
new file we have just saved to add all of the jobs to the cron daemon's schedule.
Did you get all that? If not, don't worry; you just need to look up a few methods
to find out exactly what they do. You can find each of these methods in the
built-in classes and methods section of the book Programming Ruby: The Pragmatic
Programmer's Guide (a.k.a. the "Pickaxe book").
Final Thoughts
Well, you're done for the moment, barring a few comments to make the code more maintainable. You've learned quite a bit about Ruby in just a short amount of time. I hope I've given you a good overview of the language and went just deeply enough to allow you start coding with Ruby while also leaving a spark of interest in you to make you want to continue learning more.
You now have a good back end to use in our next article, where we will create the
GUI front end for the CronJobMgr class using the Tk toolkit. In the interim,
if the desire strikes you, try creating a command line interface with Ruby that
uses the CronJobMgr class to automate the crontab command. Until the next lesson,
enjoy playing around with Ruby.
Christopher Roach recently graduated with a master's in computer science and currently works in Florida as a software engineer at a government communications corporation.
Return to the Mac DevCenter
-
About String
2006-01-23 21:18:01 shymol [View]
-
About String
2006-01-23 23:46:21 Christopher Roach |
[View]
-
Error in CronJob Class
2004-07-20 18:19:45 Christopher Roach |
[View]
-
Ruby Programming Text Onine
2004-07-04 19:14:21 Christopher Roach |
[View]
-
Answer to the self.puts dilema
2004-06-28 18:40:32 Christopher Roach |
[View]
-
Answer to the self.puts dilema
2004-07-19 18:23:49 wkaha@yahoo.com [View]
-
Answer to the self.puts dilema
2004-07-19 20:06:35 Christopher Roach |
[View]
-
Answer to the self.puts dilema
2004-07-19 16:12:40 Merc [View]
-
Answer to the self.puts dilema
2004-07-19 20:30:53 Christopher Roach |
[View]
-
Answer to the self.puts dilema
2004-07-19 16:18:43 Merc [View]
-
Using Ruby
2004-06-28 09:08:26 r.boylan [View]
-
self.puts
2004-06-28 18:06:54 aturley [View]
-
Using Ruby
2004-06-28 11:24:35 Christopher Roach |
[View]

