Category Archives: ruby

On this page:

Ruby code to quickly convert titles to ISBNs

I love the Toronto Public Library system. I can’t say that enough. I particularly love how I can go on a reading spree, place holds on a gazillion books, and have them delivered to the library branch that’s about three blocks away from the house.

Ideally, of course, these books would arrive suitably spaced apart so that a new batch arrives just as I’ve finished another. This happens when I request popular books. Most of the time, though, the books that I want to read fall in the Long Tail–obscure titles, books that have fallen off the New York Times bestseller lists, and the occasional random find.

All of these books tend to descend on the unsuspecting library branch at the same time.

There were 27 books waiting for me earlier. The librarian thanked me for clearing the shelf. J- greatly enjoyed piling them into the shopping cart we had the foresight to bring. Yes, I’ve got presentations to prepare and things to do–but reading is fun, and I’m somehow going to find time to read all those books before my three-week loan period is up. I’ll probably be able to renew them, but hey, might as well try.

So I decided I might as well try tracking them on LibraryThing. Instead of typing in all the details manually, I grabbed the list of titles from my account on LibraryElf (good reminder system for books), used ISBNdb to convert the titles into ISBNs (best guess), and imported the list of ISBNs into LibraryThing. Now my profile lists 163 books–a small fraction of the books that have passed through my hands, but it’s better than nothing. Someday I might even get myself a barcode scanner so that I can just pick up the ISBNs from the book jackets.

Anyway, I promised the Ruby code I’d quickly written to convert the titles to ISBNs:

require 'net/http'
require 'CGI'
require 'open-uri'
require 'rexml/document' 

access_key = 'YOURACCESSKEYHERE'
while (s = gets)
  s.chomp!
  url = "http://isbndb.com/api/books.xml?access_key=" + access_key + "&index1=title&value1=" + CGI::escape(s)
  xml = REXML::Document.new(open(url).read)
  if (xml.elements["ISBNdb/BookList/BookData"])
    puts xml.elements["ISBNdb/BookList/BookData"].attributes["isbn"]
  end
end

Takes titles as standard input, prints out ISBNs. Enjoy!

Lotus Connections Communities topics+replies feeds to OPML

Keeping track of discussions in Lotus Connections Communities can be difficult, so I thought I’d use a feed reader to read new forum topics and replies. Instead of subscribing to each community by hand, I wrote a Ruby script that generated an OPML file, which I then imported into FeedDemon. Win!

Here’s the script:

#!/usr/bin/ruby

email = ARGV[0]
password = ARGV[1]

require 'rubygems'
require 'rexml/document'
require 'open-uri'
require 'cgi'
require 'net/https'
base_url = 'https://w3.ibm.com/connections/communities/service/atom/'
url = base_url + 'communities/my'
opml = REXML::Document.new('<opml version="1.0"><head></head><body></body></opml>')
body = opml.elements['opml/body']
while url
  # Fetch the page
  $stderr.puts "Fetching " + url
  begin
    my_communities = REXML::Document.new open(url)
  rescue OpenURI::HTTPError
    begin
      my_communities = REXML::Document.new open(url, 
                                                {:http_basic_authentication => [email, password]})

    rescue OpenURI::HTTPError
      url = nil
    end
  end  
  my_communities.elements.each('*/entry') { |x|
    # Add it to the OPML
    $stderr.puts "Found " + x.elements['title'].text
    if x.elements['id'].text =~ /communityUuid=([^&]+)/
      uuid = Regexp.last_match(1)
    end
    body.add_element 'outline', {'title' => x.elements['title'].text,
      'xmlUrl' => 'https://w3.ibm.com/connections/news/atom/stories/public?source=communities&container=' + uuid
    }
  }
  # Set the URL to the next one
  url = nil
  if my_communities.elements['feed/link[@rel="next"]']
    url = my_communities.elements['feed/link[@rel="next"]'].attributes['href']
  end
  sleep 5
end
puts opml.to_s

If you want just discussion topics and replies, use this instead of the xmlUrl line above:

