Each of those terms actually means something! If you don’t know what any one of those terms means, then you probably won’t enjoy this post.
It’s surprising that something so cool and so central to the Ruby TDD world as Autotest is completely undocumented. Autotest with Rails and RSpec just works, and there are many googleable examples of how to add hooks for using Growl or libnotify. I couldn’t find any reliable advice for how to use Autotest with a non-Rails project and how to use RSpec instead of Unit/Test. The libnotify part is easy once the other two are solved, but I’ll share what I have going on anyway. This information may be available in pieces somewhere else, but I’ll just put it all here to help the other lost googlers.
The first thing to know is that this blog post is out of date. Autotest now speaks RSpec as one of its native “styles”. The trick is that you have to tell autotest about this style by creating a file called autotest/discover.rb in your project root. This contains:
Autotest.add_discoverydo"rspec"end
The second thing to know is that Autotest looks in your lib/ folder for your files and in your spec/ folder for your tests, which should be called file_name_spec.rb.
There’s not much more to know. You can add awesome heads-up notification using libnotify. You need to get libnotify-bin using something like sudo apt-get install libnotify-bin. Then in the root of your project directory create an .autotest file like this:
moduleAutotest::GnomeNotify# Time notification will be displayed before disappearing automatically
EXPIRATION_IN_SECONDS = 2
ERROR_STOCK_ICON = "gtk-dialog-error"
SUCCESS_STOCK_ICON = "gtk-dialog-info"# Convenience method to send an error notification message## [stock_icon] Stock icon name of icon to display# [title] Notification message title# [message] Core message for the notificationdefself.notify stock_icon, title, message
options = "-t #{EXPIRATION_IN_SECONDS * 1000} -i #{stock_icon}"system"notify-send #{options} '#{title}' \"#{message}\""end
Autotest.add_hook:reddo|at|
example_text = ""
num_examples = 0
examples = at.files_to_test.each_pairdo|key, values|
example_text += "- #{key}\n"
values.eachdo|value|
num_examples += 1
example_text += " * #{value}\n"endend
notify ERROR_STOCK_ICON, "Tests failed", "<b>#{num_examples} examples failed in #{at.files_to_test.size} files</b>\n#{example_text}"end
Autotest.add_hook:greendo|at|
notify SUCCESS_STOCK_ICON, "All tests passed, good job!", ""endend
I’ve modified mine to have very verbose notifications. I got it from here.
Probably there are others who want to do the same somewhat senseless thing: use Ext to do form validation while keeping a boring non-Ajax post-and-response. The bottom line is that Ext favors doing it the Ajax way, and the Ajax way isn’t that hard to set up with Rails (just handle the form submission as normal but return JSON or XML to signal success or failure). But if you’re like me and working on a deadline, there can be a cognitive burden to switching to Ajax posting that you might want to avoid. Paradoxically, you might find yourself wasting a lot of time trying to figure out how to do it the “old-fashioned” way. Well, here’s one working standard-submission Signup Form, with fancy validations and all the kinks worked out.
Here’s the top half of the file users/new.html.erb, which is nearly the same as the code generated by restful-authentication:
The only differences are a div wrapping the form (“no-js-form”) and the “js-form-panel” at the end. You’re going to laugh at me, but this form is buzzword-friendly; it’s unobtrusive in an ugly way. If javascript is turned on, the form will work, and the following will fail:
<script type="text/javascript">/*
Thanks to:
http://www.extjswithrails.com/2008_03_01_archive.html for standardSubmit tip (hard to find!)
http://extjs.com/forum/showthread.php?t=23068 for password confirmation
Anyone else I stole semantics from
*/// Look, I'm copying over the authenticity token to send in the JS-generated form. LOL!var authenticity_token = document['forms'][0]['authenticity_token'].value;
Ext.onReady(function(){
$('no-js-form').hide();var myForm;function submitHandler(){
form = myForm.getForm();
form_as_dom = form.getEl().dom;
form_as_dom.action= form.url;
form_as_dom.submit();}
myForm =new Ext.form.FormPanel({
monitorValid:true,
standardSubmit:true,
url:"/users",
applyTo:"js-form-panel",
title:"Signup as a New User",
width:310,
autoHeight:true,
items:[new Ext.form.TextField({
allowBlank:false,
msgTarget:'side',name:"user[name]",
id:'js_signup_name_field',
fieldLabel:"Real Name"}),new Ext.form.TextField({
allowBlank:false,
vtype:'alphanum',
msgTarget:'side',name:"user[login]",
id:'js_signup_login_field',
fieldLabel:"Username"}),new Ext.form.TextField({
allowBlank:false,
vtype:'email',
msgTarget:'side',name:"user[email]",
id:'js_signup_email_field',
fieldLabel:"Email"}),new Ext.form.TextField({
allowBlank:false,
inputType:'password',
vType:'password',
msgTarget:'side',name:"user[password]",
id:'js_signup_password_field',
fieldLabel:"Password"}),new Ext.form.TextField({
fieldLabel:"Password Confirm:",
allowBlank:false,
inputType:'password',name:"user[password_confirmation]",
initialPasswordField:'signup_password_field',
vType:'password',
msgTarget:'side',
id:'js_signup_password_confirmation_field',
fieldLabel:"Confirm Password",
validator:function(value){return(value == document.getElementById("js_signup_password_field").value)||"Your passwords do not match";}}),new Ext.form.Hidden({name:"authenticity_token",
value: authenticity_token
}),new Ext.form.Hidden({name:"user[role]",
value:"consumer"}),],
buttons:[{
handler: submitHandler,
text:"Signup",
formBind:true}]});});</script>
The noteworthy steps are: first, I hide the ‘no-js-form’, then I copy the authenticity_token that gets generated by a rails form to put in the js-generated form. Then, standardSubmit : true is the config option that makes a FormPanel not submit as an XmlHttpRequest. The funny code in the submitHandler is getting the underlying form object and calling submit on it, but as I write this it doesn’t make sense why both would be necessary. Finally, formbind : true causes the submit button to be deactivated while there are failing validations, and there’s some handy code for making sure that the password_confirmation matches password (totally lifted from somewhere else, see above).
(…after too much grief today installing Mephisto and mucking with Apache virtualhosts; I’ll get Part I back from the ether eventually) Update: Done. Update: This is a post moved over from the short-lived Mephisto blog, and ported back in time.
First of all, the alexandria binary is just a ruby script that does a require ‘alexandria’ and runs Alexandria.main.
Alexandria.main is a method on the Alexandria ‘module’ that is used throughout the code (modules are ‘namespaces’ to avoid naming conflicts). This method is found in lib/alexandria.rb:
As you should be able to see, this method isn’t doing anything but setting up some global variables (like $DEBUG) and logging, and doing something weird with http_proxy. The real line is Alexandria::UI.main. That’s in lib/alexandria/ui.rb:
Gtk.main is the main loop of a gtk program. You set up your windows and widgets before running it, and it makes them all spin until you exit. So, after Icons.init runs (guess what that does), MainApp.new does all the work from now on.
The Pango code above this is interesting for seeing some Ruby syntax and features. Pango is a text-rendering and layout library inside gtk. The code is adding an elipsizable? “question” method (return true/false) to the Pango module. self.elipsizable? means that it’s defining a class method, a method on a class that doesn’t depend on instance data. ||= is a way of saying, “set the variable to this unless it’s already been set to something else (ie, it’s not nil)”.
Unfortunately, MainApp.new is in the massive MainApp class at lib/alexandria/ui/main_app.rb. This class does a lot (too much). The main thing it does is handle all the callbacks from the main window and its widgets. Let’s just take a look at the top:
module Alexandria
module UI
class MainApp < GladeBase
attr_accessor :main_app, :actiongroup, :appbarinclude Logging
include GetText
GetText.bindtextdomain(Alexandria::TEXTDOMAIN, nil, nil, "UTF-8")module Columns
COVER_LIST, COVER_ICON, TITLE, TITLE_REDUCED, AUTHORS,
ISBN, PUBLISHER, PUBLISH_DATE, EDITION, RATING, IDENT,
NOTES, REDD, OWN, WANT, TAGS = (0..16).to_aend# The maximum number of rating stars displayed.
MAX_RATING_STARS = 5def initialize
super("main_app.glade")@prefs = Preferences.instance
load_libraries
initialize_ui
on_books_selection_changed
restore_preferences
end#... snipend# ... snipendend
A couple points here. MainApp inherits from GladeBase. The attr_accessor is a declaration that makes the @main_app, @actiongroup and @appbar instance variables publicly readable and settable. super(“main_app.glade”) calls the initialize method on GladeBase with the glade file that contains the definitions for all the widgets Alexandria uses. The names of the methods tell you about what they do (good!). Because these methods need to know about what the user’s preferences are, @prefs has been made available before they are called.
To understand what MainApp is doing, it seems like we need to understand what GladeBase is.
So GladeBase is using GladeXML to get the widgets out of the xml file and load them into memory. It then iterates through them, *adding them to MainApp (instance_variable_set is doing the work). So if there’s a widget called @main_menu, MainApp will get this variable to work with. These widgets work exactly as though they had been created “by hand”.
If you’ve been following, take a look at load_libraries and see if the code there makes sense. Here’s a short snippet:
This is where things start to get confusing. load_libraries is also being used to reload libraries, so first it checks to see if @library has been defined already (refactoring opportunity). In the normal case, Libraries gets called by by invoking Libraries.instance. To understand this, you have to know that Libraries uses a factory class method to make sure that Libraries only gets created once (making the Libraries instance a “singleton”).
At the bottom of load_libraries is some interesting code:
This is telling each library in @libraries (the Libraries singleton) to add self as an “observer”. What does this mean? It means that class Library is “observable”. To see what that means you have to look at Library. First let’s look at Libraries, in lib/alexandria/library.rb:
Libraries is including the Observable and Singleton modules to give it special methods (in Python these are called “mixins”). Singleton gave it the instance method. Observable is giving it the notify_observers method. What this method does is “call up” all the observers of this instance by calling their update methods.
Libraries has many Librarys (it’s a little weird to give a class a plural name). Each library is an observer of Libraries. Library is also Observable:
class Library <Arrayinclude Logging
# ...includeObservable
As we saw above, MainApp adds itself as an observer to each library. If you look on MainApp you’ll see that it has an update method:
def update(*ary)# ...end
*ary means that it accepts an array as its argument. This method gets called from many places in Library, like this: