Ruby Reference

This will be where I give examples of Ruby code I have found useful over time. In past experiences, especially with such a fast growing language, often times we all miss the simple and cool things we can do.

Creating a RubyGem Package

Summary:

Ruby Gems are the standard way of distributing Ruby applications and library classes. Information on RubyGems can be found at http://www.rubygems.org.

Packaging a Ruby application or library class into a Gem package is not a difficult task by any means. That said, finding straight forward documentation on it was. The Gem Spec Reference was a helpful read, but didn't really put all the pieces together to provide a gem.

Shall we get started?



The Code:

Of course, before we can create a Gem we need a Ruby application or library class. It just so happens that I have one laying around here somewhere...

begin shameless self promotion scheme

ParseConfig is a Ruby class written to parse simple configuration files in the format of 'param = value'. The key benefit is that your ruby scripts can use the same configuration files of most unix/linux applications.

Basic usage:

require('parseconfig')
config = ParseConfig.new('/path/to/my/config/file')
param1 = config.get_value('param1') => value1
param2 = config.get_value('param2') => value2
...

Taking a look at the ParseConfig project we can see the following [basic] file manifest:

./Changelog
./README
./LICENSE
./demo.rb
./demo.conf
./lib/parseconfig.rb

Without packaging into a gem, I could simply copy ./lib/parseconfig.rb to /usr/lib/site_ruby (or wherever the site_ruby directory is) and then do require('parseconfig'). But most people don't find that too fun... and more people probably don't know where that site_ruby directory is... and so on and so forth.

Regardless, we are here to talk about RubyGems so why wouldn't we package it into a gem?



The Gem Spec File

The Gem Specification File does just that... it specifies the who/what/where/when/how for this Gem package. I simply grabbed a spec from another package called Choice, and modified it to my liking:

parseconfig.gemspec:

Gem::Specification.new do |s|
  s.name = %q{parseconfig}
  s.version = "0.4.1"
  s.date = %q{2007-09-03}
  s.authors = ["BJ Dierkes"]
  s.email = %q{wdierkes@5dollarwhitebox.org}
  s.summary = %q{ParseConfig provides simple parsing of standard *nix style config files.}
  s.homepage = %q{http://www.5dollarwhitebox.org/}
  s.description = %q{ParseConfig provides simple parsing of standard *nix style config files.}
  s.files = [ "README", "Changelog", "LICENSE", "demo.rb", "demo.conf", "lib/parseconfig.rb"]
end



Building Our Gem:

Now that we have a gemspec created (mine is called parseconfig.gemspec) we can issue the following command:

wdierkes$ gem build parseconfig.gemspec 
    Successfully built RubyGem
    Name: parseconfig
    Version: 0.4.1
    File: parseconfig-0.4.1.gem

If I do say myself... that was pretty easy.



Installing Our Gem:

We all are familiar with this, but to complete the process we can now install our new gem package:

wdierkes$ sudo gem install parseconfig-0.4.1.gem 
 
Successfully installed parseconfig, version 0.4.1



Using Our Gem:

Since we are using this library via rubygems, we need to require that before our package. The following is a little bit of fun with ParseConfig:

sample.config:

db_host='localhost'
db_user='test_user'
db_password='test_password'
db_db='test_database'

sample.rb:

#!/usr/bin/env ruby
require('rubygems')
require('parseconfig')
 
config = ParseConfig.new('sample.config')
my_real_config = Hash.new()
 
my_real_config['db_host'] = config.get_value('db_host')
my_real_config['db_user'] = config.get_value('db_user')
my_real_config['db_password'] = config.get_value('db_password')
my_real_config['db_db'] = config.get_value('db_db')
 
puts """
connecting to #{my_real_config['db_host']}
using the database #{my_real_config['db_db']}
with user #{my_real_config['db_user']}
and password #{my_real_config['db_password']}
"""

And we get:

wdierkes$ ruby sample.rb 
 
connecting to localhost
using the database test_database
with user test_user
and password test_password



Conclusion:

So, as you can see getting a gem built and ready for distribution is not a big deal. Keep in mind however there are a number of different ways to do this, as well as many more options that can be added to the gemspec. Please look over the Gem Spec Reference for more info.

Additionally, there is this hot new thing called Hoe (no.. not like that). Some may want to play around with that as it has some really cool features.



Reference:

Gem Spec Reference: http://rubygems.org/.../page85


Ruby Dual Output / Logging

Summary:

Logging in Ruby is a fairly straight forward process, however I came across an interesting issue. I found that I was constantly doing both a 'log' statement, as well as a 'puts' statement to output to console. Well, this clutters code especially once your script turns into a multi-layered application. Additionally, most of the time what I am logging is the same thing I want to output to console.

In walks Log4r.

Log4r is a comprehensive and flexible logging library written in Ruby for use in Ruby programs. It features a hierarchical logging system of any number of levels, custom level names, logger inheritance, multiple output destinations, execution tracing, custom formatting, thread safteyness, XML and YAML configuration, and more."



Logging to Console:

The most generally place you want to see output from a script is to the console. Our first setup is going to configure Log4r to log to console:

sample.rb:

#!/usr/bin/env/ruby
 
require('rubygems')
require('log4r')
include Logr4
 
log = Logger.new 'sample application'
format = PatternFormatter.new(:pattern => "%l:\t %m")
log.add Log4r::StdoutOutputter.new('console', :formatter=>format)
 
log.info 'sample application logging to console'

You can see that we have configured out 'log' object, and then added a 'format' to it with the 'log.add' method. Here you can specify any format pattern that you want. I generally like to keep the log level in the output for clarity ('%l').

Our output looks like:

wdierkes$ ruby sample.rb 
INFO:    sample application logging to console



Logging to a file:

Now, say we didn't want to log to console, we only want to log to a file. This is a perfectly legit scenario. We would simply change our logger around:

#!/usr/bin/env/ruby
 
require('rubygems')
require('log4r')
include Log4r
 
log_file = './sample.log'
 
log = Logger.new 'sample application'
format = PatternFormatter.new(:pattern => "[ %d ] %l\t %m")
log.add FileOutputter.new('fileOutputter', :filename => log_file, :trunc => false, :formatter=>format)
 
puts "logging to #{log_file}"
log.info 'sample application logging to console'

Only a few changes... we are now adding a bit more to the format pattern as do most log files. We are also now using the FileOutputter class.

Lets see the new output:

wdierkes$ ruby sample.rb 
logging to ./sample.log
 
wdierkes$ cat sample.log 
[ 2007-09-03 15:51:04 ] INFO     sample application logging to console

Perfect!



Combining Console And Logfile Output in One Logger:

Here we arrive at my original dilemma. I constantly want to log the same thing to console, and the log file.. however keeping two separate formatter patterns for each. This turned out to be a lot easier than I had anticipated.

sample.rb:

#!/usr/bin/env/ruby
 
require('rubygems')
require('log4r')
include Log4r
 
log_file = './sample.log'
 
log = Logger.new 'sample application'
console_format = PatternFormatter.new(:pattern => "%l:\t %m")
log.add Log4r::StdoutOutputter.new('console', :formatter=>console_format)
 
file_format = PatternFormatter.new(:pattern => "[ %d ] %l\t %m")
log.add FileOutputter.new('fileOutputter', :filename => log_file, :trunc => false, :formatter=>file_format)
 
log.info 'sample application logging to console'

All we need to do is add both Outputters to our log object, and that is it. Lets check this bad boy out:

wdierkes$ ruby sample.rb 
INFO:    Im logging to both console and ./sample.log now
 
wdierkes$ cat sample.log 
[ 2007-09-03 15:51:04 ] INFO     sample application logging to console
[ 2007-09-03 15:56:05 ] INFO     Im logging to both console and ./sample.log now

Looking nice... real nice.



Toggle Debug

Most of my scripts have a '--debug' flag that will set the global $debug to 'true'. If not set, then $debug should be 'nil'. We can easily add the following to our logger to enable debug:

if($debug)
    log.level = DEBUG
else
    log.level = INFO
end

There are 5 default levels when you create your Logger object:

DEBUG < INFO < WARN < ERROR < FATAL

I usually only toggle between INFO and DEBUG since all other levels will be displayed in 'INFO' mode.



One Logger for all my classes?:

Being that our main goal is world domination, with limited lines of code why not combine all of your classes to use one logger? In most cases, multiple classes are working together to accomplish a single task. In generally, you might want to break up your logs if you are running: multiple daemons, multiple threads, etc.

The following lays out a standard way of accessing the same log object with multiple classes. The concept is to have a 'base' or 'core' class that is passed between the other classes. In this example, the 'base' class is used to maintain common methods that all other classes will likely access.

sample.rb:

#!/usr/bin/env/ruby
 
require('rubygems')
require('log4r')
include Log4r
 
$debug = true
 
module Sample
    class Base
         def initialize(log_file)
              @log = Logger.new 'sample application'
              console_format = PatternFormatter.new(:pattern => "%l:\t %m")
              self.log.add Log4r::StdoutOutputter.new('console', :formatter=>console_format)
              file_format = PatternFormatter.new(:pattern => "[ %d ] %l\t %m")
              self.log.add FileOutputter.new('fileOutputter', :filename => log_file, :trunc => false, :formatter=>file_format)
 
              if($debug)
                  self.log.level = DEBUG
              else
                  self.log.level = INFO
              end
        end
 
        def log=(log_obj)
            @log = log_obj
        end
 
        def log()
            @log
        end
    end
 
    class ClassA
        def initialize(base)
             @base = base  
        end
 
        def base=(base_obj)
            @base = base_obj
        end
 
        def base()
            @base
        end
    end
 
    class ClassB
        def initialize(base)
            @base = base  
        end
 
        def base=(base_obj)
            @base = base_obj
        end
 
        def base()
            @base
         end
    end
end
 
log_file = './sample.log'
base = Sample::Base.new(log_file)
base.log.info 'accessing the logger from the base object'
 
objA = Sample::ClassA.new(base)
objA.base.log.info 'accessing the logger from ClassA (same object)'
 
objB = Sample::ClassB.new(base)
objB.base.log.debug 'accessing the logger from ClassB (same obj)'

Note that the base, objA, and objB are all logging to the Logger established in the Sample::Base class. Lets see what we have:

wdierkes$ ruby sample.rb 
INFO:    accessing the logger from the base object
INFO:    accessing the logger from ClassA (same object)
DEBUG:   accessing the logger from ClassB (same obj)
 
 
wdierkes$ cat sample.log 
[ 2007-09-03 16:15:40 ] INFO     accessing the logger from the base object
[ 2007-09-03 16:15:40 ] INFO     accessing the logger from ClassA (same object)
[ 2007-09-03 16:15:40 ] DEBUG    accessing the logger from ClassB (same obj)

You like?


Reference:

Log4r: http://log4r.sourceforge.net/



Ruby: Get rid of "warning: peer certificate won't be verified in this SSL session"



Anyone who has written ruby cli apps that utilize SSL know that you get the following, most annoying message:

warning: peer certificate won't be verified in this SSL session



The work around is quite simple... just add the following the the top of one of the file you're loading:

class Net::HTTP
  alias_method :old_initialize, :initialize
  def initialize(*args)
    old_initialize(*args)
    @ssl_context = OpenSSL::SSL::SSLContext.new
    @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
  end
end



Done.