I posted in my previous entry (Timestamp based migrations in Rails 2.1) about the new UTC timestamp based migrations in Rails 2.1. After working with them a little more, I found how to apply or remove individual migrations. Simply run rake db:migrate:up VERSION=YYYYMMDDHHMMSS to apply a single specific migration, or rake db:migrate:down VERSION=YYYYMMDDHHMMSS to remove a specific migration.
However, I found a bug with db:migrate:down as noted at http://rails.lighthouseapp.com/projects/8994/tickets/369-db-migrate-down-does-not-remove-migration-version-from-schema_migrations-table. When you run this, the self.down function of the migration is called and the database is changed. However, the entry in schema_migrations for this migration is not removed. So if you run rake db:migrate later, this migration will not be applied because Rails thinks that the migration has already been applied. My work around for this is to manually delete the row in schema_migrations after I run db:migrate:down.
Monday, June 16, 2008
Wednesday, June 11, 2008
Timestamp based migrations in Rails 2.1
One really cool feature added to Rails 2.1 is UTC timestamp based migrations. Now, when you generate a new migration, instead of the migration number being sequential, it's a UTC timestamp. That way if you're working on different branches of code, you don't need to coordinate between the braches who gets what version number.
But the best part of this is how the migrations are tracked. Instead of just keeping a current database schema number like before, Rails keeps track of each migration that has been run. Then when you run rake db:migrate, Rails only applies the migrations that haven't been run, they don't have to be in order.
As an example, say I added a migration two days ago called add_new_column with script/generate migration. 20080610012942_add_new_column.rb gets generated. My co-worker John is working off of another code branch, and yesterday he added a new migration called change_accounts. 20080610225643_change_accounts.rb gets generated. Then today I add a new migration called delete_state. 20080612020139_delete_state.rb gets generated. I have two migrations, and John only has his one. When I run rake db:migrate, my two migrations get applied, and when John runs it his one migration gets applied. When we sync our code branches, we get each others migration files. Even though John's migration was created AFTER the first one that I created, when he runs migrate, both of my migrations will get added, and his migration that was already added will not get run again. And when I run migrate, only John's migration will get added, even though it was generated before the last migration that I ran.
The way Rails does this is that the first time you run a migration with Rails 2.1, a new table gets created called schema_migrations. An entry is added to this table for each migration that gets run. The old schema_info table is deleted. That way any time you run a migration, it checks each migration file against the database to see if it's been applied to your database yet.
This feature is already proving very useful for me. I no longer have to have migrations completely syncronized between myself and the other developer working on my project. We each work on our own branch, and we merge our migrations together when we're ready to merge all of our code.
A good Railscast video showing this is up at http://railscasts.com/episodes/107.
But the best part of this is how the migrations are tracked. Instead of just keeping a current database schema number like before, Rails keeps track of each migration that has been run. Then when you run rake db:migrate, Rails only applies the migrations that haven't been run, they don't have to be in order.
As an example, say I added a migration two days ago called add_new_column with script/generate migration. 20080610012942_add_new_column.rb gets generated. My co-worker John is working off of another code branch, and yesterday he added a new migration called change_accounts. 20080610225643_change_accounts.rb gets generated. Then today I add a new migration called delete_state. 20080612020139_delete_state.rb gets generated. I have two migrations, and John only has his one. When I run rake db:migrate, my two migrations get applied, and when John runs it his one migration gets applied. When we sync our code branches, we get each others migration files. Even though John's migration was created AFTER the first one that I created, when he runs migrate, both of my migrations will get added, and his migration that was already added will not get run again. And when I run migrate, only John's migration will get added, even though it was generated before the last migration that I ran.
The way Rails does this is that the first time you run a migration with Rails 2.1, a new table gets created called schema_migrations. An entry is added to this table for each migration that gets run. The old schema_info table is deleted. That way any time you run a migration, it checks each migration file against the database to see if it's been applied to your database yet.
This feature is already proving very useful for me. I no longer have to have migrations completely syncronized between myself and the other developer working on my project. We each work on our own branch, and we merge our migrations together when we're ready to merge all of our code.
A good Railscast video showing this is up at http://railscasts.com/episodes/107.
New vacation pics on Flickr
Check out my Flickr page at http://www.flickr.com/photos/valenshek/. I just posted pictures, mapped ofcourse, from my recent vacation to Spain!
Wednesday, March 19, 2008
Dynamically changing img src doesn't always show new image with Internet Explorer 6
When you dynamically change the source of an image with Javascript, Internet Explorer 7 and Firefox will display the new image fine. But sometimes, I believe when the change was triggered from an event, the image won't display in Internet Explorer 6 after the change. A blank area will appear where the image should be (if you specify a width and a height for the image), and if you right click on the area and click Show Picture, the image will display. I really have no clue why IE6 behaves this way. I found a posting at http://blogmatrix.blogmatrix.com/:entry:blogmatrix-2006-10-15-0000/ that lists two solutions. A third solution, described in Comment #1, is the easiest solution though. Simply return false in the event that triggers the change. Example:
Say you have an onClick event in a link to change a different image on the page. Your code should be:
The return false will make the image always display in IE6.
Say you have an onClick event in a link to change a different image on the page. Your code should be:
<a href="javascript:void(0)" onClick="changeImage(); return false;">Change Image</a>
The return false will make the image always display in IE6.
How to disable the image toolbar for images in Internet Explorer 6
By default, when viewing an image on a page with Internet Explorer 6, a little toolbar will appear in the upper left corner when you hold your mouse over the image for a few seconds. This toolbar really gets in the way if you want users viewing your page to do things with the image, like capture mouse clicks, drag the image, etc. Thanks to http://www.thesitewizard.com/webdesign/imagetoolbar.shtml, I found two ways to disable this. To disable the image for a whole page, add a meta tag to your header:
<meta http-equiv="imagetoolbar" content="no" />
Or to disable this on a per image basis, add:
galleryimg="no"
to the img tag.
<meta http-equiv="imagetoolbar" content="no" />
Or to disable this on a per image basis, add:
galleryimg="no"
to the img tag.
Wednesday, March 12, 2008
Assigning custom data types to new columns during migration, part 2
In my previous article, Assigning custom data types to new columns during migrations, I showed how you can create columns with custom data types during a database migration. Simply specify :"data type", instead of :integer, :string, :binary, etc. An example would be :"smallint", since there is no direct way to create a MySQL smallint during a Rails migration.
However, I recently found that this doesn't always work. It works fine if you are creating a new table in your migration. But this does NOT work if you are adding or modifying a column in an already existing table, you get the error:
rake aborted!
You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.[]
Has anyone else run in to this problem? It's annoying to have to construct SQL statements to add columns, after all that's what migrations are supposed to get rid of.
However, I recently found that this doesn't always work. It works fine if you are creating a new table in your migration. But this does NOT work if you are adding or modifying a column in an already existing table, you get the error:
rake aborted!
You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.[]
Has anyone else run in to this problem? It's annoying to have to construct SQL statements to add columns, after all that's what migrations are supposed to get rid of.
Labels:
custom data type,
migration,
rails,
ruby
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:
Next, create a new rhtml file in your controller's view folder called show_list.rhtml. This is the initial page that gets loaded:
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:
Finally, add a javascript function to a file that gets loaded with show_list.rhtml:
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.
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.
Labels:
ajax,
dynamic,
rails,
ruby,
update select list
Subscribe to:
Posts (Atom)