Ruby gets a Reddit

Posted on January 27, 2008

Reddit and programming.reddit.com are two of my favorite social news sites.

Now, courtesy of James Golick – they just opened ruby.reddit.com – sweet!

Review: Beginning Ruby

Posted on December 15, 2007

Beginning Ruby (by Peter Cooper) is a massive tome (600+ pages!) on the ruby language.

If you want to know just about everything there is to know about ruby, it’s here in Beginning Ruby: From Novice to Professional. Now just reading this book won’t make you a ruby pro, but actually applying each of the things you learn will no doubt put you in a better spot than most “rails jockeys” who are just learning RoR.

For me, coming to Ruby via Rails made things a bit harder than they should’ve been. Had I been sane at the time, I would’ve read a good chunk of a book like Beginning Ruby first, before attempting to jump into Rails. But alas, many of us learned the fundamentals (and beauty) of Ruby only after being bit by the rails bug.

Just a few of the things that are covered in Beginning Ruby:
  • ruby language basics (types, classes, inheritance, modules, etc)
  • creating and releasing ruby gems
  • parsing / working with xml
  • working with URIs and files
  • compressing files
  • basic encryption/hashing
  • ... and of course some of the basics of Rails.

If you still don’t have an introductory book on Ruby, you can grab Beginning Ruby at Amazon.com and also directly from the publisher’s website (Apress).



Home on the Range

Posted on November 29, 2007

How to effortlessly build an array of letters ‘A’ ... ‘Z’.

letters = 'A'..'Z'
letters.class # Range
letters.to_a
# ["A", "B", "C", "D", "E", "F", ...  "T", "U", "V", "W", "X", "Y", "Z"]

Comes in handy when building an index (i.e. by an author’s last name) to let your users drill down by.

How to Install Ruby via Yum

Posted on November 25, 2007
I’ve had success with the following on Fedora Core 5, etc:
yum install -y ruby
yum install -y ruby-devel ruby-docs ruby-ri ruby-irb ruby-devel ruby-rdoc

Compactness of Ruby Compared to PHP, One Example

Posted on October 30, 2007

I in no way mean to pick on PHP here. Perhaps a php maven could refactor this just as nicely.

But in an open-source PHP project I came across the following:

function get_remote_ip() {
    if (isset($_SERVER)) {
        if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
        } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
            $ip = $_SERVER['HTTP_CLIENT_IP'];
        } else {
            $ip = $_SERVER['REMOTE_ADDR'];
        }
    } else {
        if (getenv('HTTP_X_FORWARDED_FOR')) {
            $ip = getenv('HTTP_X_FORWARDED_FOR');
        } elseif (getenv('HTTP_CLIENT_IP')) {
            $ip = getenv('HTTP_CLIENT_IP');
        } else {
            $ip = getenv('REMOTE_ADDR');
        }
    }
    return $ip;
}

Rewritten in Ruby (rails):

def get_remote_ip
  e = request.env
  return e['HTTP_X_FORWARDED_FOR'] || e['HTTP_CLIENT_IP'] || e['REMOTE_ADDR'] || nil
end

16 lines down to 2!

