Autotest with Growl. And iChat?

By Phil Matarese on April 18th, 2008

Tagged with: rails, autotest, growl, ichat

Autotest is Great

It adds another layer of satisfaction onto test-driven development.  The ideal setup for TDD is to have two monitors – one for developing and another for your autotest terminal (along with server and log-tailing terminals.)  Without a second monitor, though, your autotest results need a better way of notifying you.  Many people have solved this on their Macs by using Growl's growlnotify in a .autotest file (goes in your project root or home directory.)  It could be as simple as requiring ZenTest's own growl interface:

1  require 'autotest/growl'

But, for more control, it's sometimes better to expose the guts and operate a little bit like this:

 1  module Autotest::Growl
2 PICTURE_ROOT = '/Users/philip/Pictures/Autotest/'
3
4 def self.growl msg, status
5 pri = {:fail=>2,:pending=>-1,:ok=>-2}[status]
6 title =
{:fail=>'Failed',:pending=>'Pending',:ok=>'Passed'}[
status]
7
8 msg.gsub!("\n","\\\n\n")
9 system "growlnotify -n autotest --image
\"#{PICTURE_ROOT}rails_#{status.to_s}.png\" -p
#{pri} -m \"#{msg}\" \"Tests #{title}\"
"
10 end
11
12 def self.failure_details results
13 details = ''
14 results.each do |line|
15 line = line[/[^\/]*\.rb\:[0-9]*\:/]
16 details += "#{line}\n" if line
17 end
18 return details
19 end
20
21 def self.pending_details results
22 results.slice!(0,3)
23 results.slice!(-4,4)
24 details = ''
25 results.each do |line|
26 details += "#{line[/[^\(]*/]}\n"
27 end
28 return details
29 end
30
31 Autotest.add_hook :ran_command do |at|
32 output =
at.results.last[/[0-9]*\sexample(s)*,\s[0-9]*\sfailu
re(s)*(,\s[0-9]*\spending)*
/]
33 if output =~ /\s0\sfailure(s)*/
34 if output =~ /\spending*/
35 growl "#{pending_details(at.results)}\n#{output}",
:pending
36 else
37 growl "#{output}", :ok
38 end
39 else
40 growl "#{failure_details(at.results)}\n#{output}",
:fail
41 end
42 end
43 end

Growl SettingsThat script is based on internautdesign.com's, and uses their images (rails_fail.png, rails_ok.png).  I added a few updates, like better formatting in the Growl notification and implementation of 'pending' status (rails_pending.png).  It took me a few tries to get the line-breaks right in the notification, until I realized that I needed double backslashes – one for the shell, and one for the \n.  I also decided to grab a little more than the last line of results for pending and fail statuses, which took a few ugly lines of text hacking.  Also, you should set your Growl colors to something like the picture on the right.

That Was Nice

That was nice, and it solves the single-screen-notification problem pretty well, but now it's time for something new.

Bragging about my ruby-centric iTunes playlist through iChat.Test failures publicized through iChatAnd, since I was looking for a reason to try out RubyOSA, I wrote a .autotest script that notifies my team (and my friends (and my mom)) whenever a test passes (or fails (or is pending)) via iChat's status and buddy icon.  Too fun!  Maybe this isn't the most practical way to do TDD, but it's fun code to play with.  You could even use RubyOSA to lower the volume on iTunes whenever there are too many errors – maybe.

 1  require 'rbosa'
2
3 module Autotest::Chat
4 PICTURE_ROOT = '/Users/philip/Pictures/Autotest/'
5
6 def self.chat test_stats, status
7 @@chat ||= OSA.app('iChat')
8 begin
9 @@chat.status_message = test_stats
10 @@chat.status = status==:fail ? 'away' : 'aval'
11 @@chat.image =
File.read("#{PICTURE_ROOT}rails_#{status.to_s}.png
")
12 rescue
13 #ignore it, iChat is prolly just turned off
14 end
15 end
16
17 Autotest.add_hook :ran_command do |at|
18 output =
at.results.last[/[0-9]*\sexample(s)*,\s[0-9]*\sfailu
re(s)*(,\s[0-9]*\spending)*
/]
19 if output =~ /\s0\sfailure(s)*/
20 if output =~ /\spending*/
21 chat output, :pending
22 else
23 chat output, :ok
24 end
25 else
26 chat output, :fail
27 end
28 end
29
30 Autotest.add_hook :quit do |at|
31 chat "Chillin'", :chillin
32 end
33 end

Autotest has some usefel hooks like 'quit', so I can let people now that I'm chillin' again (rails_chillin.png) after a successful round of testing.  Oh, and you can get the chat code from RubyForge with the following command.

curl 'http://rubyforge.org/snippet/download.php?type=snippet&id=329'

Today

As I wrote this post, I started looking deep into the bowels of ZenTest to make sure I was setting up my autotest scripts correctly.  What I found was heaps of code already designed to use Growl, and iChat, and KDE Notify, Snarl, Heckle, and some other stuff.  What I didn't find was much documentation about the features – you pretty much have to dig into the source.  And even with the source, some of the code's usage is mysterious – like utilizing generic autotest hooks by adding require 'autotest/growl' or require 'autotest/shame' to an empty .autotest file.

Now Back To the RUBY-CENTRIC Music

More iTunes playlist nonsense

Using Foxy Fixtures for Polymorphic Associations in Rails 2

By Scott Roth on April 5th, 2008

Tagged with: rails, rails 2.x, fixtures, polymorphic associations

As we all know, the Rathole plugin was sucked into Rails core with the release of Rails 2 and now using fixtures has become, um, foxier.  One use case that wasn't immediately clear to me was how to specify polymorphic relationships using the new and improved syntax.  It turns out that this is straightforward.

Let's say your application captures contact information for several models.  Our hypothetical application is a brand new social network for people in the music industry.  (Quick, let's pitch it to VCs!)  Each application user would have contact information and, since they are in the music industry, they belong to a Record Label.  Record Labels also have contact information stored for them.  So we get this:

 1  class Contact < ActiveRecord::Base
2 belongs_to :contactable, :polymorphic => true
3 end
4
5 class User < ActiveRecord::Base
6 has_one :contact, :as => :contactable
7 end
8
9 class Label < ActiveRecord::Base
10 has_one :contact, :as => :contactable
11 end

Now to set up the fixtures.  All you need to do is specify the contactable name and contactable_type for each contact and we are done.

 1  # User
2 music_industry_person:
3 username: person1
4 password: secret
5
6 # Label
7 label1:
8 name = Some Indie Label
9
10 # Contact
11 fred:
12 contactable: music_industry_person
13 contactable_type: User
14 first_name: Fred
15
16 sam:
17 contactable: label1
18 contactable_type: Label
19 first_name: Sam

As you can see, a piece of cake!

Update:  Phil knew a one liner way to do this:

1  sam:
2 contactable: label1 (Label)
3 first_name: Sam

 

Writing a tinymce plugin for image uploads using Rails and attachment_fu

By Scott Roth on January 15th, 2008

Tagged with: rails, tinymce, attachment_fu, tutorial

Tinymce is a pretty nice browser based rich text editor (RTE).  We are using it successfully in one of our applications to provide a user friendly way for non-techies to have some control over the styling of content they create.  However, one glaring deficiency in the included set of plugins is that there is no way to upload an image (or any file) for use in the RTE.  This makes sense when thinking about it, as file upload requires a server side component - something that the client-side tinymce package needs to be agnostic about.  So, being a good lazy web citizen, I tried to get google to write the plugin I need for me.  Searching turns up several options - if you are on a PHP backend.  It seems no one has shared their approach to doing this with a Ruby on Rails backend yet.  So, I did it myself and am going to outline the process here for posterity.

