Archive for April 2007

Export Rails ActiveRecords to CSV

For a recent “enterprisey” project I’m working on, we had to offer a variety of CSV exports for many of the models in our system. Ruby’s FasterCSV library is great for raw parsing and generation of CSV data, so I used that as the basis for a quick and dirty system to easily provide customizable exports.

The main features are:

  • Transform an array of exportable records into a whole CSV file. (By default, FasterCSV transforms arrays into a single CSV row.)
  • Export a whole table my calling .to_csv on the ActiveRecord subclass.
  • By default, export all of an ActiveRecord’s columns except for created_at and updated_at.
  • Allow simple customization of exportable columns by overriding the export_columns method in your ActiveRecord class.
  • Allow multiple CSV formats by conditionally branching inside the export_columns method depending on the format parameter.
  • Allow complete customization of the export by overriding the to_row method. (I haven’t actually needed this much customization yet.)

The simplest example:


Address.to_csv

Customizing the columns included in the CSV:

1
2
3
4
5
class Address < ActiveRecord::Base
  def export_columns(format = nil)
    %w[city state postal_code]
  end
end

Multiple output formats:

1
2
3
4
5
6
7
8
9
10
class Address < ActiveRecord::Base
  def export_columns(format = nil)
    case format
    when :local
      %w[street1 street2 city state postal_code]
    else
      %w[city state postal_code]
    end
  end
end

Here’s the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
require "fastercsv"

class ActiveRecord::Base
  def self.to_csv(*args)
    find(:all).to_csv(*args)
  end
  
  def export_columns(format = nil)
    self.class.content_columns.map(&:name) - ['created_at', 'updated_at']
  end
  
  def to_row(format = nil)
    export_columns(format).map { |c| self.send(c) }
  end
end

class Array
  def to_csv(options = {})
    if all? { |e| e.respond_to?(:to_row) }
      header_row = first.export_columns(options[:format]).to_csv
      content_rows = map { |e| e.to_row(options[:format]) }.map(&:to_csv)
      ([header_row] + content_rows).join
    else
      FasterCSV.generate_line(self, options)
    end
  end
end

AppleScript GTD tickler file for Mac

I’ve been trying to get back on the Getting Things Done wagon lately, and that’s led me to tweak my tools and processes for efficiency. While looking for an automated GTD tickler file for my Mac, I stumbled on Lifehacker’s Auto-AppleScript tickler file post and took a bit of time to tweak the scripts to suit my workflow.

Here are the main changes I made:

  • Cleaned up the scripts and fixed some minor bugs.
  • Changed month folders to use the month name and number (for example, “3 – March”) so they stay sorted in Finder, but you can still see the month names when browsing.
  • Only generate one year’s worth of folders, and do so starting from the current date.
  • Copy files into the tickler system instead of moving them. (I can still remove the original if I want to.)

After setting up the system, I added this cron job:

0 6 * * * /usr/bin/osascript /Users/bhelmkamp/Scripts/Update\ Today\ Link.scpt

Now my ticklers for the day are open in a Finder window when I sit down at my desk each morning.

Zip fileDownload AppleScript_Tickler_v1.zip

Eviscerating Gmail spam with greylisting, SPF and blacklists

For a while now, I’ve been wanting to try greylisting my email to reduce spam. Not that Gmail is bad at handling spam, of course, but as a geek I’m always looking for incremental improvements.

When I recently switched email addresses, I finally took the time to implement greylisting along with remote blacklists and Sender Policy Framework checks with Postfix. So far, I’m thrilled with the results. Here’s my new configuration:

  • My server is the primary MX record for brynary.com.
  • My server immediately rejects messages from blacklisted servers or that fail SPF checks.
  • If the sending server is not yet on my greylisting whitelist, my server greylists the message for a few minutes.
  • Messages that pass the greylisting step get forwarded to Gmail for content filtering.
  • If the message made it this far, and it passes Gmail’s content filtering, it lands in my inbox.

Fortunately, most of the information I needed to set this up was in this Filtering spam with Postfix article. Here are my notes on the process which might save you some time:

  • I left out the SASL authentication lines, since they don’t seem to be relevant to my configuration.
  • After setting up the check_helo_access directive, be sure to remember that you need to run postmap /etc/postfix/helo_access
  • relays.ordb.org is no longer providing RBL services, so leave that line out to avoid errors.
  • I had to do some extra Googling to find good instructions for setting up the Perl-based Postfix SPF implementation.
  • Running the manual tests at the bottom of that SPF article did not work as described, but, according to my logs, my SPF implementation is working properly.
  • Setting up postgrey on debian is not hard, but this article helped.

For reference, my resulting /etc/postfix/main.cf is:

# See /usr/share/postfix/main.cf.dist for a commented, more complete version

smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU)
biff = no

# appending .domain is the MUA's job.
append_dot_mydomain = no

# Uncomment the next line to generate "delayed mail" warnings
# delay_warning_time = 4h

