Episode 55: Cleaning Up the View

Posted about 7 years back at Railscasts

This episode starts off with a big, messy template. Watch as this code shrinks and becomes more readable while the interface stays the same.

The Hobo Migration Generator

Posted about 7 years back at The Hobo Blog

Right back at the start of the Hobo project we made writing migrations a little easier. We made it so you didn’t have to write the word “column” quite so many times.

create_table "users" do |t|
  t.column :name, :string
end

Became:

create_table "users" do |t|
  t.string :name
end

A pretty small contribution in the scheme of things, but kinda handy, and it made it into core Rails (by a circuitous route) which was nice.

Somehow though, it wasn’t good enough. It was quicker, but it wasn’t Hobo Quick. Don’t you always get the feeling that writing migrations is kinda mechanical? Especially those tedious down migrations. Don’t you wish you never had to write another migration again? I know I do. Or did, I should say.

Announcing the Hobo Migration Generator.

Creating your database tables in the next version of Hobo will go something like this:

Step 1. Code your models (being sure not to generate any migrations)

Step 2. ruby script/generate hobo_migration create_initial_tables

(Step 2.5. Observe db/migrate/001_create_initial_tables.rb)

Step 3. rake db:migrate

And you’re done.

Hang on one darn minute I hear you say! Where are the columns declared? One of the much loved features of Active Record is that you don’t have to enumerate the columns in your models - AR interrogates the database and finds them for you. If Hobo generated the migration, where were the columns declared?

In the model class.

class User < ActiveRecord::Base
  fields do
    name :string, :null => false
    email :string
    about :text, :default => "No description available"
  end
end

Sacrilege! Not at all - it’s actually much better this way. Work with me for a minute here.

What is it that you really love about not having to list the columns in your AR classes? It’s the fact that it’s DRY. You don’t have to list them once in the migration and then again in the class. Well that’s still true, all we’ve done is moved those declarations to where they should be - in the same place that defines all the rest of the behaviour of your model. Yes those column declarations will be in the migration too, but that is generated for you.

There’s no more trawling through old migrations or messing with MySQL in order to remind yourself what columns you have - it’s right there in your code.

The generator looks at the database, looks at your models, figures out the difference and creates a migration such that the database gets with the program.

It generates the down migration too of course.

Moving forward things get even more interesting. Say you wanted to get rid of one of those fields. Just delete it from the fields declaration. Run the migration generator again and you’ll get a remove_column migration. Change the type of a column or add a default? No problem, you’ll get a change_column.

What if you delete a model class outright? Well we’re guessing your production data is kind of important to you, so the generator is interactive. It will require you to physically type “drop users” (or whatever) before it will generate a drop_table migration. The same goes for removing columns in fact.

What about renaming a column or table? Those are kinda tricky. Say we rename the field name to username. All the generator sees is that an existing field name has gone away, and a new field username has appeared on the scene. The generator will alert you to the ambiguity and invite you to enter either “drop name” or the new name “username”.

That’s the long and short of it, but there’s a couple more niceties.

Inside the fields block you can say simply timestamps to get created_at and updated_at.

You can declare fields using Hobo’s rich types like :html and :markdown. These end up as :text columns unless you override that by giving the :sql_type option. Your DRYML tags will know what it really is and render appropriately.

As for foreign keys – don’t even bother. Just declare belongs_to as you normally would, and the migration generator will spot the need to add a foreign key. Either with the conventional name or a custom one if you gave the :foreign_key option. Delete the belongs_to later, and the migration generator will remove the foreign key.

If, like most Rails programmers, you’ve written a lot of migrations, I think you’ll find using this puppy to be a fairly pleasing experience :-) I know I do.

It’s working now in the tom_sandbox branch, and will be in the next release, which, if the coding gods be willing, will be out by the end of the month.

The Hobo Migration Generator

Posted about 7 years back at The Hobo Blog

Right back at the start of the Hobo project we made writing migrations a little easier. We made it so you didn’t have to write the word “column” quite so many times.

create_table "users" do |t|
  t.column :name, :string
end

Became:

create_table "users" do |t|
  t.string :name
end

A pretty small contribution in the scheme of things, but kinda handy, and it made it into core Rails (by a circuitous route) which was nice.

