Skip to content

Using QtRuby with Rails ActiveRecord based Qt::AbstractTableModels

Thursday, 8 February 2007  |  richard dale

I do Ruby Rails development as part of my day job, and one of the nice parts of Rails is the ActiveRecord Object-Relational Mapping framework. Today I've been playing with QtRuby using a Qt::AbstractTableModel based on ActiveRecord, and it's really simple to implement and works really well.

To use ActiveRecord without Rails you create a database.yml file with the database details like this for instance:

development:
  adapter: postgresql
  database: boat_development
  username: rdale
  password: XXXXX
I googled a bit for how to connect to the database and found this code. You read in the yaml and connect to the database (I needed to pass a '-I/usr/share/rails/activerecord/lib' to ruby to find the active record lib):
def setupDatabase
    @dbs = YAML::load(ERB.new(IO.read("database.yml")).result)
# to slurp records into production db, change this line to production.
curr_db = @dbs["development"]

# these parameters are mysql-specific, figure out how to improve
ActiveRecord::Base.establish_connection(:adapter => curr_db["adapter"], 
                                        :database => curr_db["database"],
                                        :host => curr_db["host"], 
                                        :username => curr_db["username"], 
                                        :password => curr_db["password"])

end

I have a simple table called 'boats' for this example, and I set it up with a Rails Migration:

class BoatsTable < ActiveRecord::Migration
  def self.up
    create_table :boats do |t|
      t.column :name, :string, :limit => 20
      t.column :capacity, :integer
      t.column :state, :integer			# 0: funciona, 1: no funciona
    end
    garajonay = Boat.create(:name=> "Garajonay", :capacity => 268, :state => 0)
    orone = Boat.create(:name=> "Orone", :capacity => 268, :state => 0)
  end

def self.down drop_table :boats end end

So you need to run a 'rake migrate' command to create the table from the migration. Then you can access it with a simple model like this:

class Boat < ActiveRecord::Base
end

app = Qt::Application.new(ARGV) setupDatabase() boats = Boat.find(:all) model = BoatTableModel.new(boats) table = Qt::TableView.new table.model = model table.show app.exec

Defining a custom Qt::AbstractTableModel is very simple. I based this on some code from Hans Fugal who has written a QtRuby based app to maintain a recipe database.

class BoatTableModel < Qt::AbstractTableModel
    def initialize(boats)
        super()
        @boats = boats
    end
def rowCount(parent)
    @boats.size
end

def columnCount(parent)
    3
end

def data(index, role=Qt::DisplayRole)
    invalid = Qt::Variant.new
    return invalid unless role == Qt::DisplayRole or role == Qt::EditRole
    boat = @boats[index.row]
    return invalid if boat.nil?

    v = case index.column
    when 0
      boat.name
    when 1
      boat.capacity
    when 2
      boat.state
    else 
      raise "invalid column #{index.column}"
    end || ""
    return Qt::Variant.new(v)
end

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

    v = case orientation
    when Qt::Horizontal
      ["Name","Capacity","State"][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
        s = variant.toString
        boat = @boats[index.row]
        case index.column
        when 0
            boat.name = s
        when 1
            boat.capacity = s.to_i
        when 2
            boat.state = s.to_i
        else
            raise "invalid column #{index.column}"
        end
        boat.save

        emit dataChanged(index, index)
        return true
    else
        return false
    end
end

end

And that's all there is to it. You can edit values in the table and the database is updated. And it should be very easy to create general purpose tools for doing CRUD stuff that wouldn't need to know anything about the database table, as they can get the column names and numbers of columns from ActiveRecord.