Monday, 27 October 2008

YAML

YAML is a format for structuring data in a file, like XML. Rails uses YAML to configure your databases, but it does so in such a way that you hardly notice. However, YAML is pretty neat, and you could be missing out.

YAML stands for "Yet Another Markup Language" (here). Or perhaps for "YAML Ain't Markup Language" (here). I guess it depends on whether you think it is a markup language or not (actually the latter seems the more common one).

There are basically two types of data; that which goes into a hash, and that which goes into an array.
name: value
- value

You can combine them to make complex data structures, such as this array of hashes:
-
name: Tom
age: 32
-
name: Dick
age: 19

And in fact you can also enter arrays and hashes more like Ruby:
- { name: Tom, age: 32}
- { name: Dick, age: 19}

Or as an array of arrays:
- [Tom, 32]
- [Dick, 19]

Comments start with a hash, #, just like Ruby. Ruby/YAML guesses the type from the format. Strings can be surrounded with single-quotes, double-quote (allowing escape sequences) or nothing. Repeated data can be replaced by an "anchor", by labelling the first occurance. Labels begin with an ampersand, and references to the label with an asterix. Use | or > for data that goes on to more than one line. The former preserves line breaks, the latter does not (see the example at the end of the YAML and Ruby section).

This example show all of these, and uses symbols as the keys, rather than string as above.
-
:name: Tom # A string
:age: 32 # An integer
:male: true # A boolean
:born: 1972-02-29 # A date
:address: &add
213 Main Street
Big City
England
:comment:
This is a potentially very
long comment that will be
just one line long.
-
:name: Dick
:age: 19
:address: *add
:comment: 'A very short comment'

See here for more details:
http://yaml.org/spec/current.html

By the way, NetBeans 6.1 flags up strings that go on to multiple lines as errors; this seems to be a bug in NetBeans.

YAML and Ruby
To use the data from your YAML file in a Ruby program, the first step is to load the YAML library:
require 'yaml'

Then load the data:
config = YAML.load(yaml_string)       # From string
config = YAML.load_file("config.yml") # From file

Then you can access to data just as you would any array of hashes (or whatever your data structure):
x = config[0]['age']    # x -> 32

Note that if you want to use symbols as the keys to your hashes the string needs to be prefixed with a colon (as in the examples above).

Going the other way is nearly as easy:
yaml_string = config.to_yaml            # To string
File.open('config.yml', 'w') do |out| # To file
YAML.dump(config, out)
end

Here is an example program that includes the YAML text (as a "here" document), and illustrates how YAML handles text that goes over multiple lines:
require 'YAML'

y = <<YAML_TEXT
:first: >
This is a folded block,
line breaks are discarded
for spaces. The line ends
with a return.
:second: |
This is a literal block,
and so the line-breaks
are preserved. Again the
line ends with a return
:third:
This is a folded block
too, as YAML defaults
to that. However, this time
there is no return at the end.
:fourth: And again a folded
block formated slightly
differently, with no return
at the end.
YAML_TEXT

h = YAML.load(y)
p h[:first]
# => "This is a folded block, line breaks are discarded for spaces. The line ends with a return.\n"
p h[:second]
# => "This is a literal block,\nand so the line-breaks\nare preserved. Again the\nline ends with a return\n"
p h[:third]
# => "This is a folded block too, as YAML defaults to that. However, this time there is no return at the end."
p h[:fourth]
# => "And again a folded block formated slightly differently, with no return at the end."

You can combine several data structures into a single file. Each should start with three dashes on a line on their own to indicate the start of a new document. Use the load_stream method to open the file. For example:
  data_doc = YAML::load_stream(File.open('data.yml'))
NAME_SCHEMAS = data_doc.documents[0]
DATA_TYPES = data_doc.documents[1]
CATEGORIES = data_doc.documents[2]


YAML and Rails
You can use a YAML file to kick start your database table in Rails. Rails already uses YAML, so no need for the require. The YAML file goes in the root directory for your web application.

One way that I have seen is to put the code inside your migration file, and when you migrate, the data goes straight into your table. However, this is a short term solution only. What happens in your production database or for testing? You could clone your database, but the prefered Rail way is to use Rake to generate the database tables, based on what is in db/schema.rb, and that will not have any initialisation data.

What I have done (and it may not be the best way) is to write setup methods in my models, which can be invoked from a console session. In this example, for a model called Role, defining the roles a user can have
  def self.setup_roles
return false unless Role.find(:all).length == 0
Role.create(:rolename => 'administrator')
Role.create(:rolename => 'manager')
true
end

Note that the method fitrst tests to see if there are any roles present already, and only adds the default roles if not. In your tests you can invoke the setup method before a test to load in the data, and be sure that it is the same default data as in your development and production databases.

Struggling with Ruby: Contents Page

Thursday, 23 October 2008

Single Table Inheritance

Rails supports Single Table Inheritance (STI). In this pattern, the superclass is the only model with an associated table in the database, and any object of that class and any subclass is stored in that same table. Rails use a special column named "type" which stores the class name (this means that you will have problems if you name a column "type" for any other reason).

The Models
To implement, create the top level object as a model as normal (with generate scaffold works best for me), but include a column called "type" that is a string. You also need to include every column that all your subclasses will use (though you can, of course, add columns later, as normal).

For each subclass, create a model (again, I recommend generate scaffold). Modify the model so that the class inherits from the desired superclass, and delete the database migration. Migrate the database, and you are pretty much done.

See also:
http://wiki.rubyonrails.org/rails/pages/SingleTableInheritance
http://www.juixe.com/techknow/index.php/2006/06/03/rails-single-table-inheritance/

You can access all the objects in the class hierarchy through the top level class, or a specific class through other classes.

A < B < C < ActiveRecord::Base
C.find :all # Get all objects in the table, whatever the class
B.find :all # Get objects of type B only (not A)


Your superclass will need all the fields of all the subclasses - otherwise there will be no table column for field. Each subclass needs its own controller and map.resources entry in routes.rb.

Controllers and Views

So how does STI affect the controllers and the views?

Possibly not at all. You can have one controller for each model, all completely independant, all with their own set of views. However, there is a good chance that there will be some overlap. It is quite likely that you will want to use the same "show" view for each model, for example. Let us suppose SubClass is a subclass of SuperClass; the default show method is this:

def show
@sub_class = SubClass.find(params[:id])
end


It is very easy to point the render method to the SuperClass view (remembering to rename the variable to what super_class/show.html.erb is expecting):

def show
@super_class = SubClass.find(params[:id])
render :template => 'super_class/show'
end


We can go a step further. If we set up SubClassController to inherit from SuperClassController, we do not need a show method in SubClassController at all; it can all be handled in SuperClassController. This will work without any changes to SuperClassController, but your object will be of the SuperClass type, and you will lose all the benefits of subclassing. It is better to instantiate your object as the subclass:

def show
@super_class = eval("#{params[:controller].classify}.find(params[:id])")
render :template => 'super_class/show'
end


Other methods for the SuperClass (destroy can be used as is):
def index
@super_class = eval("#{params[:controller].classify}.find")
render :template => 'super_class/index'
end

def edit
@super_class = eval("#{params[:controller].classify}.find(params[:id])")
render :template => 'super_class/update'
end

def create
@super_class = eval("#{params[:controller].classify}.new(params[:#{params[:controller].singularize}])")
if @super_class.save
flash[:notice] = 'Record was successfully created.'
redirect_to(:controller => 'super_class' , :action => 'show')
else
render :template => 'super_class/new'
end
end

def update
@super_class = eval("#{params[:controller].classify}.find(params[:id])")
if @super_class.update_attributes(params[params[:controller].singularize.to_sym])
flash[:notice] = 'Record was successfully updated.'
redirect_to(:action => 'show')
else
render :action => 'edit'
end
end


One last point. In your helps, instead of naming a model for the links, use polymorphic, eg, edit_polymorphic_path(super_class) rather than edit_super_class_path(super_class). This will make Rails point to the right controller for your subclass, whatever it might be.

Struggling with Ruby: Contents Page

Friday, 17 October 2008

The View Part 2 - Scope and Helpers

Scope
There are significant limits to what you can access from a view.

For local variables, you can only access variables defined in that view (anywhere in the file, not just that section of Ruby). You cannot access local variables in the controller, or in another view (not even in the layout that gets processed with your view).

You can access instance and class variables in your controller, as long as they have already been defined in the respective method (I guess a new instance is created with each web request). You cannot access instance and class variables that you have defined by calling a method from the view (they will always be null).

Helpers
This brings us to helpers. Helpers are methods in modules that you keep mostly hidden out of the way. The idea is to keep as much Ruby code out of the views, so helpers are mainly for use in that context. Helpers are the only methods you can access, other than instance methods for an instance you have access to.

In the helper folder
You can put your helpers in the helper folder. Rails puts in a helper :all directive, which will automatically load any helpers it finds there, as well as creating an ApplicationHelper file, plus a helper file for each controller or scaffold you generate. In this configuration all helpers are available to all views, regardless of what file you put it in.

Helpers are set up just like any other Ruby method, the only difference being that the file is a module, not a class.

module ApplicationHelper
def project_name
'My Great Web App'
end
end


The file must be named in the format "_helper.rb", and the module named "Helper".

In the lib folder
Helpers can go into the lib directory, with a filename of the form _help.rb. Again, these are modules, not classes, and look just as before. You need to point Rails to the file, with this in the controller:
helper :myfile

Rails will then look for myfile_helper.rb, which should contain a module MyfileHelper.

In the controller
If you want to be able to access your helper methods in your controller too, your only option is to put them into the controller, and flag them as helpers.

To be able to access the methods, you need to put them in your controller (or application controller to allow all views and controllers to use them). the methods are set up as normal, but you need a "helper_method" macro at the top to register the specified methods as helpers.
helper_method :myfirstmethod, :mysecondmethod

Accessing Rails' helpers
You can use the helpers in Rails in your own helpers (at least in your helpers folder). For example, the following will set up a quick link to page, using the standard link_to helper.
def link_to_log
link_to 'Sample Log', { :action => 'home', :controller => 'samples' }, :method => :get
end
As an aside, I once had a method, select_options, in a controller that was used by several views, and worked fine. I decided it would be better in a helper, and so moved it across. It stopped working, the interpreter complaining there was no such method. Other methods in the helper file worked fine, even in the same view. After some head scratching, I decided that the problem was due to a name conflict; changing the name of the method got it working again. Why would it fail to find the method (as opposed to invoking the other method or complaining about the wrong number of arguments)? I do not know. However, should your view fail to find a helper method, this could be the reason.

Testing Helpers

Naturally you will want to test your helpers. You may be able to do this in your unit tests, but you may require the infrastruction that comes with a functional test - and anyway, helpers are for views/controllers, not models, so it makes more sense here.

Here are the bare bones of a test file:
require 'test_helper'

class AppHelperTest < ActionController::TestCase
tests SampleLog::GlcsController

include ApplicationHelper
include ActionView::Helpers::UrlHelper
include ActionView::Helpers::TagHelper

def test_link_code
# code here
end
end
Note that it is necessary to specify which controller you are testing. It may not matter; just pick any. To get access to your new methods, include the helper class. To get access to other methods, include them too. In the above, I wanted access to link_to in ActionView::Helpers::UrlHelper, which in turn accessed escape_once in ActionView::Helpers::TagHelper.


For more on helpers see also:
http://api.rubyonrails.com/classes/ActionController/Helpers/ClassMethods.html


Edit: This page originally hada section on partials, however, partials have changed a lot in Rails 2.2.2, and most of it was no longer true. See this page:
http://strugglingwithruby.blogspot.com/2009/02/moving-to-rails-222.html


Struggling with Ruby: Contents Page

Thursday, 9 October 2008

Deploying a Ruby on Rails Application

I am in the process of developing a laboratory sample logging application, and before going to far down the road wanted to check it would work in the production environment. For development I am using WEBrick, MySQL and Ruby (1.8.6) with NetBeans (6.1), but the server has PostgreSQL, with Tomcat (which requires JRuby), so there are plenty of potential problems moving from one to the other. If you do not have Ruby installed, you still need to use "jruby " before rake.

Moving to JRuby and PostgreSQL
NOTE: I had a problem moving to JRuby, as I had Ruby installed as well, and I had not realised that "gem install " will install a gem to Ruby, rather than JRuby, and so I had a rather confusing time with gems installed in the wrong place. To install a gem specifcally on JRuby use "jruby -S gem install ", and to use rake and warble, likewise prefix the command with "jruby -S " The -S swich tells jruby to use its own version of the binary. See more here:
http://wiki.jruby.org/wiki/Getting_Started#How_do_I_run_rake.2C_gem.2C_etc

My first step was to install JRuby 1.1.4 on the server (JRuby already includes Gems), and then Rails (with: "jruby -S gem install rails -y --no-ri"). All very easy. Then I had to create a database server in PostgreSQL, which I did through the command line:
createdb mydb

I created a new project as normal in Rails, and modified the database.yml file to this:

development:
adapter: jdbcpostgresql
encoding: unicode
database: mydb_development
username: postgres
password:
host: localhost
port: 5432

test:
adapter: jdbcpostgresql
encoding: unicode
database: mydb_test
host: localhost
username: postgres
password:

production:
adapter: jdbcpostgresql
encoding: unicode
database: mydb_production
host: localhost
username: postgres
password:


I chose to use JDBC, if you chose not to, just delete "jdbc" from the above.

Then I could use rake to create the databases ("jruby -S rake db:create:all"). Next I copied across the contains of the app and the db/migrate folders from my PC to the server, along with routes.rb (you might want other files from config too) and the stylesheets (I did not copy the tests; there seemed no point). I migrated my databases ("jruby -S rake db:migrate").

Then I installed the gem to handle database connections, either one, depending on whether you want to use JDBC or not:
jruby -S gem install postgres-pr
jruby -S gem install activerecord-jdbcpostgresql-adapter


At this point I could fire up the server, and check everything was okay (with "jruby script/server"), or use the interactive environment (with "jruby script/console").

So I had transferred to a different computer (though it could as well been the same computer, if I had been willing to install PostgreSQL on it) with a different database and a different Ruby; there were several issues that I have glossed over, but once you know what to do, pretty straightforward. No actual deployment yet though.

Moving to the Production Environment
To ensure the production database is used, in config/environment.rb uncomment this line:
ENV['RAILS_ENV'] = 'production'

To create the database tables in the production database:
jruby -S rake environment RAILS_ENV=production db:migrate

Warbler
I used Warbler to create a .war file (Warbler replaces GoldSpike). More information from here:
http://caldersphere.rubyforge.org/warbler/http://wiki.jruby.org/wiki/Warbler

Warbler is installed with:
jruby -S gem install warbler

By default, Warbler will package Rails, but no other gems. To change that (so the database connections are including), first create a config file using warbler ("warble config"). This creates a new file in your application config/warbler.rb. Edit the file to include the gems you require, or insert the following code to include all gems:

# From http://wiki.jruby.org/wiki/Warbler
# Include all gems which are used by the web application
require "#{RAILS_ROOT}/config/boot"
BUILD_GEMS = %w(warbler rake rcov)
for gem in Gem.loaded_specs.values
next if BUILD_GEMS.include?(gem.name)
config.gems[gem.name] = gem.version.version
end

Some other configuration setting can be found in tmp/war/WEB-INF/web.xml

Now create the .war file with:
warble
This should give the following output (if it has an mk command, then it is using Ruby rather than JRuby, I think):
jar cf mydb.war -C tmp/war .

Note that warbler requires access to a Java JDK, so you need that in your path (PATH=C:\Program Files\Java\jdk1.6.0_07\bin;C:\jruby-1.1.4\bin;%path%).

Moving to Tomcat
The .war file can then be dragged to the Tomcat webapps directory. You may need to restart Tomcat, but not necessarily. Tomcat will decompress the .war file and the web application can now be accessed with:
http://<servername>:8080/<app>

8080 is the default port for Tomcat. is the project name, which is the folder name for your application.

To replace an old version in Tomcat, you need to stop Tomcat, delete the directory with your project name (which Tomcat created by decompressing your old .war file), copy across your new .war, and restart Tomcat.

Overall it probably took me two to three times longer to deploy the application than it did to build the first draft of it, however, now I know what to do, it will be much, much quicker next time!

Struggling with Ruby: Contents Page

Friday, 3 October 2008

file_column

file_column is a simple plugin that lets users upload images, which can then be displayed. There are alternatives out there; this was the first I found, but does the job so well I looked no further.

Download from here:
http://www.kanthak.net/opensource/file_column/

Install by dumping the files in vendor\plugins inside your project.

file_column does not work properly with more recent Rails. Modify file_column_help.rb
url = ""
url << request.relative_url_root.to_s << "/"
url << object.send("#{method}_options")[:base_url] << "/"


To
url = "/"
url << object.send("#{method}_options")[:base_url] << "/"


I think it allows for sub-domains and stuff like that, so is unnecessary in most cases. It fails as request (or @request) is nil. Why should that be?

Example

For a table called empire, with a column called image:

In the model, use something like this:
file_column :image, :magick => { :geometry => "200x100>" }


The magic bit uses RMagick to limit the size of the image.

In the new view, use something like this:
<% form_for(@empire, :html=> {:multipart=>true}) do f %>
<%= file_column_field "empire", "image" %>


In the show view, use something like this:
<%= unless @empire.image.nil?
image_tag url_for_file_column("empire", "image"), :align=>"right"
end %>


This will only display an image if one is entered in the database (though the file might still be missing). Note the align right option; this is not how it is documented; it should be done through a hash called option, but that did not work for me.

No changes in the controller.

Update: For Rails 2.2.2
Using this in Rails 2.2.2, I also had to make a change in file_column.rb, around line 619:
my_options = FileColumn::init_options(options,
Inflector.underscore(self.name).to_s,
attr.to_s)

Becomes:
my_options = FileColumn::init_options(options,
ActiveSupport::Inflector.underscore(self.name).to_s,
attr.to_s)


Update: Functional Testing
I found this quite a problem, with little guidance anywhere on the web as to what I should be doing. However, this is what I got working. The application is for a record of cylinder batches, each batch requiring a scanned certificate in PDF format.
test "should update cylinder_batch with file" do
login_as 'manager', @request
cb = CylinderBatch.find(:first)
filename = 'LittleBookOfRuby.pdf'
test_file = File.new("#{RAILS_ROOT}/test/fixtures/#{filename}")

put :update, :id => cb.id, :cylinder_batch => { :certificate => test_file}
assert File.exists? "#{RAILS_ROOT}/test/tmp/file_column/cylinder_batch/certificate/#{cb.id}/#{filename}"
assert_redirected_to cylinder_batch_path(assigns(:cylinder_batch))
cb2 = CylinderBatch.find cb.id
assert_equal "#{RAILS_ROOT}/test/tmp/file_column/cylinder_batch/certificate/#{cb.id}/#{filename}", cb2.certificate

get :delete_certificate, :id => cb.id
assert !File.exists?("#{RAILS_ROOT}/test/tmp/file_column/cylinder_batch/certificate/#{cb.id}/#{filename}")
assert_redirected_to cylinder_batch_path(assigns(:cylinder_batch))
cb2 = CylinderBatch.find cb.id
assert_nil cb2.certificate
end

The method is broken into three parts, the first setting up a few things, the second testing the update method with a new file, and the third part testing the delete_certificate method for removing a file. Usually I would only do one controller method per test method, but in this case one method sets up the other, while the other cleans up after the first, so this was more convenient.

I put a test file in the fixtures folders. This should be of the same type as you expect in your application. I tried using a YAML file, and while file_column would save the file, it did not update the table; I think file_column rejects file types it does not know. Also as a general point, be aware that file_column does not like anything besides letters, digits, underscores and hyphens in filenames.

