Monday, 8 September 2008

Beginning Ruby By Peter Cooper - Part 3

Last week i started reading Beginning Ruby Book By Peter Cooper.

I wanted to share few important quotations i found from the next 3 chapters(7-8-9) from the book.

1) Basic File Inclusion

The most common way to separate functionality in Ruby is to put different classes in different files. This gives you the ability to write classes that could be used in multiple projects simply by copying the file into your other project.

Example:

vowel_test.rb

require 'string_extensions'
puts "This is a test".vowels.join('-')

string_extensions.rb

class String
def vowels
self.scan(/[aeiou]/i)
end
end

If you run vowel_test.rb, the expected result would appear onscreen. The first line, require 'string_extensions', simply loads in the string_extensions.rb file and processes it as if the code were local.

2) With load, the code is loaded and reprocessed anew each time you use the load method. require, on the other hand, only processes external code once.

Example

a.rb

puts "Hello from a.rb"

b.rb
require 'a'
puts "Hello from b.rb"
require 'a'
puts "Hello again from b.rb"

Output:
Hello from a.rb
Hello from b.rb
Hello again from b.rb

Now update b.rb as
load 'a'
puts "Hello from b.rb"
load 'a'
puts "Hello again from b.rb"

Ruby programmers generally use require rather than load. The effects of load are only useful if the code in the external file has changed or if it contains active code that will be executed immediately.However, a good programmer will avoid the latter situation, and external files will only contain classes and modules that will, generally, rarely change.

3) Ruby comes with more than 100 standard libraries, as standard. They provide Ruby with a wide selection of functionality “out of the box,” from Web serving and networking tools through to encryption, benchmarking, and testing routines.

4) RubyGems is a packaging system for Ruby programs and libraries. It enables developers to package their Ruby libraries in a form that’s easy for users to maintain and install.RubyGems makes it easy to manage different versions of the same libraries on your machine, and gives you the ability to install them with a single line at the command prompt.

Each individually packaged Ruby library (or application) is known simply as a gem or RubyGem. Gems have names, version numbers, and descriptions. You can manage your computer’s local installations of gems using the gem command, available from the command line.

5) Installing a Simple Gem
Once you’ve found the name of a gem you wish to install, you can install it with a single command at the command line (where feedtools would be replaced with the name of the gem you wish to install, although feedtools is a fine gem to test with):

gem install feedtools

6) Generating Documentation with RDoc

RDoc calls itself a “Document Generator for Ruby Source.” It’s a tool that reads through your Ruby source code files and creates structured HTML documentation. It comes with the standard Ruby distribution, so it’s easy to find and use. If for some reason RDoc does not appear to come with your installation of Ruby, you can download it from the official RDoc site at http://rdoc.sourceforge.net/.

RDoc understands a lot of Ruby syntax and can create documentation for classes, methods, modules, and numerous other Ruby constructs without much prompting.

This is a simple class that’s been documented using comments. It’s quite readable already, but RDoc can turn it into a pretty set of HTML documentation in seconds.

To use RDoc, simply run it from the command line using rdoc .rb, like so:
rdoc person.rb

7) In Ruby, the rescue clause is used, along with begin and end, to define blocks of code to handle exceptions. For example:

begin
puts 10 / 0
rescue
puts "You caused an error!"
end

8) Catch and Throw

Although creating your own exceptions and exception handlers is useful for resolving error situations, sometimes you want to be able to break out of a thread of execution (say, a loop) during normal operation in a similar way to an exception, but without actually generating an error. Ruby provides two methods, catch and throw, for this purpose.

catch and throw work in a way a little reminiscent of raise and rescue, but catch and throw work with symbols rather than exceptions. They’re designed to be used in situations where no error has occurred, but being able to escape quickly from a nested loop,method call, or similar, is necessary.

catch(:finish) do
1000.times do
x = rand(1000)
throw :finish if x == 123
end
puts "Generated 1000 random numbers without generating 123!"
end

9) Simple Benchmarking

Ruby’s standard library includes a module called Benchmark. Benchmark provides several methods that measure the speed it takes to complete the code you provide. For example:

require 'benchmark'
puts Benchmark.measure { 10000.times { print "." } }

This code measures how long it takes to print 10,000 periods to the screen. Ignoring the periods produced, the output (on my machine; yours might vary) is as follows:

0.050000 0.040000 0.090000 ( 0.455168)

The columns, in order, represent the amount of user CPU time, system CPU time,total CPU, and “real” time taken. In this case, although it took nine-hundredths of a second of CPU time to send 10,000 periods to the screen or terminal, it took almost half a second for them to finish being printed to the screen among all the other things the computer was doing.

10) Because measure accepts code blocks, you can make it as elaborate as you wish:

require 'benchmark'
iterations = 1000000

b = Benchmark.measure do
for i in 1..iterations do
x = i
end
end

c = Benchmark.measure do
iterations.times do |i|
x = i
end
end

puts b
puts c

