RESTful CRUD with Rails ActiveResource and QtRuby
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 } enddef 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 < 0 || index.column <e; @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 < 0 || index.column <e; @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