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