:: Web Design, Ruby on Rails, JavaScript, Photography, and more ::

Wednesday, September 16, 2009

Call controller action from script console in rails app

An easy way to call an action in script/console of your Ruby on Rails application is to simply call "app.get" and pass parameters similar to a functional test. You then can get access to the response object with "app.response".

> app.get '/posts/1'
> r = app.response
> r.body
> r.cookies

Labels: ,


Saturday, May 30, 2009

Check if cookies are enabled on client side

Using just Javascript (with the help of jQuery and the jQuery cookie plugin), this bit of code will do the trick on checking whether the user's browser accepts cookies or not. This is helpful for posting a warning message on a login screen or when adding things to a shopping cart, etc.

<script>
jQuery(document).ready(function()
{
var TEST_COOKIE = 'test_cookie';
jQuery.cookie( TEST_COOKIE, true );
if ( jQuery.cookie ( TEST_COOKIE ) )
{
jQuery.cookie( TEST_COOKIE, null );
alert( 'Good news, cookies are enabled.' );
}
else
{
alert( 'Cookies are not enabled. Please enable and try again.' );
}
}
</script>

Labels: ,


Tuesday, April 7, 2009

Add javascript tabs to browser history (for "back" navigation)

As fancy AJAX websites become more popular, the "back" button is getting less useful and less functional. Often times you perform an action that perhaps repaints most of the browser window (think, viewing an email in Gmail), and you want to go back to your preview view (list of email in your inbox). In the old days, you would hit back and go to the previous page. But in Gmail and other AJAX-ey sites, you didn't go to a "new page" but instead had javascript repaint the divs. So hitting back would take you to the login screen or some other site. Fortunately Gmail has solved this by inserting various actions into your browser history, so that you can actually use the back and forward buttons to navigate through Gmail.

jQuery has a good history plugin that lets you add actions to the browser history, and on every page load you can register for a callback that gives you a value based on where you are in the history. It is up to you to write your JavaScript in a generic way that can save and respond to history save points. But jQuery does the cross-browser, behind the scenes heavy-lifting for you.

My use of the history plugin was for a tabbed navigation where the tabs were all done using JavaScript to show/hide divs. It became a problem when a user would load the page, click a different tab, and while viewing that 2nd tab, click a link to another page. Then the user would click back and expect to be on that 2nd tab again, but instead would be shown the first tab.

Here is some sample code that I used to easily solve this issue and add back/forward support for my AJAX tabs:


var default_tab = 'home'; // this can be set by query string, url, etc

function on_page_loaded( history_key )
{
if( history_key )
{
// this gets run for every back/forward, if something was stored in
// the browser history's location.hash
toggle_tab( history_key );
}
else
{
// this gets run for every page load where back/forward is not pressed
toggle_tab( default_tab );
}
}

jq(document).ready( function() {
// Initialize history plugin, and give it the function name
// of your callback above
jq.historyInit( on_page_loaded );

jq('a.tab_with_history').click( function()
{
// actually toggle the tab in UI
var tab_name = toggle_to_selected_tab( this );
// save to the history, the tab name -- important step!
jq.historyLoad( tab_name );
return false;
});
});


I will leave "toggle_tab" and "toggle_to_selected_tab" implementation to you as an exercise. I created a custom tab class with initialization methods that are generated from Ruby hashes and then toggling tabs is just a matter of finding the tab by name and flipping some css display values of the corresponding divs that each tab owns

HTML:


<ul>
<li><a id="tab_1" class="tab_with_history" href="#1">Tab 1</a></li>
<li><a id="tab_2" class="tab_with_history" href="#2">Tab 2</a></li>
</ul>

<div id="content_1" style="display: none">contents of tab 1</div>
<div id="content_2" style="display: none">contents of tab 2</div>

Labels: ,


Tuesday, March 31, 2009

Ruby YAML config files - hash key confusion

If you have played around with YAML files in Rails at all, you may have run across a confusing issue: often times in Rails, hashes are accessed via either a string or symbol but if you load up a YAML file, the hash can only be accessed by string!
The reason is Rails has a "HashWithIndifferentAccess" object type, that is built on top of the standard ruby Hash, and YAML files by default use the standard ruby hash.
E.g.
h = { :msg => 'Hello World', :date => Time.now }
puts h[:msg] # Hello World
puts h['msg'] # nil

h = HashWithIndifferentAccess.new( { :msg => 'Hello World', :date => Time.now } )
puts h[:msg] # Hello World
puts h['msg'] # Hello World


You can get the indifferenct access behavior in YAML files by putting this at the top of your YAML file:

--- !map:HashWithIndifferentAccess


That is the most flexible way to create your YAML files. If you strictly want symbol access only, you can also prefix each key in your YAML file with a colon:

config.yml
development:
:key_that_is_a_symbol: value 1
key_that_is_NOT_a_symbol: value 2


Once loaded, you will get this behavior:

config = YAML::load_file( 'config.yml' )['development']
puts config[:key_that_is_a_symbol] # value 1
puts config[:key_that_is_NOT_a_symbol] # nil
puts config['key_that_is_a_symbol'] # nil
puts config['key_that_is_NOT_a_symbol'] # value 2

Labels: , ,


Tuesday, March 3, 2009

How to override validation of has_many active record objects

I found myself needing to override the validation of "has_many" active record objects after using build to create them. The reason being that I was doing custom validation on each of the built objects anyway and wanted to display each of the child object's validation error message, not the generic "<object> is not valid".

For example:

class List < ActiveRecord::Base

has_many :phone_numbers, :dependent => :destroy

validate :validate_numbers

def build_items_from_file_stream( stream )
stream.split( "\n" ).each_with_index do |line, index|
next if line.blank?
phone = self.phone_numbers.build( :phone_number => line )
phone.file_line_number = index + 1
phone.unformatted_number = line
end
end

# this method overrides the automatically generated method
def validate_associated_records_for_phone_numbers
true
end

def validate_numbers
self.phone_numbers.each do |phone|
if !phone.valid?
error_prefix = "File line #{phone.file_line_number}, '#{phone.unformatted_number}': "
errors.add error_prefix, target.errors.full_messages.join( ", " )
end
end
end

end


Basically ActiveRecord will automatically create a validate method of the name:
validate_associated_records_for_
That method steps across all of the associated objects, and if !valid? does:
errors.add , "is not valid"

Without overriding that method, if you were to create a list (in my example above) that had an invalid phone number (assuming the phone_numbers class has validation too), you would get an error string something like:
- Phone number is not valid
- File line 1 '805-555-123': national number must contain 10 digits
- Phone number is not valid
- File line 2 '+11 805-555-1234': country code not found

Clearly having "phone number is invalid" multiple times is not adding any value, and in fact making the list validation message more confusing.

Labels: ,


Thursday, November 13, 2008

Growl notifications with Rails Autotest

For Ruby on Rails development, using the "Autotest" gem is a handy way to constantly have tests running in the background, to notify you of when tests fail. The great feature is that it diffs your changes since it last run, so as to only run relevant tests. This tool is great if you have a ton of screen real estate to dedicate a terminal window just for autotest so you can be monitoring the results periodically. But no serious developer has free screen space! That's where Growl notifications come in. If you aren't familiar with Growl, it is a cool Mac OS X specific application that applications can use for notifying you of events (calendar reminders, downloads, etc).

It is possible to connect Growl notifications to Autotest such that you get a notification each time tests are run and either pass or fail. There's many tutorials out on the web, but I struggled to find a single, concise, and accurate tutorial on how to set it up. And read further for how to fix the Mac Leopard / Growl bug.

1. Install Autotest
> sudo gem install ZenTest
You should now be able to run "autotest" from your rails project directory.

2. Install Growl
Download the Growl image here.
Copy growlnotify (found in the "Extras\growlnotify" folder of the Growl dmg) to somewhere that is in your path. (One suggestion is in /usr/local/bin directory).

Verify it’s running by entering:
> growlnotify -m "Testing" Hello World!
You should see a Growl notification

3. Create AutoTest Hook
Create an init file for autotest: ~/.autotest

Insert the following contents (replace the image paths with something that exists on your computer!):


module Autotest::Growl
def self.growl title, msg, img, pri=0, stick=""
system "growlnotify -n autotest --image #{img} -p #{pri} -m #{msg.inspect} #{title} #{stick}"
end

Autotest.add_hook :ran_command do |at|
output = at.results.last.slice(/(\d+).*errors/)
if output =~ /ns.*[1-9]/
growl "Test Results", "#{output}", '~/Library/autotest/rails_fail.png', 2 #, "-s"
else
growl "Test Results", "#{output}", '~/Library/autotest/rails_ok.png'
end
end
end


Images you can use:





4. Launch Autotest
If you modified your path at all while setting up growlnotify, I suggest you restart your terminal. Simply opening new tabs for me was causing the autotest/growlnotify interaction to be flaky.
Restart autotest if it is still running. Now go and edit a test such that it fails. Voila, you should see a red growl notification!

5. But wait, my notifications aren't appearing
If you find that autotest is running, and growlnotify command is being invoked correctly (by putting trace in the .autotest file for example), but your growl notifications aren't appearing, here is a workaround:

A bug exists in Growl such that with Leopard, the growlnotify success rate is about 1/3 of the time. (https://bugs.launchpad.net/growl/+bug/267767)

a) Rename growlnotify to growlnotify.wrapped
b) Create a file called growlnotify and insert:

#!/bin/sh
wrappee=/usr/local/bin/growlnotify.wrapped
exec $wrappee -w "$@" &


c) Add execute permissions to it: > chmod +x growlnotify
Ensure that you can still run growlnotify commands by trying the test above a few times

(To find out more about his fix, visit the bug page or here: http://hans.fugal.net/blog/2008/02/13/growling)

Tip
If you go into the growl notification settings (in System Preferences), you can change the background color of various priorities. The script above marks "failed test" notices as priority 2, so its on the "emergency" setting. I recommend a dark red background.

Note: This setup worked for me with ZenTest 3.11.0 and growlnotify 1.1.4 on Mac OS X 10.5.5

Resources:
http://blog.codefront.net/2007/04/01/get-your-testing-results-via-growl-notifications/
http://blog.internautdesign.com/2006/11/12/autotest-growl-goodness

Labels: ,