I’ve been playing around with Shoes (shoooes.net) lately as a way to put a cross-platform graphical user interface (GUI) on some of my small purpose-built command-line ruby scripts.
I find that it is quite easy to get started with, and lends a lot of flexibility to the way your program is structured and displayed. However, the structure feels a little bit alien compared to everyday ruby, and there are some gotcha’s you need to keep in mind while developing for Shoes.
I feel I must preface this article by saying that Shoes has excellent documentation, _why (the lucky stiff) turns documentation into its own art form. The manual, “Nobody Knows Shoes” reads a lot like a comic book, full of _why’s own original artwork and clippings from old-timey photos and art, and is complimented by the documentation at help.shoooes.net
I had a bit of trouble at first getting ActiveRecord to interface with a database from a straight port from one of my console apps because I glossed over the parts of the manual that detail the tricky behavior of the garbage collector reaping predefined classes after the app’s initial load.
The fix is pretty simple. Stick all your classes in an external file (or many) and load them using ‘require’.
Anyhow, here is a barebones example of a working implementation for using ActiveRecord in Shoes:
# in foo.rb
class Foo < ActiveRecord::Base
end
#in app.rb
Shoes.setup do
gem 'activerecord'
require 'active_record'
ActiveRecord::Base.establish_connection(
:adapter => 'sqlite3',
:dbfile => 'foos_db.sqlite3'
)
require 'foo'
end
Shoes.app do
@foos = Foo.find(:all)
para @foos
end |
# in foo.rb
class Foo < ActiveRecord::Base
end
#in app.rb
Shoes.setup do
gem 'activerecord'
require 'active_record'
ActiveRecord::Base.establish_connection(
:adapter => 'sqlite3',
:dbfile => 'foos_db.sqlite3'
)
require 'foo'
end
Shoes.app do
@foos = Foo.find(:all)
para @foos
end
Now, this example requires there is an existing sqlite database with a foos table, change out the establish_connection parameters to connect to any other database. The gem ‘activerecord’ statment tells shoes to install the activerecord gem into the shoes ruby library if it isn’t already there.
If you don’t already have a database, and just want to use a db to act as a storage layer for your app, then you might want to use ActiveRecord::Schema.define to create a database and setup the tables the same way you do for Rails migrations.
Here is a more complete example of an app to keep track of notes using ActiveRecord as the backend. I like the “base class that inherits from Shoes” pattern, so I’m using that here.
# in note.rb
class Note < ActiveRecord::Base
end
# in app.rb
Shoes.setup do
gem 'activerecord' # install AR if not found
require 'active_record'
require 'fileutils'
ActiveRecord::Base.establish_connection(
:adapter => 'sqlite3',
:dbfile => 'shoes_app.sqlite3'
)
# create the db if not found
unless File.exist?("shoes_app.sqlite3")
ActiveRecord::Schema.define do
create_table :notes do |t|
t.column :message, :string
end
end
end
end
class ShoesApp < Shoes
require 'note'
url '/', :index
def index
para 'Say something...'
flow do
@note = edit_line
button 'OK' do
Note.new(:message => @note.text).save
@note.text = ''
@result.replace get_notes
end
end
@result = para get_notes
end
def get_notes
messages = []
notes = Note.find(:all, :select => 'message')
notes.each do |foo|
messages << foo.message
end
out = messages.join("n")
end
end
Shoes.app :title => 'Notes', :width => 260, :height => 350 |
# in note.rb
class Note < ActiveRecord::Base
end
# in app.rb
Shoes.setup do
gem 'activerecord' # install AR if not found
require 'active_record'
require 'fileutils'
ActiveRecord::Base.establish_connection(
:adapter => 'sqlite3',
:dbfile => 'shoes_app.sqlite3'
)
# create the db if not found
unless File.exist?("shoes_app.sqlite3")
ActiveRecord::Schema.define do
create_table :notes do |t|
t.column :message, :string
end
end
end
end
class ShoesApp < Shoes
require 'note'
url '/', :index
def index
para 'Say something...'
flow do
@note = edit_line
button 'OK' do
Note.new(:message => @note.text).save
@note.text = ''
@result.replace get_notes
end
end
@result = para get_notes
end
def get_notes
messages = []
notes = Note.find(:all, :select => 'message')
notes.each do |foo|
messages << foo.message
end
out = messages.join("n")
end
end
Shoes.app :title => 'Notes', :width => 260, :height => 350
Here’s a screenshot:
There you are; a cross-platform desktop app that doesn’t require a full-on build environment, and can be distributed with the source exposed for later improvements.
The first time this runs, it installs Activerecord, requires it, establishes a connection, creates the table unless one already exists. Then it shows a form to add notes followed by all the existing notes in the database. Adding a new note refreshes the notes shown.
This isn’t exactly a polished app with full CRUD, but should prove a good introduction to Shoes for someone used to working with ActiveRecord.