November 27, 2006 @ 06:33 PM by nap · 0 comments
So I tried to update my OS X dev box to GemRails 1.2 RC 1 and ran into some issues the other day. For some reason, I just couldn't seem to get system Rails pointing to the new (.5618) copy. I'm usually a pretty stubborn bastard, but one of the plugins I wanted to use requires exemptfromlayout to be available on ActionController::Base, so I figured I'd use EdgeRails and freeze a copy of 1.2 RC 1 in the project's vendors directory.
One of the nice things about the way this is all wired together is that you don't have to be on the very edge of EdgeRails to take advantage of it. In this case, I just need some functionality in 1.2 RC 1 so I can update our existing project and check out using the tag:
rake rails:freeze:edge TAG=rel_1-2-0_RC1
To verify that we're on EdgeRails now using the 1.2 codebase (so we can hang out with the cool kids), we can run script/about:
Edge Rails revision rel_1-2-0_RC1
Keep in mind that this is different than just doing a regular edge freeze since we know we're getting exactly what we're asking for. If we just issued an edge freeze without the TAG parameter we're going to get the latest and greatest development version which of course means two things: 1) we get the bestest newest coolest stuff, and 2) there's a good chance that some of that stuff is a bit buggy.
For the moment, we don't have an immediate need to be running the very latest and will therefore generally tend toward valuing stability over freshness. But as our projects grow and we shed more and more of our noobskin, I'm sure we'll begin making even more exceptions to that rule, forcing us ever closer to the bleeding edge.
Pun intended, of course.
November 23, 2006 @ 04:57 AM by nap · 0 comments
In case you haven't already heard, Rails 1.2 RC 1 is out. Click here for details. Taking center stage this time seems to be the push towards creating RESTful services. Very nice. Also updates to Prototype and Script.aculo.us and, whoah, some deprecations.
So after you finish wolfing down that enormous, weight-altering dinner, sit back in your tryptophan-induced haze and take a gander over the new stuff. Me? Nah. I'm headed up to the Great White North for a couple of Internet-free days. But I know what I'll be doing when I get back... Enjoy!
November 20, 2006 @ 06:25 PM by nap · 0 comments
In the last blog entry, I wrote about doing a first pass integration between Rails and the Yahoo UI Library's Dialog widget. In the event handler for the save action, we created an Ajax.Request object using the Prototype library to make the Ajax call but I didn't elaborate on why just calling this.submit() in the handler wouldn't work. Well, the answer is pretty obvious: I wanted to tease out a second article :-).
YUI expects us to set up a callback handler for the success/failure of the XMLHttpRequest request that it generates when this.submit() is called. If you don't have a callback set up, the client receives the response but just ends up throwing it away. So let's look into how we need to change our handler to do things the "right" way (according to Yahoo, anyway). We'll also see how this is somewhat at odds with Prototype and RJS.
YUI's Connection Manager utility exists to simplify your interface to the XMLHttpRequest object and provides a handler pattern for callbacks. It's used throughout their library, so let's use it here. This updated code should obviate the need for using Prototype in the body of the save action handler:
var handleSubmit = function() {
this.callback = {
success: function(o) {
var scriptObj = document.createElement("script");
scriptObj.setAttribute("type", "text/javascript");
scriptObj.text = o.responseText;
document.body.appendChild(scriptObj);
},
failure: function(o) {
// do something here
alert(o.responseText);
}
};
this.submit();
}
In the code listing above, we define the callback object to handle the success / failure cases reported by XMLHttpRequest. On success, we're going to create a new script element in the DOM and load it up with the response that our RJS template renders. Then we have to append the new element to the DOM. Remember, what we're appending here is really just some more JavaScript that we'll use to alter the page contents. Here's the code in that RJS template again (add.rjs), as a reminder:
page.replace_html 'hello_msg', @thing[:name]
Alright, that's great. But what does the JavaScript code look like that actually gets generated?
try {
Element.update("hello_msg", "Welcome Interstate Managers");
} catch (e) {
alert('RJS error:\n\n' + e.toString()); alert('Element.update(\"hello_msg\", \"sdafdsfsdff\");'); throw e
}
That Element.update instruction is Prototype's way of saying "take the DIV identified as hello_msg and replace it's guts with the string Welcome Interstate Managers". Of course, that's just the string we gave it when we serialized our form data and sent it off (see previous article for details). Simple but powerful.
OK so that rocks, but there's a bit of redundancy here. Yahoo's Connection Manager utility and Prototype do a lot of the same stuff. That duplication is totally against the DRY spirit we're going for. Sigh. Now I'm not well versed enough in the finer details to advocate The One True Way here, but I should note that I'm much more experienced with Prototype than I am with the YUI connection manager. Prototype is also much more entrenched in Rails, serving as the basis for both Scriptaculous and RJS, and it looks to remain that way as Sam Stephenson (Prototype's creator) is part of the Rails core team.
Anyway, the point I'm trying to make is that the Yahoo UI connection manager library adds further unnecessary bloat to our applications, since it's essentially just replicating what Prototype already does. I'm sure the YUI guys have their reasons for re-implementing this stuff (Prototype isn't universally well-loved), but it's kind of unfortunate for Rails developers, who are pretty well married to Prototype at this point. That isn't to say that the Yahoo stuff is better or worse.
In the meantime, if we want to use YUI's widgets, we just have to suck it up and eat a little extra pie this holiday season. Maybe it's worth writing an alternative connection.js to act as a Prototype wrapper. Maybe we could whip up some helper modules to get these excellent UI widgets as well integrated into the Rails framework as the Scriptaculous stuff is. I'm sure this is all very possible, but just need to spend some time digging through the stuff in more detail. In any case, it seems clear that proper RoR integration needs a bit more thought. Add it to the TODO list!
PS the folks at OpenRico have done a nice job providing a set of controls that are very RoR-friendly. Their focus is somewhat different than that of the Yahoo library, more emphasis on behaviors and cinematic effects rather than on widgets, per se. They have a great accordion widget though, and a live grid too (although it currently lacks cell editing support, which is one of the key things we're looking for).
November 17, 2006 @ 09:13 PM by nap · 0 comments
RoR seamlessly integrates with Prototype and the Scriptaculous library, buying us slick auto-complete fields, sortable lists, some nifty effects and all sorts of other web2.0-ish goodies. But when it comes to more advanced UI toolkit widgets, ones you'd find in desktop app frameworks, like draggable modal dialogs, tabbed panels, and active data grids, it seems hard to beat the library that Yahoo has put together. [That is, unless you want to start mucking with Adobe's Flex. But that's a topic for another conversation...]
The Yahoo UI Library has been around for a while now (since February) and I'm just now getting around to playing with it. Unfortunately, there's no Rails helper module or anything of that sort. But that doesn't mean we can't build one ourselves, or at least make them play nice together...
Let's start with something simple. Oh yeah, I should mention that Sonjaya Tandon covered this same sorta stuff and what I'm doing is based on what I found there, but I'm going to do it a little bit differently, and demonstrate how you can leverage RJS. Here we go:
First, let's set up a Rails project and create a controller. Call it ExampleController:
class ExampleController < ApplicationController
layout "standard", :except => :add
def show
end
def add
@thing = params[:thing]
end
end
Pretty simple stuff. We'll need an RHTML view template for the show action, and an RJS template for our add action. We'll also need a layout that'll be added to everything but our add action. Let's take a look at that layout (standard.rhtml):
<html>
<head>
<title>YUI Tester: <%= controller.action_name %></title>
<%= javascript_include_tag :defaults %>
<%= javascript_include_tag "yui/yahoo", "yui/dom", "yui/event", "yui/connection", "yui/dragdrop", "yui/container" %>
<%= stylesheet_link_tag 'yui/container'%>
<script language="javascript">
YAHOO.namespace('yuitest');
function initDialog() {
var handleCancel = function() {
this.cancel();
}
var handleSubmit = function() {
//this.submit();
new Ajax.Request('/example/add', {asynchronous:true, evalScripts:true, parameters:Form.serialize(myDialogForm)});
this.hide();
}
YAHOO.yuitest.myDialog = new YAHOO.widget.Dialog("dlg", {
width: "500px",
modal: true,
visible: false,
fixedcenter: true,
constraintoviewport: true,
draggable: true });
var myButtons = [ { text: "Save", handler: handleSubmit, isDefault: true },
{ text: "Cancel", handler: handleCancel } ];
YAHOO.yuitest.myDialog.cfg.queueProperty("buttons", myButtons);
YAHOO.yuitest.myDialog.render();
}
function addThing() {
document.myDialogForm.thing_name.value = "";
YAHOO.yuitest.myDialog.show();
}
YAHOO.util.Event.addListener(window, "load", initDialog);
</script>
</head>
<body>
<div id="main">
<%= @content_for_layout %>
</div>
</body>
</html>
So there's a bunch of JavaScript in there. I'm going to work on cleaning all that up and building a helper module later but for now let's just get something functional.
We've copied the relevant YUI library javascripts to our public/javascripts directory, and now we include them in the layout along with our defaults (prototype, etc). We'd want to trim this down to just what we need before deploying an app for real world use of course.
We're going to create a dialog from the YUI and add two buttons on it (save, cancel) and the corresponding event handlers. We also need to add a function addthing() that will be called when the user clicks on the link in the show template. It will simply reveal our modal dialog. If you're confused about any of the syntax here, see the YUI docs and tutorials. Lots of good stuff over there, extremely well documented.
Now here's our show template (show.rhtml):
<div name="hello_msg" id="hello_msg">hello</div><br />
<a href="#" onclick="addThing()">Add Thing</a>
<!-- begin: dialog box -->
<div id="dlg">
<div class="hd">this is a dialog</div>
<div class="bd">
<%= form_tag({:action => :add}, {:name => 'myDialogForm'}) %>
<label for="thing_name">Thing Name:</label> <%= text_field('thing', 'name') %>
<%= end_form_tag %>
</div>
</div>
<!-- end: dialog box -->
Note that the form name (myDialogForm) must match up. Also note that the dlg DIV uses the same name we initialize the dialog to in the layout. This is important, it means the contents of this div are rendered within the modal dialog and are therefore hidden from the view by default. When you click on the 'Add Something' link, the dialog will pop up. You'll be able to drag it around the screen and enter some text in it. Yay. Then you can click save, and it should update the hello_msg DIV. There's an easier way to do this of course, but we're going to use RJS because the approach is demonstrative of a lot more powerful stuff you can do, leveraging Rails model data you may have on the backend and perhaps doing some processing or database access before rendering the results. You get the idea.
So when you hit that save button, the code in the submit handler is run. Let's look at that JavaScript again:
new Ajax.Request('/example/add', {asynchronous:true, evalScripts:true, parameters:Form.serialize(myDialogForm)});
this.hide();
We serialize the data in the form (that one text field) and submit the data asynchronously by making a direct call to Ajax.Request (thanks Prototype!). Then we hide the model dialog again, returning control to the main page. Notice that URL: /example/add. This calls the add action on our ExampleController, which does something important -- in theory -- and then renders out an RJS template. This RJS template, for our simple example, just replaces the hello_msg div's inner HTML with whatever it was we typed into that text field. Here's the RJS template, add.rjs:
page.replace_html 'hello_msg', @thing[:name]
That's it for now! We'll revisit this later if I have time, and try to figure out how to wrap it up in a module to make it easier to use and more general purpose.
November 07, 2006 @ 07:04 PM by nap · 0 comments
We need to delete some stuff. But when we delete items, we don't really delete them because hey, we don't have to (we might want them later, no?) So we have an attribute on the model, deleted, which is null if the item is current. When we delete an item, we update_attributes to set deleted to the current datetime.
Yes, yes, I know, this is really common stuff. But hold on, this is where it gets kind of interesting. We want to call find on the model, like always, and only get back current items that have not been deleted. Oh, and we don't want to have to pass in the condition every time, because that's just gross. We'd just like to, well, add the condition to the scope. This is where the magic of with_scope can help us out.
Let's declare a method find_current on the model:
def self.find_current
find(:all, :conditions => "deleted IS NULL")
end
Well that works for our base case (finding all current items) but it's pretty inflexible. What if I want to pass in an ID, specify a limit, additional conditions, etc?
def self.find_current(*args)
with_scope(:find => { :conditions => "deleted IS NULL" }) do
find(*args)
end
end
The answer is to use with_scope, which allows us to specify conditions here that will apply throughout the course of the block. Now we can do things like:
Item.find_current(:all, :limit => 10)
Item.find_current(:first, :conditions => "name LIKE '%perishable%'")
Item.find_current(13)
Which invoke the find_current method on the model with our additional parameters and includes our deleted condition in the scope automatically.
K, so still not exactly rocket science, but it is a nice, clean solution to a common issue for sure. Unfortunately, with_scope isn't found in my copy of Agile Web Development with Rails, which is my main reason for noting it here. Hopefully it'll be in v2! Some other helpful bloggers are out there though, check out Ryan Daigle's blog, which is where Ian found the article that got us started on this path.
November 04, 2006 @ 03:56 PM by nap · 0 comments
One of the things I love the most about working in Ruby is that I find myself spending more time thinking about a problem and less time actually writing code. Case in point: constructing dynamic breadcrumb trails for the RoR-based webapp we're working on here in the lab.
An obvious (and incredibly slick) way to build breadcrumbs would be to use acts_as_tree as described here. This doesn't really work in our situation though, as the nodes at different levels of our hierarchy aren't going to be the same model object, and each has a different controller to operate on it. So we need a little more versatility...
For the purposes of this discussion let's say that we have a Major controller, a Minor that lives beneath that, and a SubMinor which is the leaf node in the hierarchy, each operating on their own type of model. We have a private init method on ApplicationController for setting up instance variables that need to be available to the layout. Individual controllers can override this and construct a breadcrumb trail to display in the layout.
So given this, here's an easy way to build a breadcrumb in the SubMinor controller's init:
def init
super
if (params[:id])
current = SubMinor.find(params[:id])
@breadcrumb = Array.new
@breadcrumb << {:title => current.minor.major.name,
:link => {:controller => 'major',
:action => 'show',
:id => current.minor.major.id}}
@breadcrumb << {:title => current.minor.name,
:link => {:controller => 'minor',
:action => 'show",
:id => current.minor.id}}
@breadcrumb << {:title => current.name}
end
end
Then in the layout we render a partial:
render(:partial => 'shared/breadcrumb')
Where the contents of that template are:
<% if !@breadcrumb.nil? %>
<% @breadcrumb.each do |item| %>
<%=link_to(item[:title], item[:link]) %>
<% if !item[:link].nil? %>»<% end -%>
<% end -%>
<% end -%>
So we stuff each (title, link) pair to be represented in the trail into the @breadcrumb instance variable, and then the breadcrumb partial called from the layout just renders out whatever it finds there. If it finds an item without a :link attribute, it assumes it's reached the final step in the trail (our current location). Works great.
November 04, 2006 @ 08:16 AM by nap · 0 comments
Thus began the Zerosum dev blog, a place for folks who work on our code to muse about daily development happenings and such.
I’ve never been one to keep a blog, personally, but I think of this area as a useful place for us to post about things that don’t belong in our project trac wiki or elsewhere. Things you want to share with others. Things like: “Wow cool, Ian found a new way to do X that totally rocks. Spread the word” and “Rails migrations rule, check out this snippet!”, and “Saw this thing on Foo’s blog about how to do Y without needing Z. Maybe we should think about refactoring the BlahController to do that too?”
You get the idea.