Making CakePHP’s TreeBehavior work with scope

We’re not talking mouthwash here. We’re talking code.

CakePHP’s TreeBehavior is cool, but tree traversal is a pretty arcane concept, even for developers, and MPTT is not something that is easy to digest mentally, or to develop around. Unfortunately, it’s also not incredibly efficient on large data sets. Even not-so-large data sets, on the order of a few hundred items, make certain actions — reordering, in particular — really processor intensive.

I’m using a JavaScript drag-and-drop tool in cms34 to allow admins to manage the page tree on their sites, and the data gets stored using CakePHP’s TreeBehavior, which is MPTT-based. The problem is, it really wasn’t working. So I completely rebuilt the code that assigns the ├╝ber-critical “left” and “right” values to each node in the tree. My new way is much faster, even though it may break a few rules, and requires bypassing TreeBehavior’s callbacks.

Once I got that working, I was delighted, but then I discovered some other problems, namely pertaining to… scope. My CMS supports multiple sites in one installation, which means multiple trees, which means scope. The problem is, I was having a hell of a time figuring out just how to make scope work with TreeBehavior. Finally I found a link to a succinct and effective solution.

The upshot here is that you can’t define your $actsAs in the model, because… well… to be honest, I only have a vague understanding of why not, but essentially it’s due to the split roles of the model-view-controller framework. You’re building a rule that requires access to specific data values, which is something that needs to happen in the controller; the model is strictly for the abstract structure of the data. I understand it just enough to agree that it makes sense not to do it in the model. Which means you have to do it in the controller. The sample code from the link above goes a little something like this:

function add() {
    $this->Task->Behaviors->attach('Tree', array(
         'scope' => "Task.schedule_id = {$this->data['Task']['schedule_id']}"
    ));
    $this->Task->create();
    $this->Task->save($this->data);
}

That didn’t quite work in my situation, but it got me far enough along that I could figure out what to do from there.

I still need to do a little more testing to make sure my solution to the more efficient tree reordering is rock solid, and then I’ll post a tutorial. But for now, I hope this helps spread the word that scope does work on TreeBehavior… if you do it right.

Update (September 22, 2010): Although this didn’t really seem to be breaking anything, just throwing up a warning (when debugging was turned on), I discovered a minor issue with this code yesterday. Turns out CakePHP expects the value of scope to be an array. Just taking the string it was defined as and wrapping it in array() did the trick.

My favorite new feature in iTunes 9

Yesterday Apple released iTunes 9 and iPhone OS 3.1, and this new version of iTunes addresses one of my biggest few frustrations with the iPhone: organizing your apps.

I cringe at saying “apps,” fearing I sound like Michael Scott talking about something they sell at Dave & Busters. But, given that it’s known as the App Store, I guess that’s what to call them.

Anyway… this is not about what they’re called, it’s about how they’re organized. And up to now, the only way to organize them was to go to your iPhone’s home screen, hold your finger on an icon until they all start to wiggle, and then drag them around. Not bad, when you only have one screen’s worth of apps, or even two or three. But I have seven — and that doesn’t even count the apps I downloaded but deleted from my iPhone.

Trying to keep seven screens’ worth of icons (16 per screen) organized by this finger-dragging method is tedious to say the least. And now that even the default configuration includes two screens, Apple realized they had to do something about it.

But now, we have this:

iTunes app syncing

Brilliant. I love it. The only flaw now is that this layout is too big to fit into the iTunes interface on my MacBook without having to scroll the entire thing, since the iPhone screen is represented at actual-pixel size. (I had to take two screenshots and stitch them together in Photoshop to create the image you see above, which is scaled down slightly from the actual size.

Then again, it’s always something, isn’t it?