Getting a basic plugin working (aka: the plumbing)

I started my quest on the tinymce wiki page dedicated to plugin development.  I downloaded the dev version of tinymce as instructed, which includes a sample plugin named '_template'.  FYI, we are using a 2.x version of tinymce.  According to the wiki, due to API architecture changes, the process will be a bit different if you are on a 3.x version.  Rename the _template plugin and copy it into the plugins directory of your tinymce installation.  You'll see 2 files right away: editor_plugin_src.js and editor_plugin.js.  Copy the _src file into the other one for development purposes; this is the file that is actually read.  At the end we will copy back into the _src file and compress the editor_plugin.js file, which is the one that is actually used at runtime.  Open the editor_plugin.js file and replace all the references to 'template' with your plugin's name.

Now you need to open wherever you have your tinymce.init() method and add your plugin in there.  Specifically, add your plugin name on the 'plugins' line to register it and on your theme's button line ('theme_advanced_buttons1' for me) so that the button will show up.  You also need an image for your users to click on.  Create one (or there is a template.gif in the sample plugin if you want to use that for now) and put it in the theme image directory (themes/advanced/images/[pluginname].gif for me).

Do a hard refresh on a tinymce instance and, viola, you should see a button for your totally useless plugin.  Some javascript alerts will probably pop up as the sample plugin is alert happy out of the box.
 
When you click that button, you are going to want a popup page where the user can specify the file to upload.  Let's set that up.  In editor_plugin.js there is a function called 'getControlHTML' - change 'mceTemplate' to whatever you want your function name to be - I chose 'doUpload'.  Match that name in the 'execCommand' function.  In execCommand make sure that the template['file'] line points to a real html file.  (There is a sample popup.htm in there.)  Ok, now refresh tinymce and click the button - you should get the sample popup.

The front-end

Ok, now all the infrastructure is in place.  We should be able to wire everything up now.  The plugin that I wrote is actually a bit more complex than the code I'll show here as I created some more advanced features like an option to select a file from the list of files that have previously been uploaded into our system.  Because of time and space I'll show some simpler code here though.

We need to take the sample popup.htm and change it to support a file upload.  Something like this:

 1  <html xmlns="http://www.w3.org/1999/xhtml">
2 <head>
3 <title>Image uploader</title>
4 <script language="javascript" type="text/javascript"
src="../../tiny_mce_popup.js"></script>
5 <script language="javascript" type="text/javascript"
src="../../utils/mctabs.js"></script>
6 <script language="javascript" type="text/javascript"
src="../../utils/form_utils.js"></script>
7 <script language="javascript" type="text/javascript"
src="../../utils/validate.js"></script>
8 </head>
9 <body>
10 <form action="/binaries/upload"
enctype="multipart/form-data" id="binary-edit-form"
method="post">
11 <p><label for="binary">Upload a file:</label><br/>
12 <input id="binary_uploaded_data"
name="binary[uploaded_data]" size="30"
type="file" /></p>
13
14 <p><label for="binary_title">Give it a
title</label><br/>
15 <input id="binary_title" name="binary[title]"
size="30" type="text" value="" /></p>
16
17 <p><label for="binary_contents">Add a
description</label><br/>
18 <textarea cols="40" id="binary_contents"
name="binary[contents]" rows="5"></textarea></p>
19
20 <input type="submit" value="Upload!" />
21 </form>
22 </body>
23 </html>

All we have really done here is create a front-end with a form that posts to your rails app (/binaries/upload) and with field ids that your attachment_fu enabled model class is going to recognize.  The javascripts included at the top are some tinymce magic.  They are not all needed for this simple example, but they probably would be as you flushed out the functionality you need.  For example, if you wanted tabs in your plugin interface or wanted to do some validation.

The back-end

For my example, we will assume that you have a Binary model that is already mixing in attachment_fu and a Binary controller.  If you haven't used attachment_fu before, Mike Clark posted a great tutorial on getting it set up.  Here is the upload method in the controller (this is who you are posting to):

