Skip to content

Commit

Permalink
Get most of the way to implementing table persistence in database. NO…
Browse files Browse the repository at this point in the history
…T PASSING YET. [#4]
  • Loading branch information
marnen committed Dec 2, 2009
1 parent 0e24ad0 commit 29937a7
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 28 deletions.
21 changes: 21 additions & 0 deletions features/document/table.feature
Expand Up @@ -15,6 +15,27 @@ Feature: Table in document
When I change the cell at row 3 and column 2 to "new value"
Then I should see "new value" in the table

Scenario: Table data is persistent
Given I have a new document called "Persistent table"
And the frame "Persistent table" is the container
When I change the cell at row 5 and column 1 to "new value"
And I close the frame "Persistent table"
And the frame "Hive" is visible
And I click the menu "File/Open"
And the file chooser "Open" is visible
And I select the temporary file "Persistent table"
And I approve the file chooser
Then I should see the frame "Persistent table"
Given the frame "Persistent table" is the container
Then it should contain a table
And I should see "new value" in the table

@pending
Scenario: Table has a name
Given I have a new document called "Table name"
And the frame "Table name" is the container
Then I should see the tab "table1"

@pending
Scenario: Row numbers should not scroll separately
When I can figure out how to write this, I will! See http://www.jguru.com/faq/view.jsp?EID=87579 .
48 changes: 39 additions & 9 deletions lib/ruby/data_table_model.rb
@@ -1,13 +1,32 @@
# This is a Ruby table model implementation inheriting from javax.swing.DefaultTableModel.
# TODO: Remove inheritance from DefaultTableModel when appropriate, and just implement TableModel interface directly.
class DataTableModel < javax.swing.table.DefaultTableModel
require 'row_header_model'
require 'sequel'

def initialize
@data = Array.new(row_count) do |r|
Array.new(column_count) do |c|
"R#{r + 1},C#{c + 1}"
def initialize(filename)
super()
@db = Sequel.connect 'jdbc:h2:file:' + File.expand_path(filename)

# Yes, this is a placeholder table. We'll make this more flexible shortly.
cols = column_count
@db.create_table :table do
primary_key :id
1.upto(cols).each do |c|
String "column_#{c}"
end
end

# Make some placeholder data for now.
@data = @db[:table]
values = 1.upto(row_count).collect do |r|
h = {}
1.upto(column_count) do |c|
h[:"column_#{c}"] = "R#{r}, C#{c}"
end
h
end
@data.multi_insert values
end

def column_count
Expand Down Expand Up @@ -36,19 +55,30 @@ def row_header_view
RowHeaderModel.new(row_count).table
end

# row and col are 0-based.
def set_value_at(value, row, col)
if row >= row_count or col >= column_count
raise IndexError, "(#{row}, #{col}) is beyond the maximum coordinates, which are (#{row_count - 1}, #{column_count - 1})."
end
@data[row][col] = value
check_row(row, col)
@data.filter(:id => row + 1).update(:"column_#{col + 1}" => value)
value
end

# row and col are 0-based.
def value_at(row, col)
@data[row][col]
check_row(row, col)
@data[:id => row + 1][:"column_#{col + 1}"]
end

# Define some Java-style aliases, since these are already defined in the superclass.
[:getColumnCount, :getRowCount, :getValueAt, :setValueAt].each do |method|
alias_java_method method
end

protected

# Raise IndexError if row and col are out of range for this table.
def check_row(row, col)
if row >= row_count or col >= column_count
raise IndexError, "(#{row}, #{col}) is beyond the maximum coordinates, which are (#{row_count - 1}, #{column_count - 1})."
end
end
end
81 changes: 63 additions & 18 deletions spec/data_table_model_spec.rb
@@ -1,22 +1,35 @@
require File.dirname(__FILE__) + '/spec_helper'
require 'data_table_model'
require 'tmpdir'

describe DataTableModel do
before :each do
@tmpdir = Dir.mktmpdir 'DataTableModel'
end

describe "general properties" do
it "should implement the Java TableModel interface" do
DataTableModel.included_modules.should include(javax.swing.table.TableModel)
end
end

describe "constructor" do
it "should connect to the supplied database" do
filename = File.join @tmpdir, 'Database connection'
connection_string = "jdbc:h2:file:#{filename}"
db = Sequel.connect connection_string
Sequel.should_receive(:connect).with(connection_string).and_return db
DataTableModel.new filename
end

it "should give a new table two columns" do
DataTableModel.new.column_count.should == 2
DataTableModel.new(File.join @tmpdir, 'Columns').column_count.should == 2
end
end

describe "instance methods" do
before :each do
@model = DataTableModel.new
@model = DataTableModel.new File.join(@tmpdir, 'Instance methods')
end

describe "row_header" do
Expand Down Expand Up @@ -64,38 +77,70 @@
end
end

describe "set_value_at" do
it "should be valid" do
@model.should respond_to(:set_value_at)
end
describe "setting and getting values" do
describe "set_value_at" do
it "should be valid" do
@model.should respond_to(:set_value_at)
end

it "should return the value when the cell reference is legitimate" do
@model.set_value_at("value", @model.row_count - 1, @model.column_count - 1).should == "value"
end
it "should return the value when the cell reference is legitimate" do
@model.set_value_at("value", @model.row_count - 1, @model.column_count - 1).should == "value"
end

it "should raise an exception (and not set the value) when the cell reference is not legitimate" do
lambda{@model.set_value_at("value", @model.row_count, @model.column_count)}.should raise_error(IndexError)
begin
@model.get_value_at(@model.row_count, @model.column_count).should_not == "value"
rescue NativeException
# ignore exceptions
it "should raise an exception (and not set the value) when the cell reference is not legitimate" do
lambda{@model.set_value_at("value", @model.row_count, @model.column_count)}.should raise_error(IndexError)
begin
@model.get_value_at(@model.row_count, @model.column_count).should_not == "value"
rescue NativeException
# ignore exceptions
end
end

it "should be available as setValueAt" do
@model.setValueAt("value", @model.row_count - 1, @model.column_count - 1).should == "value"
end
end

it "should be available as setValueAt" do
@model.setValueAt("value", @model.row_count - 1, @model.column_count - 1).should == "value"
describe "value_at" do
it "should return the value when the cell reference is legitimate" do
r = @model.row_count - 1
c = @model.column_count - 1
@model.value_at(r, c).should == "R#{r + 1}, C#{c + 1}"
end

it "should raise an exception when the cell reference is not legitimate" do
lambda{@model.value_at(@model.row_count, @model.column_count)}.should raise_error(IndexError)
end

it "should be available as getValueAt" do
r = @model.row_count - 1
c = @model.column_count - 1
@model.getValueAt(r, c).should == @model.value_at(r, c)
end
end

it "should be able to retrieve the values it sets" do
row = 5 # arbitrary
col = 1 # arbitrary
value = "some arbitrary value"
@model.set_value_at(value, row, col)
@model.value_at(row, col).should == value
end
end
end

describe "Java integration" do
before :each do
@model = DataTableModel.new
@model = DataTableModel.new File.join(@tmpdir, 'Java integration')
end

it "should treat Java getter names like Ruby getters" do
@model.should respond_to(:getColumnCount)
@model.getColumnCount.should == @model.column_count
end
end

after :each do
FileUtils.rm_r @tmpdir
end
end
4 changes: 3 additions & 1 deletion src/document/document_model.rb
Expand Up @@ -10,7 +10,6 @@ class DocumentModel
attr_accessor :new_file

def initialize(filename = nil, new_file = true)
@data = DataTableModel.new
@new_file = new_file
if filename
self.filename = filename
Expand All @@ -22,8 +21,11 @@ def initialize(filename = nil, new_file = true)
def filename=(string)
if filename.nil?
if string # don't bother if we got passed another nil
# TODO: Untangle these conditions and remove hard-coded '.h2'.
@filename = string
create_file @filename if @new_file
db = File.join File.expand_path(@filename), 'data'
@data = DataTableModel.new(db) if (@new_file or File.exists?(db + '.h2'))
end
return @filename
else
Expand Down

0 comments on commit 29937a7

Please sign in to comment.