Somehow though, it wasn’t good enough. It was quicker, but it wasn’t Hobo Quick. Don’t you always get the feeling that writing migrations is kinda mechanical? Especially those tedious down migrations. Don’t you wish you never had to write another migration again? I know I do. Or did, I should say.

Announcing the Hobo Migration Generator.

Creating your database tables in the next version of Hobo will go something like this:

Step 1. Code your models (being sure not to generate any migrations)

Step 2. ruby script/generate hobo_migration create_initial_tables

(Step 2.5. Observe db/migrate/001_create_initial_tables.rb)

Step 3. rake db:migrate

And you’re done.

Hang on one darn minute I hear you say! Where are the columns declared? One of the much loved features of Active Record is that you don’t have to enumerate the columns in your models - AR interrogates the database and finds them for you. If Hobo generated the migration, where were the columns declared?

In the model class.

class User < ActiveRecord::Base
  fields do
    name :string, :null => false
    email :string
    about :text, :default => "No description available"
  end
end

Sacrilege! Not at all - it’s actually much better this way. Work with me for a minute here.

What is it that you really love about not having to list the columns in your AR classes? It’s the fact that it’s DRY. You don’t have to list them once in the migration and then again in the class. Well that’s still true, all we’ve done is moved those declarations to where they should be - in the same place that defines all the rest of the behaviour of your model. Yes those column declarations will be in the migration too, but that is generated for you.

There’s no more trawling through old migrations or messing with MySQL in order to remind yourself what columns you have - it’s right there in your code.

The generator looks at the database, looks at your models, figures out the difference and creates a migration such that the database gets with the program.

It generates the down migration too of course.

Moving forward things get even more interesting. Say you wanted to get rid of one of those fields. Just delete it from the fields declaration. Run the migration generator again and you’ll get a remove_column migration. Change the type of a column or add a default? No problem, you’ll get a change_column.

What if you delete a model class outright? Well we’re guessing your production data is kind of important to you, so the generator is interactive. It will require you to physically type “drop users” (or whatever) before it will generate a drop_table migration. The same goes for removing columns in fact.

What about renaming a column or table? Those are kinda tricky. Say we rename the field name to username. All the generator sees is that an existing field name has gone away, and a new field username has appeared on the scene. The generator will alert you to the ambiguity and invite you to enter either “drop name” or the new name “username”.

That’s the long and short of it, but there’s a couple more niceties.

Inside the fields block you can say simply timestamps to get created_at and updated_at.

You can declare fields using Hobo’s rich types like :html and :markdown. These end up as :text columns unless you override that by giving the :sql_type option. Your DRYML tags will know what it really is and render appropriately.

As for foreign keys – don’t even bother. Just declare belongs_to as you normally would, and the migration generator will spot the need to add a foreign key. Either with the conventional name or a custom one if you gave the :foreign_key option. Delete the belongs_to later, and the migration generator will remove the foreign key.

If, like most Rails programmers, you’ve written a lot of migrations, I think you’ll find using this puppy to be a fairly pleasing experience :-) I know I do.

It’s working now in the tom_sandbox branch, and will be in the next release, which, if the coding gods be willing, will be out by the end of the month.

Now Available In Tanzania

Posted about 7 years back at Sporkmonger

Maasailand

For those of you who are interested, I’ve been in Tanzania at the Cradle of Love baby home near Arusha for the past two weeks and change. It’s been interesting for sure, lots of holding and feeding babies, playing with toddlers, trying to get through the day with as little snot and spit on me as possible. There’s a few spitters who make this difficult, but they make up for it by being excessively adorable. I try not to play favorites, but Neema ends up riding my shoulders quite a bit. I haven’t taken a lot of pictures around the baby home itself, but on the various excursions we’ve taken outside, the camera has come out with a fair degree of regularity. There’s also some new kite aerial photography shots from the trip to the bush to visit the Maasai. If you have a Pro Flickr account, the high-resolution panorama shot from the kite is pretty neat. I suspect that through August at least, my Flickr photo stream will be much more interesting to subscribe to than my blog’s feed will be. Consider adjusting accordingly.