'xmlUrl' => base_url + 'community/forum?communityUuid=' + uuid

Setting up Ruby on Rails on a Redhat Enterprise Linux Rackspace Cloud Server

1. Compile Ruby from source.

First, install all the libraries you’ll need to compile Ruby.

yum install gcc zlib libxml2-devel 
yum install gcc
yum install zlib
yum install zlib-devel
yum install openssl
yum install openssl-devel

My particular application has problems with Ruby 1.9.2, so I compiled Ruby 1.8.7 instead. This can be downloaded from ftp://ftp.ruby-lang.org/pub/ruby/1.8/ruby-1.8.7-p174.tar.gz

Unpack the source code for Ruby. Configure and install it with:

./configure
make
make install

Add /usr/local/bin to the beginning of your PATH.

2. Install Ruby Gems.

Downloadcd the latest Ruby Gems package and unpack it. I got mine from http://production.cf.rubygems.org/rubygems/rubygems-1.7.1.tgz . Change to the directory and run:

ruby setup.rb

3. Install Rails and rake

gem install rails rake

If all goes well, you should now have Rails and rake.

Troubleshooting:

*builder-2.1.2 has an invalid value for @cert_chain*

Downgrade Rubygems to version 1.6.2 with the following command.

gem update --system 1.6.2

(Stack Overflow)

sqlite3-ruby only supports sqlite3 versions 3.6.16+, please upgrade!

Compile sqlite from source:

wget http://www.sqlite.org/sqlite-amalgamation-3.7.0.1.tar.gz
tar zxvf sqlite-amalgamation-3.7.0.1.tar.gz
cd sqlite-amalgamation-3.7.0.1
./configure
make
make install
gem install sqlite3

LoadError: no such file to load – openssl

  1. Install openssl and openssl-devel.
    yum install openssl openssl-devel
    
  2. Go to your Ruby source directory and run the following commands:
    cd ext/openssl
    ruby extconf.rb
    make
    make install
    

LoadError: no such file to load – readline

yum install readline-devel

Change to your Ruby source directory and run the following:

cd ext/readline
ruby extconf.rb
make
make install

(Code snippets)

You can’t access port 80 from another computer.

Port 80 (the web server port) is blocked by default on Redhat Enterprise Linux 5.5. Edit /etc/sysconfig/iptables to allow it, adding a line like:

-A RH-Firewall-1-INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT

Make sure you put it above the REJECT all line.

Load your changes with

/etc/init.d/iptables restart

(Cyberciti)

2011-04-04 Mon 11:06

Cucumber, Capybara, and the joys of integration testing in Rails

Development is so much more fun with test cases. They give you a big target to aim for, and it feels fantastic when you write the code to make them pass. Tests also avoid or shorten those late-night “oh no! I broke something!” sessions, because you can backtrack to versions that pass the tests. (You are using version control, right?)

So naturally, as I worked on my first IBM project using Ruby on Rails, I wanted to know about how to perform automated testing – not just at the unit level, but at the web/integration level.

I like using Simpletest in Drupal. I love the testing frameworks available in Rails.

You see, Cucumber for Rails allows you to write your tests in English (or something reasonably close to it). For example:

Feature: Contributor
  In order to maintain security
  As a contributor
  I want to be able to edit existing submissions
  Scenario: Contributor should not be able to create or delete submissions
    Given I am a company contributor
    And there is a 2010 survey for "Company X"
    When I view the dashboard
    Then I should not be able to delete a submission
    And I should not be able to create a submission

Putting that in my features/contributor.feature" file and executing that with =bundle execute cucumber features/contributor.feature gets me a lovely test with green signs all around.

You’re thinking: Rails is awesome, but it’s not that awesome, is it? How can it know about the specifics of the application?

Rails knows because I’ve written my own step definitions for Cucumber. Step definitions are simple. You can define them with a regular expression like this:

When /^I view the dashboard/ do
  visit root_path
end

Then /^I should not be able to create a submission/ do
  page.should_not have_button("Create submission")
end

You can also define steps that parse arguments from the string or call other steps:

Given /^there is a ([^ ]+) survey for \"([^\"]+)\"$/ do |year,name|
  @company = Company.find_by_name(name)
  assert !@company.nil?
  Given "there is a #{year} survey"
end

You can even take multi-line input, such as tables.

Automated testing is so awesome!

Rails: Exporting data from specific tables into fixtures

Rails is pretty darn amazing. There are plenty of gems (Ruby packages) that provide additional functionality. They’re like Drupal modules, except with more customizability (not just hooks) and fewer pre-built administrative interfaces (you win some, you lose some).

For example, the client asked me, “Can we edit the static content?” Now if I had asked about this as a requirement at the beginning of the project, we might have gone with Drupal instead–although the Rails Surveyor still feels cleaner than a CCK-based survey type, so we might’ve stayed with Rails.

Anyway, we were well into Rails now, so I looked for a content management system that I could integrate into the Rails 3-based website. After some experimenting with Refinery CMS (looks slick, but couldn’t get it to do what I wanted) and Comfortable Mexican Sofa (looked pretty geeky), I settled on Rich CMS. I nearly gave up on Rich CMS, actually, because I’d gotten stuck, but the web demo helped me figure out what I needed to do in order to enable it.

We’re still emptying and reloading the database a lot, though, so I wanted to make sure that I could save the CmsContent items and reload them. I didn’t want to back up the entire database, just a table or two. There were some gems that promised the ability to back up specific models, but I couldn’t figure it out. Eventually I decided to use the table-focused Rake code I saw in order to export the data to fixtures (seems to be based on code from the Rails Recipes book).

task :extract_fixtures => :environment do
  sql  = "SELECT * FROM %s"
  skip_tables = ["schema_info"]
  ActiveRecord::Base.establish_connection
  if (not ENV['TABLES'])
    tables = ActiveRecord::Base.connection.tables - skip_tables
  else
    tables = ENV['TABLES'].split(/, */)
  end
  if (not ENV['OUTPUT_DIR'])
    output_dir="#{RAILS_ROOT}/test/fixtures"
  else
    output_dir = ENV['OUTPUT_DIR'].sub(/\/$/, '')
  end
  (tables).each do |table_name|
    i = "000"
    File.open("#{output_dir}/#{table_name}.yml", 'w') do |file|
      data = ActiveRecord::Base.connection.select_all(sql % table_name)
      file.write data.inject({}) { |hash, record|
        hash["#{table_name}_#{i.succ!}"] = record
        hash
      }.to_yaml
      puts "wrote #{table_name} to #{output_dir}/"
    end
  end
end

Being a lazy programmer who doesn’t want to remember table names, I also defined the following Rake tasks:

task :save_content => :environment do
  ENV["TABLES"] = "cms_contents"
  Rake.application.invoke_task("myproj:extract_fixtures")
end
task :load_content do
  Rake.application.invoke_task("db:fixtures:load")
end

Then I can call rake myproj:save_content and rake myproj:load_content to do the right thing. Or rather, my co-developer (a new IBMer – hello, Vijay!) can do so, and then check his work into our git repository. =)

Now we can re-create the development database as often as we’d like without losing our page content!

2011-04-24 Sun 16:29

Rails: Paperclip needs attributes defined by attr_accessible, not just attr_accessor

I wanted to add uploaded files to the survey response model defined by the Surveyor gem. I’d gotten most of the changes right, and the filenames were showing up in the model, but Paperclip wasn’t saving the files to the filesystem. As it turns out, Paperclip requires that your attributes (ex: :file_value> for my file column) be tagged with attr_accessible, not just attr_accessor.

Once you define one attr_accessible item, you need to define all the ones you need, or mass-assigning attributes with update_attributes will fail. This meant adding a whole bunch of attributes to my attr_accessor list, too.

If you’re using accepts_nested_attributes_for, you will also need to use attr_accessible there, too.

Sharing the note here just in case anyone else runs into it. Props to Tam on StackOverflow for the tip!

2011-04-01 Fri 12:41