Queuetastic released!

Posted by JVaughn Thu, 02 Oct 2008 19:34:00 GMT

Looking for a way to monitor Asterisk queues in real-time and have queue reporting available? Without shelling out licensing costs?

Welcome to Queuetastic! An Asterisk Queue Real-time Monitoring, Reporting, and Analysis tool designed with Ruby on Rails.

Queuetastic primarily performs 3 major functions.

  • Monitor Asterisk Queues in real-time using AJAX through the Asterisk Manager Interface
  • Migrate and load Asterisk Queue data into a database of your choosing
  • Extract queue and agent reports

Why Queuetastic?

  1. Asterisk Guru’s Queue Stats simply would not install in my environment. It couldn’t parse my queue_log properly. The source code was closed too. Boo, Next please…..
  2. Asterisk Queue Activity did install, but didn’t have the features I was looking for. Plus it’s web views are in Spanish and I don’t speak Spanish.
  3. I did not want to shell out for an expensive commercial license to monitor and report on an open-source PBX. No, Queuemetrics “free 2 Agent license” wouldn’t cut it either.
  4. There should be an open-source application for this!
  5. So I decided to write my own web-app using Ruby on Rails
Screenshots
  • The Dashboard Dashboard

  • Live View Live View

Download Queuetastic

To download a tarball release:

To download from upstream using git:

git clone git://rubyists.com/gits/queuetastic queuetastic


or

git clone git://github.com/thedonvaughn/queuetastic.git queuetastic


Wiki


For installation and support check out Queuetastic’s Wiki

Geminating Autumn (Making a new ruby gem with newgem and RubiGen Spec helper)

Posted by Bougyman Sun, 10 Aug 2008 16:30:00 GMT

This weekend has been spent creating a gem out of autumn. The autumn IRC framework has allowed us to create single-purpose and very complex IRC (and Jabber) bots which manage workflows for both fun and profit. It allows you to create such a workflow processer with a minimum amount of code utilizing a rails-like structure.

Thanks to some great articles (drnic’s newgem article, drnic’s gem articles). Other articles pointed me there, but unfortunately the RubyGems manual does not have much information at all about creating a new gem. Thanks to RISCfuture autumn was a pretty simply transition from the full tree to a newgem generated tree. First move was to take everything in