# alias_maps = hash:/etc/aliases
# alias_database = hash:/etc/aliases
# myorigin = /etc/mailname

myhostname = mail.brynary.com
virtual_alias_domains = brynary.com
virtual_alias_maps = hash:/etc/postfix/virtual

relayhost = 

mydestination = stewie, localhost.localdomain, localhost
mynetworks = 127.0.0.0/8
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
inet_protocols = all

smtpd_delay_reject = yes
smtpd_helo_required = yes
smtpd_helo_restrictions = 
   permit_mynetworks,
   check_helo_access hash:/etc/postfix/helo_access,
   reject_non_fqdn_hostname,
   reject_invalid_hostname,
   permit

smtpd_sender_restrictions =
   permit_mynetworks,
   permit_sasl_authenticated,
   reject_non_fqdn_sender,
   reject_unknown_sender_domain,
   permit

smtpd_recipient_restrictions = 
   reject_unauth_pipelining,
   reject_non_fqdn_recipient,
   reject_unknown_recipient_domain,
   permit_mynetworks,
   permit_sasl_authenticated,
   reject_unauth_destination,
   reject_rbl_client list.dsbl.org,
   reject_rbl_client sbl-xbl.spamhaus.org,
   check_policy_service unix:private/policy,
   check_policy_service inet:127.0.0.1:60000,
   permit

/etc/postfix/master.cf is now:

# 
# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (yes)   (never) (100)
# ==========================================================================
smtp      inet  n       -       -       -       -       smtpd
#submission inet n      -       -       -       -       smtpd
#       -o smtpd_etrn_restrictions=reject
#628      inet  n       -       -       -       -       qmqpd
pickup    fifo  n       -       -       60      1       pickup
cleanup   unix  n       -       -       -       0       cleanup
qmgr      fifo  n       -       -       300     1       qmgr
#qmgr     fifo  n       -       -       300     1       oqmgr
rewrite   unix  -       -       -       -       -       trivial-rewrite
bounce    unix  -       -       -       -       0       bounce
defer     unix  -       -       -       -       0       bounce
trace     unix  -       -       -       -       0       bounce
verify    unix  -       -       -       -       1       verify
flush     unix  n       -       -       1000?   0       flush
proxymap  unix  -       -       n       -       -       proxymap
smtp      unix  -       -       -       -       -       smtp
relay     unix  -       -       -       -       -       smtp
#       -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
showq     unix  n       -       -       -       -       showq
error     unix  -       -       -       -       -       error
local     unix  -       n       n       -       -       local
virtual   unix  -       n       n       -       -       virtual
lmtp      unix  -       -       n       -       -       lmtp
anvil     unix  -       -       n       -       1       anvil
#
# Interfaces to non-Postfix software. Be sure to examine the manual
# pages of the non-Postfix software to find out what options it wants.
#
# maildrop. See the Postfix MAILDROP_README file for details.
#
maildrop  unix  -       n       n       -       -       pipe
  flags=DRhu user=vmail argv=/usr/local/bin/maildrop -d ${recipient}
uucp      unix  -       n       n       -       -       pipe
  flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
ifmail    unix  -       n       n       -       -       pipe
  flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
bsmtp     unix  -       n       n       -       -       pipe
  flags=Fq. user=bsmtp argv=/usr/lib/bsmtp/bsmtp -d -t$nexthop -f$sender $recipient
scalemail-backend unix  -       n       n       -       2       pipe
  flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension}

# only used by postfix-tls
#tlsmgr   fifo  -       -       n       300     1       tlsmgr
#smtps    inet  n       -       n       -       -       smtpd -o smtpd_tls_wrappermode=yes -o smtpd_sasl_auth_enable=yes
#587      inet  n       -       n       -       -       smtpd -o smtpd_enforce_tls=yes -o smtpd_sasl_auth_enable=yes

policy    unix  -       n       n       -       -       spawn
          user=nobody argv=/usr/bin/perl /usr/lib/postfix/policyd-spf-perl

tlsmgr    unix  -       -       -       1000?   1       tlsmgr
scache    unix  -       -       -       -       1       scache
discard   unix  -       -       -       -       -       discard

And my /etc/default/postgrey file:

# postgrey startup options, created for Debian
# (c)2004 Adrian von Bidder 
# Distribute and/or modify at will.

# you may want to set
#   --delay=N   how long to greylist, seconds (default: 300)
#   --max-age=N delete old entries after N days (default: 30)
# see also the postgrey(8) manpage

POSTGREY_OPTS="--inet=127.0.0.1:60000 --delay=180 --auto-whitelist-clients=1"

GoRuCo talk: Business Natural Language

Presented by Jay Fields

  • Business Natural Language (BNL) is Domain Specific Language (DSL)
  • Traditional feedback loop: Business <- Developer -> QA
  • With DSLs: Business stakeholders update logic

Benefits

  • Managers—Improve efficiency
  • Developers—Technical decisions not business decisions
  • Business—Reduce time to market

(BNL == BSL) # => false

So?

  • Concise or verbose?
    • Less technical users might need more verbose DSL
  • Who’s the author?

“DSL” is too general

  • BNL is a subset of DSL
  • BNL characteristics differ from general DSL characteristics

The Difference

  • Written as natural language
  • Written by business people
    • Subject matter experts

Example specification

  • “Award 2 points if the fare class is A, C, D, J, Z”
  • account.award 2.points if [A,C,D,J,Z].include?(fare)

How?

  • Use preprocessor…
  • account.instance_eval removes need for account.
  • Use gsub to downcase fares (to avoid them being read as constants)
  • Fixnum.point does nothing. It’s syntax sugar.
  • Remove the word “class”. Not needed by Ruby.
  • gsub to add brackets around a list
  • gsub to remove words without business meaning (i.e. “the”)

Result: Specification == Code

  • Specification is code
  • Specification is readable documentation

Descriptive and maintainable phrases (DAMP)

  • More verbose, but verbosity makes them maintainable.
  • Since the business users control the logic, it’s easier to maintain

Do not sacrifice readability for conciseness

  • Staff changes
  • Other people will eventually need to read it

Who is the user?

  • Who are you designing for?
  • Verbose phrases are not strictly necessary but help subject matter expert maintain the language

Training

  • BNL has room for improvement if it requires any training to understand
  • Make your language fit the requirements instead of the requirements fitting your language
Applications can be built 80% faster with DSL
  • Actually, that’s bullshit.
  • But… “A change that used to take 3-4 days now takes 10 minutes.”

Why not offload non-interesting parts of the application to people who actually care about the rules?

Getting it to execute

  • BusinessLogic returns the rules from the DB
  • eval it once when we change the method, then save it as a method
  • One run eval again when rules change

Deployment?

  • New workflow: Business <> Approver <> Production
  • Developer not in the process
  • Flag scripts as “active”, expire old scripts

Programmers: Are you out of a job?

  • Absolutely not
  • Still need:
    • Language workbench
    • Workflow
    • Syntax checking
    • Test environment

Syntax checking

  • Very important
  • Needed for subject matter experts
  • Usually need contextual recommendations (i.e. “when the far” => “Did you mean ‘fare flass’?”)
  • Can be done with AJAX

Commercial language workbenches

  • MPS – JetBrains
    • Beta, but usable
    • Fowler has “hello world” examples
  • Intentional—Not available

Another example—Employee compensation

  • Verbose, but everyone can read it

When is BNL a bad choice?

  • Broad, unrelated project
  • Not necessary for programmers

How to decide?

  • Who is the author?
  • How frequently will the logic change?

How is this relevant to Ruby?

  • Not required, but easier than in Java or others
  • Need eval

More information

GoRuCo talk: Contexts, mocks, and stubs. Oh my!

Presented by Trotter Cashion

Beyond Test::Unit

  • FlexMock
  • Mocha and Stubba
  • Test/Spec – Brings RSpec to Test::Unit
  • spec-unit—Written by Trotter. Similar to Test/Spec. Has nested contexts.
  • RSpec—Total Test::Unit replacement.
    • Switching to RSpec is not necessary to use these concepts.

Why should you care?

  • Contests
    • Let you organize specs with shared setup
    • Isolate specs that should not be affecting eachother
  • Mocks versus stubs
    • Mocks validate the method calls
    • Stubs don’t care
  • Use mocks when you need to be sure
  • Use stubs when you don’t care—helps avoid brittle tests

What I’m not talking about

  • RSpec
  • Mocha and stubba
  • Test/Spec

That leaves…

  • Spec-unit
  • FlexMock

Risks

  • Active development—APIs still changing
  • You might get too excited

Benefits of mocking

  • More focused testing
  • Better application design
    • Mocking lets you think about the interface without building client classes

Code walkthroughs followed…

GoRuCo talk: Classifying documents using Ruby

Presented by Paul Dix

UPDATE: Paul posted a zip file of the code he referenced in his talk.

What are classifiers?

  • Most common application is spam vs. ham (usually naive bayes)
  • Supervised machine learning

Useful For…

  • Detecting language (French, English, etc.)
  • Categorizing news and blogs
  • Spam detection (email, trackback, wiki, comment, etc.)
  • Sentiment detection (for example, determine positive vs. negative movie reviews)

Things You Do

  • Get training data
    • Set of documents that are labeled
    • The more the better
  • Document preprocessing—convert it to a form that machine learning programs can deal with
  • Feature selection (optional)—improves accuracy
  • Train the classifier
  • Test and update

Text classification represents documents as a vector of features

Basic representation

  • Count of terms
  • Steps:
    • Clean text of punctuation
    • Lowercase
    • Split on spaces
    • Stem all words
    • Return an array of counts

Document by term (or feature) matrix

  • Count of term frequency by document

Additional features

  • You can create complicated preprocessing
  • Examples:
    • Include link structure
    • Include metadata
    • Include social data

High dimensionality

  • Noisy features (i.e. count of the word “the”)
  • Non-discrimating features (i.e. a word only once)
  • Words that are too similar

Stemming

  • Most popular: Porter Stemmer – Dr. Martin Porter ‘79
  • "cats".stem # => "cat"
  • "international".stem # => "intern"
  • "finalize".stem # => "final"
  • "ruby".stem # => "rubi"

Advantages of features selection

  • Limits size of vocabulary and matrix
  • Increase classification accuracy
  • Avoids noisy features and over-fitting

Methods for feature selection

  • Mutual information
  • Chi-squared (statistical independence of two variables)
  • Frequency based

The process of feature selection

  • Add each document from training set
  • Calculate features
  • Select best features

Feature extraction

  • Selected features from each document

Linear classifiers

  • Finds hyperplane
  • Simple decision boundary
  • Binary classification (i.e. spam vs. not spam)

Naive Bayes

  • Classifier gem
    • Doesn’t give you much control of how everything works
  • Generative model using probabilities
  • Bayes formula

Why is it called “naive”?

  • Positional independence (words location in document does not matter)
  • Conditional independence (one word does not impact other words)
  • Also known as “Idiot’s Bayes”

Naive Bayes advantages

  • Simple
  • Very fast (runs in linear time)
  • Very effective for text classification

Other considerations

  • Binomial (occurs/does not occur) vs. multinomal (how many occurrences?) models
  • Probabilities of classes based on training set
    • Overall probability between classes (is it 50/50, or 90%/10%?)

Support vector machines (SVM)

  • State of the art
  • Hard to use
  • Ruby has bindings for SVM

Kernels

  • a.k.a. “the kernel trick”
  • Method that transforms the kernel space
  • Non-linear classification
  • Four main ones: linear, polynomial, radial basis function, sigmoid
  • Use existing code… you don’t write your own

Perceptron

  • Neural network
  • Linear classifier
  • Converges through iterations
  • Not guaranteed to converge

k nearest neighbors

  • Non-linear
  • Arbitrarily complex decision boundary
  • No traditional training
  • Can be slow for large training sets

Others

  • Decision trees
  • Rocchio
  • Compression
    • Squish by Bob Aman
    • “Mind meltingly slow”
  • Latent semantic analyxix
  • Weka—Java-based processing library
    • Could be used with JRuby

Multiple classes

  • “Any of” classification – Not mutually exclusive
    • Train classifier for each classification (for example, “about Ruby” and “not about Ruby”, “about CSS” and “not about CSS”)
  • “One of” classification
    • i.e. language identification
    • Becomes hard as number of classifications increases

Bias-variance tradeoff

  • Linear classifiers are high bias
  • Non-linear classifiers are high variance

Testing classification

  • Each classification task is different
    • Hard to predict
    • Need to test and tune
  • Constant testing

Basic methods

  • Training and test sets
  • Accuracy – # correct/# total
  • Cross validation
    • 10 fold is common

Closing observations

  • Some tasks are easier than others
    • Can expect >90% on easy tasks
  • Best for triage
  • Naive Bayes is simplest to work with

Q & A

  • Q: How much training data?
  • A: The more the better. 50 is not going to give you good results.
  • Q: What about using temporal proximity as a feature?
  • A: You could do that by having features like “close in time to X”, “close in time to Y”, etc.

GoRuCo talk: Going Camping

Presented by Jeremy McAnally

History of the tent…

  • Native Americans
  • Hippie fests
  • Crazy diagrams

What is Camping?

  • MVC architecture
  • ActiveRecord for models
  • Markaby for the views
  • Everything goes in one file
  • File has modules for each MVC component
  • Run camping

When should you use Camping instead of Rails?

  • When Rails is too fat
  • Right tool for the right job
  • You ain’t gonna need it

Controllers in Camping

  • Classes define actions instead of methods in Rails
  • Methods within those classes handle HTTP method types (GET, POST, etc.)
  • Routes baked into action definitions

Camping Views

  • Markaby is “Markup in Ruby”
  • No more ERb!
  • Methods define tags
  • Will complain about invalid HTML
  • Blocks build tag heirarchy
  • Attributes as a hash

Models in Camping

  • Simply ActiveRecord models
  • Defaults to SQLite
  • You can also use migrations
    • No down migrations, but you can script that
  • Can use acts_as_versioned and other AR-specific plugins

Deployment

  • Much easier than in Rails
  • Don’t use Camping server (just like we don’t use Webrick)
  • Can be deployed in a variety of environments
    • Mongrel!

What if my application gets big?

  • Possible to split a Camping app into multiple files (use require)
  • At that point, you should probably use Rails

Databases

  • Camping defaults to SQLite
  • But supports other RDBMS options

Sessions

  • Camping has support
  • Include a module, it acts like Rails

