Saturday 24 January 2009

The View Part 4 - Using Select in Forms

Last time around, I discussed forms, I am now going to focus on the select widget, as I found this particular mysterious at first. I am going to assume you have already read the page on forms.

Using f.select
The easiest way to use select is inside a FormBuilder block. While most FormBuilder code is in form_helper.rb, the select is in form_options_helper.

Suppose you have a column for integers in your database table (let us say "status", for example, in a table called "posts"). You want the user to be able to select an option from a drop-down list to set the value of the column. First, you need a set of options, and this is best defined in your model. This could be an array or a hash. If you use a hash, you can assign values to options yourself, but generally an array will be sufficient. I am going to use a hash, so in posts.rb, there will be this constant defined:
STATUS_OPTIONS = {'Read' => 1, 'Unread' => 2, 'Deleted' => 12}

Then, in the view, you just need a f.select. It might look something like this (with other fields removed for clarity):
<% form_for(@post) do |f| %>
<p>
Please select:
<%= f.select(:status, Post::STATUS_OPTIONS) %>
</p>
<p>
<%= f.submit "Update" %>
</p>
<% end %>

The f.select takes two parameters, the first being the name of the column (or any method as a symbol), the second is the array or hash. That is all you need to do. Rails will handle the saving and setting of the options for you.

Be aware that if you use an array, Rails will ignore the index. The value returned from the column method must be a value in the array, rather than a number for the index, and similarly what is set will be value, not the index. Personally, I found that annoying, so created a new method that would accept an array, and build a select element using the indices with the values.
# See actionpack/lib/action_view/helpers/form_options_helper.rb
module ActionView
module Helpers
class FormBuilder
def array_select(method, choices, options = {}, html_options = {})
h = {}
choices.each_index { |i| h.store(choices[i], i)}
@template.select(@object_name, method, h, objectify_options(options), @default_options.merge(html_options))
end
end
end
end


Using select_tag
The select_tag method is a bit poor, as it does not accept an array or hash, demanding instead a string with each entry surrounded by "<option>" and "</option>". Why it was not designed to accept an array and a value I cannot imagine. Instead, you have to use the options_for_select helper method, like this:
<%= select_tag("post[status]",
options_for_select(Post::STATUS_OPTIONS,
@post.status))
%>

However, there is also a select method that does the job.
<%= select(:post, :status, Post::STATUS_OPTIONS) %>

I imagine this was a later addition to Rails.

NOTE: I have read that select_tag should be used for GET commands, and select for POST (see here).

Submit on change to a select
Sometimes, you want the user to be able to select from a list, and to be taken straight to a new web page, without having to click on a button. This is pretty easy, with a bit of JavaScript. I set up a helper method to do that:
def submit_on_change
{:onchange => 'submit()'}
end

You can then add that method to your select or select_tag. Note that select takes two optional hashes, and you want to use the second, so I have put in an empty hash in that case.
<%= f.select :status, Post::STATUS_OPTIONS, {}, submit_on_change %>

<%= select_tag "post[status]",
options_for_select(Post::STATUS_OPTIONS,
@post.status),
submit_on_change %>


Another select example
Here is an example of using a select box to choose a web page. The web pages are set up in the model:
HELP_PAGES = {'Main' => 'index', 'Ruby Basics' => 'ruby', 'Ruby Classes' => 'classes'}

In the view, this code will set up the select (note that there is no submit button; be aware that any user with JavaScript disabled will not be able to navigate using this):
<% form_tag( {:action => :help, }, :method => :get) do %>
<%= select_tag "page", options_for_select(Post::HELP_PAGES),
submit_on_change %>
<% end %>

In the controller, the chosen page is handled:
def help
@page = params[:page]
# etc...
end


The collection_select method
Let us suppose you have one table associated with another, and want to be able to have the user select a record from one table for a record in the other. Let us go back to the archetypal blog application: Posts can be associated with a category (so a post belongs_to a category; a category has_many posts and the post table has a column called "category_id"). The user clicks on new post, writes his throughts, then can select from a list of categories from a drop-down list. How do we create such a thing?

This is what the collection_select method is for. As with select, there are two forms, one associated with a FormBuilder object, the other not.
<%= f.collection_select(:category_id,
Category.find(:all), :id, :name) %>
<%= collection_select(:post, :category_id,
Category.find(:all), :id, :name) %>

Note that the second form requires an extra parameter specifying the table we are modifying. The next parameter, :category_id in the example, is a method that is called to set the value; generally that will be the name of the column in the table you are modifying.

The next parameter is an array (kind of) of ActiveRecords; this is the list of options that will be available to the user. The next parameter, :id, is the method used by Rails to get values for each option of the select, while the next parameter determines the display name for the options. In effect, these two are the column names in the other table. To generate the list of options in the example, Rails iterates through the array of categories, and for each member it calls the "id" method to set the value, and the "name" method to set the text that is displayed.

As with select, there are two optional parameters for hashes of options.

As it turns out, you are not restricted to ActiveRecords. I tried it with this TestClass:
class TestClass
def initialize id, name
@id = id
@name = name
end
attr_reader :id, :name
end

Setting up an array:
  TEST_ARRAY = [
TestClass.new(12, 'First of all'),
TestClass.new(54, 'Middle'),
TestClass.new(32, 'Last and finally'),
]

And then using the collection_select like this:
<%= collection_select(:comment, :post_id,
Comment::TEST_ARRAY, :id, :name) %>

However, I have no idea why you would want to do that, rather than using a hash with select.

Selecting Dates
There are are set of methods to help you handle dates. If you are inside a FormBuilder block, just use date_select like this (Rails will even do this for you when you generate views):
<%= f.date_select :birthday %>
ActiveRecord will handle the rest. Outside FormBuilder you can use date_select or select_date (why not date_select_tag, which would be more consistent?). I found date_select easier to set up, but the values in the hash are not trivial to handle. I found some useful code here:

# Reconstruct a date object from date_select helper form params
def build_date_from_params(field_name, params)
Date.new(params["#{field_name.to_s}(1i)"].to_i,
params["#{field_name.to_s}(2i)"].to_i,
params["#{field_name.to_s}(3i)"].to_i)
end

date = build_date_from_params(:published_at, params[:article])


The API:
http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html
http://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html
http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html

See also:
http://shiningthrough.co.uk/Select+helper+methods+in+Ruby+on+Rails

Struggling with Ruby: Contents Page

Friday 23 January 2009

The View Part 3 - Using Forms

The basic component of a form for a view on Rails is the form_for method in ActionView::Helpers::FormHelper. This takes an object as an argument, and applies that object to the form components in a block. The variabe f is a FormBuilder object.
form_for(@post) do |f|
f.label :name
f.text_field :name
f.label :body
f.text_area :body
f.submit "Update"
end

Actually, it is not quite as simple as that, as you need to mix in the HTML, so in your view, the above becomes:
<% form_for(@post) do |f| %>
<p>
<%= f.label :name %>

<%= f.text_field :name %>
</p>
<p>
<%= f.label :body %>

<%= f.text_area :body %>
</p>
<p>
<%= f.submit "Update" %>
</p>
<% end %>

It is a good idea to put in <%= f.error_messages %> as the second line in your form (and indeed Rails will do this for you), as this will display error messages for you (for example, if the user's input fails validation).

There are a number of methods available for the FormHelper object that will place widgets on the web page. These ones are listed in the API:
check_box
file_field
hidden_field
label
password_field
radio_button
text_area
text_field

The check_box method should be associated with a Boolean field. The hidden_field is useful for data you do not want the user to have access to, but which has to saved with the rest of the fields (but if a field is missing from the form, the record will retain the previous value).



Using radio_button
Suppose you have a column for integers in your database table (let us say "status", for example, in a table called "posts"). You want the user to be able to select an option.

First, you need a set of options, and this is best defined in your model. I am going to use a hash, so in posts.rb, there will be this constant defined:
STATUS_OPTIONS = {'Read' => 1, 'Unread' => 2, 'Deleted' => 12}

Then, in the view, you just need something like this:
<% Post::STATUS_OPTIONS.each_pair do |k, v| %>
<%= k + f.radio_button("status", v) %>
<% end %>

Ruby will iterate through the hash, Post::STATUS_OPTIONS. For each element, it will create a radio button.

Buttons that do not submit
Sometimes you want a button that does not submit the form. You can still use the submit method, just set the :type to map to "button. Here is an example of how to create a button that will invoke a JavaScript function called calc.
<%= f.submit 'Calc', { :onclick => 'calc()', :type => "button", } %>


Other Forms
Sometimes you want to create a form that is not associated with a particular record, such as a search form. For this you use methods from the ActionView::Helpers::FormTagHelper module. The same sort of methods are available, but with _tag as a suffix.

Use form_tag to create the basic form. You can give it a URL segment, or the usual URL parameters. The form_tag method seems to default to POST, so for this show example, I had to specify the method as GET.
<% form_tag({:action => :show, :id => 1}, :method => :get) do %>
<%= submit_tag 'Show 1' %>
<% end %>

Or using the URL:
<% form_tag '/posts/update/1' do %>
<%= submit_tag 'Update 1' %>
<% end %>

Here is a more interesting example, doing the same as the first example (just for illustration - there is no good reason to not do it the other way), with radio buttons. Note that the method is now PUT for update. Also, the tag name is of the form post[status] (for the model called "post", and the field called "status"). When Rails receives the request, the value of status will be put in a hash called post, which will go inside the params hash. This is the standard Rails technique, and is what happens in the earlier example, behind the scenes. This means the controller does not need changing.
<% form_tag({:action => :update, :id => @post.id},
:method => :put) do %>
<p>
<%= label_tag 'Name' %>

<%= text_field_tag 'post[name]', @post.name %>
</p>
<p>
<%= label_tag 'Body' %>

<%= text_area_tag 'post[body]', @post.body %>
</p>
<% i = 0
Post::STATUS_OPTIONS.each_pair do |k, v| %>
<%= k + radio_button_tag('post[status]', v,
@post.status == v) %>
<% end %>
<%= submit_tag "Update" %>
<% end %>

Hopefully tomorrow I will post about select.

The API:
http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html
http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html

Struggling with Ruby: Contents Page

Wednesday 21 January 2009

Ruby Methods Part 4 - Calling Methods

Ruby has three (at least) ways to call a method on an object. This code illustrates their use. First, a class is defined with four methods, one of which is private, one is a class method and another takes a parameter. The class is instantiated, and then the methods accessed using the various techniques.
# Define a class with three methods
class MethodTest
def public_method
p 'In public_method'
end

def method_with_argument x
p "In public2_method - #{x}"
end

def self.class_method
p 'In class_method'
end

private
def private_method
p 'In private_method'
end
end

# Create instance of class
mt = MethodTest.new

# Invoke methods with the dot operator
mt.public_method
mt.method_with_argument('hello')
MethodTest.class_method
begin
mt.private_method
rescue NoMethodError
p $!
end

# Invoke methods with send
mt.send :public_method
mt.send :method_with_argument, 'Hello'
MethodTest.send :class_method
mt.send :private_method

# Invoke methods as objects
mt.method(:public_method).call
mt.method(:method_with_argument).call 'Hello'
MethodTest.method(:class_method).call
mt.method(:private_method).call

The dot operator
I guess this is the most familiar technique, and is common to other languages, like Java and C++. Private methods are not accessible, and instead a NoMethodError is raised (and a message is produced informing you that the method is private).

The send method
The send method is a part of the Object class and so is available to all objects. It invokes the named method (must be a symbol). As the named method is now being invoked from within the object, this means that all the methods are available, including private and protected methods.

Note that you can also use __send__, in case you have overwritten the send method. Overwriting __send__ will generate a warning that it is a bad idea; Rails uses __send__ a lot, for example, on the assumption that no one would be stupid enough to over-write it.

A good example of using the send method is where you want to access database columns in a large table, where the columns are numbered sequentially, say column0, column1, column2, etc. Rails will handle the generation of methods that will allow @mytable.column1 = 5 and x = @mytable.column1, but how do you get the total of the columns? Let us suppose ten such columns.
total = 0
10.times {|i|
total += send("column" + i.to_s)
}

Strangely, Ruby is quite a stickler for types. In Java or C# you could write "column" + i, but Ruby requires the to_s method to explicitly convert to a string. The send method sends a message (in OO talk) to the named method, column0, column1, etc. To assign a value, you need to append an equals sign to the method name. This loop assigns zero to each column:
10.times {i
send("column" + i.to_s + "=", 0)
}

The send method is a variable length method; just send it the right number of parameters for the method you are invoking.

The method object
Everything in Ruby is an object, including methods. You can access the method object with my_object.method(:my_method). Here is an example of using the method object for the length method of string
s = "string"
puts s.method(:length).class # => Method
puts s.method(:length).call # => 6
puts s.method(:length).methods.sort

# => ["==", "===", "=~", "[]", "__id__", "__send__", ...

As seen earlier, the method that the object represents can be invoked using the call method. As with send, this allows you to access private and protected methods. By the way, you can convert a method to a Proc using the to_proc method.

One important practical difference between using send and using the Method object is that the latter will only work on methods that are actually defined. It will not work for method calls that go though method_missing, including calls to column names for ActiveRecord or calls to the various find methods in Rails. This is because the method has to be defined to become an object.

API for the Method object:
http://www.ruby-doc.org/core/classes/Method.html


Struggling with Ruby: Contents Page

Friday 9 January 2009

Rails Mailer

Rails includes a mailer facility. To use it, you need a model - a class that inherits from ActionMailer::Base - and the best way to do that is use Rails to generate it:
ruby script/generate mailer MyMailer send_book

This also creates some default views (in this case just one, send_book) and tests, but we will discuss that later. Let us suppose we want to send an e-mail with a record from a table called books. In a moment, we will set up a method called send_book in MyMailer, but first, let us look at how we will invoke the mailer. In books_controller, to send the mail, you need to invoke a class method that starts deliver_.
MyMailer.deliver_send_book(:user => current_user, :data => find_book)

Somewhere in ActionMailer::Base, there is a method_missing method that handles your request. It creates an instance of MyMailer, does some setting up that we do not have to worry about, then invokes the method send_book. This is where we assign values specific to this e-mail. It might look like this:
def send_book(options)
@recipients = "#{options[:user].email}"
@from = "book-database@mydomain.com"
@subject = "Record from book database"
@sent_on = Time.now
@content_type = 'text/html'
@body[:user] = options[:user]
@body[:book] = options[:data]
end

Note that this will send the message in HTML format; it will default to plain text if there is no @content_type.

Next the method_missing method creates the text of the e-mail. It does this in the normal Rails manner, i.e., from a file in the views folder. In this case it will use views/my_mail/send_book.html.erb. When you create views/my_mail/send_book.erb, you have access to any variable you assign to the @body hash, so in the example above, I could use @user and @book, just as in a normal view. Similarly, you can use partials from other models/controllers in the normal way.

Finally ActionMailer sends the e-mail. It needs to know your your mail settings, which it will collect from a file mail.rb in config/initializers. This might look something like this:
# Email settings
ActionMailer::Base.delivery_method = :smtp
ActionMailer::Base.smtp_settings = {
:address => "smtp.mydomain.com",
:port => 25,
:domain => "mydomain.com",
:authentication => :login,
:user_name => "rails",
:password => "secret"
}

You can use your helper files in your mail views just like your normal views, just remember to declare them at the top of MyMailer:
helper :application

The one slight difference is that you cannot do helper :all. I imagine an oversight in Rails.

Attachments can be added easily:
  attachment "application/rtf" do |a|
a.body = File.read 'some_file.rtf'
a.filename = 'samples.rtf'
end


Testing
You can test your mailer. Rails will have generated a default unit test (no functional testing as there is no controller for a mailer).
require 'test_helper'

class MyMailerTest < ActionMailer::TestCase
tests MyMailer
def test_send_book
@expected.subject = 'MyMailer#send_book'
@expected.body = read_fixture('send_book')
@expected.date = Time.now

assert_equal @expected.encoded,
MyMailer.create_send_book(@expected.date).encoded
end

end

NB: The generated unit test includes a statement tests MyMailer. This seems to just invoke write_inheritable_attribute, which appears to make a copy of the class variables in te superclass in the subclass.

Struggling with Ruby: Contents Page

Sunday 4 January 2009

Exception Handling

You can define your own exceptions in Ruby very easily, just extend a suitable class (that is, StandardError or any of its sub-classes):
class MyError < StandardError

end

To see how exceptions are handled, let us look at this example:
try_counter = 0
begin
try_counter += 1
puts 'Here 1'
raise MyError.new "Text" unless try_counter > 5
puts 'Here 2'
rescue MyError
puts 'Here 3 - MyError encountered'
retry
rescue StandardError
puts "Here 4 - Other error encountered (#{$!.inspect})" + caller.inspect
raise
else
puts 'Here 5 - No errors'
ensure
puts 'Here 6 - Always done'
end

Exceptions are handled by blocks. The risky code goes into the first chunk of the block. If an exception is encountered, the program will jump to the end of that chunk and look for an appropriate action. In the above example, it will first look at rescue MyError. If the exception is of the MyError class - or a sub-class - then the program will not run the code folling the rescue. Note that you can list multiple exception classes, separated by commas.

Otherwise, it will look further. The next rescue is for StandardError, so this will catch all errors of the StandardError type or its sub-classes - except for MyClass, as that would have been caught earlier. All rescuable exceptions must inherit from StandardError (but see later), so this is set up to catch all exceptions, but that need not be the case; uncaught exceptions will be passed on up the stack to whatever else might handle them.

The else chunk will get performed after the initial chunk has completed successfully (i.e., without raising an exception). Finally, the ensure chunk gets performed whatever the outcome.

An exception is raised using the raise keyword. Exceptions are just objects, so are instantiated just like any other object. They typically take a string parameter, which you can use to give a descriptive message. Alternatively, you use this form:
raise MyError.new "Test"
raise MyError, "Text", caller
raise MyError, "Text"
raise "Text" # For the default RuntimeError

You can put a retry in a rescue chunk, as in the example above. The program will jump back to the start of the block, and start again. In the second rescue chunk, there is a raise on its own. This will pass the exception ouside of the block, to be handled by some higher up error handling system.

Also in that chunk, note that the $! code is the exception. If you prefer, you can set your own name for this.
rescue StandardError => err
puts "Here 4 - Other error encountered (#{err.inspect})" + caller.inspect
raise
Here is the output from the first example:
Here 1
Here 3 - MyError encountered
Here 1
Here 3 - MyError encountered
Here 1
Here 3 - MyError encountered
Here 1
Here 3 - MyError encountered
Here 1
Here 3 - MyError encountered
Here 1
Here 2
Here 5 - No errors
Here 6 - Always done

Stack Trace
To see the stack trace, use the backtrace method. This returns an array of strings. To just see the top dozen entries, you could use this:
$!.backtrace[0..12] * "\n"


Exceptions that do not inherit from StandardError
The superclass for all exceptions is Exception. The theory is that all exceptions that a program should be expected to recover from are either StandardError or a subclass of it, which is why I described them as "rescuable exceptions " earlier. If no exception is specified, rescue will default to StandardError.

However, there may be times you want to recover from other exceptions. A particular example I came across is a SyntaxError thrown by ERB. The offending syntax was in a data file, not my application; my application should have caught the error, and reported it back to the user. This is entirely different to a syntax error in my code, and in my opinion ERB should throw its own exceptions that inherit from StandardError. Time-out errors are another example, as discussed here.

To catch all exceptions use:
rescue Exception


The rescue statement modifier
Rescue can be used in a way analogous to if and unless. For example:
test rescue do_stuff

This statement will run the method test, and if it encounters an error (specifically StandardError or subclasses) it will invoke do_stuff. You can use this to assign a default value if a method fails, like this:
s = test rescue "Default value"

The rescue stament returns the value "Default Value". You could write it with brackets, which might help make sense of it:
s = (test rescue "Default value")

You can concatenate statement with semi-colons, but cannot use blocks.
s = test rescue do_stuff; "Default value"


See also:
http://whynotwiki.com/Ruby_/_Exception_handling

Catch and Throw
The catch and throw facility in Ruby is not really exception handling at all, though it does have similarities. As it shares keywords with Java exception handling, it seems to get lumped into any discussion on exceptions. This page is no different.

Calling a throw will interrupt the program flow, causing it to jump to the named catch. Use this format to set up a catch block. Any time throw :my_label is called within the block (including within methods called from the block).
catch :my_label do
do_stuff
throw :my_label
do_not_do this_stuff
end

You can use the throw to return a value to catch (which will be nil otherwise).
value = catch :my_label do
do_stuff
throw :my_label, "My result"
do_not_do this_stuff
end


Struggling with Ruby: Contents Page