Put your message here! Contact me for more information
 
 







 

Archive for the ‘Web Scripting’ Category


 

I was implementing an Ajax feature for a project and felt the need to display messages (progressing, savings prompt, etc.) to the users visibly and unobtrusively. Usually, to indicate a background activity, I tend to hide the link/button/checkbox and show a spinning indicator when the Ajax request was created, then return the link when the request has completed. In this case, there is no user-controllable submit button. It is a photo album page which allows users to rearrange their photos by simply dragging-and-dropping the photos into the correct positions. The positions are saved automatically on the fly. If I am to implement a typical spinning indicator, I have to make sure that it is visible enough for the user to know that the positions are being saved. However, if the user is at mid-page, any indicator at the top section of the page won’t be visible. If I place the indicator at the bottom of the page, I still run into the same situation. My solution is to implement a smart “Prompt” bar that stays affixed to the screen. With Prototype and Scriptaculous, implementation is a breeze.

Show, don’t just tell
Here is a demo which you can try out.

Details
This is made possible by using the “position:fixed” feature of CSS. According to W3Schools,

position: fixed
An element with position: fixed is positioned at the specified coordinates relative to the browser window. The element’s position is specified with the “left”, “top”, “right”, and “bottom” properties. The element remains at that position regardless of scrolling. Works in IE7 (strict mode)

The markup
Here is the HTML for the prompt bar. You can place this HTML anywhere in the page. Ideally it should be outside any container.

Some Prompt Here
Cross

CSS