0.800000 0.010000 0.810000 ( 0.949338)
0.890000 0.010000 0.900000 ( 1.033589)

11) Benchmark also includes a way to make completing multiple tests more convenient. You can rewrite the preceding benchmarking scenario like this:

require 'benchmark'
iterations = 1000000

Benchmark.bm do |bm|
bm.report("for:") do
for i in 1..iterations do
x = i
end
end
bm.report("times:") do
iterations.times do |i|
x = i
end
end
end

user system total real
for: 0.406000 0.000000 0.406000 ( 0.407000)
times: 0.610000 0.000000 0.610000 ( 0.609000)

12) Where benchmarking is the process of measuring the total time it takes to achieve something and comparing those results between different versions of code, profiling tells you what code is taking what amount of time.

Ruby comes with a code profiler built in, and all you have to do to have your code profiled automatically is to add require "profile" to the start of your code, or run it with ruby --r profile before your source file name.

13) We could choose to assign the file handle to a class or instance variable:

class MyFile
attr_reader :handle

def initialize(filename)
@handle = File.new(filename, "r")
end

def finished
@handle.close
end
end

f = MyFile.new("text.txt")
puts f.handle.gets
f.finished

14) You can read an I/O stream with each using a custom delimiter of your choosing.

File.open("text.txt").each(',') { |line| puts line }

You can read an I/O stream byte by byte with each_byte:

File.open("text.txt").each_byte { |byte| puts byte }

15) Here’s how to read an I/O stream line by line using gets:

File.open("text.txt") do |f|
2.times { puts f.gets }
end

You can also read an entire file into an array, split by lines, using readlines:

puts File.open("text.txt").readlines.join("--")

Last but not least, you can choose to read an arbitrary number of bytes from a file into a single variable using read:

File.open("text.txt") do |f|
puts f.read(6)
end

16) Your Position Within a File

When reading a file, it can be useful to know where you are within that file. The pos
method gives you access to this information:

f = File.open("text.txt")
puts f.pos
puts f.gets
puts f.pos

17) Renaming and Deleting Files
If you want to change the name of a file, you could create a new file with the new name and read into that file all the data from the original file. However, this isn’t necessary, and you can simply use File.rename like so:

File.rename("file1.txt", "file2.txt")

Deleting a file is just as simple. You can delete either one file at a time or many at once:
File.delete("file1.txt")
File.delete("file2.txt", "file3.txt", "file4.txt")
File.unlink("file1.txt")

18) Seeking

seek has three modes of operation:

• IO::SEEK_CUR Seeks a certain number of bytes ahead of the current position.
• IO::SEEK_END Seeks to a position based on the end of the file. This means to seek to a certain position from the end of the file you’ll probably need to use a negative value.
• IO::SEEK_SET Seeks to an absolute position in the file. Identical to pos=.

19) Finding Out When a File was Last Modified

To establish when a file was last modified, use File.mtime:

puts File.mtime("text.txt")

20) Getting the Size of a File

File.size returns the size of a file in bytes. If the file doesn’t exist, an exception is thrown,so it would make sense to check its existence with File.exist? first.
puts File.size("text.txt")

21) How to Know When You’re at the End of a File

The eof? method provides this feature:

f = File.new("test.txt", "r")
catch(:end_of_file) do
loop do
throw :end_of_file if f.eof?
puts f.gets
end
end
f.close

22) PStore
PStore is a core Ruby library that allows you to use Ruby objects and data structures as you normally would, and then store them in a file. Later on, you can reload the objects back into memory from the disk file. This technique is known as object persistence, and relies on a technique called marshalling, where standard data structures are turned into a form of flat data that can be stored to disk or transmitted over a network for later reconstruction.

class Person
attr_accessor :name, :job, :gender, :age
end

fred = Person.new
fred.name = "Fred Bloggs"
fred.age = 45

laura = Person.new
laura.name = "Laura Smith"
laura.age = 23

require 'pstore'
store = PStore.new("storagefile")
store.transaction do
store[:people] ||= Array.new
store[:people] << fred
store[:people] << laura
end

store = PStore.new("storagefile")
people = []
store.transaction do
people = store[:people]
end

# At this point the Person objects inside people can be treated
# as totally local objects.
people.each do |person|
puts person.name
end

About the Author

Peter Cooper is a highly experienced Ruby developer and trainer. He manages BigBold (www.bigbold.com), a Ruby training and development company, and has produced many commercial web sites using Ruby on Rails, the Ruby-based web framework. In addition, he created Code Snippets, one of the world’s largest public code repositories, and Congress, an online chat client utilizing Ajax and Ruby on Rails. He also created Feed Digest, a feed distribution service that was recently profiled by Business 2.0 magazine.

In addition to development work, Peter has written professionally about various development techniques and tools, producing over 100 articles. He was coeditor of WebDeveloper.com, and worked on iBoost.com and Webpedia.com.

He lives in Lincolnshire, England, with his girlfriend. In his spare time he enjoys hiking, camping, and exploring mountains.

No comments: