Monday, November 26, 2007

Assigning custom data types to new columns during migration

The set of data types that you can assign to a new column during a database migration is pretty limited. However, it's easy to set a column to a data type specific to the database that you're using. Instead of specifying :string, :integer, etc for the data type, specify :"data type". As an example, if you have MySQL and you want to add an unsigned integer, there's no way to specify a :integer to be unsigned. But you can do this:
add_column :table, :newcolumn, :"int (10) unsigned"

Creating/deleting foreign keys during migrations

As far as I know, Rails doesn't use foreign key declarations in your database tables, and therefore has no way of creating and deleting foreign keys in migrations. If you want foreign keys to be added/deleted to/from your database during migration, you can do so by executing SQL statements directly during the migration. I won't go over the merits of having foreign keys, in many ways they are unnecessary with Rails but there may be reasons that you want to have them. I've created a mixin module for MySQL ONLY with two functions to add or remove a foreign key, here is the code:
# a mixin module for adding foreign keys to MySQL databases during migrations.
module ForeignKeyOps
# adds a foreign key to the table.
# Parameters:
# table_name - the name of the table to add to
# association_name - the name of the constraint to add to the database
# local_column - the column in table_name that contains the key
# foreign_table - the name of the foreign table to reference
# foreign_column - the name of the primary key in the foreign table
# options - an optional hash with additional parameters for the
# constraint. If the following hash values are present, then the
# additional constraints will be specified:
# - on_delete: specify "cascade" or "set null"
# - on_update: specify "cascade" or "set null"
def add_foreign_key(table_name, constraint_name, local_column,
foreign_table, foreign_column, options = {})
st = "ALTER TABLE #{table_name} ADD CONSTRAINT #{constraint_name} "
st += "FOREIGN KEY (#{local_column}) REFERENCES #{foreign_table} (#{foreign_column})"
if options.has_key?(:on_delete)
st += " ON DELETE #{options[:on_delete]}"
end
if options.has_key?(:on_update)
st += " ON UPDATE #{options[:on_update]}"
end
execute st
end

# removes a foreign key constraint from a table. This will NOT delete the
# column in the table, it only deletes the constraint
# Parameters:
# table_name - the name of the table to remove the foreign key contstraint
# from
# constraint_name - the name of the constraint on the table to delete
def remove_foreign_key(table_name, constraint_name)
st = "ALTER TABLE #{table_name} DROP FOREIGN KEY #{constraint_name}"
execute st
end
end
 
To use this, in add a require for this file to your migration file. Then inside of the migration class, add extend ForeignKeyOps to the beginning of the class.

As an example, say you want to add a new table to your database for phones, and each phone belongs to a user. Here is the migration code, using ForeignKeyOps:
require 'db/migrate/foreign_key_ops'
class AddPhones < ActiveRecord::Migration
extend ForeignKeyOps
def self.up
create_table :phones do |t|
t.column :account_id, :integer
t.column :name, :string
t.column :number, :string
t.column :brand, :string
end
add_foreign_key('phones', 'fk_accounts_phones', 'account_id',
'accounts', 'id')
end

def self.down
drop_table :phones
end
end
 
Again as a reminder this only works for MySQL.

Wednesday, November 21, 2007

Using select_tag to make a form select field

I had a difficult time trying to find documentation on how to use the select_tag method to generate a form select box, so I'll post here what I've discovered for how to use it. Here are the parameters:


<%= select_tag "name",
"select options",
html_options = {} %>

 
name is the id and name of the select tag. Select options can be any text, this will appear in between the <select> and </select>. However, for this to mean anything you'll want it to contain option tags. The options_for_select helper generates this for you. See the details of this in the next paragraph.

html_options is a standard hash of additional options to specify on the select tag (class, event handlers, etc.). Specifying a size will allow you to make the select element scrollable with each option in a row. If no size is specified, it is displayed as a drop-down list. The other unique option here is :multiple. Setting this to true will allow the user to select multiple options. If mulitple is set to true, the size defaults to the number of options. By default, multiple is false.

Now, back to options_for_select. The syntax is:


options_for_select(array_of_options,
array_of_selected_values = [])

 
array_of_options is an array containing the selectable options. Each element of the array can either be a string or another array. If it's a string, an option is generated whose value and displayed text are the same. Example - "1" would generate <option value="1">1</option>. If the element is an array, it must be a two element array, with the first element of this array being the text to display, and the second element being the value of the option. Example = ["value 1", "1"] would generate <option value="1">value 1</option>.