Testing

  • Use Mosquito for bug-free Camping
  • Not in the default package, but still available

Form helpers

  • Gregory Brown wrote a snippet to get form helper support in Camping
  • Can use form_for syntax

Serving static files

  • Does not work like Rails
  • Static files can be embedded in a Camping application
  • They can be read from disk using File.read (better)
  • Have the web server do it (best)

Before/after filters

  • Override the service method and put logic before and after it
  • Not as flexible as Rails’ filter DSL

Decamper

  • Converts Camping application to Rails applications
  • Status is unknown

Kindling

  • Library I started to pull top 5-10 “Railsisims” in Camping
    • before/after filters
    • file upload
    • etc.

Q & A

  • Q: Is it possible to use something other than Markaby with Camping like XMLBuilder? (from Jay Phillips)
  • A: Yes. Extend my ERb templating code to use Builder or something else. The code is simple.
  • Q: Can you exaplain the syntax for inheriting a class from R?
  • A: Uses regular expression to do routing. You have to use your own.

GoRuCo talk: JRuby: Ready for Prime Time

_Presented by Nick Sieger

JRuby: Java on the JVM

  • Currently targetting Ruby 1.8
  • Some work on trunk Ruby also
  • Started in 2002 with a direct C to Java port
  • JRuby aims to be a great way for running Ruby applications

But really… Java?

  • “COBOL of the Internet age”
  • Applets never fulfilled their promise—“Really cool!”
  • It’s Enterprisey!
  • “Write one, run anywhere” = “Write once, run nowhere” (Linux)

Seriously though…

  • Java brings battle-tested performance in the form of the JVM to Ruby
  • Makes up for deficiencies in C Ruby implementation

HotSpot: World Class VM

  • Dynamic analog to a C compiler
  • We used to optimize C by writing assembly
  • HotStop will compile your code down to bytecode
  • Determines “hot spots” in code and optimizes
    • Method inlining
    • Removing sync bottlenecks
    • Lots of other stuff

Threading

  • Ruby uses green threads
  • To use multiple CPUs you must have multiple processes
  • JRuby maps Ruby threads to Java native threads
  • Let you stay in-process and leverage multiple CPUs

Unicode

  • Ruby’s take—String is just a byte array
  • Java’s take—Everything is Unicode
  • JRuby must bridge this gap
  • Uses strings as byte arrays, but performs conversion

Garbage Collection

  • HotSpot has four GCs
  • Seperates long-lived and short-lived objects
  • Has options for optimizing GC performance
  • Lots of tuning variables
  • Ruby doesn’t have these

Runtime GUI

  • Java has a GUI to let you see CPU usage, threads, memory of running JVM
  • This could be hooked up to JRuby

JRuby Compiler

  • Not everything, but “good enough”
  • Can run in ahead-of-time or “just in time” (JIT) mode
  • Can fall back to interpretted mode
  • A compiler (JRuby Compiler) on top of another compiler (HotSpot)

Dual-Compilation

  • Multiple steps of compilation and optimization
  • Potentially have Ruby code compiled down to native code in the future

YARV Benchmarks – Ruby vs. JRuby

  • JRuby in interpretted mode slower than C Ruby, but getting better
  • JRuby in compiled mode is faster than C Ruby for some benchmarks

What’s Coming

  • 0.9.9 release coming on Monday
  • 0.9.9 is focused mostly on compatibility
  • 0.9.9 overall approx. 40% faster than 0.9.8

Compatibility – Test Suites

  • Using whatever we can find
  • Rubicon, Rubinius specs, ruby_test, Rails tests, RSpec’s specs, etc.
  • Two Google Summer of Code projects using RSpec to spec Ruby language

What’s Supported?

  • Rubygems
  • RSpec
  • Rails
  • Rake
  • Hpricot
  • Camping

Automated Builds

  • JRuby has over 900 tests

Demo Time!

  • Swing GUI in Ruby
    • Avoid ridiculous amount of Java code for Swing apps
  • RSpec

Deployment

  • WAR file is a web application in a package
  • J2EE spec says you can drop in WAR files for easy deployment (Sometimes not actually the case)
  • We want to allow the same database.yml file for JRuby deployment in the next few months
  • Mephisto running under JRuby from a WAR file. \m/

Q & A

  • Q: Is it possible to have Java class files dynamically compiled and loaded into Ruby?
  • A: There’s no straightforward way to do that. Ryan should do Java version of Ruby inline.
  • Q: Is there any effort for one-step JRuby web serving?
  • A: WAR plugin has support for Jetty. It would be good to have a Locomotive-style package. We haven’t done it yet. Still working on making it easier for plain vanilla Java deployment.
  • Q: Can you compare JRuby with Jython?
  • A: Similar projects. Jython is not as far along. Jython is compiled only. Serving same purpose for respective languages. Jython has been around longer. We’ve got some Jython users on the JRuby mailing list.

Resources

GoRuCo talk: Pwning your phone with Arhearsion and Asterisk

