Friday, February 8, 2008

How to dynamically update the contents of one select list from selections in a different list

Rails support for making AJAX requests is great, it's very easy to make simple requests perform actions on the page in the background. A common scenario where you might want to use AJAX to make your pages more dynamic is to repopulate a select list in a form when the user modifies other elements on the form. The built in capabilities of Rails seem to have some problems dealing with select boxes where multiple items can be selected while doing these AJAX requests, however.

I will provide a simple case here where you have two select boxes - one is a list of vehicle types, and the other is a list of vehicles. Instead of having one huge list of all vehicles, you might want the list of vehicles to only show vehicles that are of the selected types. Using AJAX updaters form observers, you can dynamically update the vehicle list based on the type list being changed.

For this to happen, you need to place observe_field Rails helpers in your view for the page, to observe for changes to the field. Typically you can specify here what the URL of the action to call is, but observe_field has some trouble sending parameters for select boxes with multiple selections, so it will instead call a Javascript function. In the Javascript function (which isn't necessary if you aren't using a select multiple), you need to manually make create an Ajax.updater to update the "results" selection box items, creating the query string dynamically based on the selected types. Then you need an action in your controller to respond to the Ajax request, and a view for this action to fill in the correct values to the select box.

I created a simple example with a page where you can select what types of vehicles you want displayed (car, SUV, or truck), and whenever you change the type, the list of vehicles will be updated.

First, add two new actions to your controller. The first is the regular action to get the page for the first time, which I'll call show_list. Within this action, the list of types (from the constant TYPES) gets put into a variable for later loading. The second action, update_list, is what the AJAX request will call to update the vehicle list with selected types. Here is the code for the controller:
# these are just some constants.  Most likely you'll want to find things 
# in your database to return.
TYPES = {"car" => [["Honda Accord", "1"], ["Scion Tc", "2"],
["Ford Focus", "3"], ["BMW M6", "4"]],
"suv" => [["Jeep Wrangler", "5"], ["Ford Explorer", "6"],
["Toyota Highlander", "7"]],
"truck" => [["Ford F150", "8"], ["Dodge Ram", "9"]] }

# this is the action to get the page for the first time.  
def show_list
@types_all = [["Car", "car"], ["SUV", "suv"], ["Truck", "truck"]]
end

# this is the action that the AJAX request will call.  types[] will be
# specified in the parameters list, containing the different vehicle
# types that the user has selected.
def update_list
@vehicles_all = []
if params[:types]
params[:types].each do |t|
@vehicles_all = @vehicles_all.concat(TYPES[t])
end
end
# this part is really important, if you don't tell it not to render the 
# layout, the area where your selection box is will have the whole 
# controller's layout around it.
render :layout => false
end


Next, create a new rhtml file in your controller's view folder called show_list.rhtml. This is the initial page that gets loaded:
<% # this is a function I wrote in my application controller to load a
# Javascript file.  show_list.js must be loaded.
add_javascript_file("show_list", false) -%>

<% # I don't actually have anything to respond to this form, it's just to show
# select boxes.
form_tag({}, {:name => "show_list", :id => "show_list"}) do -%>

<p>Vehicle Types:
<%= select_tag "types[]", options_for_select(@types_all, []),
{:size => 3, :multiple => true, :id => "types[]"} %>
</p>
<% # the observe_field must call a function and not directly call a method
# on the controller.  Select form elements with multiple elements do not
# get correctly passed as parameters to the controller action, so we
# have to manually do it in a Javascript function.
# with any other type of field (including single selects), you can
# specify :url as a parameter and it will call that url.
# if :frequency is not present, a request will be made every time the
# selection is changed.  This isn't recommended, because the user may
# very quickly change selections.  0.5 ensures that a request is made
# at most every half second while the user is changing the selection.
-%>
<%= observe_field "types[]",
:function => "typeChanged('vehicle_list', $F('types[]'))",
:frequency => 0.5 %>

<% # the hourglass will show while the browser is waiting for a response
# from the server, to let the user know something is going on.
-%>
<div style="display:none; text-align:center;" id="hourglass">
<%= image_tag "ani-busy.gif", {:width => 32, :height => 32} %>
</div>

<p>Vehicles from selected types:
<% # the whole select tag must be in a div.  This is what will be updated.
# If the browser is Firefox, you can put the select tag ID as the area
# to update in the AJAX call, and return a list of options in your rhtml,
# but this will NOT work in Internet Explorer.
-%>
<div id="vehicle_list">
<%= select_tag "vehicles[]", "", {:size => 9, :multiple => true} %>
</div>
</p>

<% end -%>

Next, create another new rhtml file in your controller's view folder called update_list.rhtml. This is what gets sent to the specific div section that gets updated whenever the type selection list gets updated:
<% # render just the select tag here
-%>
<%= select_tag "vehicles[]", options_for_select(@vehicles_all, []),
{:size => 9, :multiple => true} %>

Finally, add a javascript function to a file that gets loaded with show_list.rhtml:
function typeChanged(areaToUpdate, typesToSend) {
// Manually create a request string with parameters
var queryStr = "/node/update_list";
var started = false;
if (typesToSend) {
for (var i = 0; i < typesToSend.length; i++) {
// the first type must have ? before it to start the parameters list.
// after that, it should be prepended with & to specify an additional
// parameter.
queryStr += (started ? "&" : "?") + "types[]=" + typesToSend[i];
started = true;
}
}

// before we send the request, show the hourglass.
$('hourglass').show();

// manually send an Ajax request to update the area passed in to the
// function.  Upon completion, hide the hourglass.  You can also specify
// functions to run for onError, onSuccess, and many others.
new Ajax.Updater(areaToUpdate, queryStr,
{onComplete: function() { $('hourglass').hide(); } });
}

This may seem like a lot to add for a simple page, but it's very easy to set all this up and work with much more complicated scenarios.

Thursday, February 7, 2008

Binding a Ruby TCPSocket to a local address and/or port

The Ruby TCPSocket class is a really easy to use class for making outbound connections and sending/receving data. But there's no way to bind to a local address or port for making the connection. The Socket class, which TCPSocket inherits from, has a bind call, but the constructor for TCPSocket immediately attempts to establish a connection using the default any port and any address. So it wouldn't do any good to bind on TCPSocket, since the connection is already made when you create it.

If you need to bind to a local address, you'll have to create a Socket object. The code below will bind to a port and address, and then attempt to connect. After that point, you can read from or write to the socket just like you can with TCPSocket. I'm not sure how to specify any address, or localhost, for in_addr. If anyone knows how to do that, please post here.

require 'socket'

# this is the address to bind to.  The first argument is the port, the second
# is the IP address (a string).  Pass 0 for the port to not bind to a specific
# port (the port will be auto assigned by the OS when a connection is made).
# The address MUST be an actual external address of the computer - 127.0.0.1 
# won't work.
in_addr = Socket.pack_sockaddr_in(12345, "10.10.1.3")

# for the outbound address, you must specify both a port and address
out_addr = Socket.pack_sockaddr_in(80, "www.google.com")

s = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
s.bind(in_addr)
s.connect(out_addr)

# from this point you can read from and write to using the standard Socket
# and IO methods.

s.close

Sunday, January 20, 2008

Picture of mine used on Schmap.com

A picture that I took in Montreal in August 2007 and posted on Flickr has been selected to be used by the online guide Schamp.com for their Montreal city guide! Check it out at
http://www.schmap.com/montreal/sights_historic/. Click Bonsecours Market, and cycle through the pictures on the right to find mine.

Tuesday, January 15, 2008

New version of Ruby In Steel without Visual Studio license

The folks at SapphireSteel Software have released a new version of Ruby In Steel, called Text Edition, that doesn't require you to already have a Visual Studio license, and it's only $49 at this time!

For those who don't know about it, Ruby In Steel is a full Ruby and Rails development platform for Windows with real time debugging (breakpoints, mousing over and typing variables to see their values), syntax error detection, color coding, indenting, and anything else that you'd want in a full featured IDE. Check my previous blog entry, Ruby In Steel, Ruby on Rails add on for Visual Studio, for a better description. Previously, Ruby In Steel was an add on for Microsoft Visual Studio, you already had to have a Visual Studio license for this to work.

This new version comes with a free version of Visual Studio 2008 that it uses behind the scenes, but you can only do Ruby and Rails development with it. They still sell the Visual Studio add on. The main differences are that the add on version has a faster debugger, and uses the Intellisense libraries that show possible values for objects and attempts to highlight incorrect syntax. If you already have Visual Studio, I'd still recommend the regular version, but if the price is too much the Text edition will still work great. The Intellisense in the full version still doesn't work too well with Rails code (but what can you expect since it's not strongly typed), but it does work pretty well for strict Ruby code. But if you don't have Visual Studio, the Text edition provides a very powerful full featured Ruby and Rails IDE for a great price, I would highly recommend it! You can get it at http://www.sapphiresteel.com/.

Wednesday, January 9, 2008

Exception_notification plugin for automated exception emailing

I found a really useful plugin called exception_notification that I've been using on my servers for several months now. It will send emails to addresses that you configure any time an exception is generated on your server, if the server is in production mode. The email contains the full details of the exception, including the call stack. It's almost as useful as the error page that gets displayed for exceptions in development mode.

The plugin has helped me discover and fix a whole bunch of obscure errors that I never would have discovered on my own or through users complaining.

This may not be appropriate for a server where you have a large volume of users. You probably wouldn't want to get thousands of emails of the exact same error a day especially if it's a bug that you already know about. But it works great for servers with a low volume of users, or for internal test servers. For a test server, your testers won't need to record the details of the error and send it to you separately, it will automatically be sent to you!

To get started, run
script/plugin install exception_notification

Then edit your config/environment.rb file to include the following line, specifying the email addresses to send exceptions to:
ExceptionNotifier.exception_recipients = %w(email_recipient_1@address.com email_recipient_2@address.com)

Another useful option you can add to environment.rb specifies who the sender of the exception should appear as:
ExceptionNotifier.sender_address = "\"App Error\" <apperror@domain.com>"

Then add this to your application controller if you want all actions in your application to generate exception emails.
class ApplicationController < ActionController::Base
include ExceptionNotifiable 

You can also include this only on the controllers that you want to generate exception emails.

The full source for this is at http://dev.rubyonrails.org/browser/plugins/exception_notification. Read the README file there for more detailed information on configuring it.

Thursday, December 20, 2007

AES encryption and decryption in Ruby

AES encryption and decryption in Ruby is very simple, although I had a hard time finding documentation on how to do it. Ruby has a wrapper over OpenSSL, but I had trouble finding documentation on how to use OpenSSL as well. I found a pretty good piece of code at http://snippets.dzone.com/posts/show/576.

Using this and other information that I found, I created a module for encrypting and decrypting blocks of data. It isn't just limited to AES encryption and decryption, since OpenSSL supports many other types of encryption, but I only use it for AES 256. To get a list of the types of encryption that OpenSSL uses (and what the string would be that you pass in to my functions as cipher_type), open a command prompt, type openssl, then help, and the cipher types are listed under cipher-commands. The two types for AES 256 encryption are "AES-256-CBC" and "AES-256-ECB". Here is my module:


require 'openssl'

module AESCrypt
# Decrypts a block of data (encrypted_data) given an encryption key
# and an initialization vector (iv). Keys, iv's, and the data
# returned are all binary strings. Cipher_type should be
# "AES-256-CBC", "AES-256-ECB", or any of the cipher types
# supported by OpenSSL. Pass nil for the iv if the encryption type
# doesn't use iv's (like ECB).
#:return: => String
#:arg: encrypted_data => String
#:arg: key => String
#:arg: iv => String
#:arg: cipher_type => String
def AESCrypt.decrypt(encrypted_data, key, iv, cipher_type)
aes = OpenSSL::Cipher::Cipher.new(cipher_type)
aes.decrypt
aes.key = key
aes.iv = iv if iv != nil
aes.update(encrypted_data) + aes.final
end

# Encrypts a block of data given an encryption key and an
# initialization vector (iv). Keys, iv's, and the data returned
# are all binary strings. Cipher_type should be "AES-256-CBC",
# "AES-256-ECB", or any of the cipher types supported by OpenSSL.
# Pass nil for the iv if the encryption type doesn't use iv's (like
# ECB).
#:return: => String
#:arg: data => String
#:arg: key => String
#:arg: iv => String
#:arg: cipher_type => String
def AESCrypt.encrypt(data, key, iv, cipher_type)
aes = OpenSSL::Cipher::Cipher.new(cipher_type)
aes.encrypt
aes.key = key
aes.iv = iv if iv != nil
aes.update(data) + aes.final
end
end

Thursday, December 6, 2007

Assigning values to has_many through relationships

I found out the hard way today that you can't directly assign values to a has_many :through relationship like you can with a has_and_belongs_to_many relationship. First let me explain the two approaches for many to many relationships if you're not sure what they are.

Say you have a "group", and groups can have any number of accounts. Accounts can also belong to any number of groups. You can accomplish this two ways. First, create a join table called accounts_groups, with no id, and two primary keys, account_id and group_id. Then simply put this one line in the group class
has_and_belongs_to_many :accounts

and this in the account class
has_and_belongs_to_many :groups

The downside of this approach is that you can't have additional information about the association (like number_of_logins, etc.). If you don't need additional information about the association, I think has_and_belongs_to_many (habtm) is the better approach (less code, less complicated), even though this seems to have fallen out of favor in the Rails community.

If you do need information in the association, or you just want to use the Rails community's preferred approach, first create a join table named memberships, with a primary key id, account_id, and group_id. Then add a class named Membership:
class Membership < ActiveRecord::Base
belongs_to :account
belongs_to :node
end

Add this to your group class:
has_many :memberships
has_many :accounts, :through => :memberships

And this is your account class:
has_many :memberships
has_many :groups, :through => :memberships

One downside of has_many :through is that you can't assign directly to the relationship. For the above example, say you had an array of accounts that you want to assign to a group. You can't code
@group.accounts = array

But you can with habtm You can also assign an array of id's to a has_and_belongs_to_many relationship by using relationship_ids = array. Note that it's the singular form of the relationship in the name of the function. One more reason to use habtm.

BUT I found a really good function to allow you to assign values to has_many through relationships at the Grinding Rails blog (http://tuples.us/2007/05/09/metaprogramming-has_many-through-accessors/). It's really useful when using has_many through relationships.