Episode 54: Debugging with ruby-debug

Posted about 7 years back at Railscasts

This episode will show you how to debug a rails app using ruby-debug: set a breakpoint, inspect variables, change the code and more.

Ruby en Rails Amsterdam - Ruby on Rails Podcast

Posted about 7 years back at Ruby on Rails Podcast

Interviews from Ruby en Rails, Amsterdam.

RV2 Camping on Gentoo

Posted about 7 years back at Mike Mondragon

Evan Weaver wrote a SysV init.d setup for daemonizing Camping apps on a *Nix system and documents it in rv, a tool for luxurious camping

I used that blog entry as a reference to build one called RV2 that is a little bit more Gentoo specific. Here are my scripts and directory layout

RV2 Camping on Gentoo

Posted about 7 years back at Mike Mondragon

Evan Weaver wrote a SysV init.d setup for daemonizing Camping apps on a *Nix system and documents it in rv, a tool for luxurious camping

I used that blog entry as a reference to build one called RV2 that is a little bit more Gentoo specific. Here are my scripts and directory layout

On a Gentoo system make a directory /etc/rv2 and any files in that directory will trigger the rv2 init.d script to attempt to start a corresponding camping app.

for hurl it the file is /etc/rv2/hurl_rv2.conf and it contains something like:

sample my-app-rv2.conf
CAMPING_ENV=production
RV2_APP_DIR=/path/to/app/hurl
PORT=1999
ADDRESS=127.0.0.1

The rv2 init.d script expects RV2_APP_DIR to specify the application directory, PORT to specify the port that the Mongrel harness will listen to, and ADDRESS the machine address that Mongrel will bind to. See the source code for the Hurl Campling app to see how Hurl makes use of a ENV variable CAMPING_ENV to help in configuration for testing and production.

I’m running Hurl as Mongrel behind an Apache Proxy, more details about that can by found in my Maintaining Your Own Typo 4.0.3 which is nice guide for rolling your own Rails stack.

Here is the rv2_harness.rb that resides in /path/to/app/hurl. Every Camping app that uses RV2 will need to have a harness named rv2_harness.rb in its application directory

sample rv2_harness.rb @ /path/to/app/hurl/rv2_harness.rb
# Example mongrel harness for camping apps with rv2
# based on Evan Weaver's original rv implementation:
# http://blog.evanweaver.com/articles/2006/12/19/rv-a-tool-for-luxurious-camping
#
# author: Mike Mondragon
# url: http://blog.mondragon.cc/
# license: AFL 3.0

# from the command line:
# ruby rv_harness2.rb PORT ADDRESS

require 'rubygems'
require 'mongrel'
require 'mongrel/camping'
$LOAD_PATH.unshift File.dirname(__FILE__)

ENV['CAMPING_ENV'] ||= 'production'

LOGFILE = "#{File.dirname(__FILE__)}/mongrel.log"
PIDFILE = "#{File.dirname(__FILE__)}/mongrel.pid"

# or whatever else you want passed in
PORT = ARGV[0].to_i
ADDR = ARGV[1]

# this is your camping app
require 'hurl'
app = Hurl

if ENV['CAMPING_ENV'].eql?('production')
  app::Models::Base.establish_connection :adapter => 'mysql',
    :database => 'hurl',
    :host => 'localhost',
    :username => 'root',
    :password => ''
else
  app::Models::Base.establish_connection :adapter => 'sqlite3',
   :database => 'db/hurl.db'
end

app::Models::Base.logger = Logger.new(LOGFILE) # comment me out if you don't want to log
app::Models::Base.threaded_connections=false
app.create

config = Mongrel::Configurator.new :host => ADDR, :pid_file => PIDFILE do
  listener :port => PORT do
    uri '/', :handler => Mongrel::Camping::CampingHandler.new(app)
    # use the mongrel static server in production instead of the camping controller
    uri '/static/', :handler => Mongrel::DirHandler.new("static/")
    uri '/favicon.ico', :handler => Mongrel::Error404Handler.new('')
    setup_signals
    run
    write_pid_file
    log "#{app} available at #{ADDR}:#{PORT}"
    join
  end
end
Below is the /etc/init.d/rv2 script. After you install it be sure to enable it for system startup and shut down:
rc-update add rv2 default
Also since the script is running the Mongrel as the “apache” user and group be sure to chown your applications directory with those credentials since Mongrel will bite if it doesn’t have permissions to write its pid file
chown -R apache.apache /path/to/your/camping/app
/etc/init.d/rv2
#!/sbin/runscript
depend() {
        need net
        use mysql apache2
        after apache2
}

USER=apache
GROUP=apache
RV2_HARNESS=rv2_harness.rb
RV2_CONF_DIR=/etc/rv2

start() {
        ebegin "Starting Camping Apps in RV2" 
        for apps in `ls -1 ${RV2_CONF_DIR}`
        do
                # the config needs to have CAMPING_ENV,
                # RV2_APP_DIR, PORT, and ADDRESS specified
                . ${RV2_CONF_DIR}/$apps

                start-stop-daemon --start --quiet --background \
                        --chuid ${USER:-apache}:${GROUP:-apache} \
                        --exec /usr/bin/env ruby \
                        -- $RV2_APP_DIR/${RV2_HARNESS} $PORT $ADDRESS 1>&2

                res=$?
                # if one is broken do not continue
                if [ $res -ne 0 ]; then break; fi
        done
        eend ${res}
}

stop() {
        ebegin "Stopping Camping Apps in RV2" 
        for apps in `ls -1 ${RV2_CONF_DIR}`
        do
                . ${RV2_CONF_DIR}/$apps
                start-stop-daemon --stop --pidfile $RV2_APP_DIR/mongrel.pid
                ret=$?
                rm -f $RV2_APP_DIR/mongrel.pid
                # force unloading of all apps
        done
        eend ${ret}
}

restart() {
        svc_stop
        svc_start
}

Enjoy!

small urls with Camping

Posted about 7 years back at Mike Mondragon

I am happy to announce yet another very small URL generator called

hurl it => http://hurl.it/

In fact its first entry is this very blog post.

There are obvious predecessors and the recent (and very cool) Rails based urlTea which itself I think is inspired by rubyurl

But hurl it is not those things, hurl it is a Camping application. Camping is a Microframework written by why the lucky stiff, a MVC based framework that is a total size of 4K and written in Ruby. Camping applications are small, light weight, and are intended to do one thing really well. hurl it does one thing very well – RESTfully create and serve very small URLs.

hurl it has these attributes

  • is a Camping application
  • RESTful (index, show, and create verbs) and responds to application/xml
  • tunes its use of ActiveRecord for use with the performanced minded MyISAM engine
  • developed with Agile practices including full unit and functional tests using Mosquito
  • is open source under the MIT License
  • has a neat-o base 62 number algorithm using the alphabet of 0-9,A-Z,a-z that would be good to know on an interview

hurl it is meant to be the honey layer in your peanut butter sandwich on wheat bread. hurl it is not the sandwich nor tries to be anything more than really super awesome at representing long URLs as really short URLs.

Things I’ve found helpful during hurl it development

I wrote a SysV init.d setup for Gentoo based on Evan’s work call RV2

Source

http://github.com/monde/hurl hurl hurl

small urls with Camping

Posted about 7 years back at Mike Mondragon

I am happy to announce yet another very small URL generator called

hurl it => http://hurl.it/

In fact its first entry is this very blog post.

There are obvious predecessors and the recent (and very cool) Rails based urlTea which itself I think is inspired by rubyurl

But hurl it is not those things, hurl it is a Camping application. Camping is a Microframework written by why the lucky stiff, a MVC based framework that is a total size of 4K and written in Ruby. Camping applications are small, light weight, and are intended to do one thing really well. hurl it does one thing very well – RESTfully create and serve very small URLs.

hurl it has these attributes

  • is a Camping application
  • RESTful (index, show, and create verbs) and responds to application/xml
  • tunes its use of ActiveRecord for use with the performanced minded MyISAM engine
  • developed with Agile practices including full unit and functional tests using Mosquito
  • is open source under the MIT License
  • has a neat-o base 62 number algorithm using the alphabet of 0-9,A-Z,a-z that would be good to know on an interview

hurl it is meant to be the honey layer in your peanut butter sandwich on wheat bread. hurl it is not the sandwich nor tries to be anything more than really super awesome at representing long URLs as really short URLs.

Things I’ve found helpful during hurl it development

I wrote a SysV init.d setup for Gentoo based on Evan’s work call RV2

Source

svn checkout http://svn.mondragon.cc/svn/hurl hurl

or

svn checkout http://svn.mondragon.cc/svn/hurl/tags/hurl-1.0/ hurl

Episode 53: Handling Exceptions

Posted about 7 years back at Railscasts

When an exception is raised in development you get the full error along with the stack trace. In production, only a simple message is displayed. Learn why this is and how to customize the handling of exceptions.

Episode 52: Update through Checkboxes

Posted about 7 years back at Railscasts

See how to select multiple items using checkboxes and perform an action on the selected items in this episode.

An Introduction to Scraping with Hpricot

Posted about 7 years back at zerosum dirt(nap) - Home

For one of my hobby projects, I’ve been building a comic book release schedule webapp in Ruby. Obviously, a large part of that involves locating data sources for comic book publishers and importing those sources. Unfortunately, none of the major publishers have seen fit to make their release schedules available in RSS or Atom or an other structured format for that matter. Sigh.

Fortunately, all is not lost. With the Hpricot gem and a little scraping know-how, we can overcome almost any parsing obstacle, as long as the data is in a somewhat predictably arranged state. Let’s see how it works…

For our example, we’ll consider DC Comics, home of Superman, Batman, Aquaman, and… Super-Chief (Apache Chief? No, he’s different). DC makes their weekly release schedule available through their website at this URL. That’s nice and convenient. But it’d certainly be more convenient if they had a feed available. (If they do have a feed available, hidden deep within their website, and I haven’t found it, please let me know!)

As we click through to next/previous weeks and it becomes pretty clear that passing the dat=<year><month><day> parameter gives you the appropriate listing. Note that they display a month at a time, so all you really have to do is ask for dat=<year><month>01 every time. We’re going to build a little scraper that just grabs the current months’ books, but armed with the knowledge of how this works, you should find grabbing 3-4 months worth of books at a time to be no challenge whatsoever (comic book publishers usually solicit about 3 months in advance).

OK. So now let’s take a peep at the structure of the document itself. We can do this by just viewing source in a browser. It seems that every comic listed in the release schedule has a link to a full description of the issue, with a cover art previews, a short synopsis, writers/artists listed, etc. And every one of those links seems to have a CSS class of ‘contentLink’. Oh, lucky day.

This is certainly starting to smell like a job for Hpricot, the super fast (and delightful!) HTML parser for Ruby, written by the enigmatic why the lucky stiff. Gem install that sucker!

gem install hpricot

Now let’s fire up IRb and chew on some delicious Ruby syntax:

require 'hpricot'
require 'open-uri'

URL_DC = "http://www.dccomics.com/comics/"

doc = Hpricot(open("#{URL_DC}?dat=#{Time.now.strftime('%y%m01')}"))
books = (doc/"a.contentLink")
books.each { |book| read\_comic(book.innerHTML.strip, 
  "#{URL_DC}#{book.attributes['href']}") }

def read\_comic(title, url)
  puts "#{title} - #{url}"
end

Run that, and you’ll get a list of stuff that looks like this:

THE ALL-NEW ATOM #12 – http://www.dccomics.com/comics/?cm=7447
BATMAN: TURNING POINTS – http://www.dccomics.com/comics/?cm=7251

Each output line lists a title with a URL, for each comic solicited in a given month. How does it work? Well, first we open the URL and feed it into Hpricot. Then the line books = (doc/“a.contentLink”) uses a CSS selector to yank out just the elements that match the selector. We could have also used XPath-style syntax to accomplish the same thing. Anyway, those elements we’re selecting are all the links to comics being released this month. Hpricot hands us an array of these elements, and then we iterate over them, calling the read_comic function and passing it the title (the innerHTML of the link, stripped of excess whitespace), and the URL (an absolute link to the href attribute of the link).