#prompt
{ position: fixed; width: 400px; height: 20px; border: 1px solid #7F7F7F;z-index: 9999; background-color: #F3FF8F; }

#prompt_message
{ float: left; text-align: center; width: 380px; font-weight: bold;}
#prompt_close
{ float: right; width:16px;}

We have to set the prompt bar’s position to be fixed so that it will stay in place when we scroll the browser window.

The script
The javascript code is fairly simple, since Prototype and Scriptaculous already do the all the heavy lifting (e.g. positioning, animation). The only catch with position:fixed div is that we cannot center the prompt div using CSS. We have to explicitly set the left coordinate before we display the bar (line 16, _center() method). This is to also useful when the user resizes the browser window, we recalculate the position and center the div accordingly.

    var Prompt = {
      autoHideTime: 5000

      ,display: function( msg, options ) {
        $('prompt_message').innerHTML = msg;
        Prompt.show();
        if( options && options.autoHide )
          setTimeout('Prompt.hide()', options.autoHideTime || Prompt.autoHideTime );
      }

      ,working: function(msg, options) {
        msg = '  ’ + msg;
        Prompt.display( msg, options );
      }

      , _center: function(){
        $(’prompt’).style.left = ( ( document.viewport.getWidth() - parseInt( $(’prompt’).offsetWidth ) ) / 2 ) + ‘px’ ;
      }

      ,show: function(){
        Prompt._center();
        new Effect.Move ($(’prompt’),{ x: parseInt( $(’prompt’).style.left ), y: 0, mode: ‘absolute’, duration: 0.2 });
      }
      ,hide: function( msg) {
        new Effect.Move ($(’prompt’),{ x: parseInt( $(’prompt’).style.left ), y: -30, mode: ‘absolute’, duration: 0.1 });
      }
    }

Since the code is simple enough, I’d like to leave the details to you to work out.

Enjoy!

Download

I packaged the demo as a zip file so you can grab it and have some fun. Cheers!

view comments
 

I was hacking the acts_as_taggable_on_steroids plugin to support different categories for tags and taggings and I bumped right into the issue of how to have dynamic conditions for the has_many association. The problem with associations in general is that they are CLASS METHODs, e.g. they are run when the class declaration is being loaded into memory at runtime. However, our condition has to be evaluated at evaluation time. This means if we write (psuedo-ruby code lifted from my hack on the the acts_as_taggable_on_steroids Tag model)
class Tag
has_many :taggings, :conditions => “taggings.category_id= #{self.category_id}”
end

this will very well error out on us.  Since, at class loading time, Ruby has no idea about the particular category_id attribute of the “self” instance of the Tag object. On his blog, Dweedb proposed a solution of creating a new souped-up has_many association, e.g. has_many_with_args. I wasn’t convinced about this solution since it means I may very well end up with writing/extending every association within Rails with something else if I need to support some special dynamic conditions.

I was playing with lambda function (e.g. :conditions => Proc.new { “some_clever_dynamic_conditions_here”} ) but for some reasons I kept getting the “calling private gsub method” error for the Proc when ActiveRecord was interpolating (constructing?) the SQL.  Nonetheless, I got an important hint from the Dweedb’s post about the use of single quote on the :conditions to keep the it as string literal and delay the evaluation of the conditions till, well, evaluation time. “Excelente!”, as my friend Javier would shout in Espanol!

The solution is made possible by the use of the native Ruby #send(:action, *args) method to read the attribute of the model instance. Since if we wrap the conditions around the single quotes, when the model class is being loaded to the Ruby VM, everything still remains as-is. Only at runtime, when ActiveRecord is busy substituting the conditions string into the generated SQL statement, it inadvertently executes the code inside, which then invokes the send() method on the current instance to return the value of the attribute (You know what just popped into my mind? HOLY SMOKE! IT IS CODE-INJECTION through SQL-INJECTION!)

The updated code for the new Tag model is

has_many :taggings, :conditions => ‘#{Tagging.table_name}.category_id = #{self.send(:category_id)}’

Anyway, I was able to get all the tags and their associated taggins from the same category, dynamically and hacklessly. With this approach, any conditions for the various associations can be dynamicalized using single quotes and the (god)send method. Hurray!

view comments
 

PrototypeJs LogoThe recent release of Prototype 1.60RC1 inadvertently breaks the drag and drop effect of Scriptaculous in IE6 (and 7). I don’t know about the exact cause of the error but according to sources around the internet, it’s because Prototype always returns false for the leftClick event in IE6. Luckily, the trunk version of Prototype has this bug patched (since revision 7954).

To fix the draggable, just patch your prototype.js file, replacing the code block “Event.method” around line 3686 with the newer code block from “event.js” in the prototype main trunk.

For your convenience, here is the patched prototype.js (release 1.60RC1) with fix from Event.js (rev 8037)

  • Download: the patched prototype.js for Scriptaculous Draggable
view comments
 

RailsI’ve just released ActiveFixture, an enhancement to the Rails db:fixtures:load rake task. Currently db:fixtures:load doesn’t take into account the foreign key constraints hence with any tables that have foreign keys defined, db:fixtures:load would just fail miserably. As Rails is increasingly getting more into the enterprise world, the ability to handle foreign key correctly becomes more important.

I flew to New York last weekend for the VSLive! Conference. While attending VSLive! talks during the day, my mind was mostly occupied with how to solve this foreign key issue at night. After 2 days and trying multiple ways and still unable to solve the problem, I suddenly remembered about graph (good old CS classes). I turned the problem of determining the order of loading fixtures files into a graph problem of how to traverse the graph in a certain order. Breadth-first-search doesn’t give the correct answer, Depth-first-search seems to have some potentials. When I tried Depth-first-search Post order traversal, suddenly I realized that DFS post order gives the perfect solution. What’s even more beautiful is the entire traversal algorithm was only a couple lines of Ruby code as you can see from the source.

I’ve learned quite a bit about Rails and Ruby while writing ActiveFixture. I poked around the core ActiveRecord classes and see how fixture is being handled, I then learned how to use reflection to get meta data from the ActiveRecord models, then how to write rake task and componentize everything into a plugin. In the end, I feel I like working with Ruby and Rails even more. .NET is cool but somehow, to me, Ruby is more fun to work with, except for the fact that there’s no GUI debugger. I would say I would have been able to write ActiveFixture in much less amount of time had I had a Rails/Ruby IDE with an interactive debugger.

The next time I write something for Rails, I would probably give Ruby in Steel a shot. It’s quite frustrated when you are exploring Ruby, especially when someone like me isn’t that familiar with Ruby. I love the Watch and Intermediate Window in Visual Studio simply because I can hit a breakpoint and examine the application and all the variables at that moment. Currently we don’t have anything similar for Ruby and Rails for free. Ruby in Steel seems to have potentials, but $199 is quite a barrier to entry to any ruby-rails-fan. In fact, one of the main question that my boss at work asked me about Ruby on Rails, is if Ruby has any good IDE. My company is a Microsoft shop so we use Visual Studio extensively. It’s hard to convince someone who code in Visual Studio for a living to switch to a notepad-like editor (”no Intellisense, no thanks”) and start learning Ruby on Rails. Personally I use (and pay for) e-editor, however, I do miss the intellisense and the ability to hit F5 anytime to kill that freaking bug…Okay, enough of my ranting.

Anyway, here’s the good news in short: ActiveFixture is released and ready for abused. Current version is under-tested (I haven’t written any unit tests yet) and may contain bugs. For Wars of Earth, I have about 20 tables with a whole bunch of foreign key constraints (with circular references too) so I’m quite confident that ActiveFixture will work for you.

Download:

  • SVN: http://activefixture.rubyforge.org/svn/

Install:

  • Copy into the RAILS_ROOT/vendors/plugins
  • As always, RTFM. See README for more information. I wrote a little explanation on how ActiveFixture works so enjoy :) It’s a nice and elegant algorithm to solve a tricky problem.