Presented by Jay Phillips

  • Ruby is more than a language for building websites

Past & Today

  • Started playing with extending Asterisk, ended up with Adhearsion
  • Got into Rails, rewrote Adhearsion from scratch
  • Released Adhearsion last Christmas
  • Writing a section on Adhearsion for the next O’Reilly Asterisk book

Adhearsion in a nutshell

  • More of a collaboration framework than an “Asterisk development framework”
  • Asterisk is a good way to do VOIP
  • Adhearsion is a good way to do Asterisk development
  • Adhearsion is open source software
  • Adhearsion attempts not to loose any Asterisk functionality

Why VoIP Rocks

  • Being a hacker rocks… and you can hack VoIP
  • VoIP can allow you to cut business expenses
  • Now you can use Ruby!

Fun VoIP Projects

  • Unlock your apartment door with your cell phone (with voice authentication)
  • Control your hacked Xbox Media Center with a cell phone
  • Rarely have to pay for a phone call
  • Crazy phone-based reminder system
  • Control anything with voice commands
  • Phone-controlled Roomba

What is Asterisk?

  • Open source phone call manager (PBX)
  • Revolutionized the telecom world forever
  • Makes hackers really happy
  • Makes cash flow statements really happy
  • Does everything you could want with a phone

Existing Asterisk Control Grammars Suck At…

  • Conditional looping and branching
  • Variables
  • Complex data structures
  • Database/LDAP integration
  • etc.

Old Dialplan Example

  • Looks like mutated BASIC and Perl hell
  • GOTO statements FTW

Why use Adhearsion?

  • Breaks down barrier of entry to Asterisk
  • Grammar feels like assembler or Excel
  • Adds many new features to your PBX
  • Adhearsion dramatically improves Asterisk
  • Database integration with ActiveRecord
  • Integrate Rails and VoIP!

Other Aspects of Adhearsion

  • Helper system for extending Asterisk framework
  • One of the first times VoIP code can be traded
  • Integrates with on-phone micro-browsers (via subframework Micromenus)
  • Use other collaboration technologies together (AIM, email, SMS, etc.)
  • Instant messaging, Growl, weather, reverse number lookup, etc.

Getting Started with Adhearsion

Asterisk?! Isn’t that hard to install?

  • NO! Use Digium’s AsteriskNOW Linux distro
  • Available on Live CD, VMWare and Xen
  • Installed and running when booted
  • Asterisk 1.4 has a web GUI
  • Many good resources
  • Works with Adhearsion

Installing Adhearsion

  • gem install adhearsion

Writing Adhearsion Dialplans

  • Dialplans – ways to process phone calls
1
2
3
internal {
  loop { play 'hello-world' }
}
  • Including other contexts:
1
2
3
4
5
6
7
8
9
internal {
  +foo
}

foo {
  record {
    dial SIP/:out_trunk/1234
  }
}
  • Above code is Ruby – overloading operators
  • SIP is session initiating protocol – lets you make calls
  • Another example:
1
2
3
4
5
internal {
  play %w"a-connect-charge-of
          16 cents-per-minute
          will-apply"
}
  • Adhearsion unifies API for playing numbers and other sounds
1
2
3
4
5
6
7
8
9
10
11
12
13
14
internal {
  case extension
    when 10...100
      dial SIP/extension  # transfer
    when 6000...6820
      join extension      # join conf call
      
    when _"21XX"
      
      if Time.now.hour.between? 2, 10
        dial SIP/"berlin-office"/extension[2..4]
      else speak "It is not business hours now in Germany"
      end
}
  • #_() is a method that converts String to Regex

Database-Driven Dialplans

1
2
3
4
5
6
7
8
9
10
11
service {
  customer = Account.find_by_phone_number callerid
  usage = customer.usage_this_month
  
  if usage >= 100 then +beyond_limit
  else
    customer.usage_this_month += time do
      dial IAX2/"main-trunk"/extension
    end
  end
}
  • #time method times execution of a block and returns the result

How Does Adhearsion Talk to Asterisk?

  • Asterisk receives a call normally
  • Asterisk connects to Adhearsion via socket and sends all call info
  • Ashearsion evaluates dialplan, executes appropriate context

Things You Can Do From Rails

  • Invoke virtually any PBX event with Adhearsion’s sexy syntax
    • Uses DRb
    • Start calls, view live channels, record channel
  • Share Adhearsion’s ActiveRecord models
    • Manage users, groups, etc.
  • Use your imagination!

What’s a helper?

  • Code loaded when Adhearsion boots
  • Introduce a technology to the framework
  • Can do virtually everything
  • Can be written in Ruby, C, Java (JRuby)

Micromenus

  • Many modern phones have micro-browsers
  • Use custom XML schema over HTTP
  • Use a Ruby DSL to generate
  • Mini-web app framework
  • Also viewable in a web browser!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
item "Adhearsion Server Statistics" do
  item "View Registered SIP users" do
    PBX.sip_users.each do |u|
      item "SIP user #{u.uername} on IP #{u.ip}"
    end
  end
  
  item "View System Uptime" do
    item `uptime`
  end
  
  item "Network" do
    heading "Network Interface condig" do
      ...
    end
  end
end
1
2
3
4
5
6
item "Call an Employee" do
  item "Select an employee below."
  User.find(:all).each do |user|
    ...
  end
end

...And then my laptop battery died…

Get Involved

Q & A

Mac tip: Keyboard shortcut for screen saver

For a while now, I’ve been in the habit of locking my computer as I walk away from it. I had setup the bottom right corner of my screen to be an Active Screen Corner to start my screen saver (in my Dashboard & Exposé preferences pane), but I recently stumbled upon a way to trigger the screen saver with a single keystroke.

The screen saver is actually just a regular Mac application tucked away in your System directory. So start by linking to it from a more accessible location:

$ sudo ln -s /System/Library/Frameworks/ScreenSaver.framework/Versions/\
> A/Resources/ScreenSaverEngine.app/ /Applications/Screen\ Saver.app

Then, after Quicksilver’s catalog updates, add a Quicksilver hotkey trigger with the object “Screen Saver.app” and the action “Open.” I set mine to ⌘+ESC.

Capistrano tasks for BackgrounDRb

We’re using Ezra’s BackgrounDRb to handle batch processing of CSVs uploaded to our Rails application and have been very happy with the results. Our users now even get a progress indicator as we process their job.

When it came time to deploy the new functionality to our severs, however, we ran into a known issue where the BDRb process did not detach from Capistrano, and therefore got killed as Capistrano exits.

We solved this issue with the following BackgrounDRb Capistrano tasks:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
desc "Stop the backgroundrb server"
task :stop_backgroundrb , :roles => :app do
  run "cd #{current_path} && ./script/backgroundrb/stop"
end

desc "Start the backgroundrb server"
task :start_backgroundrb , :roles => :app do
  run "cd #{current_path} && RAILS_ENV=production nohup ./script/backgroundrb/start -d  > /dev/null 2>&1"
end

desc "Start the backgroundrb server"
task :restart_backgroundrb, :roles => :app do
  stop_backgroundrb
  start_backgroundrb
end

And then we enhanced the restart task so that it would restart BackgrounDRb in addition to our Mongrel processes:

1
2
3
4
5
task :restart, :roles => :app do
  stop_mongrel_cluster
  restart_backgroundrb
  start_mongrel_cluster
end

Fluent interface for Ruby delegation

On our latest project at EastMedia, we’ve been using Ruby’s Forwardable delegation library to help avoid violating the Law of Demeter. Unfortunately, Forwardable’s interface has a few problems:

  • It doesn’t read well.
  • It requires you to remember the order of the arguments.
  • It has no support for automatically prefixing method names.
  • It has no support for automatically setting up writers and accessors.

Back in February, Jay Fields wrote an extension for Forwardable that resolved the last two points, but he didn’t try to address the first two. In a comment on that post, David Chelimsky suggested a possible fluent interface for delegation, but to my knowledge no one had built and released it.

Here are some examples of what I came up with:

1
2
3
4
5
6
7
8
class Order
  extend Forwardable

  delegate_reader(:line_items).to(:line_item_list)
  delegate_reader(:total_value).as(:subtotal).to(:line_item_list)
  delegate_writers(:first_name, :last_name).to(:owner)
  delegate_accessors(:city, :state, :zip).with_prefix(:shipping).to(:shipping_address)
end

The library I wrote wraps Forwardable and also includes support for prefixing and automatic writers and accessors in the style of Ruby’s attr_reader, attr_writer and attr_accessor. Here is the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
require "forwardable"

module FluentForwardable
  class FluentForwarder
    def initialize(klass, readers, writers, *methods)
      @klass    = klass
      @readers  = readers
      @writers  = writers
      @methods  = methods
    end
    
    def with_prefix(prefix)
      @prefix = prefix
      self
    end
    
    def as(custom_name)
      raise "Cannot delegate multiple methods with a custom name" if @methods.size > 1
      @custom_name = custom_name
      self
    end
    
    def to(receiver)
      for method in @methods
        new_method = new_method_name(method)
        if @readers
          @klass.class_eval { def_delegator receiver, method, new_method }
        end
        if @writers
          @klass.class_eval { def_delegator receiver, :"#{method}=", :"#{new_method}=" }
        end
      end
    end
    
  protected
    
    def new_method_name(method)
      return @custom_name if @custom_name
      @prefix ? "#{@prefix}_#{method}" : method
    end
  end
  
  def delegate_reader(*methods)
    FluentForwarder.new(self, true, false, *methods)
  end
  alias_method :delegate_readers, :delegate_reader
  
  def delegate_writer(*methods)
    FluentForwarder.new(self, false, true, *methods)
  end
  alias_method :delegate_writers, :delegate_writer
  
  def delegate_accessor(*methods)
    FluentForwarder.new(self, true, true, *methods)
  end
  alias_method :delegate_accessors, :delegate_accessor