Next, let’s beef up the read_comic function to do something useful. We’ll have it make another remote connection, this time to the URL specified for the detailed comic description, parse out the talent, description, and some other information about the issue and stuff it into a model object. But first let’s examine the source of one of those pages. The Trials of Shazam! #7 should do nicely.

We note in our examination of the page source that the data we want to scrape is all contained in tags, with different classes, as listed below. Note that this time we’ve chosen to use XPath-style syntax for the selectors. Note also that the span tag with class=“display\_copy” appears twice. The first time, it contains what appears to be the description of the issue, and the second time it lists the publication date. So instead of returning a single element, display\_copy gets an Array of 2 (or possibly more) elements.

def read\_comic(title, url)
  doc = Hpricot(open(url))
  display\_talent = (doc/"span[@class=display\_talent]").innerHTML
  display\_copy = (doc/"span[@class=display\_copy]") # 2 elements
  puts "====="
  puts "title: #{title}"
  puts "talent: #{display\_talent}"
  puts "copy (0): #{display\_copy[0].innerHTML}
  puts "copy (1): #{display\_copy[1].innerHTML}
end

Now we’re iterating through each book from the remote source, and dumping out it’s title, the writer and artist responsible for it, a quick synopsis, and some other information (publication date, etc). Alright. If we just had a Comic model in our application, we could be somewhere!

So let’s make one. In fact, let’s do it in Ruby, with ActiveRecord. First the schema:

DROP DATABASE IF EXISTS comics;
CREATE DATABASE comics;
USE comics;

CREATE TABLE comics (
  id int(11) NOT NULL AUTO\_INCREMENT,
  name VARCHAR(255),
  publisher VARCHAR(255),
  talent VARCHAR(255),
  description TEXT,
  published\_on DATETIME,
  PRIMARY KEY (id)
);

Load this up and then add the following code to the top of your comics scraper. In fact, put it in a file called comics.rb so you can execute it on the command line.

require 'active\_record'

ActiveRecord::Base.establish\_connection(
  :adapter  => 'mysql',
  :host     => 'localhost',
  :username => 'root',
  :password => '',
  :database => 'comics')

class Comic < ActiveRecord::Base
end

Now we’ve established a connection to the database via ActiveRecord and defined a Comic model that inherits from ActiveRecord::Base, thus wrapping our database schema and giving us some handy getters and setters. Our next step will be to trade in the read_comic function in favor of an import class method on the Comic model.

class Comic < ActiveRecord::Base
  def self.import(title, url)
    doc = Hpricot(open(url))
    display\_talent = (doc/"span[@class=display\_talent]").innerHTML
    display\_copy = (doc/"span[@class=display\_copy]") # 2 elements

    comic = Comic.new(:name => title)
    comic.publisher = "DC"
    comic.talent = display\_talent
    comic.description = display\_copy[0].innerHTML
    comic.published\_on = Date.parse(display\_copy[1].innerHTML.
      sub('on sale', ''))

    comic
  end
end

When Comic.import receives a title and a URL it makes a connection to the URL specified and fires up Hpricot. It uses Hpricot to parse out the information we’re looking for, and then instantiates an instance of the Comic class. We set the talent, the description (the first of the display\copy spans) and then parse the date out from the second display\copy span.

We’ll remove all the output from there and put it in the book loop, since it’s clearly not the job of the model code to be rendering a view of any sort. Our new book loop will use Comic.import on each element of the books Array, creating the model, saving it, and then printing out some attributes. Here’s the final code for comics.rb:

require 'rubygems'
require 'active\_record'
require 'open-uri'
require 'hpricot'

ActiveRecord::Base.establish\_connection(
  :adapter  => 'mysql',
  :host     => 'localhost',
  :username => 'root',
  :password => '',
  :database => 'comics')

URL_DC = "http://www.dccomics.com/comics/"

class Comic < ActiveRecord::Base
  def self.import(title, url)
    doc = Hpricot(open(url))
    display\_talent = (doc/"span[@class=display\_talent]").innerHTML
    display\_copy = (doc/"span[@class=display\_copy]") # 2 elements?

    comic = Comic.new(:name => title)
    comic.publisher = "DC"
    comic.talent = display\_talent
    comic.description = display_copy[0].innerHTML
    comic.published\_on = Date.parse(display\_copy[1].innerHTML.
      sub('on sale', ''))

    comic
  end
