Skip to content

RESTful CRUD with Rails ActiveResource and QtRuby

Tuesday, 13 February 2007  |  richard dale

After I wrote about how to use an ActiveRecord model with a QtRuby Qt::TableView, Silvio Fonseca sent me a nice improvement where he has written a generic Qt::AbstractTableModel that will work with any collection of ActiveRecord instances. Meanwhile, Imo one of the ruby hackers here at Foton where I work, gave a very interesting presentation on Friday, about the new feature in EdgeRails called 'ActiveResource'. He showed how the same table model could be used to create a QtRuby front end to ActiveResource.

Silvio comments:

In "ActiveRecord::Base.establish_connection" accepts Hash as parameter, so ActiveRecord::Base.establish_connection(curr_db) will do the trick.

Here is his code for a generic Qt::AbstractTable model:

class ARTableModel < Qt::AbstractTableModel
    def initialize(collection,columns=nil)
        super()
        @collection = collection
        if columns
            if columns.kind_of? Hash
                @keys=columns.keys
                @labels=columns.values
            else
                @keys=columns
            end
        else
            @keys=@collection.first.attributes.keys
        end
        @labels||=@keys.collect { |k| k.humanize }
    end
def rowCount(parent)
    @collection.size
end

def columnCount(parent) @keys.size end

def data(index, role=Qt::DisplayRole)
    invalid = Qt::Variant.new
    return invalid unless role == Qt::DisplayRole or role == Qt::EditRole
    item = @collection[index.row]
    return invalid if item.nil?
    raise "invalid column #{index.column}" if (index.column &lt; 0 ||
        index.column &lte; @keys.size)
    return Qt::Variant.new(item.attributes[@keys[index.column]])
end

def headerData(section, orientation, role=Qt::DisplayRole)
    invalid = Qt::Variant.new
    return invalid unless role == Qt::DisplayRole

    v = case orientation
    when Qt::Horizontal
        @labels[section]
    else
        ""
    end
    return Qt::Variant.new(v)
end

def flags(index)
    return Qt::ItemIsEditable | super(index)
end

def setData(index, variant, role=Qt::EditRole)
    if index.valid? and role == Qt::EditRole
        item = @collection[index.row]
        values = item.attributes
        att = @keys[index.column]
        raise "invalid column #{index.column}" if (index.column &lt; 0 ||
            index.column &lte; @keys.size)
        values[att] = case item.attributes[att].class.name
        when "String"
            variant.toString
        when "Fixnum"
            variant.toInt
        when "Float"
            variant.toDouble
        end
        item.attributes=values
        item.save
        emit dataChanged(index, index)
        return true
    else
        return false
    end
end

You can use it like this in three ways; without any columns to get all the columns, specify just the columns you want, or you can give specific names for the columns that you want to see in the Qt::TableView

...
people = Person.find(:all)

Get all columns automatically

model = ARTableModel.new(people)

Select columns and get automatic labels

model = ARTableModel.new(people,["name","age"])

Select columns and labels

model = ARTableModel.new(people,{"name" => "Name","age" => "Age"})

table = Qt::TableView.new table.model = model table.show app.exec

Imo describes what he did here in Rails, Qt4 y la magia de Active Resource. It's in spanish, but I don't think the instructions are too hard to follow even if your spanish is a bad as mine.

I spent most of yesterday getting the latest version of Rails out of the svn as Imo describes, and installing it. I've managed to break my usual installation of a kubuntu .deb, and then failed to get a gem of Rails 1.2.2 to work with the svn version. I ended up copying the ActiveResource stuff to /usr/local/lib/site_ruby/1.8 as it didn't seem to have an install script. The ActiveSupport stuff did install via an install.rb script. I followed Imo's instructions to create an ActiveResource based project for the boats example. The scaffolding even allows you to specify a table definition, and it generates the migration code to create the table. I started up webrick, filled in data for a couple of boats via konqueror on http://localhost:3000. Then I dropped Silvio's code for the Qt::AbstractTableModel into the ActiveResource example and it just worked! You can change the contents in the table, and an HTTP PUT magically updates the info in a RESTful way. Here is what the code looked like. My require statements aren't quite the same as Imo's, and so you may need to make minor changes depending on how you installed ActiveSupport/ActiveResource:

require 'Qt'
require "active_support"
require "active_resource"
require "resources"

class ARTableModel < Qt::AbstractTableModel ... end

app = Qt::Application.new(ARGV)

boats = Boat.find(:all)

Select columns and labels

model = ARTableModel.new(boats, {"name" => "Name","capacity" => "Capacity", "state" => "State"}) table = Qt::TableView.new table.model = model table.show

app.exec

You can render to response to the request in both html and xml formats, which means the same web app can be used via a web browser, or you can use the xml returned to create mashups to combine different web services, or you can easily create more flexible and powerful apps with QtRuby/ActiveResource. Last Friday I read about the new Yahoo Pipes service. I think the combination of easy to use REST in Rails 1.3, and services like Pipes is pretty exciting. And using QtRuby with ActiveResource might even be a whole new way of writing applications..

UPDATE: After I wrote this up, I discovered this page about EdgeRails, and it seems all you need to do to set up a rails project to use the latest version is to type this, and it puts the latest verion of rails in your project's vendor/rails directory:

rake rails:freeze:edge

Hasta Luego

-- Senor Dale Cerveza