Usage:

  • Make sure you have all the required yml/yaml/csv files
  • Defined all belongs_to relationships inside your models
  • Run
rake db:fixtures:activeload

And Support:
Please vote for ActiveFixture to get included in Rails core. I’ll have even more incentives to write even more useful Rails plugins.

keywords spam: db:fixtures:load, db:fixtures:activeload, rails foreign key constraints fixtures, fixtures loading order

view comments
 


{{http://alexle.net/wp-content/uploads/2007/06/rails.thumbnail.png }}Working on Wars of Earth by myself, I run into several situations when I’d like to move migration files around to get them executed in a particular order. The trick is simple: just rename the sequence of the migration files to get them to be executed in the order you want.

Besides sloppy schema design (hey, I’m trying to be agile here - fix it as you go), an example of a situation when you want to move migration files around: table A has a foreign key to table B. Migration file for A is at 005, while that of B is at 010. If you try to put in the FORIEGN KEY constraint in A migration file after the table creation, migration will fail since table B hasn’t been created yet, not until migration #010. You can add another migration file just to add the FORIEGN KEY constraint from A to B, say at #020. But then you will have fragmented migration code all over the place. For production environment, this is the only solid and proven way to perform database changes. However, during development, you have the luxury to drop and recreate the entire database from scratch, it’s just a lot more convenient and makes more sense to be able to move migration files around. Ideally, you just need to run migration for B first, then you can run migration for A, which also execute the SQL to add in the FOREIGN KEY constraint properly.

Until now, you have to it manually. Imagine if you have 20 migration files in between, from 5 to 25 (like what I have), you will probably have to renumber 20 migration files. And if your project is in SVN, it would be more time-consuming and error-prone. You can either do “svn rename”, or just rename in the shell, then deleted the previous migration files and added the newly-named ones back into the repository. But what would happen if you yawn for a second and misnumber a file at #6, so you have two 006 migration files. Oops.

Worry no more, here comes **numergrate** utility to the rescue. In short, numergrate is “to numerate migration files” (or to renumber migration files)

What you need is to drop in the numergrate script (download below) inside your Rails’ script folder and you are ready to go. With this utility, you can execute at the root of the Rails project

ruby script/numergrate 5 before 25
to (test) move 005 to position 024 instead.

or

ruby script/numergrate 10 after 2 --svn
to move 010 before 002 and also rename the files in the Subversion repository.

I also took the opportunity to integrate the utility with Subversion in case your migration files are in a repository. There’s no easy way for CVS so I didn’t implement it. However, implementing renaming mechanism for other SCM should be straight forward if you know their command line renaming tool.

I hope this comes handy for you.

==== Supported tools ====

* {{http://alexle.net/wp-content/uploads/2007/06/subversion_logo.thumbnail.jpg}} Subversion (renaming files in repository)
* {{http://alexle.net/wp-content/uploads/2007/06/utilities-terminal.png}} Shell (just like renaming manually)

==== Script ====

#!/usr/bin/env ruby
# (c) 2007 Alex Le
# www.alexle.net - nworld3d@yahoo.com
# This script is developed for www.warsofearth.com. (shameless self-promotion)
# Released under the same license as Ruby
# Disclaimer: The author is not responsible for any incorrect results from running
# the script. Use it at your own risk.
#
# USAGE:
# ------
# This script is used to re-number the migration files into the desired sequence
# In development, this comes handy as you can organize migration files into logical groups
# by running a simple command line utility instead of manually renaming the filenames.
#
# numergration = numerate migration files
#
# INSTALL
# -------
# Copy numergration into script/ folder of your Rails application.
# On linux system you may have to chmod the script to be executable (+x)
#
# HOW TO RUN
# ----------
# At your Rails application root, run:
# On Windows:
# > ruby script/numergrate before|after [mode]
# On Linux: users can just run the script without calling the ruby executable since
# there’s a #! on top, provided that you set the permission correctly. (chmod to +x)
# $ script/numergrate before|after [mode]
#
# The [mode] options are
# –test Default mode to test the result before you run
# with –shell or –svn
#
# –shell Renaming file as you would do manually in the shell
#
# –svn Integrate with Subversion by executing `svn rename` on each file
# This option alters your working copy so please be extra careful.
# (a.k.a. use it at your own risk)”
#
# Example:
# ——–
# a. Move Migration file 50 to position 3, hence shifting migration files
# from 3 to 49 to the right by 1.
# > ruby script/numergrate 50 before 3
# (the above will just execute with the –test default option)
#
# Or to actually rename the files,
# > ruby script/numergrate 50 before 3 –shell (or –svn)
#
# Or you can even run
# > ruby script/numergrate 50 after 3
# to put migration file 50 after migration file 3 (shifting migration
# file 4 to 49 to the right by 1)
#
# TODO:
# —-
# 1. Better sequence handling. Currently it’s default to 000 for the
# sequence series. However, there can be potentially a lot migration files.
# (more than 999 files). The solution is to find the max sequence and
# use that as the series template
# 2. Better SVN integration.
# a. Do some checking to see if the svn client exists before running.
# Otherwise throw an error
# 3. Better sequence checking. Currently it doesn’t check for input
# range so we can have “index out of bound” errors.
#

require ‘fileutils’
include FileUtils

# which folder we would skip while iterate through
SKIPPED_FILES = [’.', ‘..’,’.svn’]

# check for arguments
unless ARGV.size == 3 or ARGV.size == 4
puts ‘invalid syntax’
exit
end

# this class hold the information about the migration file
class MigrationFile
attr_accessor :sequence, :name, :new_sequence

def to_s(options={})
if @new_sequence != @sequence
s = sprintf(”%03d”,@new_sequence) << "_#{@name}"
else
s = sprintf("%03d",@sequence) << "_#{@name}"
end
end

def initialize(sequence, name)
@sequence = sequence
@name = name
@new_sequence = @sequence
end

def shift_left()
@new_sequence -= 1
end

def shift_right()
@new_sequence += 1
end

def is_changed?
return @new_sequence != @sequence
end

def old_name
s = sprintf("%03d",@sequence) << "_#{@name}"
end

def new_name
s = sprintf("%03d",@new_sequence) << "_#{@name}"
end
end

# 123 after 234 --test
src, task, dest, mode = [ARGV[0].to_i, ARGV[1], ARGV[2].to_i, ARGV[3]] # got to explicitly convert to number for comparision
# exit if don't have to move
exit if src == dest

# default mode to --test
mode ||= "--test"

#grab the migration files
files = []
Dir.entries("db/migrate").each { |file|
unless SKIPPED_FILES.include?file
files << MigrationFile.new(file.to_i, file.match(/.+?_(.*)/)[1])
end
}

# now perform shifting
files.each{ |file|
if src > dest
if file.sequence == src
if task == “before”
file.new_sequence = dest
elsif task == “after”
file.new_sequence = dest + 1
end
else
# shift the innner range files
if file.sequence >= dest && file.sequence < src
if task == "before"
file.shift_right
else
file.shift_right unless file.sequence == dest # if insert after, we don't need to shift the dest
end
end
end
elsif src < dest
if file.sequence == src
if task == "before"
file.new_sequence = dest - 1
elsif task == "after"
file.new_sequence = dest
end
else
# shift the innner range files
if file.sequence <= dest && file.sequence > src
if task == “before”
file.shift_left unless file.sequence == dest # if insert before, we don’t need to shift the dest
else
file.shift_left
end
end
end
end # if src > dest
}

#files.each{ |f| puts f if f.new_sequence != f.sequence }

# now issue
puts “”
puts ” Execute using #{mode} option”
puts “”
puts ” You can execute with these options: ”
puts “”
puts ” –test Default mode to test the result before you run”
puts ” with –shell or –svn”
puts “”
puts ” –shell Renaming file as you would do manually in the shell”
puts “”
puts ” –svn Integrate with Subversion by executing `svn rename` on each file”
puts ” This option alters your working copy so please be extra careful.”
puts ” (a.k.a. use it at your own risk)”
puts “”

files.each{ |file|
if file.is_changed?
if mode == “–shell”
puts ” rename ” << "db/migrate/" << file.old_name
puts " to " << "db/migrate/" << file.new_name
cp("db/migrate/" << file.old_name, "db/migrate/" << file.new_name )
rm("db/migrate/" << file.old_name)
elsif mode.downcase == "--svn"
#puts "executing svn commmand here"
puts " svn rename " << "db/migrate/" << file.old_name
puts " to " << "db/migrate/" << file.new_name
system 'svn rename --force db/migrate/' << file.old_name << " db/migrate/" << file.new_name
elsif mode.downcase == "--test"
puts " [TEST] rename " << "db/migrate/" << file.old_name
puts " to " << "db/migrate/" << file.new_name
end
end
}

==== Download ====
* To download, please click here: [[http://alexle.net/wp-content/uploads/2007/06/numergrate.zip|numergrate]] (version 1.0, 06/17/2007)

==== Note ====
* Moving migration files around can do serious damage to your database. Please be careful. This comes extremely dangerous if you are working with other people on the same repository. Please be smart about it. Use it at your own rick.
* Comments and suggestions are very welcome.
* Support me at [[http://www.warsofearth.com|Wars of Earth]] if you can. Another shameless self-promotion.

view comments