1  def upload 
2 binary = Binary.create(params[:binary])
3 @insertString = "<img src=\"#{binary.public_filename}\" />"
4 render :layout => false
5 end

What a skinny method!  Thanks to the magic of attachment_fu, we can upload and save a binary file with just one line.  In the second line, we create the html string that will be inserted into the client page's DOM by tinymce.  We then render the view.  I am keeping this very simple for the example, but you can see some immediate enhancements to this controller.  For example, you may want some error checking to make sure that the binary uploaded and saved appropriately.  Also, if you are allowing uploads of any files, not just images, you'll need to be smarter about the html string you construct.  For example, if a PDF is uploaded, you may want to display your application's PDF icon as a link to the PDF, which would be code something like this:

1  @insertString = "<a href=\"#{binary.public_filename}\"><img 
src=\"/images/pdfIcon.gif\" /></a>
"

Now we need to return some javascript to the browser to insert our image into the RTE.  The view file (upload.rhtml using my code):

1  <script language="javascript" type="text/javascript" 
src="/javascripts/tiny_mce/tiny_mce_popup.js"></script>
2 <script language="javascript" type="text/javascript">
3 tinyMCEPopup.execCommand("mceInsertContent", false, '<%=
@insertString %>');
4 tinyMCEPopup.close();
5 </script>

This, also, is pretty simple.  Using the built-in tinymce function, mceInsertContent, we take our contructed html string and put it in the DOM and then close the popup window.

Check it out, y'all

Now we have all the pieces in place to try out our new plugin.  You should be able to upload an image and have it appear immediately in the tinymce textarea in your application.  If you are ready to go to production and want to follow tinymce best practices, don't forget to copy your editor_plugin.js to back to the _src file and to strip the runtime file down for faster loading. (Moxiecode suggests jstrim.)

As I mentioned earlier, there are many more interesting and complex tasks you can accomplish with a plugin like this.  The intent here was to get a full round-trip uploading plugin working that could be used as a foundation for future work.  Hopefully this helps you get jump-started. 

Changing the session store in Rails 2.0

By Scott Roth on January 8th, 2008

Tagged with: rails, rails 2.x, sessions, configuration

We just converted one of our apps to Rails 2.0.2 today.  When testing the conversion, I got the application's login page but then a CookieOverflow error immediately after that.  Ok, that is easy enough to fix.  Cookies have a 4k limit and we, in a part of the app that we have long known could use some refactoring, store some data which causes sessions to occasionally grow over that limit.

To get things working again, all I needed to do is configure Rails to use the old default, the PStore.  For future reference, here is the configuration setting that goes into environment.rb:

1  config.action_controller.session_store = :p_store

Now all I have to do is bump the session handling refactoring up the priority list so that we can use the CookieStore in our next application release :)

Farewell to "--include-dependencies"

By Phil Matarese on December 20th, 2007

Tagged with: rake, gems, rails

Of Course I Want Dependencies

With the latest version of RubyGems (1.0.0), you no longer have to explicitly state that you want dependencies included.  The new default mode is to assume that you'll want whatever dependencies are needed to make this new gem work.

gem install rails

This was a great design choice, as I can't think of a time when I've ever wanted to skip the dependencies.  Anyone else?

This Is Nice, Too

Are you tired of typing gem list | grep ^[A-z] to get a nice simple list of all your installed gems?  All anyone really wants to know is "which gems" and "what version".  Well, now you can just type gem list, and you're all set.  (If you really want all the gory details, you can add --gory-details.  Just kidding, you add --details.  No, I'm serious this time.  Really, try it.)

gem list

Another win for convention over configuration!  I don't think it gets any better than this.

(Update) There's More

The latest update to rake also adds some task-listing niceties. See, all the formatting is a lot nicer:

rake --tasks
sudo gem up rake
rake --tasks