In the parameters for the update method, I simply map the file to the appropriate column name. The file object contains the filename as well as the contents, and file_column can sort it all out. I then check that the file exists in the appropriate directory, the database has been updated correctly and the user redirected.

In the last section, after calling the delete_certificate method in the controller, I just check the file has gone, the database entry is nil and the redirect again. Simple. When you know how.

Struggling with Ruby: Contents Page

Saturday, 13 September 2008

Ruby GUI

I have been looking at how easy it would be to put a GUI front-end on Ruby. Ruby has no native support for a GUI (one place Java and C# really win over Ruby), and while Rails has become the standard for web aplications, there are numerous options for a GUI plug-in.

I have had a look at Tk, Fox and Swing (for JRuby). Recently, however, I found Ruby Shoes. Shoes is a very simple GUI, but that is part of its appeal. I doubt it has the comprehensive range of widgets that Tk does, for instance, but it can cope with JPEGs, which Tk cannot (as far as I could find). Also Shoes is dead easy to use:. Download. Install. Start the Shoes application, point it at your ruby file (or one of the samples included). Your application is runnning.

Unlike most other GUI toolkits, Shoes is not just a Ruby front end to an existing kit, which seems to make it feel more Ruby-like.

However, the big problem with Shoes is that there is no menu support incorporated. See this forum thread for a discussion on that:
http://www.ruby-forum.com/topic/165398

Find Shoes here:
http://code.whytheluckystiff.net/shoes/

My Quick Guide to Shoes

Everything in Shoes goes inside a Shoes.app block (usually, anyway). The simplest Shoes application is therefore

Shoes.app do
end


You can put in some options at this point - in a hash of course.

Shoes.app :title => "A Great Application", :width => 400, :height => 600, :resizable => false do

As usual, your require statements go at the very top of the file. Inside the Shoes.app block you can place the code that determines the GUI format and your methods.


Widgets are laid out inside either of two layouts, stacks for vertical stacking, and flows for horizonal flowing widgets. They can be nested for complex interfaces.

flow do
end

stack :margin_left => 5, :margin_right => 10, :width => 1.0, :height => 200, :scroll => true do
end


Note that the width in the second example is 1.0, i.e., 100%. You can assign the layout to a class variable, and then manipulate it later in a method for dynamic interfaces.

@gui_completed = stack
@gui_completed.clear


Flow and stack are not analogous. In the flow layouts, components fill a line, then go on to the next line, while in a stack, it is strickly one above the other. Shoes does not like horizontally scrollbars (neither do I). Sometimes components will stretch to fill the available room, and if they do that for a flow, they end up on the next line, and it looks more like a stack. The solution is to specify widths.

You can also use absolute positioning with :left and :top.

The background method set the background. If this is inside a layout, the background for the layout is set. The border method works the same

background white
background "back.jpg", :height => 40 # Set the top 40 pixels tan
border blue, :strokewidth => 5
background "#000".."#FFF", :curve => 15 # Gradient filled, black at the top
background "#000".."#FFF", :curve => 15, :angle => 90 # Gradient rotated 90 degrees anti-clockwise


You can use :width, :height, :right, :bottom to put different backgrounds along a certain side or corner. The :curve option should give your panel rounded corners, but not if you specify a border (note that the KNS manual says :radius; this is out of date).

You can manipulate your panels.

@my_stack.clear
@my_stack.clear { add_new_stuff }
@my_stack.append { add_new_stuff }
@my_stack.prepend { add_new_stuff }
@my_stack.before existing_component { add_new_stuff }
@my_stack.after existing_component { add_new_stuff }
@my_stack.remove existing_component
@my_stack.hide
@my_stack.show


Text can be done with: banner, title, subtitle, tagline, caption, para, inscription (in descending order of size).

title 'Here is my Application'
para 'some simple text'
caption "A caption, in red", :margin => 8
para strong('Some text in bold (like caption)')
para "Some fancy text", :stroke => red, :fill => yellow, :font => "Monospace 12px"


As well as accepting a string, these methods will also take arrays. You can use that to put in formatting within a line. In the second example, a link is created (looking as on a web page); click on it and the do_stuff method is invoked.

para ['Some text with ', strong('this'), ' in bold']
capture 'Please click ', link('here') { do_stuff }, '.' # Square brackets are optional


Options include strong, em, code, del, ins, link, span, sub, sup. It does not seem to cope well with characters outside the standard ASCII set. You can dynamically change content with the replace method.

You can change the style of a link (for the whole application).
style(Link, :underline => false, :stroke => 'red')

Text boxes can be done with edit_line or edit_box. The second example has some default text. The block gets invoked each time the text box is used, so @note will always have the text currently in the text box. How easy is that?

@text1 = edit_line :margin_left => 10, :width => 180
@text2 = edit_box "Default text", :width => 1.0, :height => 200, :margin_bottom => 20 do
@note = @text2.text
end


Buttons are easy too. When the button is pressed, the code in the block is invoked.

button("Add", :margin_left => 5) { add_todo(@add.text); @add.text = '' }
button "Swap" do
swap
end


There are some built in functions for dialog boxes:

ask("What is your name?")
confirm("Would you like to proceed?")
ask_open_file
ask_save_file
ask_open_folder
ask_save_folder
ask_color("Pick a Color")


Most components can be moved and resized

@comp.move(x, y)
@comp.size(w, h)


You can capture mouse movement like this:

motion do x, y
@o.move width - x, height - y
end


click do button, x, y
# button is 1for left button, 2 for right
end


Images are very easy, just use image, with the path to your image (plus style optins as required). Images can be changed on the fly by setting the path attribute (but why not have a replace method like there is for text elements?).

@little_image = image 'picture.jpg', :width => 50, :height => 50
@little_image.path = 'alternative.jpg'


You can even access the clipboard:

self.clipboard = ""

Shoes supports dropdown lists (list_box), checkboxes (check) and radio buttons (radio). see the manual included with the download for details. The big omission is menus, as mentioned earlier, but hopefully this will be rectified by the end of the year.

See also:
http://hackety.org/2008/06/12/martinDemellosGooeyChallenge.html
http://www.infoq.com/news/2007/09/ruby-shoes

Struggling with Ruby: Contents Page

Wednesday, 3 September 2008

Ruby Hashes

Ruby uses hashes a lot to pass data around, and one reason for that is that they are just so easy to use. A hash is a object that contains a number of name-value pairs; a dictionary. A hash with a single entry is created like this:
h = {name1 => value1}

In practice, the name is usually a symbol, and the value often a string, so this would be the usual format:
h = {:name1 => 'value1'}

For multiple entries, separate with commas.
h = { :name1 => 'value1', :name2 => 'value2', :name3 => 'value3' }

In method calls the curly brackets seem not to be required or even desired. This can make it seem as though the method takes several parameters rather than a single hash; I think that is deliberate.

New name-value pairs can be added
h[:newname] = 'new value'
h.store :newname, 'new value'


Values can be accessed like an array:
value = h[:name1]
h[:name1] = 'new value'
h[:name1] = nil

Some web sites suggest the last of these will delete the entry; not so! To delete, use the delete method:
h.delete(:mykey)


However, accessing the key with the value is not trivial (as far as I can see). Here are two options:
h.invert[:value1]
h.find{ |k,v| == :value1 }[0]


To determine if a hash has a certain key, do this:
h.key? :my_key


You can loop through a hash with the each method. This example outputs each name-value pair (I believe the order is arbitrary).
h.each { |x,y| puts "Name #{x} Value #{y}" }

The delete_if method iterates through the hash and removes any entry for which the block evaluates to true. Note that as with the delete_if method in Aaray, this does not conform to the convention of an explanation mark for methoids that change the original.

Useful article:
http://www.informit.com/articles/article.aspx?p=26943&seqNum=3

By the way, Rails uses HashWithIndifferentAccess, which is special as it allows strings and symbols to be used interchangeably as keys when accessing values.


Struggling with Ruby: Contents Page