end

Forwardable.send :include, FluentForwardable

I’ve put the specs into a Pastie to save space. The resulting specdoc is:

delegating readers
- should delegate the reader to the receiver
- should not delegate writers

delegate writers
- should delegate the writer to the receiver
- should not delegate readers

delegating accessors
- should delegate readers
- should delegate writers

delegating to a method with a different name
- should use the specified name as the new method name

delegating with a prefix
- should use the prefix for any readers
- should use the prefix for any writers

I couldn’t think of any way to implement this without requiring that the to() segment be last, but I’d love to hear suggestions about how that could be done. Also, as always, I’m interested in any constructive critique of this code and the specs.

Two weeks until GoRuCo

The first annual Gotham Ruby Conference (or GuRoCo, as it is affectionately known) is set to take place on April 21st, just over two weeks from today. From what I can tell, it’s going to be the largest gathering ever of Ruby hackers in New York City.

We’ve got an excellent lineup of speakers and talks, Google’s new Manhattan office booked as the conference venue, and an official afterparty hosted by Indaba Music. All in all, it looks like it’s going to be a full day of geeking out with some of the brightest developers in the industry.

I’d like to thank Jeff Boulet, one of my colleagues at EastMedia, for designing the logo and conference materials. As you can see, it looks great.

Finally, I’ll be doing my best to liveblog the events of the day, especially the talks. Hope to see you there!

Mac tip: Add extra shortcuts for Exposé

Exposé preferences I always disliked the default keyboard shortcuts for Mac’s Exposé feature. The F9-F11 keys are in the top-right corner of the keyboard, but using Exposé requires you to have one hand on the mouse.

The result was either reaching all the way across the keyboard to hit the F keys with my left hand, or switching my right hand between the keyboard and the mouse as I used Exposé. Yes, I’m lazy enough where this irked me.

Thanks to a tip from my friend Dan, I learned it’s possible to assign alternate keyboard+mouse combination shortcuts for the Exposé. Not only is it possible, it’s downright fast.

I set All Windows to ALT+Right Click, Application Windows to CTRL+Right Click, and Desktop to CMD+Right Click.

Now, I can keep my right hand on the mouse when I’m switching windows, and toggle between Exposé actions by holding my left hand on the appropriate modifier key.

It take’s a little trick to set this up. After you open the right dropdown menu to set a mouse shortcut for one of these items, you’ll only see “Seconday Mouse Button” (a.k.a. right click), “Mouse Button 3”, and “Mouse Button 4” as options. In order to select a keyboard+mouse combination, you must hold down the modifier key(s) you want while the dropdown is open, and then make your selection.

E-mail address change

With the launch of this blog, this seems like as good a time as any to change my e-mail address, something I’ve been meaning to do for some time.

From now on, please e-mail me at bryan at this domain name dot com. This is replacing my old e-mail address of bhelmkamp at Google’s mail service.

Thanks.

DRY RSpec by generating specifications in a loop

Occasionally, when doing Behavior Driven Development with RSpec, I'll need to specify a number of cases where the internal structure of the spec is identical. Specifying a simple function, for example, might lead to code like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
context "My one adder" do
  setup do
    @processor = Adder.new(1)
  end
  
  specify "should add 1 and get 2" do
    @processor.add(1).should == 2
  end
  
  specify "should add -1 and get 0" do
    @processor.add(-1).should == 0
  end
  
  specify "should add 0 and get 1" do
    @processor.add(0).should == 1
  end
end

Given the information being conveyed, this is very verbose and also not DRY. (When writing specs, it's important to note that DRY is not the #1 priority—that's clarity—but overly "wet" code can still increase the chance of bugs.)

One way to reduce the length of the spec is to condense the expecations into a single specification block:

1
2
3
4
5
6
7
8
9
10
11
context "My one adder" do
  setup do
    @processor = Adder.new(1)
  end
  
  specify "should add numbers correctly" do
    @processor.add(1).should  == 2
    @processor.add(-1).should == 0
    @processor.add(0).should  == 1
  end
end

This introduces two disadvantages. First, a failure on any expectation in the spec will cease execution of the specify block, and thus all further failures will be masked. Second, the specdoc produced by the sample is not as clear in conveying the expectations for the object.

Thanks to a quick tip from David Chelimsky, it's easy to have my cake and eat it too:

1
2
3
4
5
6
7
8
9
10
11
12
context "My one adder" do
  setup do
    @processor = Adder.new(1)
  end
  
  expected_results = {-1 => 2, -1 => 0, 0 => 1}
  expected_results.each do |input, result|
    specify "should add #{input} and get #{result}" do
      @processor.add(input).should == result
    end
  end
end

This produces the ideal specdoc, is DRY, and will not allow one failure to mask another during a spec run.