Rails Debugging TypeError (singleton can't be dumped)

Posted on October 24, 2007

This was a tricky error to debug because it was only seen sporadically. Eventually we saw that the exception was, generally, only happening when users were uploading large files. (10mb or above)

I’m still not sure as to the root cause of the problem. I suspect it has to do with the combination of plugins we are running (including filecolumn). If this was happening in vanilla Rails it would be a serious bug that the core team would’ve addressed by now.

First I was able to consistently repo the issue by uploading a 10mb file as an attachment. If you don’t have any lying around I found some test files here at cachefly.

The problem occurs when these file data objects (class Tempfile) are left in the params hash for the next session. Clearing out the Tempfile from the params hash prevents this.

What I’ve got now in application.rb:

  after_filter  :clear_filedata_params

  def clear_filedata_params
    params.each_pair do |k, v|
      params.delete(k) if v.is_a?(Tempfile) || v.is_a?(File)
    end
  end
  helper_method :clear_filedata_params

So from now on, if you’re passing a file data object as a root param, i.e. {:file_data => #File<...>}, this will automatically be cleared from params and should work ok.

If you pass it in as, {:foo => {:file_data => #File<...>}}, you’ll need to clear it out manually, since the after_filter won’t catch it. (Or if you know how to flatten a Ruby Hash easily, let me know!) In this case you could simply do a “params.delete(:foo)”.

Entire Stacktrace (for the google bot):
TypeError (singleton can't be dumped):
    /usr/local/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/session/active_record_store.rb:83:in `dump'
    /usr/local/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/session/active_record_store.rb:83:in `marshal'
    /usr/local/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/session/active_record_store.rb:137:in `marshal_data!'
    /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/callbacks.rb:333:in `send'
    /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/callbacks.rb:333:in `callback'
    /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/callbacks.rb:330:in `each'
    /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/callbacks.rb:330:in `callback'
    /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/callbacks.rb:241:in `create_or_update'
    /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/base.rb:1545:in `save_without_validation'
    /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/validations.rb:752:in `save_without_transactions'
    /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/transactions.rb:129:in `save'
    /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/connection_adapters/abstract/database_statements.rb:59:in `transaction'
    /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/transactions.rb:95:in `transaction'
    /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/transactions.rb:121:in `transaction'
    /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/transactions.rb:129:in `save'
    /usr/local/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/session/active_record_store.rb:311:in `update'
    /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/base.rb:867:in `silence'
    /usr/local/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/session/active_record_store.rb:311:in `update'
    /usr/local/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/session/active_record_store.rb:318:in `close'
    /usr/local/lib/ruby/1.8/cgi/session.rb:324:in `close'
    /usr/local/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/base.rb:1184:in `close_session'
    /usr/local/l
  

Ruby Daisy-Chaining for Fun & Profit

Posted on October 10, 2007

One of the things I love about Ruby is the ability to easily daisy-chain several functions together. This feature is by no means unique to Ruby, but here I’ll show how one might do this the Ruby way.

You have an array representing days of the week, indexed by 0 – 7, that you get back from a web post as strings.

days = params[:days] # {"5" => "1", "6" => "1", "2" => "1"}

But we want the keys as an array of integers in sorted order.

One way would be:

keys = days.keys
sorted = keys.sort
ints = []
sorted.each { |i| ints << i.to_i }

Of course you wouldn’t actually ever do it this way once you’re familiar with Ruby idioms and are comfortable reading daisy-chained code (it can get ugly if abused!).

In Ruby, the above four lines can be condensed into the following one-liner:

ints = days.keys.sort.map(&:to_i)

Note: Rails monkeypatches collect/map to allow for the map(&:to_i) shortcut.

scRUBYt! Tutorial for Parsing Financial Data

Posted on September 06, 2007

Doug Bromley has posted a scRubyt tutorial on how to parse Yahoo! stock data for the FTSE 100 index.

If you enjoyed my FSX Trader post/gem, check it out here .

Another neat library I discovered while tinkering with FSX is YahooFinance which can be used to get just about any stock data imaginable via a Yahoo! Finance CSV “api” of sorts.

Mini automated hedge fund, anyone?

With all these free services floating around and APIs available into them, it wouldn’t take much to put together an application that allowed individuals to put together a kind of mini automated trading hedge fund.

Professional traders I’ve spoken to about this have said that automated systems should be used to alert one to trading opportunities / possibilities, but not necessarily executing trades automatically (sans human intervention).

HOW TO: Automate Facebook Interaction using Ruby and WWW::Mechanize

Posted on September 05, 2007

I told those fudgepackers that I liked Michael Bolton’s music. – Michael Bolton, Office Space

Overview

By the end of this tutorial, you will have learned how to use Ruby and WWW::Mechanize to login to your Facebook account and post a random Office Space quote on your friend’s wall!

Origins of this Tutorial

I first got interested in automating Facebook actions due to limitations in the the Fantasy Stock Exchange app on Facebook—I wanted a way to automate stop loss orders like a real brokerage firm might offer.

See this post on FSX Trader for more on that project, which implements the same techniques in this tutorial and more. You can download its source code here on rubyforge. Now onto the tutorial:

Autobots: Mechanize!

First you’ll need the mechanize ruby gem:

sudo gem install mechanize

Next create a new file called mechanize_ruby.rb. First we’ll need to make available the mechanize library:

require 'rubygems'
require 'mechanize'

For this brief example, let’s create a MechanizeFacebook class that will login to Facebook. It will take two paramaters, an auth Hash and boolean telling it whether or not to be verbose in its output:

class MechanizeFacebook
  def initialize(auth, verbose = false)
    @auth = auth
    @verbose = verbose
    @agent = WWW::Mechanize.new
    @agent.user_agent_alias = 'Mac FireFox'
    @agent.redirect_ok = true
  end

  def login
  end
end

auth = {'email' => 'my-fb-email@gmail.com', 'pass' => 'my-fb-password'}
mf = MechanizeFacebook.new(auth,  true)
mf.login

Above you can see we’ve instantiated a new WWW::Mechanize object, set its user agent alias and redirect_ok to true. For more on user agent aliases available to Mechanize (it can masquerade as IE on Windows, Mac Safari, etc) see its documentation here on rubyforge.

Change the ‘auth’ hash credentials to your own if you’d like to tinker with this on your machine.

Now let’s add the login functionality.

To get a page via WWW::Mechanize and print its HTML/form/link/etc data, we can now do add this to our ‘login’ stub above:

page = @agent.get('http://www.facebook.com/')
pp page if @verbose

If @verbose is true, this will output something like:

#<WWW::Mechanize::Page
 {url #<URI::HTTP:0xa629c6 URL:http://www.facebook.com/>}
 {meta}
 {title "Facebook | Welcome to Facebook!"}
    ..
 {links
  #<WWW::Mechanize::Link " " "http://www.facebook.com">
    #<WWW::Mechanize::Link
   "Forgot Password?" 
   "http://www.facebook.com/reset.php">
  #<WWW::Mechanize::Link "Login" "http://www.facebook.com/login.php">
    ...

Next let’s grab the login form. In this particular case, it happens to be the first form:

login_form = page.forms.first

If it were the second form on the page, we could do:

"page.forms[1]"

Time to set some form variables:

login_form.email = @auth['email']
login_form.pass = @auth['pass']
pp login_form if @verbose
If everything is peachy at this point, the output here should be:
#<WWW::Mechanize::Form
 {name "loginform"}
 {method "POST"}
 {action "https://login.facebook.com/login.php"}
 {fields
    ...
    #<WWW::Mechanize::Field:0x1433590
   @name="email",
   @value="your-fb-email@gmail.com">
  #<WWW::Mechanize::Field:0x1432dd4 @name="noerror", @value="1">
  #<WWW::Mechanize::Field:0x1432618 @name="pass", @value="your-fb-pass">}
Now let’s submit this bad boy:
page = @agent.submit(login_form)
pp page if @verbose

The output from this should yield your main personal Facebook homepage (activity feed links, personal app links, etc).

Getting Sneaky

What we’ve done so far has been an interesting learning tool but relatively straightforward.

Let’s combine a few steps and post a random quote from Office Space to one of your friend’s walls (or your own wall, but that’s not nearly as fun, eh?). Example of the resulting wallpost:

First, see this article on how to parse Office Space quotes off of WikiQuote.org. The “randomquote.rb” file from that project is included in randposter.zip file. Let’s break down what our new post method in mechanize_facebook.rb will do:

friends_profile_url = FRIEND_PROFILE_URL
page = @agent.get(friends_profile_url)
pp page if @verbose

Now let’s grab some quotes and instantiate a quote factory :

qg = QuotationGrabber.new('Office_Space', 10)
rq = RandomQuote.new('Office_Space')

The following assumes your friend’s Wall form is the last form on their profile page. This is normally true unless they’ve heavily customized their profile :

# Get the post to Wall form
wall_form = page.forms.last
# Set the wall message post value to the following:
wall_form.text = "Random Office Space quote of the day:\n\n#{rq.random_quote}"

Next we inspect the output of the form to make sure it’s doing what we expect, then submit the form!

pp wall_form if @verbose
# Submit the form!
@agent.submit(wall_form)

Now if you log into Facebook and look at your buddy’s wall—your random Office Space quote should be listed at the top.

Quick Hack: List all of Your Rails Controllers and Models

Posted on August 31, 2007

Let’s say you’re working on a small team and need to divvy up your models and controllers to do a code sweep.

You want to place these in a Google Docs spreadsheet or something. The code:

files = []
Dir["app/controllers/*"].each do |f|
 f = f.gsub('app/controllers/', '')
 files << f
end

Dir["app/models/*"].each do |f|
 f = f.gsub('app/models/', '')
 files << f
end

files.each do |f|
  puts f
end

Note: does not handle nested subdirectories within your app/models/ or app/controllers/ directories.