AUTUMN_ROOT/libs/
and move it to
NEWGEM_ROOT/lib/autumn/
. Once that was complete I moved on to the autumn script/ directory, files which generate autumn leaves and control the autumn process. First thing I noticed is that the original autumn ‘generate’ and ‘destroy’ were not rubigen generators, but self-written generators in the rails generator/RubiGen style by RISCfuture. After some digging in to RubiGen and what it would take to convert these specialized generators, I decided to put that in the code debt category and just rename them to autumn_generate and autumn_destroy and put them in the script directory as such. A few changes to the autumn libs (mostly replacing libs/ with autumn/), getting ./script/console, ./script/daemon, ./script/server running was fairly straightforward, again mostly lib path replacing (libs/ -> autumn/). Next step was to create the autumn (bin/autumn) application generator to create a bare autumn tree, ready to use the gem lib/autumn/* libraries and lib/autumn/resources/ libraries instead of local ones. This led to an immersion course into the wonderful RubiGen system, particularly the generator_test_helper.rb, which is used to create Unit tests for the application generator. After corresponding with RISCfuture, I heard the good news that he has been working on writing a spec suite for testing autumn itself, so I decided it was time to delve into spec myself (I’ve always used Test::Unit thus far). This meant creating an equivalent to generator_test_helper.rb that was spec-oriented.

The new helper results (generator_spec_helper.rb):

module RubiGen
  module GeneratorSpecHelper
    # Runs the create command (like the command line does)
    def run_generator(name, params, sources, options = {})
      generator = build_generator(name, params, sources, options)
      silence_generator do
        generator.command(:create).invoke!
      end
      generator
    end

    # Instatiates the Generator
    def build_generator(name, params, sources, options)
      options.merge!(:collision => :force)  # so no questions are prompted
      if sources.is_a?(Symbol)
        if sources == :app
          RubiGen::Base.use_application_sources!
        else
          RubiGen::Base.use_component_sources!
        end
      else
        RubiGen::Base.reset_sources
        RubiGen::Base.prepend_sources(*sources) unless sources.blank?
      end
      RubiGen::Base.instance(name, params, options)
    end

    # Silences the logger temporarily and returns the output as a String
    def silence_generator
      logger_original=RubiGen::Base.logger
      myout=StringIO.new
      RubiGen::Base.logger=RubiGen::SimpleLogger.new(myout)
      # TODO redirect $stdout to myout
      yield if block_given?
      RubiGen::Base.logger=logger_original
      # TODO fix $stdout again
      myout.string
    end

    # asserts that the given file was generated.
    # the contents of the file is passed to a block.
    def generated_file?(path)
      file_exists?(path)
      File.open("#{APP_ROOT}/#{path}") do |f|
        yield f.read if block_given?
      end
    end

    # asserts that the given file exists
    def file_exists?(path)
      File.exists?("#{APP_ROOT}/#{path}").should eql(true)
    end

    # asserts that the given directory exists
    def directory_exists?(path)
      File.directory?("#{APP_ROOT}/#{path}").should eql(true)
    end

    # asserts that the given class source file was generated.
    # It takes a path without the <tt>.rb</tt> part and an optional super class.
    # the contents of the class source file is passed to a block.
    def generated_class?(path,parent=nil)
      path=~/\/?(\d+_)?(\w+)$/
      class_name=$2.camelize
      generated_file?("#{path}.rb") do |body|
        it "should define #{class_name} in #{path}.rb" do
          body.should match(/class #{class_name}#{parent.nil? ? '':" < #{parent}"}/)
        end
        yield body if block_given?
      end
    end

    # asserts that the given module source file was generated.
    # It takes a path without the <tt>.rb</tt> part.
    # the contents of the class source file is passed to a block.
    def generated_module?(path)
      path=~/\/?(\w+)$/
      module_name=$1.camelize
      generated_file?("#{path}.rb") do |body|
        it "should define #{module_name} in #{path}.rb" do
          body.should match(/module #{module_name}/)
        end
        yield body if block_given?
      end
    end

    # asserts that the given unit test was generated.
    # It takes a name or symbol without the <tt>test_</tt> part and an optional super class.
    # the contents of the class source file is passed to a block.
    def generated_test_for?(name, parent="Test::Unit::TestCase")
      generated_class? "test/test_#{name.to_s.underscore}", parent do |body|
        yield body if block_given?
      end
    end

    # asserts that the given methods are defined in the body.
    # This does assume standard rails code conventions with regards to the source code.
    # The body of each individual method is passed to a block.
    def has_method?(body,*methods)
      methods.each do |name|
        it "should define the method #{name.to_s}" do
          body.should match(/^  def #{name.to_s}\n((\n|   .*\n)*)  end/)
        end
        yield( name, $1 ) if block_given?
      end
    end

    def app_root_files
      Dir[APP_ROOT + '/**/*']
    end

    def rubygem_folders
      %w[bin examples lib test]
    end

    def rubygems_setup
      bare_setup
      rubygem_folders.each do |folder|
        Dir.mkdir("#{APP_ROOT}/#{folder}") unless File.exists?("#{APP_ROOT}/#{folder}")
      end
    end

    def rubygems_teardown
      bare_teardown
    end

    def bare_setup
      FileUtils.mkdir_p(APP_ROOT)
    end

    def bare_teardown
      FileUtils.rm_rf TMP_ROOT || APP_ROOT
    end

  end
end

This of course requires including RubiGen and spec in the autumn_generator_spec.rb. RubiGen also provided a nice local test_generator_helper.rb which I converted to spec_generator_helper.rb:

begin
    require 'spec'
rescue LoadError
    require 'rubygems'
      gem 'rspec'
        require 'spec'
end


require 'fileutils'

# Must set before requiring generator libs.
TMP_ROOT = File.dirname(__FILE__) + "/tmp" unless defined?(TMP_ROOT)
PROJECT_NAME = "autumn" unless defined?(PROJECT_NAME)
app_root = File.join(TMP_ROOT, PROJECT_NAME)
if defined?(APP_ROOT)
  APP_ROOT.replace(app_root)
else
  APP_ROOT = app_root
end
if defined?(RAILS_ROOT)
  RAILS_ROOT.replace(app_root)
else
  RAILS_ROOT = app_root
end

begin
  require 'rubigen'
rescue LoadError
  require 'rubygems'
  require 'rubigen'
end
require File.join(File.dirname(__FILE__), 'generator_spec_helper')
$:.unshift(File.dirname(__FILE__) + '/../app_generators/autumn')
require 'autumn_generator'

And finally the autumn_generator_spec.rb (not yet complete):

require File.join(File.dirname(__FILE__), '/spec_generator_helper.rb')

module AutumnGeneratorSpecHelper
  include RubiGen::GeneratorSpecHelper
  def generator_path
    "app_generators"
  end

  def sources
    [RubiGen::PathSource.new(:test, File.join(File.dirname(__FILE__),"..", generator_path))
    ]
  end
end

# Time to add your specs!
# http://rspec.info/
describe AutumnGenerator, "when application is generated" do
  include AutumnGeneratorSpecHelper
  before(:all) do  
    bare_setup
    run_generator('autumn', [APP_ROOT], sources)
  end

  %w{lib log script test tmp}.each do |dir|
    it "should create#{dir}" do
      directory_exists?(dir)
    end
  end

  %w{console destroy generate autumn_generate autumn_destroy daemon server}.each do |script|
    script_path = File.join("script", script)
    it "should create #{script_path}" do
      file_exists?(script_path)
    end
  end

  after(:all) do
    bare_teardown
  end

end

As I add to autumn_generator_spec.rb, I add the code necessary to pass the tests to ../app_generators/autumn/autumn_generator.rb. Should be complete by the end of today (Sunday). Check back for the release announcement.

CalendarHelper Meet Builder::XmlMarkup

Posted by Trey Wed, 06 Aug 2008 12:20:00 GMT

Your choices for calendar builders for rails aren't many, but the CalendarHelper plugin originally by Jeremy Voorhis has worked well for me—that is until I needed to drastically change the HTML it outputs. Immediately after digging in to the code I found the following comment: "TODO Use some kind of builder instead of straight HTML." Read on for my conversion of CalendarHelper to use Builder::XmlMarkup.


CalendarHelper

It's usage is simple. It mixes in to ActionView::Base, adding a method "calendar" which takes a hash of options and yields to a block whose return value is rendered as each date cell. In addition to the standard concerns for a calendar, it allows for configurable links in the header, css classes on various elements, day of the week abbreviations, and most importantly day of the week ordering. The last feature causes a nice twist in the code, but we'll deal with that in a moment. First let's look at the code as it is originally.

This gives you a feel for how the HTML is generated.

 # TODO Use some kind of builder instead of straight HTML
    cal = %(<table class="#{options[:table_class]}" border="0" cellspacing="0" cellpadding="0">)
    cal << %(<thead><tr>)
    if options[:previous_month_text] or options[:next_month_text]
      cal << %(<th colspan="2">#{options[:previous_month_text]}</th>)
      colspan=3
    else
      colspan=7
    end
    cal << %(<th colspan="#{colspan}" class="#{options[:month_name_class]}">#{Date::MONTHNAMES[options[:month]]}</th>)
    cal << %(<th colspan="2">#{options[:next_month_text]}</th>) if options[:next_month_text]
    cal << %(</tr><tr class="#{options[:day_name_class]}">)

This is a fairly common pattern: accumulate the output in a variable and return the result at the end. It works fine for small code blocks. Many of my helper methods use this same pattern. However, when the output grows in complexity the code can be difficult to maintain. It requires extra effort to make the HTML output readable within the code—here docs do help. And even then the outputted HTML isn't always pretty. Enter Builder::XmlMarkup. It exists to make XML generation simple and allow the structure of the code to mirror the structure of the outputted data.


Builder::XmlMarkup

Builder::XmlMarkup takes advantage of ruby's immensely useful method_missing. method_missing serves as a catch all for any instance method that is not defined in a class. Through it's magic ruby allows for things like:

    xhtml = []
    cal = Builder::XmlMarkup.new(:target => xhtml, :indent => 2)
    cal.table(:class => options[:table_class], :border => 0, :cellspacing => 0, :cellpadding => 0) do
      cal.thead do
        cal.tr do

Any method called on the builder object is converted to a tag name. The method can be called with or without a block. When invoked with a block any calls to the builder contained in the block are nested within the block's tag. When invoked without a block the first argument is treated as the contents of the block. Either one can be invoked with a hash representing the attributes for the tag.

Converting the existing code to use the builder was a straight forward process of going through the code and replacing it with the corresponding builder. Unfortunately, the existing code's output statements did not logically follow the code nesting. In some places tags were started within loops and terminated at a different level. Rather than add conditionals to the logic so that it followed the structure of the document it was outputting, I took an opportunity to refactor the algorithm it used, which presents us with the twist I mentioned earlier.


Algorithm-smalgorithm

A month does not always start on the first day nor does it end on the last day of a week. The calendar then can be thought of as containing three distinct sections: days from the previous month, days within the month, and days from the next month.

This is how the existing code treats a month, as three distinct sections. Each section's loop may or may not terminate the row tr tag, which is where the nesting problem from above occurs.

To fit the problem in to the confines of our builder—each week's row tr tag must be a single block--we have to rethink the algorithm. A month calendar can be thought of as a two dimensional array. The first dimension representing the weeks of the month, and the second representing the days of a week. This fits nicely with the requirements of our builder, but we only know the first and last day of the month which do not necessarily start on the first and end of the last day of the week. What we need to find given the first and last day of the month, is the date of the first and last day of the week.

Finding this is easy enough. The Date object gives us the day of the week in the wday method. Counting is done with Sunday equal to zero. So we take the first day of the month's week day and subtract that many days to find the date of the start of the week.

    first = Date.civil(options[:year], options[:month], 1)
    first = first - first.wday

After using the corresponding algorithm to find the last day of the last week of the month we can then iterate from the new first day to the new last day of the month in 7 day chunks to build the calendar.


The Twist

But wait. Remember the twist? CalendarHelper allows us to define which day is the first day of the week. If the first day of the week is not Sunday, zero, the logic breaks. When counting days the week we have a series: 0, 1, 2, 3, 4, 5, 6, representing each day from Sunday through Saturday. When we change the start of the week—say to Wednesday—we end up with the new series Wed., Thurs., Fri., Sat., Sun., Mon., Tues., or 3, 4, 5, 6, 0, 1, 2.

What we want is the offset, the number of days, from the start of the week to the date of the start of the month within that week. Using that offset we can subtract it from the first of the month to find the date of the first day of that week just like we did above.

Our offset can be thought of as counting the days of the week from 0, 1, 2...6. We need a way to convert from the rotated series 3, 4, 5, 6, 0, 1, 2 to the zero based one. Enter the modulus operator, %. Our friend the modulus operator which gives the remainder of the division of the two operands, is wonderful for repeated, bounded series. If we have a series with n -> inf and the statement n % C with C the number of elements in our series we get 0, 1, 2, ... C, 0, 1, 2, ... C ... Useful but how does this help us?


0, 1, 2, Huh?

We want to convert from 3, 4, 5, 6, 0, 1, 2 to 0, 1, 2, 3, 4, 5, 6. Let's say the start of the month falls on a Friday, 5. If we take start of the shifted series, 3, and subtract it from the size of the series, 7, and take that sum and add it to the day, 5, and modulo that with size of the series, 7, we get

= (5 + (7 - 3)) % 7
= (5 + 4) % 7
= 9 % 7
= 2

2, which is counted as the third item in the series from zero to six, which when subtracted from the start of the month gives us the date of the start of the week.

But wait. Huh?

If we were to have the series start at 0 and apply the same algorithm we have

= (5 + (7 - 0)) % 7
= 12 % 7
= 5

which is just the next repetition of our series. By subtracting 3 we are accounting for the shift factor within our original series. Mmmmm math. Tasty.

To find the last day of the week we start with the last day of the week, 6, and subtract from it the offset between the last day of the month and the first day of the week. So now we have

    first = Date.civil(options[:year], options[:month], 1)
    last = Date.civil(options[:year], options[:month], -1)
    first -= ((first.wday + (7 - options[:first_day_of_week])) % 7)
    last += 6 - ((last.wday + (7 - options[:first_day_of_week])) % 7)

Full Code

I've submitted my changes to the CalendarHelper developers. This or something similar should show up soon in the calendar helper svn.

require 'date'
require 'builder'

# CalendarHelper allows you to draw a databound calendar with fine-grained CSS formatting
module CalendarHelper

  VERSION = '0.3.0'

  # Returns an HTML calendar. In its simplest form, this method generates a plain
  # calendar (which can then be customized using CSS) for a given month and year.
  # However, this may be customized in a variety of ways -- changing the default CSS
  # classes, generating the individual day entries yourself, and so on.
  # 
  # The following options are required:
  #  :year  # The  year number to show the calendar for.
  #  :month # The month number to show the calendar for.
  # 
  # The following are optional, available for customizing the default behaviour:
  #   :table_class       => "calendar"        # The class for the <table> tag.
  #   :month_name_class  => "monthName"       # The class for the name of the month, at the top of the table.

  #   :other_month_class => "otherMonth" # Not implemented yet.
  #   :day_name_class    => "dayName"         # The class is for the names of the weekdays, at the top.
  #   :day_class         => "day"             # The class for the individual day number cells.
  #                                             This may or may not be used if you specify a block (see below).
  #   :abbrev            => (0..2)            # This option specifies how the day names should be abbreviated.
  #                                             Use (0..2) for the first three letters, (0..0) for the first, and
  #                                             (0..-1) for the entire name.
  #   :day_names         => nil               # An optional array of names for the days of the week.
  #   :first_day_of_week => 0                 # Renders calendar starting on Sunday. Use 1 for Monday, and so on.
  #   :accessible        => true              # Turns on accessibility mode. This suffixes dates within the
  #                                           # calendar that are outside the range defined in the <caption> with 
  #                                           # <span class="hidden"> MonthName</span>
  #                                           # Defaults to false.
  #                                           # You'll need to define an appropriate style in order to make this disappear. 
  #                                           # Choose your own method of hiding content appropriately.
  #
  #   :show_today        => false             # Highlights today on the calendar using the CSS class 'today'. 
  #                                           # Defaults to true.
  #   :previous_month_text   => nil           # Displayed left of the month name if set
  #   :next_month_text   => nil               # Displayed right of the month name if set
  #   :pass_builder      => false             # Pass the builder as the second argument to the block.  Ignore the
  #                                             the text that is returned from the block. You are responsible
  #                                             for adding a 'td' to the builder output.
  #
  # For more customization, you can pass a code block to this method, that will get one argument, a Date object,
  # and return a values for the individual table cells. The block can return an array, [cell_text, cell_attrs],
  # cell_text being the text that is displayed and cell_attrs a hash containing the attributes for the <td> tag
  # (this can be used to change the <td>'s class for customization with CSS).
  # This block can also return the cell_text only, in which case the <td>'s class defaults to the value given in
  # +:day_class+. If the block returns nil, the default options are used.
  # 
  # Example usage:
  #   calendar(:year => 2005, :month => 6) # This generates the simplest possible calendar.
  #   calendar({:year => 2005, :month => 6, :table_class => "calendar_helper"}) # This generates a calendar, as
  #                                                                             # before, but the <table<'s class
  #                                                                             # is set to "calendar_helper".
  #   calendar(:year => 2005, :month => 6, :abbrev => (0..-1)) # This generates a simple calendar but shows the
  #                                                            # entire day name ("Sunday", "Monday", etc.) instead
  #                                                            # of only the first three letters.
  #   calendar(:year => 2005, :month => 5) do |d| # This generates a simple calendar, but gives special days
  #     if listOfSpecialDays.include?(d)          # (days that are in the array listOfSpecialDays) one CSS class,
  #       [d.mday, {:class => "specialDay"}]      # "specialDay", and gives the rest of the days another CSS class,
  #     else                                      # "normalDay". You can also use this highlight today differently
  #       [d.mday, {:class => "normalDay"}]       # from the rest of the days, etc.
  #     end
  #   end
  #
  # An additional 'weekend' class is applied to weekend days. 
  #
  # For consistency with the themes provided in the calendar_styles generator, use "specialDay" as the CSS class for marked days.
  # 
  def calendar(options = {}, &block)
    raise(ArgumentError, "No year given")  unless options.has_key?(:year)
    raise(ArgumentError, "No month given") unless options.has_key?(:month)

    defaults = {
      :table_class => 'calendar',
      :month_name_class => 'monthName',
      :other_month_class => 'otherMonth',
      :day_name_class => 'dayName',
      :day_class => 'day',
      :abbrev => (0..2),
      :day_names => nil,
      :first_day_of_week => 0,
      :accessible => false,
      :show_today => true,
      :previous_month_text => nil,
      :next_month_text => nil,
      :pass_builder => false
    }
    options = defaults.merge options

    first = Date.civil(options[:year], options[:month], 1)
    last = Date.civil(options[:year], options[:month], -1)
    first -= ((first.wday + (7 - options[:first_day_of_week])) % 7)
    last += 6 - ((last.wday + (7 - options[:first_day_of_week])) % 7)
    today = first

    if(options[:day_names])
      day_names = options[:day_names]
    else
      i = 0
      day_names = Date::DAYNAMES.inject(Array.new(7)) { |a, d| a[(i + (7 - options[:first_day_of_week])) % 7] = d; i += 1; a }
    end

    xhtml = []
    cal = Builder::XmlMarkup.new(:target => xhtml, :indent => 2)
    cal.table(:class => options[:table_class], :border => 0, :cellspacing => 0, :cellpadding => 0) do
      cal.thead do
        cal.tr do
          if options[:previous_month_text] or options[:next_month_text]
            cal.th(options[:previous_month_text], :colspan => 3)
            colspan = 3
          else
            colspan = 7
          end

          cal.th("#{Date::MONTHNAMES[options[:month]]} #{options[:year]}", :colspan => colspan, :class => options[:month_name_class])
          cal.th(options[:next_month_text], :colspan => 3, :class => 'rightMonth') if options[:next_month_text]
        end

        cal.tr(:class => options[:day_name_class]) do
          day_names.each { |d| cal.th(d[options[:abbrev]], :scope => 'col') }
        end
      end

      cal.tbody do
        while(today <= last) do
          cal.tr do
            1.upto(7) do
              if(today.month != options[:month].to_i)
                css_class = options[:other_month_class] + ([0, 6].include?(today.wday) ? ' weekendDay' : '')
                if(options[:accessible])
                  cal.td(:class => css_class) do
                    cal << today.day.to_s
                    cal.span(Date::MONTHNAMES[d.month], :class => 'hidden')
                  end
                else
                  cal.td(today.day.to_s, :class => css_class)
                end
              else
                if(block_given? && options[:pass_builder])
                  block.call(today, cal)
                else
                  cell_text, cell_attrs = block.call(today) if block_given?
                  cell_text ||= today.day.to_s
                  cell_attrs ||= { :class => options[:day_class] }
                  cell_attrs[:class] += " weekendDay" if [0, 6].include?(today.wday) 
                  cell_attrs[:class] += " today" if (today == Date.today) and options[:show_today]  

                  cal.td(cell_attrs) { cal << cell_text }
                end
              end

              today += 1
            end
          end
        end
      end
    end

    xhtml.join
  end
end

ActionView rcsv template handler

Posted by Trey Thu, 31 Jul 2008 15:07:00 GMT

The way the rjs and builder template handlers work in rails is nice. I do a lot of work with csv for generating reports, and having all the csv generation code in the controller doesn't seem to fit the MVC paradigm. I dug in to how ActionView template handlers work in Rails 2.1 and came up with the following solution.

The code uses the FasterCSV gem so you'll need that installed. One gotcha i noticed is that if format.csv is before format.html in the respond_to block the request gets sent incorrectly.

Code


RAILS_ROOT/config/initializers/template_handlers.rb

require 'rcsv_template_handler'
ActionView::Template.register_template_handler :rcsv, ActionView::TemplateHandlers::RCSV

RAILS_ROOT/lib/rcsv_template_handler.rb

require 'fastercsv'

module ActionView
  module TemplateHandlers
    class RCSV < TemplateHandler
      include Compilable

      def self.line_offset
        2
      end

      def compile(template)
        content_type_handler = (@view.send!(:controller).respond_to?(:response) ? "controller.response" : "controller")
        "#{content_type_handler}.content_type ||= Mime::CSV\n" +
        "if controller.request.env['HTTP_USER_AGENT'] =~ /msie/i\n" +
        "  controller.headers['Pragma'] = 'public'\n" +
        "  #{content_type_handler}.content_type ||= Mime::TEXT\n" +
        "  controller.headers['Cache-Control'] = 'no-cache, must-revalidate, post-check=0, pre-check=0'\n" +
        "  controller.headers['Expires'] = '0'\n" +
        "else\n" +
        "  #{content_type_handler.content_type ||= Mime::CSV\n" +
        "end\n" +
        "csv_doc = ::FasterCSV.new(:row_sep = \"\r\n\") do |csv|\n#{template.source}\nend"
      end

      def cache_fragment(block, name = {}, options = nil)
        @view.fragment_for(block, name, options) do
          eval('csv_doc', block.binding)
        end
      end
    end
  end
end

Usage


RAILS_ROOT/app/controllers/name_controller.rb

class NameController < ApplicationController
  def action
    @data = Data.find :all
    respond_to do |format|
      format.html # Render action.html.erb
      format.csv  # Render action.csv.rcsv
    end
  end
end

RAILS_ROOT/app/view/name/action.csv.rcsv

@data.each do |datum|
  csv << datum
end

RJS Errors with Firebug Goodness

Posted by Trey Fri, 18 Jul 2008 19:02:00 GMT

I use firebug for all of my javascript debugging. It’s an excellent application, but one thing that has always bothered me is that rail’s built-in RJS exception handler shows it’s output in an alert. Firebug has the very handy console.log() function. Why can’t rails use that?

Now it can.

The code is smart enough to tell if Firebug is loaded or not and gracefully falls back to alert if it isn’t. As an added bonus you can now cut and paste the content as well as see multiple errors.

Usage

Copy the code to a file that is loaded at rails start up. RAILS_ROOT/config/initializers/rjs_debug.rb would work nicely.


module ActionView
  module Helpers
    module JavascriptHelper
      class JavaScriptGenerator
        module GeneratorMethods
          def to_s #:nodoc:
            returning javascript = @lines * $/ do
              if ActionView::Base.debug_rjs
                source = javascript.dup                                                                                                                                                                          
                javascript.replace "try {\n#{source}\n} catch (e) "                                                                                                                                              
                javascript << "{ var rjs_debug = alert; if(window.console && window.console.firebug) { rjs_debug = console.log }; rjs_debug.apply(this, ['RJS error:\\n\\n' + e.toString()]); rjs_debug.apply(this, ['#{source.gsub('\\','\0\0').gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" }}']); throw e }" 
              end                                                                                                                                                                                                
            end                                                                                                                                                                                                  
          end
        end
      end
    end
  end
end

Adding hooks to class

Posted by Bougyman Tue, 15 Jul 2008 16:28:00 GMT

Ugly way to create a hook for your class which wraps any method you create in any filters you wish to add. In this case simply states the new and old method names.

class Fee
  def self.method_added(meth, *args)
    unless method_defined?("_aliased_#{meth}".to_sym) or meth.to_s.match(/^_aliased_/)
      puts "Added method"
      puts meth.inspect
      puts args.inspect
      alias_method "_aliased_#{meth}".to_sym, meth
      define_method meth do |*args|
        puts "Running original #{meth}: _aliased_#{meth}"
        self.__send__("_aliased_#{meth}".to_sym, *args)
      end
    end
  end
end

class Fee
  def my_meth(hey)
    puts hey + " you"
  end
end

Looks like so:

$ irb -r /tmp/fee.rb
Added method
:my_meth
[]
irb(main):001:0> m = Fee.new;m.my_meth("you")
Running original mymeth: _aliasedmy_meth
you you
=> nil
irb(main):002:0>

Automating Helpdesk (Trac Tickets)

Posted by Bougyman Sat, 28 Jun 2008 00:25:00 GMT

I inherited an environment using a big bloated helpdesk application, Track-IT, and finally got fed up with it today. The replacement? Trac, using the new Trac beta with configurable ticket workflows. Here’s the little ruby (used a rails tree just for the autoloading magic and actionmailer) that took care of the glue which trac needed to be a full Track-IT replacement. That is, the ‘download mail from a pop box and make a ticket’.
This is bin/receive_mail.rb, made from snippets found online.
require 'net/pop'
require File.dirname(__FILE__) + '/../config/environment'

logger = RAILS_DEFAULT_LOGGER

logger.info Time.now.to_s + ": Running Mail Importer..." 
Net::POP3.start("mailserver", nil, "helpdesk", "password") do |pop|
  if pop.mails.empty?
    logger.info Time.now.to_s + ": NO MAIL" 
  else
    pop.mails.each do |email|
      begin
        logger.info Time.now.to_s + ": receiving mail..." 
        Mailman.receive(email.pop)
        email.delete
      rescue Exception => e
        logger.error "Error receiving email at " + Time.now.to_i.to_s + "::: " + e.message
      end

    end
  end
end
logger.info "Finished Mail Importer." 
It utilizes lib/mailman.rb:
require "tmail"
class Mailman
  def self.receive(pop)
    File.open(File.join(RAILS_ROOT, "mails", "help_mail.#{Time.now.to_f.to_s}.txt"), "wb+") { |f| f.puts pop }
    mail = TMail::Mail.parse(pop)
    ticket = Ticket.create(:description => mail.parts.first.body.strip, :summary => mail.subject.strip, :reporter => mail.from.first.split('@')[0].sub('.',''))
    RAILS_DEFAULT_LOGGER.info Time.now.to_s + ": Created ticket ##{ticket[:id]}"
  end
end
This uses the Ticket model, which uses the Trac db. I did this model in AR before I realized I’d need a data mapper pattern for the SessionAttribute model (more on that later). So this model should really be rewritten in DataMapper, which I tried to see how it compared to my standard data mapping lib, rbatis. I enjoyed Data Mapper, but had a hard time finding a few things and ran into an issue where I could not create a new record without declaring a (composite) key on the SessionAttribute model. Here’s app/models/ticket.rb:
class Ticket < ActiveRecord::Base
  set_table_name "ticket"
  validates_presence_of :description
  validates_presence_of :summary
  validates_presence_of :reporter
  before_save :set_defaults
  after_save :send_notification
  after_save :setup_session_attributes
  def self.inheritance_column
    nil
  end

  protected
  def set_defaults
    self[:type] ||= "something is broken"
    self[:component] ||= "maintenance"
    self[:severity] ||= "unstarted"
    self[:milestone] ||= "Help Desk"
    self[:owner] ||= "Dilbert"
    self[:priority] ||= "must-have"
    self[:status] ||= "new"
    self[:time] ||= Time.now.to_i
    self[:changetime] ||= Time.now.to_i
  end

  def send_notification
    HelpDeskNotification.deliver_new_ticket(self.reload)
  end

  # Add a session attribute for name, email, and timezone for the ticket reporter so they
  # get responses from trac and the trac view looks proper.  Users authenticate to trac
  # via ldap so they don't have to be added to the trac db, they just need preferences set, which
  # live in session_attribute.
  def setup_session_attributes
    email = SessionAttribute.get(self.reporter, "email")
    if email.nil?
      email = SessionAttribute.new(:sid => self.reporter, :authenticated => 1, :name => "email", :value => self.reporter + "@ourdomain.com")
      email.save
    end
    name  = SessionAttribute.get(self.reporter, "name")
    if name.nil?
      name = SessionAttribute.new(:sid => self.reporter, :authenticated => 1, :name => "name", :value => self.reporter.split(/([A-Z][a-z]+)/,2).join(" ").strip)
      name.save
    end
    tz  = SessionAttribute.get(self.reporter, "tz")
    if tz.nil?
      tz = SessionAttribute.new(:sid => self.reporter, :authenticated => 1, :name => "tz", :value => "US/Central")
      tz.save
    end
  end

end
The real job of ticket.rb, outside of creating the ticket, is sending the notification and making sure the reporter is properly set up in trac so they get email tracking and a sane view when they log in to trac (using ldap authentication store for trac users). Here’s the mailer, app/models/help_desk_notification.rb:
class HelpDeskNotification < ActionMailer::Base
  
  def new_ticket(ticket)
    subject    "New Helpdesk Ticket: #{ticket.id}"
    recipients ['admins@mydomain.us', ticket.reporter + "@mydomain.us"]
    from       'helpdesk@mydomain.us'
    sent_on    Time.now
    body       :ticket => ticket
  end

end
And the
app/views/help_desk_notification/new_ticket.html.erb
view:
A new ticket https://intranet/trac/ticket/<%= @ticket.id %> has been added by <%= @ticket.reporter %>

Subject: <%= @ticket.summary %>
Description: 
<%= @ticket.description %>

Assigned to: <%= @ticket.owner %>
All that’s left is the SessionAttribute model, app/models/session_attribute.rb:
require "dm-core"
DataMapper.setup(:default, 'postgres://trac_user:trac_password@db_server/trac')
class SessionAttribute
  include DataMapper::Resource
  storage_names[:default] = 'session_attribute'
  property :sid, String, :key => true
  property :authenticated, Integer
  property :name, String, :key => true
  property :value, String
end
bin/receive_mail.rb runs in a cron every 5 minutes and ticket creation/notification is blissful. Thanks again, ruby.

Working with FTP sites in ruby

Posted by Trey Mon, 16 Jun 2008 18:15:00 GMT

I routinely build packages for my Solaris server. While building vim I ran in to a recurring problem of how to automate downloading patches via FTP. Read on for an example in ruby.

The vim developers regularly release patches. If you want to stay up to date you have to download many hundreds of files–315 for vim 7.1 right now. The vim maintainers simplify downloading by providing a set of cumulative patches every 100 revisions. This is great for reducing connections to the FTP server for downloading files, but complicates creating an automated process for downloading the files. Yes I could just mirror all the files, or I could only download the single patches, ignoring the cumulative ones, but that’s no fun. Here’s my solution.

This script demonstrates communicating with an ftp server using Net::FTP, array and string manipulation with regular expressions, and GZip decompression with Zlib.

My goals for this project were to have a script i could run that would download all the cumulative patches and then download the remaining individual patches. I also wanted it to remove any individual patches that are covered by a cumulative patch. And finally it would be good if it uncompressed the cumulative patches.

Our first problem is to figure out which individual patches we need. We can find this out by looking at the last cumulative patch. First we download all the cumulative patches, if we don’t already have them. Luckily the vim maintainers pad their file names with zeros so that things sort numerically in an ascii sort. By looking at the last cumulative patch we can find the last revision number, which gives us a starting point for fetching the individual patches. We can find the last individual patch by looking at a directory listing of all the patches and extracting the last patch filename after sorting. Then we can fetch the individual patches.

To handle removing patches we don’t need, we use the revision number for our first individual patch and remove any file that has a version number less than it.

Decompressing the gzip’d files is done using Zlib::GzipReader which behaves like a simple IO object.

Enjoy.

#!/usr/sfw/bin/ruby

require 'net/ftp'
require 'zlib'

VIM_VERSION = '7.1'

puts "Connecting to ftp.vim.org"
ftp = Net::FTP.new('ftp.vim.org')
ftp.login
ftp.chdir("/pub/vim/patches/#{VIM_VERSION}")

puts "Searching for patches for vim #{VIM_VERSION}"
cumulative_patches = ftp.nlst("#{VIM_VERSION}.*-*.gz")
all_patches = ftp.nlst("#{VIM_VERSION}.*")

cumulative_patches.each do |patch|
  uncompressed_patch = patch.sub(/\.gz$/, '')
  unless(File.exists?(patch) || File.exists?(uncompressed_patch))
    puts "Fetching #{patch}"
    ftp.getbinaryfile(patch)
  end
end

first_patch = 0

if cumulative_patches.size > 0
  puts "Looking for the last cumulative patch number"
  cumulative_patches.sort!
  m = cumulative_patches.last.match(/-(\d+)\.gz$/)
  first_patch = m[1].to_i if(m)
end

first_patch += 1
all_patches.reject! { |f| f.match(/\.gz$/) }
all_patches.sort! { |a, b| a.sub(/^#{Regexp.escape(VIM_VERSION)}\./, '').to_i <=> b.sub(/^#{Regexp.escape(VIM_VERSION)}\./, '').to_i }
last_patch = all_patches.last.sub(/^#{Regexp.escape(VIM_VERSION)}\./, '').to_i
puts "Fetching standard patch #{first_patch} to #{last_patch}"

first_patch.upto(last_patch) do |patch|
  filename = "#{VIM_VERSION}.%03i" % patch
  unless(File.exists?(filename))
    puts "Fetching #{filename}"
    ftp.getbinaryfile(filename)
  end
end

ftp.close

puts "Removing uneeded patches"
files = Dir["#{VIM_VERSION}.*"]
files.reject! { |f| f.match(/\.gz/) }
files.each do |file|
  m = file.match(/^#{Regexp.escape(VIM_VERSION)}\.(\d+)$/)
  if(m && m[1].to_i < first_patch)
    puts "Removing #{file}"
    File.unlink(file)
   end
end

puts "Expanding cumulative patches"
cumulative_patches.each do |patch|
  uncompressed_patch = patch.sub(/\.gz$/, '')
  unless(File.exists?(uncompressed_patch))
    puts "Uncompressing #{patch}"
    Zlib::GzipReader.open(patch) do |gz|
      File.open(uncompressed_patch, 'w') { |f| f.puts gz.read }
    end
    File.unlink(patch)
  end
end

# vim: et ts=2 sw=2