array_of_selected_values is an array containing all values that you want to be selected when the page loads, if multiple selection is allowed (see html_options). Each element should contain the option value to be selected, not the text displayed.

So, let me give an example. The Rails ERb


<%= select_tag "test",
options_for_select([["value 1", "1"],
["value 2", "2"], "3", "4"],
["1", "3"]), {:multiple => true} %>

 
generates the following HTML:

<select id="test" multiple="multiple" name="test">
<option value="1" selected="selected">value 1</option>
<option value="2">value 2</option>
<option value="3" selected="selected">3</option>
<option value="4">4</option></select>

 
This will generate a selection box 4 rows long, with 1 and 3 selected.

Note that you can also put integers as option values. However, if you do, the selected options must also be integers. If you have an option with a value of 1 (integer), but put "1" in the selected options array, option value 1 will not be selected.

Saturday, November 17, 2007

Putting DIV inside of DL breaks Internet Explorer DOM

While writing a page that extensively uses definition lists (dl), I came across a pretty bad problem with Internet Explorer (6 and 7). Each dl must be completely contained in one div, you cannot put a div inside of the dl. If you do, all of the style information from all DIV's above the DL will be lost when you close the DL. Here's an example:
<div style="font-weight: bold;">
<dl>
<dt>Term 1</dt>
<dd>Definition 1</dd>
<div style="text-align: center;">
<dt>Term 2</dt>
<dd>Deinition 2</dd>
</div>
<dt>Term 3</dt>
<dd>Definition 3</dd>
</dl>
</div>

This page will render fine in Firefox - 1 and 3 are bold but not centered, and 2 is bold and centered. Things are different in IE however. 1 is bold and not centered (correct). 2 is bold but not centered (incorrect) even though the div explicitly sets centering, and 3 is not bold (incorrect), even though it is within the DIV tag specifying bold. To get DIVs and DLs to work correctly in IE, you cannot put a DIV inside of a DL. You must make a separate definition list for every div element. Here is the above example changed to work in IE:

<div style="font-weight: bold;">
<dl>
<dt>Term 1</dt>
<dd>Definition 1</dd>
</dl>
<div style="text-align: center;">
<dl>
<dt>Term 2</dd>
<dd>Deinition 2</dd>
</dl>
</div>
<dl>
<dt>Term 3</dt>
<dd>Definition 3</dd>
</dl>
</div>

This isn't that much of a pain to do, it just took some hair pulling on my part to figure out why things were all messed up in IE. If you know of a different way to do this, or know why IE behaves this way, please post a comment.

Ruby In Steel, Ruby on Rails add on for Visual Studio

If you're a Windows Ruby on Rails developer, and you already have Visual Studio, there's an awesome add on for Visual Studio to allow Ruby on Rails development from within the Visual Studio IDE called Ruby In Steel (http://www.sapphiresteel.com/). Note this isn't a Microsoft product. It gives you the full power of Visual Studio for developing RoR apps. You can set break points (even in rhtml and rjs files), mouse over things to get their values, get popups with all of the functions that classes provide, and just about everything else that you are used to from doing C# and other .NET development. This, combined with Firebug (https://addons.mozilla.org/en-US/firefox/addon/1843) to debug Javascript on the client end, gives you a full breakpoint, code step through functionality in a GUI environment for RoR. Oh, and as a pure text editor it's awesome too, since you get the full power of Visual Studio for highlighting, snippets, etc. It works great for writing Ruby scripts too.

Before using this, I was using RadRails, and to me there's no comparison between the two. If you already have Visual Studio 2005 or higher I would highly recommend this, it makes developing RoR apps so much better. I'm not sure if it would be worth getting Visual Studio just to get this addon, but if you already have it I don't think there's another IDE for RoR that's comparable.

Does anyone else know of any other Windows RoR IDEs? What are your thoughts on them?

Welcome!

Welcome! This is the first posting for my blog. I'll be posting things that I've learned while developing Ruby on Rails web apps. The posts won't be limited to strictly Ruby on Rails, I'll also post stuff for HTML and Javascript. Post a comment for this blog if you want to get in touch with me.