end

doc = Hpricot(open("#{URL_DC}?dat=#{Time.now.strftime('%y%m01')}"))
books = (doc/"a.contentLink")
books.each do |book|
  comic = Comic.import(book.innerHTML.strip, 
    "#{URL_DC}#{book.attributes['href']}")
  if comic.save
    puts "====="
    puts "name: #{comic.name}"
    puts "description: #{comic.description}"
    puts "release date: #{comic.published\_on}"
  else
    puts "uh-oh! we should handle errors!"
  end
end

And here’s the final result:

name: TRIALS OF SHAZAM! #7 (OF 12)
description: Freddy must find Hercules for his next trial,
which is considerably more difficult than he expected,
since Herc is behind bars!
release date: 2007-06-13

Obviously we can do a lot more with this. We can build a series model, that has_many issues or episodes. We can build a publisher model. We can suck in the images and use RMagick to generate thumbnails. We can discriminate between graphic novels, trade paperbacks, and issues of a standard series book. We can roll this into a Rails application, and allow the results to be browsable, users to add comics to their pull lists, create collections, comment on them, rate them, and so on. Actually, that’s exactly what I’m working on for my hobby project (if you’re interested, email me and I’ll let you take a look — I’m hoping to release it relatively soon-ish).

To go further with scraping, we’ll need to pay particular attention to handling errors, because it’s an inexact science and, since we have no hard format, things are subject to change or break in weird ways. That’s the obvious downside to scraping. But when you have no other alternative for automating mass import of data like in this scenario, it’s certainly a good thing to know how to do.

If you want to learn more, \_why’s Hpricot site is chock full of useful information, and you may also want to check out scRUBYt, which combines Hpricot and WWW::Mechanize into a full-on web scraping “toolkit”.

Episode 51: will_paginate

Posted about 7 years back at Railscasts

In edge rails (soon to be Rails 2.0), the built-in pagination has been moved into a plugin: classic_pagination. I recommend jumping over to the will_paginate plugin as shown in this episode.

Free-for-all: Tab Helper (Summary)

Posted about 7 years back at The Rails Way - all

The first RailsWay free-for-all came off quite well. Many of you posted your favorite solutions to the problem of tab-based navigation, as posed by Nate Morse.

Jamis’ Take

Of all the solutions posted, my personal favorite was the pragmatic and simple CSS-based solution given by Mr. eel (Nate Morse came to the same solution independently):

I take a completely different approach. I ID the body of the page with the name of the current controller. Then I use a descendent CSS selector to highlight the current tab based on the body id and an id given to each link. I don???t bother with replacing the current tab link with a span. If the user wants to click that link again??? then it???s the same as refreshing. Totally up to them.

With html like:

1
2
3
4
5
6
<body id="users">
  <ul>
    <li><a href="/users" id="usersNav">Users</a></li>
    <li><a href="/comments" id="commentsNav">Comments</a></li>
    <li><a href="/posts" id="postsNav">Posts</a></li>
  </ul>

I would use CSS like this

1
2
3
4
5
6
#users #usersNav,
#comments #commentsNav,
#posts #postsNav {
  background:red;
  font-weight:bold;
}

What a great approach. Although I would make the choice of the body ID explicit (rather than depending on the controller name), it is otherwise really nice. It shrugs off the whole issue of “should the current tab be a link” by saying it just doesn’t matter—every tab is always a link. Such pragmatism gets right to the heart of the Rails Way: implement just what matters, and nothing more.

Koz’s Take

A number of solutions relied on tightly coupling the controller and tabs. While this may seem like a time-saver at first, I believe that it’s unlikely to remain useful as your application grows. You’ll find yourself moving functionality into strange locations in order to make your tabs highlight correctly.

The problem is amplified with a restful application where your choice of controllers are dictated by the resources that you’re managing. You may have a list of comments in several different sections of your application, but not want to highlight the ‘comment’ tab whenever you display them.

Personally, I prefer the really simple approach of a before filter and a navigation partial.

1
2
3
def set_current_tab
  @current_tab = :people
end

Thanks, everyone for your submissions!