Announcing Django 0.96!

Django : Posted on Django Project at March 23, 2007 09:48 PM

We're incredibly pleased to announce the release of Django 0.96!

The primary goal for 0.96 was a cleanup and stabilization of the features introduced in 0.95. The release notes cover the few backwards-incompatible changes, but for most people the upgrade process should be simple.

One particular change affects users of MySQL on older servers: If you get an error about Django requiring a newer version of MySQLdb, you'll need to either upgrade MySQLdb to 1.2.1p2 or later, or switch your DATABASE_ENGINE setting to "mysql_old". You can read more about this change in the release notes.

Though the main focus of 0.96 is stability and maturity, it also includes some juicy new features to help you write even better code. Of particular note:

  • Django now ships with a comprehensive set of testing tools. You can write tests based on doctest or unittest, test your views with a simple test harness, and load initial data ("fixtures") automatically.

    If testing is indeed like flossing, think of these new tools as super-comfortable, deliciously flavored, top-of-the-line thread.

  • Django 0.96 also ships with a brand new forms library, django.newforms. This is a replacement for django.forms, the old forms/manipulator/validation framework. Both APIs are available in 0.96, but over the next two releases we're going to switch completely to this awesome new system.

Read the release notes for more details on these and other changes.

We can't possible thank our amazing development community enough. This release contains the work of over 200 different people. To put this in perspective: when we released 0.95 we thanked "the dozens of Django contributors"; this time, it would be more appropriate to thank hundreds of contributors. In other words, this release wouldn't have happened without this amazing community.

To everyone who's reported a bug, submitted a patch, participated on our mailing lists, hung out in our IRC channel: our most heartfelt thanks. As always, We try (but inevitably fail) to list everybody in the AUTHORS file.

So, what are you waiting for? Go download 0.96, and let us know what you think!

How to use the official CakePHP test suite

CakePHP : Posted by cakebaker at March 23, 2007 02:55 PM

If you are using CakePHP 1.2 you probably noticed the folders /app/tests and /cake/tests. They are part of the official CakePHP test suite which comes with Cake.

Before you can use it, you have to download SimpleTest, the testing framework used by the test suite. Extract the package to your vendors folder and you are ready to execute tests. (If you have an advanced setup you also have to change the value of CAKE_CORE_INCLUDE_PATH in app/webroot/test.php)

To execute tests you have to go to example.com/test.php and you will see the following page:

Testsuite

There you can select whether you want to execute the core tests or your own application-specific tests. These application-specific tests must be placed in the respective folders in app/tests/cases resp. in app/tests/groups if they are group tests. The file names of the tests must end with “.test.php” (e.g. “user.test.php” for the user model test) respectively “.group.php” (e.g. “helpers.group.php” for a group test of all helpers). It’s imho a bit illogical, as everywhere else in the framework an underscore is used to separate the name parts. As usual in Cake, the class names are camel-cased, so the class names will be “UserTest” and “HelpersGroup”. Apart from those conventions the tests are “normal” SimpleTest tests, there is no magic (yet). You can find many real-world examples of such tests in /cake/tests.

Personally, I still prefer my own test suite, even though it requires a bit more configuration work.

Happy testing :)

Criki - the creation of a wiki with CakePHP, Part 3

CakePHP : Posted by cakebaker at March 21, 2007 10:15 AM

On IBM’s developerWorks the third part of the series “Create an interactive production wiki with PHP” has been published. This part deals with file uploads and access control (no, it doesn’t cover ACL).

Read the article online (registration needed) or download it as a PDF.

Previous parts:
Part 1 as PDF
Part 2 as PDF

PS1: CraZyLeGs’ blog has a new virtual home: http://www.devmoz.com/blog.
Ps2: I did a little redesign on my blog and switched back to a two-column layout. I hope it is now I bit less cluttered ;-)

othy moves to devmoz !

CakePHP : Posted by othy at March 21, 2007 03:58 AM

Alright,

This blog will move moves here http://www.devmoz.com/blog/ so please update your bookmarks and feedreaders (Sorry for the inconvenience ). This will probably be the last post here, so see you there :)

Review: Highrise, part 2

RubyOnRails : Posted by Robby Russell at March 20, 2007 06:53 PM

It’s been five days since I posted my initial review of Highrise, that shiny new application by our friends at 37signals. I’ve been getting adjusted to my new process of managing contacts and have had to remind myself a few times that there is a brand new tool that aims to make my life a little easier.

Contact Form Integration

I haven’t heard about a Highrise API available yet, but I will definitely be looking into tighter integration once that is available.

Direct Submissions (not yet)

It seems that Highrise isn’t going to allow direct emails to be sent to it, they need to come from an existing contact in your account. For example, our contact form sends an email to our customer service mailing list. At one point, we had it connected to the Basecamp API to submit each new contact request as a new message in a designated project, but it didn’t really give me what I was looking for. Since each user in Highrise has a custom dropbox email address, I thought that I would try to link up the contact form to submit directly to Highrise.

I got the following response back from Highrise. ;-)

Hi Robby- An email was sent to your Highrise dropbox from john@cusackforpresident.com. This address does not correspond to any address that you have recorded for yourself in your Highrise account, and so the email was discarded.

So, in the meantime, I’m following this process with new contact requests as well as the other people at PA who are responsible for responding to Contact Requests.

Contact Request Submission

So… let’s say that John Cusack (one of my favorite actors while growing up) is having a weird dream and wants to get a website built for the record store that he ran in High Fidelity.

PA Contact Request Form

He fills out the form and submits it, which our application than stores and also sends over his contact information to our customer service email address.

A few minutes later…

Manually Review in Mail.app (and apply 2-minute rule)

Here I am in Mail.app and doing a double-take… “is that the real John Cusack?” (no, it’s just test data).

Email in Mail.app

I then ask myself the following questions…

  • Can I answer this in less than 2 minutes?
    • If yes, respond immediately (forward to Highrise, if contact info will be needed again)
    • If no, forward to Highrise

Okay, so I’ve decided to forward this contact to Highrise as I decided to go ahead and speak with John over the phone, since he was kind enough to leave his phone number.

As I mentioned in my last post, I’m using Act-On for forwarding emails to Highrise.

(back-tic h)

Mail.app with Act-On

...and off the email goes.

View/Edit message/contact in Highrise

I’m now logged into Highrise and looking at my dashboard. As you can see, John Cusack is now at the top of my dashboad and waiting for me to decide if I want to do something with it.

Highrise Dashboard

Schedule Follow Up tasks

As I mentioned, I spoke with John over the phone and promised him that I’d send him a follow up email with a proposed date/time for a meeting next week.

Adding task in Highrise

...and that’s one way that I’m now using Highrise to getting all my contacts organized.

Five Day Review

Well, after five days of using Highrise, I’m still impressed with it. Our Administrative Assistant began using it last Friday and is using it to schedule follow up tasks for me. This definitely beats the old process of leaving post-it notes on my desk with names and phone numbers. :-)

We also upgraded to a paying account and paid for invoice #4.... and I plan to hit contact #200 later today within our account.

A few bugs:

  • Forwarding email from Thunderbird doesn’t currently work (as of last Friday)
  • A few forwarded emails from Mail.app didn’t work right (garbled… html emails perhaps?)

Also… it appears that 37signals has opened the doors to the public earlier today.

Have fun!

More conferences

Symfony : Posted by fabien.potencier@symfony-project.com (Fabien POTENCIER) at March 20, 2007 11:48 AM

If you missed PHP Quebec, here are some more conferences with symfony talks. Dustin Whittle, from Yahoo!, will talk about symfony a lot this year:

PHP Quebec Conference

Symfony : Posted by fabien.potencier@symfony-project.com (Fabien POTENCIER) at March 19, 2007 09:02 PM

Last week, I attended the PHP Quebec conference. It was my first conference as a speaker and PHP Quebec 2007 turned out to be a really good conference.

I spoke on the first day about symfony in french. I also spoke on the second day about symfony, but in english this time. I reworked my slides between those 2 talks, so english slides are slightly better (with less typos/errors).

The slides are available here:

As all the talks were recorded, I will also try to synchronize slides and voice later this month.

symfony 1.0.1 released

Symfony : Posted by fabien.potencier@symfony-project.com (Fabien POTENCIER) at March 19, 2007 08:09 PM

We released symfony 1.0.0 just a month ago. Time for another release. symfony 1.0.1 is a bug fix only release. The symfony 1.0 branch only contains bug fixes (no new features).

Here are all bugs fixed in this release:

  • r3624: fixed security.yml case sensitivity
  • r3599: fixed sfYaml::load() not returning correct values
  • r3598: removed unneeded usage of JavaScript helpers in the web debug toolbar
  • r3597: fixed sfConsoleRequest::initialize() signature
  • r3541: fixed typo in the cache classes when logging

The major bug fix concerns the security.yml configuration file and actions case sensitivity (see r3624 changeset and symfony mailing-list for more information of the issue).

So, if your application contains some secured modules, this is a recommended upgrade. As for every 1.0.X release, you can upgrade to 1.0.1, clear the cache and you're done.

“Model/field” has been deprecated

CakePHP : Posted by cakebaker at March 19, 2007 05:51 PM

Up to now if you used the HTML or the form helper, you did it in the following way:

$form->input('User/password');

With changeset 4630 this approach has been deprecated in CakePHP 1.2 (but it will still work, of course). The new approach uses a dot notation:

$form->input('User.password');

MVC with Javascript

CakePHP : Posted by cakebaker at March 17, 2007 02:38 PM

Javascript was one of those things I tried to avoid up to now. I don’t know why, but somehow I disliked it (probably because it was a pain to use it in the early days of Javascript) ;-) Sure, I used a bit of Ajax here and there, but thanks to the CakePHP Ajax helper I didn’t had to touch the Javascript.

Lately, I decided to do an Ajax application and so I had to learn Javascript. To make my life easier, I decided to use the JQuery framework. I have chosen JQuery over Prototype because I heard many good things about JQuery, and the API documentation was (and still is) more intuitive.

Of course, my first attempt ended in a total mess. The result looked more like spaghetti than code ;-) That’s when I recalled an article (in German) about a Javascript MVC framework called Jamal, developed by Timo Derstappen. From the Jamal website:

The MVC concept is easy to adopt for javascript

  • Controller: Interaction with the user interface (events)
  • Model: Business Logic and AJAX calls
  • View: DOM, CSS modifications

That makes sense. So I had a closer look at Jamal. It looked nice, but it bothered me that you would have to use a CSS class “jamal” in the HTML code to make it work. So I wrote my own implementation called “jscake” which avoids that. You can find the first version in the downloads section.

Let’s create a simple “Hello world” example with jscake. Our example (CakePHP) view will contain a link and a div to show the messages:

<a href="">click</a>
<div id="messages"></div>

First we create our model. All models are in the “$m” namespace (please correct me if that is the wrong term), which is a shortcut for “jscake.models”. So our model is called “$m.Example”. The model contains one function to return the text which should be displayed:

// app/webroot/js/models/example.js
$m.Example = {

    getText: function() {
        return "hello world";
    }
};

The view is similar to the model, it contains only one function, too. In the function the element with the id “messages” is located and the text appended to that element. Similar to the models the views are in the “$v” namespace.

// app/webroot/js/views/examples.js
$v.Examples = {

    showMessage: function(message) {
        $('#messages').append(message);
    }
};

Last, but not least, we have to create the controller. The index function is automatically called when the JQuery ready event occurs. In this function we simply say that when someone clicks on the link, the other function of the controller (sayHelloWorld) should be executed.

//app/webroot/js/controllers/examples_controller.js
$c.ExamplesController = {

    index: function() {
        $('a').click(this.sayHelloWorld);
    },

    sayHelloWorld: function() {
        $v.Examples.showMessage($m.Example.getText());

        return false;
    }
};

To make our example work we have to include all needed Javascript files in our view, with CakePHP 1.2 it looks like:

<?php $javascript->link(array('jquery-1.1.2', 'jscake', 'controllers/examples_controller',
'models/example', 'views/examples', 'start'), false); ?>
<a href="">click</a>
<div id="messages"></div>

Have fun with jscake! Feedback is welcome :)

Review: Highrise, part 1

RubyOnRails : Posted by Robby Russell at March 15, 2007 11:45 PM

So, today I got what I’ll call a platinum ticket from one of our pals at 37signals for their upcoming new application, Highrise, which is what they’d call a “shared contact manager.” The rest of you can keep hoping that you’ll win a golden ticket this weekend. ;-)

For the past year and a half, I’ve been wanting to build some sort of contact and task management tool for organizing all of the contact requests that PLANET ARGON receives about our Design and Development and Rails Hosting services. If I go away for a week, I come back to a huge backlog of people who may be waiting a response from me. Having a tool to allow others at PA to see what is in my queue and in some cases, respond on my behalf… has been needed. When I first heard about Highrise long ago, I got excited and have tried several different tools and each of those tools has left me feeling uneasy. Perhaps I’ll post some reviews of the other tools one day.

First Impressions

The signup process looks familiar… :-)

highrise signup

Look and Feel

Well, it definitely looks and feels like a 37signals application. There might have been a time when I thought that would be silly… but really, when you look at other product suites, consistency is extremely important to the user experience. While they are definitely going to attract people to Highrise who have never used any of their other products, I’d also expect a huge majority of their initial customers will be users of their other products. It’s obvious that Highrise was in response to a void in the market that people (likely customers) were asking for in other products like Basecamp.

Highrise has all the Ajaxy goodness that you’d expect in a brand new modern web application. Most of it seems very intuitive, but I found myself getting caught up on the extra tabs across the top of the screen. When new tabs appear, my natural response was to try to close them when I was finished looking at the page. Perhaps this is just a design decision that I’ll learn to really like. At the moment, I’m still not quite sure because I expect the tabs to change quite frequently.

Highrise tabs

(few minutes later)

Actually… I wonder if the interface designers at 37signals did this to help their users avoid having several tabs open in their web browser. I use Safari for Basecamp and generally have 5-8 tabs open throughout the day for different projects that our team is working on because the Dashboard view doesn’t really give me a good feel for what is happening throughout the day on our various internal and client projects. I’ll try to pay attention to my usage habits to see if I’m opening less browser tabs in Highrise.

So far, this is the one thing that I’m not quite sure about (yet).

Highrise meets Act-On

Once I saw that you could forward emails to Highrise and it’d auto-magically create a contact and store it, I jumped for joy (not literally… but I got an evil grin). I have been using (more like heavily relying on) Mail Act-On for what seems a really long time. I’m constantly forwarding emails off to my colleagues to keep things from sitting stagnant for too long. So, guess what I did?

Mail Act-On + Highrise

This is working beautifully and allowed me to move about 20 contact requests to Highrise in just a few minutes.

With this new ability, I can remove that one project in Basecamp that I was using to collect contact request information. That information now has a proper home!

Manage your Peeps

PLANET ARGON peeps

I’m taking more screenshots and going to continue putting more of our contacts into Highrise… so… consider this part one of a short series of posts.

To be continued…

BaseUrl finding, Simple event broadcasting and element sorting in JS

CakePHP : Posted by Felix Geisendörfer at March 15, 2007 02:52 PM

Hey folks,

it's been a while since my last post on JS (and in general) and I apologize for that. There is just way too much stuff for me to do right now (client work, my own web app, school finals, etc.) but most of that will be over at some point and then I'll have a thousand interesting things to blog about as well as the time for it. Meanwhile here comes some fun JS stuff I'm using in the web app I'm working on right now and I hope you'll enjoy it. When you see the '$' sign in the code then it's the jQuery library I'm absolutely in love with and not the evil prototype one ; ). All code I'll post now can be written without it, but I don't have the time to present alternative solutions right now.

Alright let's get started. First of all I advocate the usage of a namespace for the functions I'll present since that makes it less likely to run into conflicts with 3rd party scripts as well as allowing for the code base to stay more maintainable. I'm assuming you are using CakePHP so please create a file called 'common.js' in /app/webroot/js/. The other file you put in there is the latest 'jquery.js' that you downloaded from jquery.com. In your html layout you include jquery.js first and common.js second.

The first thing I got for you is a function capable of determining the base url where your application is installed. This is very useful when doing lot's of ajax requests and you want to be able to always use '/controller/action' style url references like you do in CakePHP internally. It's also nice to know image files will always be located in '/img/...'. The way I do it is inspired by the script.aculo.us lib and looks like this:

JavaScript:
  1. var Common =
  2. {
  3.     baseUrl: null
  4.    
  5.     , setBaseUrl: function(url)
  6.     {
  7.         Common.baseUrl = url || $('script[@src$=js/common.js]')
  8.             .attr('src')
  9.             .replace(/js\/common.js$/,'');
  10.     }
  11.    
  12.     , url: function(url)
  13.     {
  14.         return this.baseUrl+url.replace(/^\/+/, '');
  15.     }
  16. }
  17.  
  18. Common.setBaseUrl();

The code should be pretty straight forward. After defining the Common object (used as a namespace) we call it's setBaseUrl function without parameters. This causes a jQuery selector to find the script element of our common.js file and use it's src attribute to determine the baseUrl. From that point on we can do calls like this in our other JS files:

JavaScript:
  1. $.getJSON(Common.url('/tasks/view/1.json'), function(Task)
  2. {
  3.     alert('It is time to "'+Task.name+'"!');
  4. })

Alright this is useful (at least I hope so), but there is more stuff to come. One thing I found myself working a lot with was collections of objects of the same type that need to exchange messages through events with one another. For example you have a class called 'Task' and all of them are managed by another object called 'TaskList'. Now let's say the User is able to click on each Task in your TaskList which causes this particular Task to get the focus (a couple of DOM elements getting an 'active' class). When one Task get's focused, naturally all other tasks need to loose their focus. Here is some code similar to what I'm using in my application right now which will make this become very easy and fail-safe:

A new generic broadcastEvent function for our Common namespace. It basically loops through all listeners (array of JS objects) and sees if they have listeners for the particular task defined and call those up. It also set's the context of the listener function to the object that is listening and uses whatever additional parameters are passed to broadcastEvent for calling it:

JavaScript:
  1. var Common =
  2. {
  3.     broadcastEvent: function(listeners, event)
  4.     {
  5.         var params = arguments;
  6.        
  7.         $.each(listeners, function()
  8.         {
  9.             if (typeof this.events[event] == 'function')
  10.             {
  11.                 this.events[event].apply(this, [].slice.call(params, 2));
  12.             }
  13.         });
  14.     }
  15. }

And here comes the TaskList that object that manages all Task's on the screen. It basically uses a table (element) as it's construct parameter, loops through all rows with a 'td' in them (those that are not part of the table header) and passes the table row elements as an initialization parameter to the Task object that is created for each one of it and added to a list of tasks. TaskList.broadcastEvent uses Common.broadcastEvent to allow the tasks inside the TaskList to communicate with one another easily which you'll see in the Task class.

JavaScript:
  1. var TaskList = function(element)
  2. {
  3.     this.construct(element);
  4. };
  5.  
  6. TaskList.prototype =
  7. {
  8.     element: null
  9.     , tasks: []
  10.  
  11.     , construct: function(element)
  12.     {   
  13.         this.element = $(element)[0];
  14.        
  15.         var self = this;
  16.        
  17.         $('tr[td]', this.element).each(function()
  18.         {
  19.             self.add(new Task(self, this));
  20.         });
  21.     }
  22.    
  23.     , add: function(TaskObject)
  24.     {
  25.         this.tasks.push(TaskObject);
  26.     }
  27.    
  28.     , broadcastEvent: function(event)
  29.     {
  30.         var params = [this.tasks, event];
  31.         params.push.apply(params, [].slice.call(arguments, 1));
  32.        
  33.         Common.broadcastEvent.apply(null, params);
  34.     }
  35. };

And here comes the Task class that triggers the event broadcasting and listens to events. It's constructor takes two arguments. The first one is a reference to the parent TaskList object and the second on is the table row (tr) element that hold the DOM representation of this Task. When the name of the task ('a.name') is clicked then it causes that Task to get the focus and all others to be blured (as they receive the focus event and react on it):

JavaScript:
  1. var Task = function(parent, element)
  2. {
  3.     this.construct(parent, element);
  4. };
  5.  
  6. Task.prototype =
  7. {
  8.     id: null
  9.     , parent: null
  10.    
  11.     , construct: function(parent, element)
  12.     {
  13.         this.parent = parent;
  14.         this.element = element;
  15.        
  16.         this.bindEvents();
  17.     }
  18.    
  19.     , bindEvents: function()
  20.     {
  21.         var self = this;
  22.        
  23.         $('td.task a.name', this.element).bind('click', function()
  24.         {
  25.             self.focus();
  26.             return false;
  27.         });
  28.     }
  29.    
  30.     , focus: function()
  31.     {
  32.         $('a.name, a.time', this.element)
  33.             .removeClass('active')
  34.             .addClass('active');
  35.            
  36.         this.parent.broadcastEvent('focus', this);
  37.     }
  38.    
  39.     , blur: function()
  40.     {
  41.         $('a.name, a.time', this.element)
  42.             .removeClass('active');
  43.     }
  44.        
  45.     , events:
  46.     {       
  47.         focus: function(Task)
  48.         {
  49.             if (Task !== this)
  50.             {
  51.                 this.blur();
  52.             }
  53.         }
  54.     }
  55. };

Alright that's it. The code above is of course just meant for educational purposes and not the complete code I'm using in my web app which is a little more complex and would only distract from demonstrating the Common.broadcastEvent function. So if it doesn't run it's probably because I didn't test the simplified version presented here.

So let's come to my last little trick, sorting elements in JS. Javascript has a very cool function build into the Array class named sort that is very useful for doing all kinds of sorting as it allows you to define a callback for comparing the array elements yourself. It basically works like this:

The following code uses jQuery to get all list items inside of a unorder list and then sorts the array using the anchor text (for simplicity, innerHTML).

JavaScript:
  1. var elements = $('ul#my-list li').get();
  2.  
  3. elements.sort(sortByName);
  4.  
  5. function sortByName(a, b)
  6. {
  7.     if (a.innerHTML <b.innerHTML)
  8.     {
  9.         return -1
  10.     }
  11.    
  12.     if(a.innerHTML> b.innerHTML)
  13.     {
  14.         return 1
  15.     }
  16.    
  17.     return 0
  18. }

But since we don't only want to sort them inside the array but also in the DOM, we need to get a little more tricky. In the simplest case this means to remove all li elements from our unordered list before sorting and them put them back into our list after we sorted them:

JavaScript:
  1. var elements = $($('ul#my-list li')
  2.     .remove()
  3.     .get()
  4.     .sort(sortByName))
  5.     .appendTo('#my-list');

If you don't like the train-wreck style you'd write the above like this:

JavaScript:
  1. var elements = $('ul#my-list li').remove().get();
  2. elements.sort(sortByName)
  3. $(elements).appendTo('#my-list');

So of course if you work with large lists of items you might want to actually swap elements using the Array.sort callback instead of re-populating the entire list, but for this example I wanted to keep it simple. Also here is a live demo where you can check out how the method presented here works on a small list. You basically can not tell that all elements where removed and then added again.

Alright, I hope you find some of the above stuff useful and if you have any questions feel free to ask them in the comments.

-- Felixd

New maintenance release of Catalyst-Runtime.

Catalyst : Posted on Marcus Ramberg at March 15, 2007 01:36 PM

This week we pushed a new Catalyst release to cpan. While it doesn't contain any revolutionary news, it has some neat performance improvements and bug fixes that makes it well worth the upgrade. Check out the release announcement here.

Typo Blog 4.1 for Rails released and 4.2 expected soonafter...

RubyOnRails : Posted by Benjamin at March 15, 2007 05:38 AM

Remember that blog named Typo, the one that was really active under development and a lot of us got our start with when rails first became known as the hip framework that it is? Well, they’ve been plugging away for six months or so in the back of seedy bars and diners on their laptops to finally release their 4.1 version with talk of 4.2 soonafter. I even have to upgrade myself for the ruby on rails blog which is still running typo for those of you curios enough to ask…

Here’s the scoop on what’s new:

The changelog is quite impressive, but I’ll only deal with the visible part of the iceberg :

* Ruby on Rails 1.2 support.
* Complete functionnal revamping of the back office, and partial ergonomic rebuild.
* Internationalization and localization support using localization plugin. The application now runs in French.
* Comment and trackback default moderation.
* Lots of bugfixes and code improvement.
* RSS support for tags and categories.
* Plugins now use Rails plugin engine. We’re gonna release packed plugins soon.

Typo 4.2 is due in 2 months, and the roadmap is quite impressive :

* Support of a publishing workflow and users roles.
* Multiple blogs support with a single Typo instance.
* Switch from Localization to Globalization.
* Integrate proposed patchs as plugins.
* Finish the admin revamping.
* Support more languages.
* Stop doing stupid things like starting to support localization the day before the planned release date.

The project is looking for translators a designer to work with me on the admin while I’m doing the ergonomic stuffs.

You can download the source or install Typo via the gem :

laptop # gem install -y typo laptop # typo install /some/path

[edit] There’s a bug in the migration process if you come from the 4.0 version. Before doing the migration, edit db/migrate/056createnotifications.rb and comment the following line : drop_table :notifications

If you have already started the migration, comment both lines : renametable :notifications, :oldnotifications drop_table :notifications

Ruby on Rails goes 1.2.3 and Mongrel has come a long ways too!

RubyOnRails : Posted by Benjamin at March 14, 2007 08:26 PM

The Ruby on Rails team is at it again with another update to… you guessed it, Ruby on Rails. According to the administration, this release irons out the few wrinkles there was between Ruby 1.8.6 and Rails 1.2.2. Not a critical upgrade for most but a useful one for all of us using Ruby 1.8.6.

On another note, why stop at upgrading rails.

Check out the latest version of mongrel as well. That fine piece of software has really gone a distance. That isn’t to say I haven’t had some unexplainable issues lately with session freezes but that could be config issues on my part. I’ll actually be posting about it once Tommy or I figure it out.

To upgrade both of them, simply do: gem update rails mongrel -y

And there you have it! Have fun and try not to lose your wrench!

Referencing CSS files

CakePHP : Posted by cakebaker at March 14, 2007 10:42 AM

Some time ago I wrote a post titled “Referencing Javascript files” in which I explained how you can reference Javascript files.

Now I noticed that the same approach also can be used for CSS files. For this purpose an additional parameter was added to the css() function in the HtmlHelper in CakePHP 1.2. So to load a page-specific CSS file you can add the following code to your view (notice the fourth parameter):

$html->css('page_specific', null, null, false);

Or if you want to load multiple CSS files:

$html->css(array('first', 'second'), null, null, false);

To make those snippets work you must have the variable $scripts_for_layout in your layout:

<head>
    <?php echo $scripts_for_layout; ?>
</head>

It is the same variable used in the approach to reference Javascript files.

New community page, lots of plugins

Symfony : Posted by francois.zaninotto@symfony-project.com (Francois ZANINOTTO) at March 13, 2007 05:54 PM

If you are a regular visitor of the symfony community page, you probably noticed that it radically changed recently. Instead of a simple hub page leading to the forums, mailing-lists and wiki, the community page is now an aggregation of content from all these sources, as well as an overview of the latest posts mentioning symfony in the blogosphere.

If you want your weblog to be included in the list of sites visited by our aggregator, please add it to the list of symfony bloggers in the wiki. You will receive a visit once a day from our server, so it shouldn't change your weblog stats too much...

The new community page makes a heavy use of two new symfony plugins, sfWebBrowser and sfFeed2. The first is a lightweight HTTP client, RESTful and similar in syntax to the sfTestBrowser. The second is a refactoring of the original sfFeedPlugin, which you probably heard of when following the askeet tutorial. What's new in sfFeed2 is that not only does it publish RSS and Atom feeds based on an array of objects, it also reads feeds fetched from the Internet and allow their manipulation and aggregation.

These two plugins are just a sample of the fantastic plugin activity since the 1.0 release. Several new plugins are released every week, and the list of available plugins now shows more than fifty plugins, and counting. I mention only a few ones here, just as an appetizer:

  • sfDoctrine: A full-featured model layer, providing integration of the phpDoctrine ORM. Comes with admin generator, schema.yml, i18n, fixtures loading, etc.

  • sfPokaYokePlugin: A client-side validation in JavaScript, using the rules defined in your YAML validation files.

  • sfPropelActAsNestedSetBehaviorPlugin: An implementation of nested sets for Propel, packaged as a behavior.

  • sfUJSPlugin: A replacement for Prototype, aiming at unobtrusiveness and ease of use. Currently uses jQuery instead of Prototype.

Plugin authors are advised to publish a post in the users mailing-list whenever they release a new plugin or an important upgrade. As for the developers, they already know how to spot new plugins by following the trac timeline.

We strongly advise you to watch the plugins page and test the newly released plugins - developers love feedback, and who knows, a plugin may save your day. If you feel like contributing a piece of code that you find particularly useful, you now have plenty of examples on how to package it into a reusable plugin.

So write about symfony and contribute new plugins, the symfony community will thank you!

From twitter

Catalyst : Posted on Marcus Ramberg at March 13, 2007 10:04 AM

davehodg: ilmari just pointed out today is today is 13/3/7!

In case of paranoia - restricting max upload size

Catalyst : Posted on Marcus Ramberg at March 13, 2007 09:56 AM

In case you are paranoid about people uploading 2 gig kitty-porn to DOS your server, like zamolxes, and you are running lighttpd like any sensible person (read 'me'), here's the option to restrict the max size of post requests:

server.max-request-size 104857600

would restrict it to 100 megabytes. If you need a smaller size, just ask google :)

Poor Communication and IT Projects

RubyOnRails : Posted by Robby Russell at March 12, 2007 10:25 PM

InformationWeek has a short story titled, Poor Communications, Unrealistic Scheduling Lead To IT Project Failure.

“Communications failures top the list of reasons IT projects fail, according to poll results from the Computing Technology Industry Association.

About 28% of 1,000 respondents identified poor communications as the main cause of project failure, according to CompTIA, which offers project management training.”

So, while we’re all spending so much of our time focused on improving our technical skills, are we also investing our time into becoming communication superstars?

If you look back at the following posts, you’ll see some links to some excellent books on this topic.

Stikkit wins award at SXSW

RubyOnRails : Posted by Robby Russell at March 12, 2007 01:13 PM

After kicking myself all weekend for not heading to SXSW... was getting ready to head to sleep last night and saw a post by Michael Buffington announcing that Stikkit won an award for Best Technical Achievement.

Congrats to Rael, Michael, and the rest of the Stikkit team!

Invited to the Microsoft Technology Summit 2007

RubyOnRails : Posted by Robby Russell at March 12, 2007 01:01 PM

As mentioned a few weeks ago, I’ll be up in the Seattle area in a few weeks. I took the invitation to head to the 2007 Microsoft Technology Summit in Redmond, WA. When I received the invitation… my immediate response was, “Why me?”

Their response?

“The event is specifically for people other than Microsoft fanboys… they want to have a dialogue with influential members of the developer community outside of their comfort zone to see what we can learn from each other..”

Fair enough. Perhaps they’ll convince me to switch our entire team to Microsoft products… but I highly doubt my team would be cool with that. ;-)

It looks like Michael Koziarski (of Rails Core fame) will also be at the event. Supposedly, there will only be 50-60 people… so if you’re also going to be there, let me know.

I’m not sure what to expect yet from the event and they don’t have a web page dedicated to it. The most that I could find was a few blog posts from attendees of previous years. I plan to do some blogging during the event to share my experiences (good and bad).

p.s. thanks to those who invited me to have drinks/dinner while I’m staying in Bellevue. I’ll be responding to your emails in the next week. if you’re interested in meeting up, drop me a line.

Formats for the Selenium IDE

CakePHP : Posted by cakebaker at March 11, 2007 10:42 AM

The Selenium IDE (a plug-in for Firefox) allows you to record tests. A guy called “wishcow” published now a format which makes it possible to export those tests for the Selenium helper. Based on his format I wrote a format for creating test cases for the Selenium test suite. You can find it (plus the helper and the test suite) in the downloads section,

The installation is simple (you can find a graphical installation guide on “wishcow”’s blog):

  • Open the Selenium IDE
  • Select “Options”/”Options…”
  • Switch to “Formats”
  • Choose “Add”
  • Define a name and copy&paste the format in the text area
  • Click on “Ok” to save it

Happy testing :)

Expandable links

CakePHP : Posted by cakebaker at March 10, 2007 03:30 PM

Even though I am not a design/usability specialist I will discuss the design process for a rather minor detail in an application.

I have one of those lists you will find in almost every application which lists the items the user entered. Now it should be possible to move these items to two different categories. How could this be solved?

The easiest solution would be to add two links to the end of each row. But how do you name those links? “Move to Category A” and “Move to Category B”? Hm, that takes too much space and it looks ugly if you have to repeat it on all rows.

The next idea is to use icons instead of text. But this causes a similar problem as above. How should the icons look so people understand them?

Another idea is to put a checkbox in front of each row and to have a select box with the possible actions at the top and/or the bottom of the list. The problem here is that the user has to click at least three times to move an item.

What about a drag and drop solution? Well, drag and drop is not very intuitive, and it is difficult to visualize that an object is draggable.

Maybe we could add a select box to the end of each row? Well, it is still two clicks for the user. Can we reduce it somehow to a one-click action?

We could add a link to the end of each row and then on a mouse over event we show the possible actions. So with only one click the user can move a list item to the desired category. With that I have found the solution for the problem described at the beginning of this post. Here the visualization of this idea which I called “expandable link”:

Closed link
Expanded link

The first image shows simply a link, and the second shows what happens on a mouse over event on this link.

Up to now I didn’t test this solution in the wild so I cannot say how it will be accepted by the users, but if they are like me, they will find it cool ;-)

[blog] Time for a diet...

CakePHP : Posted on Fabio Cevasco (h3rald) at March 09, 2007 06:59 PM

My fiancandeacute;e keeps telling me that too many cakes are not good for me, and I never listen: I always liked cakes! I did like the CakePHP™1 framework too, once, and I did write some articles about it in the past, and I believe at least a bunch of Bakers found them useful, especially at the time. I do believe the Cake™ Software Foundation1 quite liked having their framework featured on popular websites like php|architect and SitePoint, and I believe that I contributed - to some extent - to make it one of the most popular frameworks available for the PHP programming language. Unfortunately though someone decided that two of such articles and my personal website were no longer worth a mention on CakePHP official website frontpage. […]

Seth Godin on Dialogue

RubyOnRails : Posted by Robby Russell at March 09, 2007 05:37 PM

It appears that Seth Godin is catching on to the concept of Dialogue.

Seth writes, “Some organizations are good at listening. Some are good at talking. A few are even good at both.”

I’ve been spending a lot of time thinking about how I listen to clients, employees, friends, and family. All of our relationships are a series of conversations. Sometimes we can have healthy dialogue, sometimes we just fall victim to debate. (see Dialogue vs Debate)

If you’re really interested in Dialogue, I’d encourage you to review the technology of Dialogue... and check out the Dialogue-Driven Development project and introduce yourself.

Heresy and Fried Onions

Seaside : Posted by Avi at March 09, 2007 02:21 AM

There’s story in Primo Levi’s The Periodic Table (via, via) about a recipe for varnish that included a raw onion. Nobody at the varnish plant where he worked knew what its purpose was. It turned out that it had originally been used as a thermometer: if the onion fries, things were hot enough. Now, they had (and used) better thermometers, but they kept on throwing the onion in because that’s just what you did.

I think that web frameworks have a lot of onions in them. A lot of the design decisions that are unconsicously adopted by modern frameworks like Rails or Django were made 10 or 15 years ago. They were probably good decisions at the time, but we need to re-evaluate them now that we have new and better tools available to us.

That’s the thesis behind the Applied Web Heresies tutorial I’m giving at ETech later this month. It’s a follow up to the Web Heresies talk I gave at OSCON last July. Both of these talks are about Seaside, but they’re not about how or why you should run out and use Seaside yourself - there just aren’t that many prospective Smalltalkers out there. Instead, you might say they’re about Seaside’s recipe, and what new ingredients it gets to use after taking the bold step of chucking the onion into the compost pile. In the tutorial, I’m hoping that we’ll build some mini Seaside-style frameworks in Ruby, Python, and other dynamic languages. I don’t know how far we’ll get, but at least whatever we make will be onion-free.

As an appetizer, here are some things I think are onions: template systems, hidden fields, meaningful query parameters, and sessions stored in databases. Why? Because we have, but aren’t fully appreciating, these thermometers: CSS, RSS, fast CPUs with gigs of RAM, and smart load balancers. What new ingredients do we get to use? By far the spiciest of these is closures or blocks: it’s amazing how much pain they can take away once you’re committed to using them pervasively at the framework level.

I think it’ll be a fun tutorial, and I’m really looking forward to it. See you in San Diego.

How to update your primary key with current DBIx::Class CPAN release

Catalyst : Posted on Marcus Ramberg at March 08, 2007 12:58 PM

10:41 <@mst> marcus: 
    $obj->result_source
        ->resultset
        ->search($obj->ident_condition)
        ->update({ id => $new_id })

From the #dbix-class channel on irc.perl.org

RadRails moves to Aptana

RubyOnRails : Posted by tshine at March 08, 2007 03:06 AM

I was hanging out in #radrails on IRC when Kyle Shank (lead developer of RadRails) came in the room and announced that RadRails will be moving over to the Aptana project (http://aptana.com). This will mean that developers will have the power of Aptana's HTML, JS and CSS editors along with the features they have already grown to love from RadRails such as templates, database view, code generators, rake tasks view, etc. This appears to be a win win situation. I would like to take this time to thank Kyle Shank and Matt Kent for getting RadRails to the point it is today. I have used RadRails since the first version and have high hopes for its future in the hands of the Aptana developers. Please join us in #aptana on IRC (freenode) to help get the community started.

[blog] Too many cooks spoil the Cake book

CakePHP : Posted on Fabio Cevasco (h3rald) at March 07, 2007 09:45 AM

I am sorry to announce that my upcoming book, CakePHP Recipes, will not be published anymore. As a matter of fact, it wasn’t finished because some of the people involved failed to comply with the terms of their contract in delivering material which was suitable for publication. To quote an email I received from my publisher a few days ago, _”[…] The Cake Software Foundation has informed us they are withdrawing from the CakePHP Recipes project, and returning the advances they’ve received. […]

Criki - the creation of a wiki with CakePHP, Part 2

CakePHP : Posted by cakebaker at March 07, 2007 09:01 AM

On IBM’s developerWorks the second part of the series “Create an interactive production wiki using PHP” has been published. It deals with user registration and custom markup rendering.

I am not so happy with this part. First, it uses functions of the HTML helper which are deprecated since version 0.9.2 of CakePHP, like passwordTag() and submitTag() (you should use password() resp. submit() instead). And second, I think the rendering of the markup shouldn’t be done in the controller. It should be done in a component or a helper (depends on whether you look at it as business logic or presentation logic). The advantages:

  • the code would be easier to test
  • the controller would be easier to read
  • the markup rendering could be reused

Anyway, here the links to the article: article (registration needed), PDF

Part 1

Free PeepCode Coupon and T-Shirt

RubyOnRails : Posted by topfunky at March 06, 2007 06:42 PM

Since yesterday’s product toss went so quickly1, how about another shot?

In the photo above you can see the my final project from my letterpress class (printed last night).

I designed this as a fake concert ad where most of the band names include some kind of typographical term2. The first three people to correctly identify at least 8 of the 9 will receive a coupon for an episode of PeepCode and a PeepCode t-shirt shipped to their house at my expense (anywhere in the world).

Void where prohibited. The Topfunky Corp is not responsible for lost, misdirected, or SPAM-tagged email. Employees of the Topfunky Corp not eligible. Entries by people with either “Jacob” or “Harris” in their name will be checked twice to verify accuracy.

UPDATE! Congrats to Michele Finotto, Keith Frost, and Luke Redpath for being quick on the trigger and also having correct answers. They will be receiving a PeepCode t-shirt in the mail soon.

Correct Answers
bastarda
A gothic script from France and Germany used in the 14th and 15th centuries. Was used for documents of minor value or importance.
beardline
The lower extent of descenders in letters like g, p, or y.
mutton
A measurement equal to the width of the letter “m.” Also called an “em space.”
nut
A measurement equal to the width of the letter “n.” Also called an “en space.”
hedera
A decorative character in the shape of an ivy leaf.
slug
A piece of lead or other metal used to make space between lines of text.
pilcrow
The paragraph sign. ¶
full bleed
The technique of printing past the edge of a page, or printing out to an area and then cutting the paper to a smaller dimension.
neitherhanded
Not technically a typographical term, but refers to typefaces that do not lean to the right or the left. Early typefaces were made to look as if they had been drawn by a right-handed person, but others abandon this and have a more upright appearance (for example, bodoni).

Thanks to everyone who entered, including Jacob Harris!


1 Ten coupons in about 15 minutes. Honestly, I’m a little worried that people are stalking my blog so frequently that they notice within one minute of a new blog post.

2 Most of the people in the class didn’t find this funny, which baffles me. How is “The Rusty Slug” not funny?

20 Trusted AJAX, DHTML and JavaScript Tool Sites

RubyOnRails : Posted on Max Kiesler at March 06, 2007 06:33 PM


AJAX Tools AJAX Patterns
AjaxPatterns.org began as a collection of design patterns, which formed the basis of the book, Ajax Design Patterns, and grew into a publicly editable wiki on anything and everything Ajax. All pages (except this homepage) are now editable, no registration required. Feel free to contribute!

FiftyFourEleven
Ajax examples (XMLHttpRequest examples), code snippets and proof of concepts - the links below should help get you started on building your own functions with XMLHttpRequest and Ajax.

Google Web Toolkit - Build AJAX apps in the Java language
Google Web Toolkit (GWT) is an open source Java software development framework that makes writing AJAX applications like Google Maps and Gmail easy for developers who don't speak browser quirks as a second language. Writing dynamic web applications today is a tedious and error-prone process; you spend 90% of your time working around subtle incompatibilities between web browsers and platforms, and JavaScript's lack of modularity makes sharing, testing, and reusing AJAX components difficult and fragile.

Kabuki Ajax Toolkit
The Kabuki Ajax Toolkit (AjaxTK) is now available for separate download for object-oriented UI programmers that want to deliver some of that Zimbra Ajax richness within their own web applications.

MiniAjax
A showroom of nice looking simple downloadable DHTML and AJAX scripts.

Moo.ajax
Moo.ajax is a very simple ajax class, to be used with prototype.lite from moo.fx. It's roughly based on the prototype one, so their usage are very similar.

Nomadic Functions
Why include ten .js files (ala Yahoo UI) to create one simple effect? This may be a drastic example, however, in any case, even when using MooFX's small 2k library you are including a lot of filler code. What's the solution? Nomadic Functions. Nomadic functions are small, optimized, and fairly specific. An example may be anything from a simple Drag & Drag function, to a AJAX Star Rating, or a Text Scroller. They are like widgets... but even smaller.

Oracle - Web 2.0 and Ajax Resources
On this website you will find hype free Ajax and Web 2.0 collateral, samples and pointers that you will find useful when building web applications today. In addition to having a strong focus on today's Web 2.0 technologies and Ajax, this page will also expose component information, best practices and hints and tips about the ADF Faces Rich Client components in JDeveloper 11.

Sun - Ajax Developer Resource Center
Details on AJAX App Development, Free Articles, Tips, Tools & More.

Yahoo! User Interface Library
The Yahoo! User Interface (YUI) Library is a set of utilities and controls, written in JavaScript, for building richly interactive web applications using techniques such as DOM scripting, DHTML and AJAX. The YUI Library also includes several core CSS resources.

DHTML Tools DhtmlGoodies
DhtmlGoodies was launched the 6th of September 2005. The site is developed and maintained by Alf Magne Kalleland from Norway. The purpose of this site is to provide you with a library of well working DHTML and AJAX scripts.

Dthmlsite
At DHTMLSite, you will find a directory of useful AJAX/DHTML scripts and tutorials.

Dynamic Drive
One of the largest and best places on the internet to find DHTML scripts and tutorials.

Javascript Tools DZone Snippets - Javascript
Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

Face
FACE was developed to allow standards-compliant web developers to put more life and energy into their pages, without having to learn Javascript or Flash: all it takes is basic math skills and a good understanding of CSS. FACE is built entirely from JavaScript and the CSS that you provide to control the animation.

Javascript Toolbox
This site is intended to be a repository of code and reusable libraries which address common needs that many web developers encounter. The code found here is based on standards but also tries to be backwards-compatible for browsers which don't support the standards. The information on the site emphasizes standards-compliance for best results, and best practices which should be followed. This is not a site containing snippets of code submitted by anonymous, unreliable coders. All code on the site is written by one person, in a consistent fashion, tested thoroughly, and used in practice by thousands of web sites around the world.

PublicWarehouse - Javascript Database
An amazing list of scripts in almost any category you can think of.

Remedial JavaScript
The JavaScript Programming Language suffers from premature standardization. Some feature omissions can be corrected by adding new functions and basic methods to our standard programming toolkit. That is what we will be doing here. There are functions that I feel should have been in the Standard and required in every implementation. Fortunately, JavaScript is such an expressive language that we can easily fix the omissions locally.

Ten Javascript Tools Everyone Should Have
Javascript frameworks have exploded on the scene over the last few years but they're no replacement for a good toolbox: those little snippets of code you seem to include in every single project. Here's my list of 10 essential Javascript tools everyone should have at their fingertips!

Top 10 Custom JavaScript Functions of All Time
If there was ever a universal common.js shared among the entire develosphere, you'd fine these ten (plus one bonus) functions. It would be the swiss army knife no developer would go into production without. They have no doubt been tested tried and true and have proven usefulness and helpfulness to all those who've used them. So without further ado, here are what I believe to the top ten greatest custom JavaScript functions in use today.

PeepCode Page Caching and httperf

RubyOnRails : Posted by topfunky at March 05, 2007 11:37 PM

Programmers love to see benchmarks and statistics. Admit it! You love it as much as I do. Show us a table of figures or a chart and we go ga-ga.

Who cares that these “authoritative” benchmarks often measure incomplete implementations or fail to re-measure their data when presented with red flags such as the idea that Rails 1.2 is 2-4 times slower than 1.1.6. Or that most omit one of the most basic of statistical measurements.

Many of these are presented innocently and without the intent to deceive but they aren’t models for meaningful benchmarking. Most of the time you’re not looking for big numbers and shocking statistics, you just need to find out which implementation is faster.

If I fragment cache my tag cloud, will it really make the site any faster? Are memcached sessions across the network really any faster than ActiveRecord sessions?

Most of the time this means benchmarking your entire Rails stack: webserver, app server, Rails, database, filesystem. One of the best tools for benchmarking a full stack is httperf. It has a pretty good set of features and it gives you statistically useful information.

Learn the Basics

I’ve spent the last few weeks picking Zed Shaw’s brain and learning all I can about httperf. I’ve put all that information into the latest PeepCode. It starts with an episode on Page, Action, and Fragment Caching, but if you are already familiar with caching you can jump straight to the main course and view the httperf episode.

More and more I’m finding that the best way I can communicate is through a screencast, but here are a few tips that have helped me:

  • You can’t do benchmarking without knowing some basic statistics. And no, understanding the concept of “average” isn’t enough! Learn about what standard deviation is and what it says about the data you pulled from your benchmarking run. No server performs at exactly 32.746 requests per second…there’s a range of error and if you don’t express your benchmark along with that range then you haven’t communicated anything. It isn’t very hard to understand and the benefit to your powers of evaluation will be huge. If nothing else, you’ll be able to properly criticize poorly executed benchmarks on other blogs! See also Zed’s rant.
  • Learn how to read the output of your benchmarking tool of choice. Even ab gives you some results with standard deviation, but it’s not often used. For httperf, it’s the “Reply rate” line. Here are a few from separate runs against the same server running two kinds of software:
Reply rate [replies/s]: min 100.6 avg 101.0 max 101.4 stddev 0.6 (2 samples)
Reply rate [replies/s]: min 100.6 avg 100.7 max 100.8 stddev 0.1 (2 samples)
Reply rate [replies/s]: min 94.0 avg 96.4 max 98.8 stddev 3.4 (2 samples)
Reply rate [replies/s]: min 103.8 avg 104.4 max 105.0 stddev 0.9 (2 samples)
  • Be a scientist. Running a benchmark should be done with the same care that a chemist measures a blood sample for a trace of a fatal disease. Quit all the other programs you have running. Use two separate computers so your benchmarking tool doesn’t compete with your web application for the CPU.

Here’s a script that will parse a text file full of “Reply rate” lines and give you a Gruff graph as seen above.

Calendar

I’m traveling a bit in March. If you’re at any of these places in the next few weeks, find me and I’ll give you a PeepCode t-shirt (limited to availability):

If not, you can buy a PeepCode t-shirt at my Shopify store (USA only for the first set).

Resources

And a free episode for the first 10 people to figure out how to use this coupon code: PEEP-NUBY

(The coupons were fetched up snappily in about 15 minutes.)

Baking a Cake - From the domain model to Cake models

CakePHP : Posted by cakebaker at March 04, 2007 04:53 PM


Warning: Division by zero in /home/.funtime/dhofstet/cakebaker.42dh.com/wp-content/plugins/tla_83727.php on line 407

Do you remember the domain model from a previous post? No? No problem, here it is again:

Domain model

The next logical step on the way from the domain model to the Cake models would be to refine the domain model to a database diagram. I say “would be” as it is something I almost never do, especially not for small projects. For me it isn’t worth the additional effort as a database diagram doesn’t add much value if you know some basic rules (Cake conventions). And well, I don’t have found a free tool for this task which convinced me ;-)

So I will go on and create the tables manually. For this purpose I will create a create.sql file in app/config/sql.

The first rule is that table names are plural. So we can write the SQL statement for the table for the “User” class (I omit the other classes as it is the same for them):

CREATE TABLE users (
);

The second rule is that each table has a primary key field with the name “id”. So we can modify the script from above to:

CREATE TABLE users (
    id INT(11) NOT NULL AUTO_INCREMENT,
    PRIMARY KEY (id)
);

The third rule is optional, but you can add the fields “created” and “modified” to the table, which are later automatically populated by CakePHP:

CREATE TABLE users (
    id INT(11) NOT NULL AUTO_INCREMENT,
    created DATETIME,
    modified DATETIME,
    PRIMARY KEY (id)
);

The fourth rule affects 1:n relations: The “n” table must contain a field named “singular name of ‘1′ table” + “_id”. In our domain model the relation between User and Project is such a 1:n relation: one User can have many Projects. So we have to add the field “user_id” to the “projects” table:

CREATE TABLE projects (
    id INT(11) NOT NULL AUTO_INCREMENT,
    user_id INT(11) NOT NULL,
    created DATETIME,
    modified DATETIME,
    PRIMARY KEY (id)
);

The fifth rule affects n:n relations. Such relations cannot be mapped directly to the database, we have to introduce a join table. The name of this join table is “plural name of first ‘n’ table” + “_” + “plural name of second ‘n’ table” whereby the names are sorted alphabetically. In our domain model we have only one n:n relation, between User and Project. So the table name is “projects_users”. We also have to apply the fourth rule, as the relations between “projects” and “projects_users” resp. between “users” and “projects_users” are 1:n relations. And so we get:

CREATE TABLE projects_users (
    project_id INT(11) NOT NULL,
    user_id INT(11) NOT NULL
);

Now we can add the attributes from the conceptual classes as fields to the respective tables and write the drop statements. And we get:

CREATE TABLE users (
    id INT(11) NOT NULL AUTO_INCREMENT,
    created DATETIME,
	modified DATETIME,
    PRIMARY KEY (id)
);

CREATE TABLE projects (
    id INT(11) NOT NULL AUTO_INCREMENT,
    user_id INT(11) NOT NULL,
    created DATETIME,
    modified DATETIME,
    PRIMARY KEY (id)
);

CREATE TABLE messages (
    id INT(11) NOT NULL AUTO_INCREMENT,
    project_id INT(11) NOT NULL,
    title VARCHAR(255) NOT NULL,
    content TEXT NOT NULL,
    created DATETIME,
    modified DATETIME,
    PRIMARY KEY (id)
);

CREATE TABLE feeds (
    id INT(11) NOT NULL AUTO_INCREMENT,
    user_id INT(11) NOT NULL,
    url VARCHAR(255) NOT NULL,
    created DATETIME,
    modified DATETIME,
    PRIMARY KEY (id)
);

CREATE TABLE projects_users (
    project_id INT(11) NOT NULL,
    user_id INT(11) NOT NULL
);

And in app/config/sql/drop.sql:

DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS projects;
DROP TABLE IF EXISTS messages;
DROP TABLE IF EXISTS feeds;
DROP TABLE IF EXISTS projects_users;

To execute these SQL scripts I use a simple Ant script (app/config/sql/build.xml):

<?xml version="1.0"?>
<project name="n" default="default">
    <property name="driver" value="com.mysql.jdbc.Driver"></property>
    <property name="url" value="jdbc:mysql://localhost/n?characterEncoding=UTF-8"></property>
    <property name="user" value="root"></property>
    <property name="password" value=""></property>

    <target name="default">
        <sql driver="${driver}" password="${password}" url="${url}" userid="${user}" src="drop.sql" encoding="UTF-8"></sql>
        <sql driver="${driver}" password="${password}" url="${url}" userid="${user}" src="create.sql" encoding="UTF-8"></sql>
    </target>
</project>

After executing the Ant script, we are ready to bake the models. I will use the bake script (available in cake/scripts) to generate the User model, as I was asked to show how bake.php is used. I think it should be self-explanatory:

Baking model, part 1

Baking model, part 2

This will create the following model in app/models/user.php:

<?php
class User extends AppModel {
    var $name = 'User';

    //The Associations below have been created with all possible keys, those that are not needed can be removed
    var $hasMany = array(
        'Feed' => array('className' => 'Feed',
            'foreignKey' => 'user_id',
            'conditions' => '',
            'fields' => '',
            'order' => '',
            'limit' => '',
            'offset' => '',
            'dependent' => '',
            'exclusive' => '',
            'finderQuery' => '',
            'counterQuery' => ''),
        'Project' => array('className' => 'Project',
            'foreignKey' => 'user_id',
            'conditions' => '',
            'fields' => '',
            'order' => '',
            'limit' => '',
            'offset' => '',
            'dependent' => '',
            'exclusive' => '',
            'finderQuery' => '',
            'counterQuery' => ''),
    );

    var $hasAndBelongsToMany = array(
        'Project' => array('className' => 'Project',
            'joinTable' => 'projects_users',
            'foreignKey' => 'user_id',
            'associationForeignKey' => 'project_id',
            'conditions' => '',
            'fields' => '',
            'order' => '',
            'limit' => '',
            'offset' => '',
            'unique' => '',
            'finderQuery' => '',
            'deleteQuery' => '',
            'insertQuery' => ''),
    );
}
?>

It generates also a test case for the model in app/tests/cases/models/user.test.php:

<?php 

loadModel('User');

class UserTestCase extends UnitTestCase {
    var $object = null;

    function setUp() {
        $this->object = new User();
    }

    function tearDown() {
        unset($this->object);
    }

    /*
    function testMe() {
        $result = $this->object->doSomething();
        $expected = 1;
        $this->assertEqual($result, $expected);
    }
    */
}
?>

As you see, when using the bake script you have to answer many questions. That’s a bit of a pain when you want to bake multiple models. And if you don’t want to write tests you have to remove them (that’s at least what I do with files which are not used). These are the reasons I prefer to write the models manually.

Here are the other models (please notice that I omit the $name variable as I am a) using PHP5 and b) lazy):

// app/models/feed.php
<?php

class Feed extends AppModel {
    var $belongsTo = array('User');
}
?>
// app/models/project.php
<?php

class Project extends AppModel {
    var $hasMany = array('Message');
    var $hasAndBelongsToMany = array('User');
}
?>
// app/models/message.php
<?php

class Message extends AppModel {
    var $belongsTo = array('Project');
}
?>

To test whether we defined the associations correctly, we can create for all models the corresponding controllers. Here the UsersController (the other controllers are similar):

// app/controllers/users_controller.php
<?php

class UsersController extends AppController {
    var $scaffold;
}
?>

With that, we have something to play, we can add, edit, and remove records. But it isn’t very useful yet ;-)

That’s it for today.

(other articles from this series)

Please Make Fun of the Boss

RubyOnRails : Posted by Robby Russell at March 02, 2007 06:26 AM

While reviewing some articles related to small business management, I came across the following post… titled, Note From Boss to Employees, by Michael Wade. As a young business owner, who only 16 months ago was working in his attic… to now trying to figure out how to run a company with over ten employees (and growing), posts like this remind me that we all have so much to learn. :-)

Here are a few that I appreciated…

“I may not have been given a huge amount of training before being named to a supervisory position. As a result, I’ve had to learn through trial and error. That’s not always bad. Many of my responsibilities can only be learned through practice.”

Yep… that’s me! The only difference is that I promoted myself instead of being promoted by someone else. I’m still not sure what I got myself into sometimes. ;-)

“I will make mistakes. Please give me the same understanding that you’d like me to give you when you blunder.”

This reminded me of a blog post from last year, titled, Avoiding the most common software development goofs, which points out that things like ignorance and stress are often to blame for mistakes in development. I feel like these are reasons for goofs in just about any environment, especially business. Let’s face it. We’re not perfect and we’re going to make a lot of mistakes. Once we’ve agreed on this, let’s take the next step and see what happens.

“If I do something dumb or am on the verge of doing so, please tell me. Don’t hint. Tell me.”

Perhaps this is a common problem for most small business owners. Are employees afraid to tell me that I’m doing something dumb?

“If either of us has a problem with the other’s performance, let’s talk about it.”

As they say, real friends will be honest with you about your faults. Not because they want to make you look bad, but because they care.

Each of the points that I have listed here are pointing to is… healthier Dialogue, which is always a challenge to accomplish… in any relationship… whether with clients, coworkers, bosses, or employees.

I’d like to add a few to this list.

  • It’s easier to ask for forgiveness, than to ask for permission.
  • I’m still trying to get the hang of this GTD stuff, so.. you might remind me if I forgot something.
  • Ask yourself on a regular basis, “Am I having fun?” If not, make time for some.
  • Please make fun of the boss! :-)

Profile Your Catalyst/DBIC App with QueryLog

Catalyst : Posted by gphat at March 01, 2007 08:37 PM

Months ago I implemented DBIx::Class::Storage::Statistics with the intent of making some sort of profiling tool. I finally got off my ass and did it. DBIx::Class::QueryLog should be on CPAN this weekend.

At $work we are replacing a legacy system with one based on Catalyst. One of the deliverables for this year is a new order entry application. Since our customer service folks are used to working in a terminal based system speed is of great concern to us.

So I installed DBIx::Class::QueryLog as the debugobj as described in it’s documentation, dropped it in the stash and added some code to our wrapper.tmpl:

[% IF querylog %]
      <div class="featurebox">
        <h3>Query Log Report</h3>
        [% SET total = querylog.time_elapsed | format('%0.6f') %]
        <div>Total SQL Time: [% total | format('%0.6f') %] seconds</div>
        [% SET qcount = querylog.count %]
        <div>Total Queries: [% qcount %]</div>
        [% IF qcount %]
        <div>Avg Statement Time: [% (querylog.time_elapsed / qcount) | format('%0.6f') %] seconds.</div>
        <div>
         <table class="table1">
          <thead>
           <tr>
            <th colspan="3">5 Slowest Queries</th>
           </tr>
          <

/thead> <tbody> <tr> <th>Time</th> <th>%</th> <th>SQL</th> </tr> [% SET i = 0 %] [% FOREACH q = querylog.get_sorted_queries %] <tr class="[% IF loop.count % 2 %]odd[% END %]"> <th class="sub">[% q.time_elapsed | format('%0.6f') %] <td>[% ((q.time_elapsed / total ) * 100 ) | format('%i') %]%</td> <td>[% q.sql %]</td> </th></tr> [% IF i == 5 %] [% LAST %] [% END %] [% SET i = i + 1 %] [% END %] </tbody> </table> </div> [% END %] </div> [% END %]

The first run yielded this:

Well. That select from users could use an index on username, eh?

Weee! That one disappeared. What are all these role selections? We use Catalyst::Plugin::Authorization::Roles but this looks fishy. We might could use an index on the role name but there are only 6 rows in that table. A bit more investigation finds a loop in our Root controller that iterates over a set of roles calling check_user_roles() to try and find out where we should redirect the user. So lets swap that out for creating an array of roles and using grep…

Kick ass! We are down to two measly queries. We reduced the number of queries by about 75% and decreased the time spent executing SQL by about 75% as well. These queries were being executed on every page load.

UPDATE:Yeah, the average statement time was broken during that. Oops.

Further evidence that Microsoft still didn't get it: video.msn.com

Catalyst : Posted on Marcus Ramberg at March 01, 2007 04:27 PM

So, as part of my development of iwatchthis.com, I've been researching the various video sites out there. Youtube, Google Video, and most of the minor sites out there does this is a fairly similar way. Almost everybody uses a flash player (With DivX as a honorable exception, requiring a download, but providing high-res content), so I guess you could attribute the video boom to Macromedia/Adobe, who made this trivial in recent versions.

Apart from that, some sites do blunders in navigation, sometimes going overboard with AJAX :) Others have sucky title-tags, Most of them tries to lead you into their site by showing you related videos, Lots of them use tags to caterize content, alot of them provide a comment system. All in all, the content is the largest differentiator for these sites, with some getting bonus points for good design solutions.

Last week, I worked on adding support for http://vids.myspace.com on http://iwatchthis.com/. Despite their trademark hideous design, they also mostly fall into the above group. The 'Myspace.com' title that appears on every video page isn't really search-engine or bookmark friendly, but that's a fairly common mistake in this segment. This week however, I'm looking at a site that's really decided to reinvent the whole concept of video sites.

Fair enough, http://video.msn.com/ does sport a Beta tag, but in recent years, we've come accustomed to seeing those on several web apps out there, and it's a bleak excuse for major design flaws. Screwing bookmarkability and navigation is just the start of it.

Welcome to AJAX hell. Leave your 'back' button at the door. Of course, MS provides a patch for this problem.. However rather than using the common 'bookmark this' link to give a bookmarkable URL, they do a neat bit of Javascript fuckery to open your browser's bookmark toolbar. Never mind that some of us like to use online bookmark managers like magnolia,delicious or stumbleupon. Scratch that. never mind that the javascript pile of crap actually bookmarks whatever page you landed on in the video site, be it the frontpage or some video link a friend sent you, not the video you are currently watching. Admittedly, I am a Firefox user on a Mac, and this app might be shooting sparks out of it's ass in MSIE 7, but I didn't get very impressed.

On my test of the site, they also seemed to have some serious infrastructure issues. When I tried to watch some of the music videos listed on the sidebar menu, I got a screen says 'The Video you requested is not available'. They did manage to show me the ad first tho. While we're on the subject of ads, Microsoft's use of AJAX has facilitated another "innovation". You can't switch videos during ad play. It just ignores the click. This means that if you change your mind about which video you want to see just after clicking the link, you have to wait while the 10-15 video sequence plays, then watch another 10-15 sequence before you hopefully get to the content you wanted to see.

After the 'not available' video, MSN Video goes on to display another neat "feature". Rather than giving me the option to pick some related content to continue with, MSN picks for me, determining that the appropriate followup to not seeing a Pink video was a report from the Anna Nichole Smith verdict, the current non-issue raging in the US press. Gee, thank you Microsoft!

As a final insult, MSN has decided that the 'embed' feature that every other site out there provides to allow videos embedded in blogs and other web sites was a blind path. That means I don't get to refuse to support them on iwatchthis, they've refused to support me. Oh well. I wish MS good luck on their continued voyage as a iceberg floating through cyberspace, isolated from all the others. Wonder why they fear Google, huh?

How to create an input field without a label

CakePHP : Posted by cakebaker at March 01, 2007 09:59 AM

If you use the form helper from CakePHP 1.2 to generate an input field, it automatically creates a label for the input field. So this snippet:

echo $form->input('Project/name');

will generate the following HTML code:

<div class="input">
    <label for="ProjectName">Name</label>
    <input name="data[Project][name]" type="text" value="" id="ProjectName" />
</div>

But sometimes you don’t want a label. How can you accomplish that? My first two attempts failed, the label “Name” was still shown:

// first attempt
echo $form->input('Project/name', array('label' => ''));
// second attempt
echo $form->input('Project/name', array('label' => null));

With the third attempt I was then successful, and the label disappeared:

echo $form->input('Project/name', array('label' => false));

In retrospect the solution is logical, but sometimes you don’t see the obvious solution ;-)

drake in progress - part 2

CakePHP : Posted by sentino at March 01, 2007 02:36 AM

This is my second post about Drake which is a combination of Drupal + CakePHP = Drake. I’ve used Drake few days after Felix of thinkingphp.org released it to the public as part of his proof of concept, it was very experimental during that time where in fact CakePHP wasn’t in its stable stage but Drupal was very stable, knowing CakePHP’s core gave me confidence to use Drake in my own production project and it went pretty well, there were some minor glitches but not a show stopper in production.

Drake is a framework(CakePHP) within a framework(Drupal) which lets you build web apps within CP while not limited to using Drupal’s whole functionality (template system, permission, etc).

A few days ago Mariano Iglesias added comments from my previous post “Drake in progress” letting me know about new Drake’s progress; He is now leading the development of Drake though haven’t tried it yet but highly recommended. Great job Mariano!

thanks

Show up smiling

RubyOnRails : Posted by Robby Russell at February 28, 2007 03:20 PM

“Showing up on time …with a smile on your face is almost always more important than what you actually say or do.”—Seth Godin

overhead here...

Switch your OpenID server on-the-fly

CakePHP : Posted by cakebaker at February 28, 2007 09:28 AM

OpenID allows you to use your own domain as OpenID and to delegate it for authentication purposes to an OpenID server by adding two link tags to the head section of a HTML page. In my case I use http://openid.42dh.com as OpenID and in the head section you will find:

<link rel="openid.server" href="http://www.myopenid.com/server" />
<link rel="openid.delegate" href="http://dh.myopenid.com" />

This approach comes with two potential problems:

  • I cannot authenticate myself if my domain is down,
  • and the same applies if the OpenID server is down

To the first problem I don’t have a solution yet, but for the second problem the solution is easy. Modify the snippet above and you can still authenticate with your OpenID by using a different OpenID server. It is just a bit cumbersome to login to your server and to do that change manually. So I wrote a very simple CakePHP application which allows you to switch the used OpenID server on-the-fly. It is one of those “15 minutes” projects where it is almost overkill to use CakePHP ;-)

You can find the application in the downloads section. The installation is simple, the zip contains a complete app folder (no database needed). You may have to modify app/webroot/index.php and to make the tmp folder writable, and, of course, you have to add the OpenID servers and delegates you want to use. They are defined in app/models/delegate.php. Now, to change the OpenID server you simply call example.com/change and a different OpenID server will be used.

That’s it.

The case for stating OpenIDs as complete URLs

RubyOnRails : Posted on David Heinemeier Hansson at February 27, 2007 07:35 PM

OpenID will have to co-exist with regular user names and passwords for some time to come in most cases. So as developers we have to come up with good conventions on how to deal with this duality. To do that, it would help greatly if we were able to distinguish an OpenID from a regular user name.

Consider this flow:

OpenID as username

With OpenID distinguishable from regular user names, you don't have to use an additional field to complicate the login or signup screen further.

It seems that there is already some support for the notion of specifying OpenIDs as complete URLs and not just their host names. Both Zoomr and Jyte explicitly encourage/require usage of complete URLs:

Zoomr Openid

Jyte Openid

But there are definitely other cases where just the host name is used. So neither approach seems to have taken hold as a defacto standard. I'd recommend that we move in the direction of full URLs. And if your site only takes OpenID, then you could still reinforce that idea by having login be something like http:// [input field].

quote of the day: server tricks

RubyOnRails : Posted by Robby Russell at February 27, 2007 07:03 PM

Daniel

...overheard on an internal forum…

“My my passion for nifty server tricks will blow peoples minds”Daniel Johnson

Data Visualization Software, Resources, Tutorials and Source Code

RubyOnRails : Posted on Max Kiesler at February 27, 2007 07:01 PM


Consider this list a primer. I've spent the last year looking at data visualizations and have compiled a list of resources that will give you a good view of what's going on in the field. Please use the comments section of this post to let the community know of any useful resources I've left out. Also, if there is enough interest in this post I'll be happy to open up a public wiki on data and content visualization. Please let me know.

Places to see Visualizations

We Feel Fine
Since August 2005, We Feel Fine has been harvesting human feelings from a large number of weblogs. Every few minutes, the system searches the world's newly posted blog entries for occurrences of the phrases "I feel" and "I am feeling". When it finds such a phrase, it records the full sentence, up to the period, and identifies the "feeling" expressed in that sentence (e.g. sad, happy, depressed, etc.). Because blogs are structured in largely standard ways, the age, gender, and geographical location of the author can often be extracted and saved along with the sentence, as can the local weather conditions at the time the sentence was written. All of this information is saved.

Peter Mayer - Lives Connected
This is an oral history of individuals experiences during Hurricane Katrina and its aftermath. It is also an experiment in content visualization.

Pedestrian Levitation
The creation of the work is based on the movement of pedestrians on a pedestrian crossing in public space. Some pedestrians walk only on the sidewalk and use the pedestrian crossing for crossing the street, other pedestrians freely make shortcuts on the formally imposed trafic situation. Pedestrian Levitation.net is an artwork in public space that reflects on this movement. It visualises the real movement of people, and adds a virtual movement based on the assumption that the mind of people is not subject to gravity or any other physical limitations.

Collective Subconscious
Collective Subconscious is an installation that imprints a dynamic collage of reverberating thoughts on a public space as people move through it. This project involves leaving behind traces of one's thoughts in the space that one passes through and collaging it with other people's thoughts. New messages will be prominently placed while older messages remain in the background and slowly fade out of existence. Words that are repeated over the day by many people will become more prominent and resonates with other instances of the words. As such the display become a visual representation of the state of being for people passing through that area.

3D Live Stats
If you've ever wanted to see your website usage on a large screen in a very visual way this is the application for you. From the demo video the visuals look stunning. As the earth turns you see your website visitors pop up on the globe in real-time also showing you a variety of other statistical data. One of the coolest features is the ability to hook up an interactive whiteboard or SMART board and be able to turn the earth with your fingers. Just like in the movie "Minority Report". I do wish they would produce a web base application so my website visitors could see this information in real-time. Nice product.

Digg BigSpy
One of the first sites that got me interested in real-time data visualizations was digg spy. I had just become hooked on digg when the spy came out, and watching the stories asynchronously roll by was even more intriguing than going to the regular home page. As they say on the digg site, "Digg BigSpy places stories at the top of the screen as they are dugg. As new stories are dugg, older stories move down the list. Bigger stories have more diggs. The projects currently in Digg Labs are the results of collaboration with Digg partner Stamen Design. As the project matures, we'll be releasing a public API to allow outside developers access to this data".

Musiclens
There are many search tools for finding new music on the web. Just type in, "artist, genre or title and you'll find some results. Ho Hum. I've been forced to use this same music search paradigm for years. However, there is a new breed of music sites cropping up that will give you new ways to discover new music. "Musiclens enables users to find pieces of music using very vaguely described criteria, such as loudness (perceived volume), mood or purpose. The search or recommendation query can be enhanced or limited by adjusting the ten navigation control sliders". This site works well and has a great visualization tool.

Gapminder
The Stockholm based website Gapminder provides wonderful interactive content visualizations of important global trends. This non-profit provides information from universities, UN organisations, public agencies and non-governmental organizations to graphically show us the state of what's happing in our world. The site looks at 16 different human conditions and plots them by year and by region. Conditions include, urban population, life expectancy, military budget, and 13 other world conditions you should know. Currently, this is the most important content visualization site for consciously minded world citizens. Please visit this site to see what's really going on in the world you life in. Giant hats off to the developers, Ola Rosling, Anna Rosling Ronnlund and Hans Rosling!

Lovelines
Lovelines is one of the most unique content visualization ideas I've seen recently. "Using a data collection engine created by the artists for their recent collaboration, We Feel Fine - wefeelfine.org, Lovelines examines thousands of blogs every few minutes to find expressions of love and hate, posted by all manner of people. When it can, Lovelines identifies and saves the age, gender, and geographical location of the person who wrote the post, and then presents that information along with the post." Lovelines shows it's data in words, pictures and superlatives. Words and pictures present individual examples of love and hate. Superlatives provides a daily zeitgeist of the most loved and hated things. All in all a very amazing content visualization of how the blogosphere feels about love and hate on a up to the minute basis.

Software to Make Visualizations

Prefuse Visualization Toolkit
A Java-based toolkit for building interactive information visualization applications.

Processing
Processing is an open source programming language and environment for people who want to program images, animation, and sound. It is used by students, artists, designers, architects, researchers, and hobbyists for learning, prototyping, and production.

Packet Garden
Packet Garden captures information about how you use the internet and uses this stored information to grow a private world you can later explore. To do this, Packet Garden takes note of all the servers you visit, their geographical location and the kinds of data you access. Uploads make hills and downloads valleys, their location determined by numbers taken from internet address itself. The size of each hill or valley is based on how much data is sent or received. Plants are also grown for each protocol detected by the software; if you visit a website, an 'HTTP plant' is grown. If you share some files via eMule, a 'Peer to Peer plant' is grown, and so on.

Enterprise Visualizations Solutions

i2
i2 Inc. is the leading worldwide provider of visual investigative analysis software for law enforcement, intelligence, military and Fortune 500 organizations.

ClearForest
ClearForest Corporation is a provider of text-driven business intelligence solutions, supplying the analytical bridge between two previously disconnected worlds of information--unstructured text and enterprise data. Our award-winning solutions offer manufacturers, publishers, federal, chemical & financial service organizations critical links to situational context buried in text for use in Business Intelligence [BI] systems.

Visualization Tutorials

Kyle Scholz Blog
A nice group of visualizations tutorials with example code.

Interactive Visualization of Volumetric Data on Consumer PC Hardware
Interactive visualization is no longer restricted to expensive workstations and dedicated hardware thanks to the fast evolution of consumer graphics. Course participants will learn to leverage new features of graphics hardware to build applications for the interactive visualization of volumetric data. A large body of the course deals with high-quality volume rendering. Beginning with basic texture-based approaches, the algorithms are improved and expanded incrementally, covering illumination, non-polygonal isosurfaces, transfer function design, volumetric effects, and hardware-accelerated high-quality filtering. The final session of the course discusses volumetric flow visualization and aspects of system design. Course participants are provided with documented source code covering details usually omitted in publications.

Websites to Make Visualizations

Swivel
Swivel is a Web site for curious people to explore data. Swivel was founded in December 2005 by Dmitry Dimov and Brian Mulloy. We both studied physics in college, Dmitry in Russia and Brian at the University of Michigan. We both worked together at a big software company. And we both love geeking out about data. Actually, all of us here at Swivel: Tao Ge, Visnu Pitiyanuvath, Seema Sharma, Huned Botee, and Richard Nghiem are a little nerdy about data and curious about all sorts of stuff. Data makes us go.

vuvox
As a workflow and easy to use online service, VUVOX can enable you to create personal, collaborative & emotive expressions using your own digital media - including video, photos, music and text. VUVOX reflects your life. VUVOX founders have created this foundation with your story in mind. Publish your creations to your own website, blog or MySpace page. More About VUVOX

Websites as Graphs
More visually appealing, but with less functionality, is this map by Sala Aharef's Websites as Graphs. It helps you see the density of a network, with color-coded indications of links, images and more, but is not very navigable.

Touchgraph
The TouchGraph Google Browser reveals the network of connectivity between websites, as reported by Google's database of related sites.

Many eyes
Many Eyes is a bet on the power of human visual intelligence to find patterns. Our goal is to "democratize" visualization and to enable a new social kind of data analysis. Jump right to our visualizations now, take a tour, or read on for a leisurely explanation of the project.

outside.in
Philosophically, this site is all about letting locals share their knowledge in ways that make sense to them, and so we've tried to make the tools here simple ones that will encourage many different ways of using the site.

Data Visualization Resources

Visualizing Email Content
This paper describes the interface and content-parsing algorithms in Themail. It also presents the results from a user study where two main interaction modes with the visualization emerged: exploration of "big picture" trends and themes in email (haystack mode) and more detail-oriented exploration (needle mode). Finally, the paper discusses the limitations of the content parsing approach in Themail and the implications for further research on email content visualization.

Non-Flash Content Visualization

Box Grid
New models for content visualization are popping up all the time now. Box Grid was originally developed as an experimental blog site. The two things I find most fascinating about Box Grid are the fact that it was originally released in 2002, and that does not use flash. The interface is all CSS and javascript based. While this type of content visualization will not work for every project it is place where we can start to imagine new ways to surf a website. The source code is also downloadable!

Search Visualizations

Grokker
Search visualization.

Vivisimo
Search visualization.

Snap
Recently new forms of search visualization have been on the rise. Snap is a great example of a new way to view search results. The applications allows you to view an image of the page your about to visit before you go there. The site is broken into two panes one with the search results and the other with a screenshot of the page. In the screenshot pane you can choose three different sizes of screenshots. You can view the site with either a new window or in the larger left pane. As a visual person I found it to be a fun way to browse search results.

mnemo map
Most of the sites that I've featured recently that have to do with content visualization have really been about deep discovery. If you take that notion and apply it to a search engine you have mnemo map. With mnemo you can search Yahoo!, flickr, and YouTube by tags, synonyms and translations for any search term. mnemo, "combines the technologies of social networking, search engines and other data sources to help you formulate search queries and find really relevant information".

Visualization Blogs, Categories and Posts

Visual Complexity
VisualComplexity.com intends to be a unified resource space for anyone interested in the visualization of complex networks. The project's main goal is to leverage a critical understanding of different visualization methods, across a series of disciplines, as diverse as Biology, Social Networks or the World Wide Web. I truly hope this space can inspire, motivate and enlighten any person doing research on this field.

Ben Fry
Ben Fry received his doctoral degree from the Aesthetics + Computation Group at the MIT Media Laboratory, where his research focused on combining fields such as Computer Science, Statistics, Graphic Design, and Data Visualization as a means for understanding complex data. After completing his thesis, he spent time developing tools for the visualization of genetic data as a postdoc with Eric Lander at the Eli & Edyth Broad Insitute of MIT & Harvard. For the 2006-2007 school year, Ben is teaching in Pittsburgh as the Nierenberg Chair of Design for the the Carnegie Mellon School of Design.

Cairns
Content visualization sections in her blog

Josh Spear
Content visualization sections in his blog

My favourite “bug”

CakePHP : Posted by Felix Geisendörfer at February 27, 2007 06:22 PM

Hey folks,

here comes an example of my favorite "bug" that drives me insane on occasion:

PHP:
  1. if (!$this->hasField($field));
  2. {
  3.     // Do Something
  4. }

It just got me again and caused me to debug Model::hasField, finally implementing my own version of it until I realized what an idiot I am ... ; ). What's your favourite?

-- Felix Geisendörfer aka the_undefined

Source Code Released for aPop and footoo AJAX Photo Galleries

RubyOnRails : Posted on Max Kiesler at February 26, 2007 05:22 PM


When posting updates to aPop or footoo please post comments on this new post so others can keep up with development. If I find there is an interest in this project I'll start a wiki so members of my community can post new developments. Please let me know if this is something you will use.

Below you'll find links to my original post and to the source code downloads. Please enjoy!

See my original post on the galleries

Download the aPop source code

Download the footoo source code

Meet... Chris, Graeme., and Gary

RubyOnRails : Posted by Robby Russell at February 26, 2007 05:01 PM

Okay, this is a little overdue… but better late than never! ;-)

We’ve had several new people start with PLANET ARGON over the past few months. Some of them are blogging about their experience of working with Ruby on Rails and being a part of our team. I wanted to quickly introduce you to a few of them and their blogs, which I hope that you consider subscribing to.

Chris

For quite some time, we’ve been needing more design assistance, so late last year… we hired Chris Griffin, who moved here last year from Florida. He’s our new User Interface Designer and gets to work within the Rails environment everyday with the rest of us. It seems that Brian and Chris worked over the weekend to get his new blog up. Chris is self-proclaimed genius. I suggest that you keep an eye on his blog… because I’m sure it’s going to be a pretty active one. Chris joining our team marks a pivotal point in our teams evolution as we continue to place more emphasis in our Design and Development process on the User Experience.

Graeme

Graeme Nelson

Our newest hire is Graeme Nelson, who recently moved to Portland from Seattle. He just joined our Design and Development team and if you’ve been reading the Rails-related blogs, you might have seen his blog already. He’s been blogging a lot about using RSpec with Rails and other fun things. He’s been contracting with us since the start of the year and I’m really excited that he’s accepted a job offer and joined the team!

Gary

Gary eats sushi

Last… but not least is Gary Blessington. I believe that I first offered Gary a job with PLANET ARGON about 2 1/2 years ago when we were still focused on PHP/PostgreSQL…. but PHP apparently wasn’t enough of a catalyst. Gary and I previously worked together at Imark Communications several years ago, when I first started doing web development. He was the senior developer on the team and was an important mentor during my early days of developing in a professional environment. Late last year, he hung up his .NET tool belt to become our Design and Development Director. He started blogging earlier this year and is sharing his experience of switching from .NET to Ruby on Rails.

I’ll introduce the others as they start blogging and such. :-)

Should you use Model::query() in the controller?

CakePHP : Posted by cakebaker at February 26, 2007 09:49 AM

The query() function of the model is sometimes very practical as it allows you to execute any SQL statement you want. But to me it seems it is often used in a “wrong” way (I have to include myself *g*), in a way which works but is not that clean. I am talking about calling query() from the controller:

$this->MyModel->query('Here comes the SQL statement');

Sure, following the Cake conventions this is a valid usage of query(), as the function is public. But it introduces database-related code to the controller, which is a violation of the MVC pattern.

I think a cleaner solution is to move the SQL statement to the model. So the snippet from above can be refactored to:

// in the model
function doSomething() {
    $this->query('Here comes the SQL statement');
}

// in the controller
$this->MyModel->doSomething();

Ok, this solution needs more code, but it comes with some additional advantages:

  • it is easier to test
  • it is more readable (at least if you give the function a better name than I did)
  • the query can be re-used without copy & paste in other actions/controllers

What do you think about this approach?

Drake :: Drupal-CakePHP 1.0.1b Released

CakePHP : Posted by Felix Geisendörfer at February 26, 2007 06:32 AM

Hey folks,

this is just a quick announcement that a completely new Drake has has been released by Mariano Iglesias. For those of you who don't remember: I used to have some fun with integrating CakePHP with other PHP applications around such as drupal which in term created a project called Drake. I only released a couple versions and by looking at Mariano documentation for Drakes none of them were as easy to set up.

Here is his announcement from the google group:

My fellow bakers,

After my experience with Jake, and thanks to Felix’s goodwill in giving the catchy name Drake for me to use, I’m pleased to announce the first beta release of Drake, a Drupal module that lets you execute your CakePHP applications inside Drupal.

Project home page: http://dev.sypad.com/projects/drake

Documentation: http://dev.sypad.com/projects/drake/documentation

Download: http://drupal.org/project/drake

Demo (Cheesecake Photoblog on Drupal 5.1): http://dev.sypad.com/projects/drake/demo/drake

Bake on, with Drake ;)

-MI

So if you like to work with an existing system such as Drupal go ahead and give it a try!

-- Felix Geisendörfer aka the_undefined

Good ideas for London in March?

RubyOnRails : Posted on David Heinemeier Hansson at February 26, 2007 05:09 AM

I'm going to London with Mary for a long weekend in the beginning of March. We've both been there a couple of times and consumed the usual suspects of turismo. Skipping those, where should we go? Which restaurants must we dine? What plays, performances, or exhibitions must we see? Thanks, lazy web. You're the best.

OpenID makes web identities real and appealing

RubyOnRails : Posted on David Heinemeier Hansson at February 26, 2007 04:54 AM

I've loved the idea of OpenID ever since I sat down with the boys from Verisign and East Media to hear them talk about PiP and the Rails integration, but I never really got around to playing with it. "How big of a deal is having multiple logins really?", went the thinking. For a lot of people the answer is still "not a lot". But I'm starting to see the light.

Over a few hours today, I read up on and implemented OpenID logins for Highrise. That opened my eyes. Both to how easy it is to get working as an alternate login solution and, more importantly, how decent the flow actually is.

Yes, there's redirection to another site involved, but once you've saved the login for a certain site in your OpenID and you're logged into your provider, you don't even see the redirection. At first that might seem a little scary. Logging into a site without even entering a password? But then again, that's pretty much how I authenticate with my application servers around the world through SSHKeychain and on all sites that gives you Remember me?

There are certainly still concerns to be addressed. Chief among which is probably the phishing issue. But smart men are hard at work and I'm sure we'll be able to come up with something clever. I don't think this or any of the other current uncertainties are reason enough to hold back.

What we need is adoption. Luckily, OpenID seems to have already gotten the attention of plenty of enterprising startups and big behemoths. Most prominent in the latter category is AOL, which with a swing of its wand granted everyone with an AIM account an OpenID. It's as simple as http://openid.aol.com/<screenname>. The simplicity of that solution was part of what got me started today. There's a lot of power in not even having to signup for anything new. At least for getting off the ground.

Making it as easy as not to
So we got millions of people with an OpenID by virtue of their AIM account. Great. Now they just need a place or two to actually use it. That's where the technology comes in. The toolkits. The "making it as easy as not to". While there's already a fair number of generators out there for Rails on the subject, I'm generally not a big fan of generators as anything but learning tools or for setting up conventions. I'd much rather try to find a clever API that's so simple there's no need for boiler plating.

A very first stab at that with Rails that I came up with today is as follows (which I'll wrap up as a plugin shortly):


class SessionController  ApplicationController
  include OpenIdAuthentication
 
  def create
    if open_id?(params[:name])
      open_id_authentication(params[:name])
    else
      password_authentication(params[:name], params[:password])
    end
  end
 
 
  private
    def password_authentication(name, password)
      if @current_user =
          @account.users.find_by_name_and_password(name, password)
        successful_authentication
      else
        failed_authentication "Sorry, that username/password doesn't work"
      end
    end
 
    def open_id_authentication(identity_url)
      authenticate_with_open_id(identity_url) do |status, identity_url|
        case status
        when :missing
          failed_authentication "Sorry, the OpenID server couldn't be found"
 
        when :canceled
          failed_authentication "OpenID verification was canceled"
 
        when :failed
          failed_authentication "Sorry, the OpenID verification failed"
 
        when :successful
          if @current_user =
              @account.users.find_by_identity_url(identity_url)
            successful_authentication
          else
            failed_authentication "Sorry, no user by that identity URL exists"
          end
        end
      end
    end
end

The only extra pieces you need is to implement successful_authentication, which creates the session and redirects into the application, and failed_authentication, which redirects back to the login screen with an error flash message. params[:name] doubles as both a regular user name and as an OpenID. I distinguish between the two by whether it looks like a URL or not (starts with http). The underlying mechanics are delegated to the ruby-openid library.

It's most certainly still half-baked. It only works for the authentication part, not for the create the initial account and get additional attributes part. But it's a start and I'm excited.

I truly believe that OpenID stands a great shot at becoming real and useful even to people who would start yawning at the mere mention of the word "authentication". It won't happen overnight and it certainly won't happen unless we all dig in and make it happen. What's here today is more than good enough to get started. So less excuses, more implementions.

If that pitch still hasn't infected you with OpenID fevor, I recommend watching Scott Kveton's quick pitch. Then digesting Simon Willison's talk on OpenID and his Six cool things you can build with OpenID.

Then consider the fact that Firefox 3 will ship with native support for OpenID and that Microsoft is getting on board as well. Suddenly a future of OpenID everywhere starts looking mighty plausible.

The Host with the Most

RubyOnRails : Posted by topfunky at February 24, 2007 07:52 PM

In the past few years I’ve installed Rails applications on a few different hosts. I won’t recommend any single host for all situations, but here are the ones I’m currently using and what I think they are best at.

DISCLAIMER: This is not an attempt to get hosting referral money…I’ve already done that and ended up putting $1,500 toward a special event for the Seattle.rb. But most of these places have referral plans if you want to mention Topfunky when you signup.

Dreamhost Shared Hosting
Best qualities: Cheap, huge bandwidth, tons of disk space
Worst qualities: Slow
Best for: Static or page-cached sites, static files
Server Specs: Apache 1.x, FastCGI
Currently hosts

I had a Dreamhost account before I learned Rails, so it seemed like the natural place to start installing apps. My first app took a few days to get up and running (but that was before Capistrano).

Dreamhost gives a huge amount of bandwidth. If you’re hosting a podcast or vidcast, this is the way to go (even just for the files). Their cheapest plan gives you 174 GB disk space and 1.74 TB of bandwidth.

However, I rarely login without seeing the load under 5 and it’s often over 15. Shared hosts are finely tuned to serve static files and PHP, but not Rails. At one point I had 5 different sites hosted there, but after a few months of flakiness I moved my blog elsewhere.

The Rails Podcast is still hosted there. It works well because the whole thing is page cached, which lets a shared host do what it does best…serve static files. If you’re running on a shared host I highly recommend using a third-party service like FeedBurner to serve your RSS or ATOM feed since that will constitute the majority of the hits to your site.

Here’s the .htaccess rewrite rule I use:

RewriteCond %{HTTP_USER_AGENT} !FeedBurner
RewriteRule ^podcast\.xml$ http://feeds.feedburner.com/rubyonrailspodcast [R,L]
TextDrive Shared Hosting
Best qualities: Official Rails host
Worst qualities: Strict limits, crashes
Best for: Static or page-cached sites, Subversion hosting
Server Specs: Apache 1.x, FastCGI
Currently hosts

I jumped at the opportunity to get a lifetime account for only $400. I thought, “I’ll be set for the rest of my life and will never need to buy hosting again!”

Unfortunately, seemingly frequent server crashes and stringent resource enforcement made it quite hard to run a Rails app reliably. Yes, the app I deployed was using RMagick and probably had a higher-than-average memory usage. I eventually moved all my Rails sites back to Dreamhost (and later to virtual hosts).

I tried running lighttpd under proxy, but that was even worse. TextDrive bots kill any process that goes over a memory or CPU threshold, which will kill your lighttpd server. And, you are restricted from running applications that monitor processes and restart them.

I haven’t tried to run a Rails app there in the past year, so things may have improved.

It works well for Subversion hosting, which is probably all the benefit I will get out of my $400 lifetime account.

Rimuhosting VPS
Best qualities: Reliable, great support, open-source discount
Worst qualities: None, but requires knowledge of Unix sysadmin
Best for: Rails apps that need to run reliably
Server Specs: 360 MB RAM, Debian Linux, lighttpd, FastCGI, MySQL
Currently hosts

I moved my blog to Rimuhosting after a few months of frustration at Dreamhost. It took a few days to install everything using Ezra’s instructions. I’ve since done a lot of tweaking to fine-tune everything.

It has been very reliable. Although customer support has been helpful and responsive, I haven’t had to use it much. It’s a virtual server, so you have root access but also need to be able to do basic sysadmin tasks: install software, monitor processes, setup log rotation, configure webservers.

A few months ago I installed Apache 2 and Mongrel, but the memory usage was a lot higher than lighty. I reverted to lighty and it has been running great ever since. For the most efficient memory usage, I’ve heard that lighty, FastCGI, and Postgres are the best (but nginx might be a good lightweight option, too).

I also run a few instances of PHP-fastcgi for Mint. This gives me a central place to run PHP without needing to install it on my other Rails-only servers.

The Production Log Analyzer tells me that my blog is serving between 40 and 80 requests per second. The load is usually under 0.1.

RailsMachine VPS
Best qualities: Reliable, solid hardware, great support, easy setup and deployment
Worst qualities: None, but requires knowledge of Unix sysadmin
Best for: Mission-critical Rails apps
Server Specs: 512 MB RAM, CentOS Linux, Apache, Mongrel
Currently hosts

RailsMachine is the best Rails-specific host that I’ve used. The railsmachine gem does all the setup in a few easy commands. It took me a total of one hour from the time I got my SSH login info to the time that I loaded the first page of my fully-functional site. Even better, I know that everything is setup properly with startup scripts, svn, and room for more Rails apps.

Knowing that my site is running on a RAID 10 storage system helps me sleep better at night. Support staff are very quick to respond and have intimate knowledge of Rails. The founder wrote the mongrel_cluster gem.

RailsMachine has been super reliable. You can find lower prices elsewhere, but the quality is definitely worth the price, in my opinion. If you have a site with any kind of revenue, I would recommend hosting it at a high-quality, reliable host like RailsMachine.

My only criticism is that I like Debian flavors of Linux (including Ubuntu), but only CentOS is offered. However, this isn’t too much of a problem.

Slicehost VPS
Best qualities: Inexpensive, quick setup with the deprec gem
Worst qualities: Unknown…have only used for a few weeks
Best for: A couple Rails apps
Server Specs: 256 MB RAM, Ubuntu Linux, Apache, Mongrel, MySQL
Currently hosts

A few weeks ago I was frustrated with the flakiness of the Ruby on Rails Workshops Calendar which was running on Dreamhost. Although I could have stuffed it into one of my existing servers, I wanted to try out the inexpensive Slicehost with a non-critical site.

I used the deprec gem to install Apache, Ruby, Rails, Mongrel, and MySQL in only 45 minutes (screencast). I also wanted to have an extra non-critical server to experiment on (with nginx or other deployment strategies).

I was about to write this article last week, but the entire host was down for about an hour. Fortunately the support staff was very responsive and things were back up quickly.

If you are a full-time Rails developer, I think you owe it to yourself to learn how to operate a server. Spending $20/month on your own education will be worthwhile and will help you make mistakes on your own before making them on a paying client project. Slicehost would be a great environment for that kind of self-education.

Relevant PeepCode Episodes

Cake 1.2’s Set class eats nested arrays for breakfast!

CakePHP : Posted by Felix Geisendörfer at February 24, 2007 12:28 PM

Hey folks,

I was just taking a little trip through the CakePHP core code trying to wrap my head around Acl, Model behaviors and all sorts of stuff. While doing so I saw that the core code starts to be using the Set class more and more that was added a while ago. So far this has been a little dark spot for me in the core and from my previous quick looks at the class I've never been quite able to figure out what it's exact purpose was. Until now all I knew was "well it's probably some fancy array manipulation code that is somewhat obfuscated and undocumented". Oh boy, I wish I had spent more time on this earlier. It's probably one of coolest new features in 1.2 and nobody realizes it ; ).

So before starting to drool over it too much ahead of time, let's take a look at a simple example. You have an array of $users as it could have been returned from a findAll call to your User model:

PHP:
  1. $users = array
  2. (
  3.     0 => array
  4.     (
  5.         'User' => array
  6.         (
  7.             'id' => 1
  8.             , 'name' => 'Felix'
  9.         )                            
  10.     )
  11.     , 1 => array
  12.     (
  13.         'User' => array
  14.         (
  15.             'id' => 2
  16.             , 'name' => 'Bob'
  17.         )
  18.     )
  19.     , 2 => array
  20.     (
  21.         'User' => array
  22.         (
  23.             'id' => 3
  24.             , 'name' => 'Jim'
  25.         )
  26.     )
  27. );

What you really want however, is just a simple array containing all user 'name's: array('Felix', 'Bob', 'Jim'). Hmm. Up until today I'd probably have written some code like this to do it:

PHP:
  1. $userNames = array();
  2. foreach ($users as $user)
  3. {
  4.     $userNames[] = $user['User']['name'];
  5. }

Simple enough, right? Not any more! Using the new Set class we can achieve the exact same outcome like this:

PHP:
  1. $userNames = Set::extract($users, '{n}.User.name');

Doesn't blow you away yet? Well, let's look at another example. Let's say our User model as a hasMany associations to an Item model. Then we would get an array like this:

PHP:
  1. $users = array
  2. (
  3.     0 => array
  4.     (
  5.         'User' => array
  6.         (
  7.             'id' => 1
  8.             , 'name' => 'Felix'
  9.             , 'Item' => array
  10.             (
  11.                 0 => array
  12.                 (
  13.                     'id' => 1
  14.                     , 'name' => 'Mouse'       
  15.                 )
  16.                 , 1 => array
  17.                 (
  18.                     'id' => 2
  19.                     , 'name' => 'KeyBoard'
  20.                 )
  21.             )
  22.         )                            
  23.     )
  24.     , 1 => array
  25.     (
  26.         'User' => array
  27.         (
  28.             'id' => 2
  29.             , 'name' => 'Bob'
  30.             , 'Item' => array
  31.             (
  32.                 0 => array
  33.                 (
  34.                     'id' => 3
  35.                     , 'name' => 'CD'
  36.                 )
  37.             )
  38.         )
  39.     )
  40.     , 2 => array
  41.     (
  42.         'User' => array
  43.         (
  44.             'id' => 3
  45.             , 'name' => 'Jim'
  46.             , 'Item' => array
  47.             (
  48.                 0 => array
  49.                 (
  50.                     'id' => 4
  51.                     , 'name' => 'USB Stick'
  52.                 )
  53.                 , 1 => array
  54.                 (
  55.                     'id' => 5
  56.                     , 'name' => 'MP3 Player'
  57.                 )
  58.                 , 2 => array
  59.                 (
  60.                     'id' => 6
  61.                     , 'name' => 'Cellphone'
  62.                 )
  63.             )
  64.         )
  65.     )
  66. );

Now here is how I would have traditionally turned this into a 'User.name' => 'User.items' array:

PHP:
  1. $userItems = array();
  2. foreach ($users as $user)
  3. {
  4.     foreach ($user['User']['Item'] as $item)
  5.     {
  6.         $userItems[$user['User']['name']][] = $item['name'];
  7.     }
  8. }

But using the new Set class this is still pretty much a simple one-liner (split up in multiple lines so you don't have to scroll):

PHP:
  1. $userItems = array_combine
  2. (
  3.     Set::extract($users, '{n}.User.name')
  4.     , Set::extract($users, '{n}.User.Item.{n}.name')
  5. );

Both methods will output:

PHP:
  1. (
  2.     [Felix] => Array
  3.         (
  4.             [0] => Mouse
  5.             [1] => KeyBoard
  6.         )
  7.  
  8.     [Bob] => Array
  9.         (
  10.             [0] => CD
  11.         )
  12.  
  13.     [Jim] => Array
  14.         (
  15.             [0] => USB Stick
  16.             [1] => MP3 Player
  17.             [2] => Cellphone
  18.         )
  19. )

"But doesn't it cost more performance to loop through the array twice in the Set example?" I hear some of you cry. Yes it does. And? Have you built your application yet? Does it implement all features you are dreaming of? And most importantly: Do your web stats indicate you are going to have 1 million hits / day soon? If so go back into your code and remove the Set example with the less succinct foreach alternative. If not, listen to Chris Hartjes who's motto for 2007 is Just Build It, Damnit!.

Anyway, here comes my last fun thing to do with Set::extract - parsing an RSS feed for all post titles. For my example I'll use the new XML class in Cake 1.2. Right now Set::extract only supports arrays but hopefully it will either natively support Xml objects at some point, or the Xml class get it's own extract function. For now I've written a little function that can turn an Xml instance into an array that looks like this:

PHP:
  1. function xmltoArray($node)
  2. {
  3.     $array = array();
  4.    
  5.     foreach ($node->children as $child)
  6.     {
  7.         if (empty($child->children))
  8.         {
  9.             $value = $child->value;
  10.         }
  11.         else
  12.         {
  13.             $value = xmltoArray($child);
  14.         }
  15.        
  16.         $key = $child->name;
  17.        
  18.         if (!isset($array[$key]))
  19.         {
  20.             $array[$key] = $value;
  21.         }
  22.         else
  23.         {
  24.             if (!is_array($array[$key]) || !isset($array[$key][0]))
  25.             {
  26.                 $array[$key] = array($array[$key]);
  27.             }
  28.            
  29.             $array[$key][] = $value;
  30.         }
  31.     }
  32.    
  33.     return $array;
  34. }

So now let's assume we would want to extract all post titles from my feed: http://feeds.feedburner.com/thinkingphp we could leverage the Set class to make our code as succinct as:

PHP:
  1. uses('Xml');
  2.  
  3. $feed = xmltoArray(new XML('http://feeds.feedburner.com/thinkingphp'));
  4. $postTitles = Set::extract($feed, 'rss.channel.item.{n}.title');

Which will give you a $postTitles array like this:

PHP:
  1. (
  2.     [0] => How-to: Use Html 4.01 in CakePHP 1.2
  3.     [1] => Looking up foreign key values using Model::displayField
  4.     [2] => Bug-fix update for SVN/FTP Deployment Task
  5.     [3] => Access your config files rapidly (Win32 only)
  6.     [4] => Making error handling for Model::save more beautiful in CakePHP
  7.     [5] => Full content RSS feed
  8.     [6] => Visual Sorting - Some Javascript fun I had last night
  9. )

Now that's beauty right there and a good way to end this post ; ). Take a look at the Set classes source to find out about some other cool methods it has, but to me this is by far the coolest.

-- Felix Geisendörfer aka the_undefined

Symfony conferences

Symfony : Posted by francois.zaninotto@symfony-project.com (Francois ZANINOTTO) at February 23, 2007 06:39 PM

Two upcoming events may be of great interest for symfony fans.

The French PHP User association (AFUP) organizes a conference about symfony 1.0 in Paris. The event will take place on March 6 at 8pm at Espace FIAP - 30 rue Cabanis - 75014 Paris. Fabien Potencier will be the speaker, and he'll expose the benefits of using symfony in a professional context.

Also, the PHP Quebec Conference features two sessions about symfony, one in English and one in French. Once again, you'll have the pleasure to appreciate Fabien's English accent and learn to build maintainable and upgradeable PHP applications with symfony.

"Oldskool" video

Catalyst : Posted by LTjake at February 22, 2007 08:36 PM

I stumbled upon a link to a fellow doing "full motion video" on an old 8088 (the link is probably about a year old, but, whatever). I watched the video and thought it looked an awful lot like ANSIMation. There was related link to another fellow that reversed engineered the file format.

Each cell is represented by an attribute byte (defines foreground color, background color, and flashing) and the actual extended ASCII character (0..255).

This is exactly how an XBin (which basically a fancy ANSI-like format) is stored.

I peeked at his code to get an idea of the palette, and some of the details on frame size. I came up with the following "frame grabber" code. It takes a data file as the first input (extract the data file from the original 8088 Corruption package), and a frame number (0 being the first frame) as the second parameter (it'll pick a random frame if ommitted). It's a little long since i've embedded the 8x8 font at the bottom of the script. It uses GD to spit out a png image.

Here's a sample of the output of perl grab.pl 8088_COR.DAT 1085 > frame_1085.png

use strict;
use warnings;

use GD;
use IO::File;
use File::Temp ();
use MIME::Base64 ();

my $VIDEO_FRAME_WIDTH  = 40;
my $VIDEO_FRAME_HEIGHT = 25;
my $VIDEO_FRAME_SIZE   = $VIDEO_FRAME_WIDTH * $VIDEO_FRAME_HEIGHT * 2;
my $AUDIO_FRAME_SIZE   = 735;
my $TOTAL_FRAME_SIZE   = $VIDEO_FRAME_SIZE + $AUDIO_FRAME_SIZE;

my @palette = (
    0x000000, 0x0000AA, 0x00AA00, 0x00AAAA,
    0xAA0000, 0xAA00AA, 0xAA5500, 0xAAAAAA,
    0x555555, 0x5555FF, 0x55FF55, 0x55FFFF,
    0xFF5555, 0xFF55FF, 0xFFFF55, 0xFFFFFF
);

my $dat = $ARGV[ 0 ] || die 'No data file specified';
die 'File not found' unless -e $dat;

my $filesize = -s $dat;
die 'Invalid file size' unless $filesize % $TOTAL_FRAME_SIZE == 0;

my $frames = $filesize / $TOTAL_FRAME_SIZE;
my $image  = GD::Image->new( $VIDEO_FRAME_WIDTH * 8, $VIDEO_FRAME_HEIGHT * 8 );
my @colors = map { $image->colorAllocate( ( $_ & 0xFF0000 ) >> 16, ( $_ & 0x00FF00 ) >> 8, $_ & 0x0000FF ) } @palette;
my $font   = get_gd_font();

my $frame = $ARGV[ 1 ] || int( rand( $frames ) );
my $file  = IO::File->new( $dat );
my( $buffer );

$file->binmode;
$file->seek( $frame * $TOTAL_FRAME_SIZE, 0 );
$file->read( $buffer, $VIDEO_FRAME_SIZE );
$file->close;

render_frame( $buffer );

sub render_frame {
    my( $buffer ) = @_;
    my @frame     = unpack( 'C*', $buffer );
    my $frame_idx = 0;

    for my $y ( 0..$VIDEO_FRAME_HEIGHT - 1 ) {
        for my $x ( 0..$VIDEO_FRAME_WIDTH - 1 ) {
            my( $idx, $attr ) = @frame[ $frame_idx++..$frame_idx++ ];

            my $scale_x = $x * 8;
            my $scale_y = $y * 8;
            $image->filledRectangle( $scale_x, $scale_y, $scale_x + 8, $scale_y + 8, $colors[ $attr >> 4 ] );
            $image->string( $font, $scale_x, $scale_y, chr( $idx ), $colors[ $attr & 0x0F ] );
        }
    }

    print $image->png;
}

sub get_gd_font {
    my $data = MIME::Base64::decode_base64( do { local $/; <DATA> } );
    my $temp = File::Temp->new;

    binmode( $temp );
    print $temp $data;
    close $temp;

    return GD::Font->load( $temp->filename );
}

__DATA__
AAEAAAAAAAAIAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAAEAAAAAAAABAQABAAABAAE BAAAAAAAAAQEA
AQEBAQABAQAAAQEAAAEBAAAAAAAAAQABAQEBAQEAAAEBAQEBAQABAQEBAQEBAQEBA AEBAAEBAQEB
AQEBAQEBAQAAAAABAQEBAQAAAQEBAQEBAQEBAQEAAQEBAQEBAAABAQABAQAAAQEBAQE BAQABAQEB
AQEBAAEBAQEBAQEAAAEBAQEBAAAAAAEBAQAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAA AAAAQEB
AAAAAAEBAQEBAAABAQEBAQEBAAABAQEBAQAAAAABAQEAAAAAAAABAAAAAAAAAAAAAAAAAAA BAQEA
AAAAAQEBAQEAAAAAAQEBAAAAAQEBAQEBAQABAQEBAQEBAAEBAAEAAQEAAAAAAQAAAAAAAAEBA QAA
AAAAAAEAAAAAAAABAQEAAAAAAQEBAQEAAAEBAQEBAQEAAQEBAQEBAQAAAQEBAQEAAAAAAAEAAAA A
AAABAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEAAAAAAAEBAQEAAAAAAQEBAQAAAAAAAQEAAAAA
AAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQAAAQEBAQEAAAAAAQEBAQAAAAABAQEB
A QAAAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAABAQEBAAAAAQEAAAEBAAABAAAAAAEAAAEA
AAA AAQAAAQEAAAEBAAAAAQEBAQAAAAAAAAAAAAABAQEBAQEBAQEBAAAAAAEBAQAAAQEAAAEBAAEB
AQEAA QEAAQEBAQABAQAAAQEAAAEBAQAAAAABAQEBAQEBAQEBAAAAAAEBAQEAAAAAAAEBAQAAAAAB
AQEBAAE BAQEBAAEBAQAAAQEAAAEBAAABAQAAAQEAAAEBAAAAAQEBAQAAAAAAAQEBAQAAAAEBAAAB
AQAAAQEAA AEBAAABAQAAAQEAAAABAQEBAAAAAAABAQAAAAABAQEBAQEAAAAAAQEAAAAAAAEBAQEB
AQAAAQEAAAE BAAABAQEBAQEAAAEBAAAAAAAAAQEAAAAAAAEBAQAAAAABAQEBAAAAAAEBAQAAAAAA
AAEBAQEBAQEAA QEAAAABAQABAQEBAQEBAAEBAAAAAQEAAQEAAAABAQABAQAAAQEBAQEBAAABAQAB
AQAAAAAAAAAAAAE BAAAAAQEAAQEAAQEAAAEBAQEAAAEBAQAAAQEBAQEBAAABAQEAAAEBAQEAAAEB
AAEBAAEBAAAAAQEAA AABAAAAAAAAAAEBAQAAAAAAAQEBAQEAAAABAQEBAQEBAAEBAQEBAAAAAQEB
AAAAAAABAAAAAAAAAAA AAAAAAAAAAAAAAAAAAQAAAAAAAQEBAAAAAQEBAQEAAQEBAQEBAQAAAAEB
AQEBAAAAAAABAQEAAAAAA AAAAQAAAAAAAAAAAAAAAAEBAAAAAAABAQEBAAAAAQEBAQEBAAAAAAEB
AAAAAAAAAQEAAAAAAQEBAQE BAAAAAQEBAQAAAAAAAQEAAAAAAQEAAAEBAAABAQAAAQEAAAEBAAAB
AQAAAQEAAAEBAAABAQAAAQEAA AAAAAAAAAAAAQEAAAEBAAAAAAAAAAAAAAEBAQEBAQEBAQABAQAB
AQEBAAEBAAEBAAEBAQEAAQEAAAA BAQABAQAAAAEBAAEBAAAAAQEAAQEAAAAAAAAAAAAAAQEBAQEA
AAEBAAAAAAEAAAEBAQEAAAABAQAAA QEAAAEBAAABAQAAAAEBAQEAAAEAAAAAAQEAAAEBAQEBAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAABAQEBAQEAAAEBAQEBAQAAAQEBAQEBAAAA
AAAAAAAAAAAAAQEAAAAAAAEBAQEAAAABA QEBAQEAAAAAAQEAAAAAAQEBAQEBAAAAAQEBAQAAAAAA
AQEAAAABAQEBAQEBAQAAAAEBAAAAAAABAQE BAAAAAQEBAQEBAAAAAAEBAAAAAAAAAQEAAAAAAAAB
AQAAAAAAAAEBAAAAAAAAAAAAAAAAAAABAQAAA AAAAAEBAAAAAAAAAQEAAAAAAAABAQAAAAABAQEB
AQEAAAABAQEBAAAAAAABAQAAAAAAAAAAAAAAAAA AAAAAAAAAAAABAQAAAAAAAAABAQAAAQEBAQEB
AQAAAAAAAQEAAAAAAAEBAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAABAQAAAAAAAQEAAAAA
AAEBAQEBAQEAAAEBAAAAAAAAAAEBAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AQEAAAAAAAABAQAAAAAAAAEBAAAAAAAAAQEBAQEBAQAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAEAAAEAAAABAQAAAQEAAQEBAQEBAQEAAQEAAAEBAAAAAQA AAQAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAQEAAAAAAAEBAQEAAAABAQEBAQEAAQEBAQEBA QEBAQEBAQEBAQAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAAA AAQEBAQAAAAAAAQEAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAAAAAAABAQEBAAAAAAEBAQE AAAAAAAEBAAAAAAAAAQEA
AAAAAAAAAAAAAAAAAAEBAAAAAAAAAAAAAAAAAQEAAAEBAAABAQAAAQEAA AABAAABAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAAEBAAAAAQE AAQEAAAEBAQEBAQEA
AAEBAAEBAAABAQEBAQEBAAABAQABAQAAAAEBAAEBAAAAAAAAAAAAAAAAAAEBA AAAAAABAQEBAQAA
AQEAAAAAAAAAAQEBAQAAAAAAAAABAQAAAQEBAQEAAAAAAAEBAAAAAAAAAAAAAAA AAAAAAAAAAAEB
AAAAAQEAAQEAAAEBAAAAAAABAQAAAAAAAQEAAAAAAAEBAAABAQABAQAAAAEBAAAAA AAAAAAAAAAB
AQEAAAAAAQEAAQEAAAAAAQEBAAAAAAEBAQABAQABAQABAQEAAAEBAAABAQAAAAEBAQA BAQAAAAAA
AAAAAAAAAAEBAAAAAAAAAQEAAAAAAAEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAA
AAAAAAAAAAAAAAAAAAAAAQEAAAAAAAEBAAAAAAABAQAAAAAAAAEBAAAAAAAAAQEAAAAAAAA AAQEA
AAAAAAAAAQEAAAAAAAAAAAAAAAABAQAAAAAAAAABAQAAAAAAAAABAQAAAAAAAAEBAAAAAAAAA QEA
AAAAAAEBAAAAAAABAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAAABAQAAAAEBAQEAAAEBAQEBAQE B
AAABAQEBAAAAAQEAAAEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAAAAAAAAAQEAAAAA
AQEBAQEBAAAAAAEBAAAAAAAAAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
A AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAAAAAAAAAQEAAAAAAAEBAAAAAAAAAAAAAAAAAAAA
AAA AAAAAAAAAAAAAAAABAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEAAAAAAAABAQAAAAAAAAAA
AAAAAAA AAAABAQAAAAAAAQEAAAAAAAEBAAAAAAABAQAAAAAAAQEAAAAAAAEBAAAAAAAAAQAAAAAA
AAAAAAAAA AAAAAAAAQEBAAAAAAEBAAEBAAABAQAAAAEBAAEBAAEAAQEAAQEAAAABAQAAAQEAAQEA
AAAAAQEBAAA AAAAAAAAAAAAAAAABAQAAAAAAAQEBAAAAAAAAAQEAAAAAAAABAQAAAAAAAAEBAAAA
AAAAAQEAAAAAA QEBAQEBAAAAAAAAAAAAAAEBAQEBAAABAQAAAAEBAAAAAAAAAQEAAAAAAQEBAAAA
AAEBAAAAAAABAQA AAQEAAQEBAQEBAQAAAAAAAAAAAAABAQEBAQAAAQEAAAABAQAAAAAAAAEBAAAA
AQEBAQAAAAAAAAABA QABAQAAAAEBAAABAQEBAQAAAAAAAAAAAAAAAAABAQEAAAAAAQEBAQAAAAEB
AAEBAAABAQAAAQEAAAE BAQEBAQEAAAAAAAEBAAAAAAABAQEBAAAAAAAAAAAAAQEBAQEBAQABAQAA
AAAAAAEBAAAAAAAAAQEBA QEBAAAAAAAAAAEBAAEBAAAAAQEAAAEBAQEBAAAAAAAAAAAAAAAAAQEB
AAAAAAEBAAAAAAABAQAAAAA AAAEBAQEBAQAAAQEAAAABAQABAQAAAAEBAAABAQEBAQAAAAAAAAAA
AAABAQEBAQEBAAEBAAAAAQEAA AAAAAEBAAAAAAABAQAAAAAAAQEAAAAAAAABAQAAAAAAAAEBAAAA
AAAAAAAAAAAAAAEBAQEBAAABAQA AAAEBAAEBAAAAAQEAAAEBAQEBAAABAQAAAAEBAAEBAAAAAQEA
AAEBAQEBAAAAAAAAAAAAAAABAQEBA QAAAQEAAAABAQABAQAAAAEBAAABAQEBAQEAAAAAAAABAQAA
AAAAAQEAAAABAQEBAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAEBAAAAAAAAAQEAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAQEAAAAAAAABAQAAAAAAA AAAAAAAAAAAAAAAAAAAAAABAQAAAAAAAAEBAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAEBAAAAAAAAAQE AAAAAAAEBAAAAAAAAAAAAAQEAAAAAAAEBAAAAAAAB
AQAAAAAAAQEAAAAAAAAAAQEAAAAAAAAAAQEAA AAAAAAAAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAE BAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAEBAAAA
AAAAAAEBAAAAAAAAAAEBAAAAAAAAAAEBAAAAAAABA QAAAAAAAQEAAAAAAAEBAAAAAAAAAAAAAAAA
AAABAQEBAQAAAQEAAAABAQAAAAAAAQEAAAAAAAEBAAA AAAAAAQEAAAAAAAAAAAAAAAAAAAEBAAAA
AAAAAAAAAAAAAQEBAQEAAAEBAAAAAQEAAQEAAQEBAQABA QABAQEBAAEBAAEBAQEAAQEAAAAAAAAA
AQEBAQAAAAAAAAAAAAAAAAABAQEAAAAAAQEAAQEAAAEBAAA AAQEAAQEBAQEBAQABAQAAAAEBAAEB
AAAAAQEAAQEAAAABAQAAAAAAAAAAAAEBAQEBAQAAAAEBAAABA QAAAQEAAAEBAAABAQEBAQAAAAEB
AAABAQAAAQEAAAEBAAEBAQEBAQAAAAAAAAAAAAAAAAEBAQEAAAA BAQAAAQEAAQEAAAAAAAABAQAA
AAAAAAEBAAAAAAAAAAEBAAABAQAAAAEBAQEAAAAAAAAAAAAAAQEBA QEAAAAAAQEAAQEAAAABAQAA
AQEAAAEBAAABAQAAAQEAAAEBAAABAQABAQAAAQEBAQEAAAAAAAAAAAA AAAEBAQEBAQEAAAEBAAAA
AQAAAQEAAQAAAAABAQEBAAAAAAEBAAEAAAAAAQEAAAABAAEBAQEBAQEAA AAAAAAAAAABAQEBAQEB
AAABAQAAAAEAAAEBAAEAAAAAAQEBAQAAAAABAQABAAAAAAEBAAAAAAABAQE BAAAAAAAAAAAAAAAA
AAABAQEBAAAAAQEAAAEBAAEBAAAAAAAAAQEAAAAAAAABAQAAAQEBAAABAQAAA QEAAAABAQEAAQAA
AAAAAAAAAAEBAAAAAQEAAQEAAAABAQABAQAAAAEBAAEBAQEBAQEAAQEAAAABAQA BAQAAAAEBAAEB
AAAAAQEAAAAAAAAAAAAAAAEBAQEAAAAAAAEBAAAAAAAAAQEAAAAAAAABAQAAAAAAA AEBAAAAAAAA
AQEAAAAAAAEBAQEAAAAAAAAAAAAAAAAAAQEBAQAAAAAAAQEAAAAAAAABAQAAAAAAAAE BAAABAQAA
AQEAAAEBAAABAQAAAAEBAQEAAAAAAAAAAAAAAAEBAQAAAQEAAAEBAAABAQAAAQEAAQEAA AABAQEB
AAAAAAEBAAEBAAAAAQEAAAEBAAEBAQAAAQEAAAAAAAAAAAABAQEBAAAAAAABAQAAAAAAAAE BAAAA
AAAAAQEAAAAAAAABAQAAAAEAAAEBAAABAQABAQEBAQEBAAAAAAAAAAAAAQEAAAABAQABAQEAA QEB
AAEBAQEBAQEAAQEBAQEBAQABAQABAAEBAAEBAAAAAQEAAQEAAAABAQAAAAAAAAAAAAEBAAAAAQE A
AQEBAAABAQABAQEBAAEBAAEBAAEBAQEAAQEAAAEBAQABAQAAAAEBAAEBAAAAAQEAAAAAAAAAAAAA
AQEBAQEAAAEBAAAAAQEAAQEAAAABAQABAQAAAAEBAAEBAAAAAQEAAQEAAAABAQAAAQEBAQEAAAAA
A AAAAAAAAQEBAQEBAAAAAQEAAAEBAAABAQAAAQEAAAEBAQEBAAAAAQEAAAAAAAABAQAAAAAAAQEB
AQA AAAAAAAAAAAAAAAABAQEBAQAAAQEAAAABAQABAQAAAAEBAAEBAAAAAQEAAQEAAAABAQABAQAA
AQEBA AABAQEBAQAAAAAAAAEBAQABAQEBAQEAAAABAQAAAQEAAAEBAAABAQAAAQEBAQEAAAABAQAB
AQAAAAE BAAABAQABAQEAAAEBAAAAAAAAAAAAAAABAQEBAAAAAQEAAAEBAAAAAQEAAAAAAAAAAQEA
AAAAAAAAA QEAAAABAQAAAQEAAAABAQEBAAAAAAAAAAAAAAABAQEBAQEAAAEBAQEBAQAAAQABAQAB
AAAAAAEBAAA AAAAAAQEAAAAAAAABAQAAAAAAAQEBAQAAAAAAAAAAAAABAQAAAAEBAAEBAAAAAQEA
AQEAAAABAQABA QAAAAEBAAEBAAAAAQEAAQEAAAABAQAAAQEBAQEAAAAAAAAAAAAAAQEAAAABAQAB
AQAAAAEBAAEBAAA AAQEAAQEAAAABAQABAQAAAAEBAAABAQABAQAAAAABAQEAAAAAAAAAAAAAAAEB
AAAAAQEAAQEAAAABA QABAQAAAAEBAAEBAAEAAQEAAQEAAQABAQABAQEBAQEBAAABAQABAQAAAAAA
AAAAAAABAQAAAAEBAAE BAAAAAQEAAAEBAAEBAAAAAAEBAQAAAAABAQABAQAAAQEAAAABAQABAQAA
AAEBAAAAAAAAAAAAAAEBA AABAQAAAQEAAAEBAAABAQAAAQEAAAABAQEBAAAAAAABAQAAAAAAAAEB
AAAAAAABAQEBAAAAAAAAAAA AAAEBAQEBAQEAAQEAAAABAQABAAAAAQEAAAAAAAEBAAAAAAABAQAA
AQAAAQEAAAEBAAEBAQEBAQEAA AAAAAAAAAAAAAEBAQEAAAAAAQEAAAAAAAABAQAAAAAAAAEBAAAA
AAAAAQEAAAAAAAABAQAAAAAAAAE BAQEAAAAAAAAAAAAAAQEAAAAAAAAAAQEAAAAAAAAAAQEAAAAA
AAAAAQEAAAAAAAAAAQEAAAAAAAAAA QEAAAAAAAAAAQAAAAAAAAAAAAAAAQEBAQAAAAAAAAEBAAAA
AAAAAQEAAAAAAAABAQAAAAAAAAEBAAA AAAAAAQEAAAAAAQEBAQAAAAAAAAAAAAAAAAABAAAAAAAA
AQEBAAAAAAEBAAEBAAABAQAAAAEBAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEB
AQEBAQAAAQEAAAAAAAAAAQEAAAAAAAAAAQEAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE BAQEAAAAAAAAAAQEAAAABAQEBAQAAAQEAAAEB
AAAAAQEBAAEBAAAAAAAAAAAAAQEBAAAAAAAAAQEAA AAAAAABAQEBAQAAAAEBAAABAQAAAQEAAAEB
AAABAQAAAQEAAQEAAQEBAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAQEBAQEAAAEBAAAAAQEA
AQEAAAAAAAABAQAAAAEBAAABAQEBAQAAAAAAAAAAAAAAA AABAQEAAAAAAAABAQAAAAEBAQEBAAAB
AQAAAQEAAAEBAAABAQAAAQEAAAEBAAAAAQEBAAEBAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAB
AQEBAQAAAQEAAAABAQABAQEBAQEBAAEBAAAAAAAAAAEBAQEBA AAAAAAAAAAAAAAAAQEBAQAAAAEB
AAABAQAAAQEAAAAAAAEBAQEBAAAAAAEBAAAAAAAAAQEAAAAAAAE BAQEAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAEBAQABAQABAQAAAQEAAAEBAAABAQAAAAEBA QEBAAAAAAAAAQEAAAEBAQEB
AAAAAQEBAAAAAAAAAQEAAAAAAAABAQABAQAAAAEBAQABAQAAAQEAAAE BAAABAQAAAQEAAQEBAAAB
AQAAAAAAAAAAAAAAAAEBAAAAAAAAAAAAAAAAAAEBAQAAAAAAAAEBAAAAA AAAAQEAAAAAAAABAQAA
AAAAAQEBAQAAAAAAAAAAAAAAAAAAAAEBAAAAAAAAAAAAAAAAAAABAQAAAAA AAAEBAAAAAAAAAQEA
AAEBAAABAQAAAQEAAAEBAAAAAQEBAQAAAQEBAAAAAAAAAQEAAAAAAAABAQAAA QEAAAEBAAEBAAAA
AQEBAQAAAAABAQABAQAAAQEBAAABAQAAAAAAAAAAAAAAAQEBAAAAAAAAAQEAAAA AAAABAQAAAAAA
AAEBAAAAAAAAAQEAAAAAAAABAQAAAAAAAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAQEB
AAEBAAABAQEBAQEBAAEBAAEAAQEAAQEAAQABAQABAQABAAEBAAAAAAAAAAAAAAAAAAA AAAAAAAAA
AAAAAAEBAAEBAQAAAAEBAAABAQAAAQEAAAEBAAABAQAAAQEAAAEBAAABAQAAAAAAAAAAA AAAAAAA
AAAAAAAAAAAAAAAAAQEBAQEAAAEBAAAAAQEAAQEAAAABAQABAQAAAAEBAAABAQEBAQAAAAA AAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAQEAAQEBAAAAAQEAAAEBAAABAQAAAQEAAAEBAQEBAAAAAQEAA AAA
AAEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEAAQEAAQEAAAEBAAABAQAAAQEAAAABAQEBAQA A
AAAAAAEBAAAAAAABAQEBAAAAAAAAAAAAAAAAAAAAAAABAQABAQEAAAABAQEAAQEAAAEBAAAAAAAA
AQEAAAAAAAEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQABAQAAAAAAAAAB
A QEBAQAAAAAAAAABAQABAQEBAQEAAAAAAAAAAAAAAAABAQAAAAAAAAEBAAAAAAEBAQEBAQAAAAAB
AQA AAAAAAAEBAAAAAAAAAQEAAQEAAAAAAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQAA
AQEAA AEBAAABAQAAAQEAAAEBAAABAQAAAQEAAAABAQEAAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAQE AAAABAQABAQAAAAEBAAEBAAAAAQEAAAEBAAEBAAAAAAEBAQAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAA AAAAAEBAAAAAQEAAQEAAQABAQABAQABAAEBAAEBAQEBAQEAAAEBAAEBAAAAAAAAAAAA
AAAAAAAAAAA AAAAAAAAAAAABAQAAAAEBAAABAQABAQAAAAABAQEAAAAAAQEAAQEAAAEBAAAAAQEA
AAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAQEAAAABAQABAQAAAAEBAAEBAAAAAQEAAAEBAQEBAQAA
AAAAAAEBAAEBAQE BAQAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEAAAEAAAEBAAAAAAABAQAAAAAA
AQEAAAEAAAEBAQEBA QAAAAAAAAAAAAAAAAABAQEAAAAAAQEAAAAAAAABAQAAAAABAQEAAAAAAAAA
AQEAAAAAAAABAQAAAAA AAAABAQEAAAAAAAAAAAAAAAABAQAAAAAAAAEBAAAAAAAAAQEAAAAAAAAB
AQAAAAAAAAEBAAAAAAAAA QEAAAAAAAABAQAAAAAAAAAAAAAAAAEBAQAAAAAAAAABAQAAAAAAAAEB
AAAAAAAAAAEBAQAAAAABAQA AAAAAAAEBAAAAAAEBAQAAAAAAAAAAAAAAAAABAQEAAQEAAQEAAQEB
AAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAEAAAAAAAABAQEAAAAAAQE AAQEAAAEBAAAAAQEAAQEAAAABAQABAQEBAQEBAAAAAAAAAAAA
AAEBAQEBAAABAQAAAAEBAAEBAAAAA AAAAQEAAAAAAAABAQAAAAEBAAABAQEBAQAAAAAAAAEBAAAA
AQEBAQAAAAEBAAABAQAAAAAAAAAAAAA BAQAAAQEAAAEBAAABAQAAAQEAAAEBAAABAQAAAQEAAAAB
AQEAAQEAAAAAAAAAAAAAAAAAAQEAAAAAA AEBAAAAAAEBAQEBAAABAQAAAAEBAAEBAQEBAQEAAQEA
AAAAAAAAAQEBAQEAAAAAAAAAAAAAAAEBAQE BAAABAAAAAAABAAABAQEBAAAAAAAAAAEBAAAAAQEB
AQEAAAEBAAABAQAAAAEBAQABAQAAAAAAAAAAA AEBAAAAAQEAAAAAAAAAAAAAAQEBAQAAAAAAAAAB
AQAAAAEBAQEBAAABAQAAAQEAAAABAQEAAQEAAAA AAAAAAAAAAAEBAAAAAAAAAAEBAAAAAAEBAQEA
AAAAAAAAAQEAAAABAQEBAQAAAQEAAAEBAAAAAQEBA AEBAAAAAAAAAAAAAAABAQAAAAAAAAEBAAAA
AAABAQEBAAAAAAAAAAEBAAAAAQEBAQEAAAEBAAABAQA AAAEBAQABAQAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAQEBAQEBAAEBAAAAAAAAAQEAAAAAAAAAA QEBAQEBAAAAAAABAQAAAAABAQEAAAAA
AQEBAQEAAAEAAAAAAAEAAAEBAQEBAAABAQAAAAEBAAEBAQE BAQEAAQEAAAAAAAAAAQEBAQEAAAAA
AAAAAAAAAQEAAAABAQAAAAAAAAAAAAABAQEBAQAAAQEAAAABA QABAQEBAQEBAAEBAAAAAAAAAAEB
AQEBAAAAAAAAAAAAAAAAAQEAAAAAAAAAAQEAAAAAAQEBAQEAAAE BAAAAAQEAAQEBAQEBAQABAQAA
AAAAAAABAQEBAQAAAAAAAAAAAAAAAQEAAAEBAAAAAAAAAAAAAAABA QEAAAAAAAABAQAAAAAAAAEB
AAAAAAAAAQEAAAAAAAEBAQEAAAAAAAAAAAAAAAEBAQEBAAABAAAAAAA BAAAAAQEBAAAAAAAAAQEA
AAAAAAABAQAAAAAAAAEBAAAAAAABAQEBAAAAAAAAAAAAAAAAAQEAAAAAA AAAAQEAAAAAAAAAAAAA
AAAAAQEBAAAAAAAAAQEAAAAAAAABAQAAAAAAAQEBAQAAAAAAAAAAAAABAQA AAAEBAAAAAQEBAAAA
AAEBAAEBAAABAQAAAAEBAAEBAQEBAQEAAQEAAAABAQABAQAAAAEBAAAAAAAAA AAAAAABAQEAAAAA
AQEAAQEAAAABAQEBAQAAAQEAAAABAQABAQEBAQEBAAEBAAAAAQEAAQEAAAABAQA AAAAAAAAAAAAA
AAEBAAAAAAABAQAAAAABAQEBAQEBAAEBAAAAAAAAAQEBAQEAAAABAQAAAAAAAAEBA QEBAQEAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQAAAAABAQAAAAABAQEBAQEAAQEAAQE AAAAAAQEB
AQEBAAAAAAAAAAAAAAABAQEBAQAAAQEAAQEAAAEBAAABAQAAAQEBAQEBAQABAQAAAQEAA AEBAAAB
AQAAAQEAAAEBAQAAAAAAAAAAAAABAQEBAQAAAQAAAAAAAQAAAQEBAQEAAAEBAAAAAQEAAQE AAAAB
AQABAQAAAAEBAAABAQEBAQAAAAAAAAAAAAABAQAAAAEBAAAAAAAAAAAAAAEBAQEBAAABAQAAA AEB
AAEBAAAAAQEAAQEAAAABAQAAAQEBAQEAAAAAAAAAAAAAAAABAQAAAAAAAAABAQAAAAABAQEBAQA A
AQEAAAABAQABAQAAAAEBAAEBAAAAAQEAAAEBAQEBAAAAAAAAAAAAAAABAQEBAAAAAQAAAAABAAAA
AAAAAAAAAAEBAAABAQAAAQEAAAEBAAABAQAAAQEAAAABAQEAAQEAAAAAAAAAAAAAAQEAAAAAAAAA
A QEAAAAAAQEAAAEBAAABAQAAAQEAAAEBAAABAQAAAQEAAAEBAAAAAQEBAAEBAAAAAAAAAAAAAQEA
AAA BAQAAAAAAAAAAAAEBAAAAAQEAAQEAAAABAQABAQAAAAEBAAABAQEBAQEAAAAAAAABAQABAQEB
AQEAA AEBAAAAAQEAAAABAQEAAAAAAQEAAQEAAAEBAAAAAQEAAQEAAAABAQAAAQEAAQEAAAAAAQEB
AAAAAAA AAAAAAAABAQAAAAEBAAAAAAAAAAAAAQEAAAABAQABAQAAAAEBAAEBAAAAAQEAAQEAAAAB
AQAAAQEBA QEAAAAAAAAAAAAAAAAAAQEAAAAAAAABAQAAAAABAQEBAQEAAQEAAAAAAAABAQAAAAAA
AAABAQEBAQE AAAAAAQEAAAAAAAABAQAAAAAAAQEBAAAAAAEBAAEBAAAAAQEAAAEAAAEBAQEAAAAA
AAEBAAAAAAAAA QEAAAEBAAEBAQEBAQAAAAAAAAAAAAAAAQEAAAEBAAABAQAAAQEAAAABAQEBAAAA
AQEBAQEBAAAAAAE BAAAAAAEBAQEBAQAAAAABAQAAAAAAAAEBAAAAAQEBAQEAAAABAQAAAQEAAAEB
AAABAQAAAQEBAQEAA QABAQAAAAEBAAEBAAABAQEBAQEAAAABAQABAQAAAAEBAQAAAAABAQEAAAAA
AQEAAQEAAAABAQAAAAA AAQEBAQAAAAAAAQEAAAABAQABAQAAAAABAQEAAAAAAAAAAAAAAAAAAAAB
AQAAAAAAAQEAAAAAAAEBA QEAAAAAAAAAAQEAAAABAQEBAQAAAQEAAAEBAAAAAQEBAAEBAAAAAAAA
AAAAAAAAAAEBAAAAAAABAQA AAAAAAAAAAAAAAAABAQEAAAAAAAABAQAAAAAAAAEBAAAAAAABAQEB
AAAAAAAAAAAAAAAAAAABAQAAA AAAAQEAAAAAAQEBAQEAAAEBAAAAAQEAAQEAAAABAQABAQAAAAEB
AAABAQEBAQAAAAAAAAAAAAAAAAA BAQAAAAAAAQEAAAAAAQEAAAEBAAABAQAAAQEAAAEBAAABAQAA
AQEAAAEBAAAAAQEBAAEBAAAAAAAAA AAAAAEBAQABAQABAQABAQEAAAAAAAAAAAAAAQEAAQEBAAAA
AQEAAAEBAAABAQAAAQEAAAEBAAABAQA AAAAAAAAAAAABAQEAAQEAAQEAAQEBAAAAAAAAAAAAAAEB
AQAAAQEAAQEBAQABAQABAQABAQEBAAEBA AABAQEAAAAAAAAAAAAAAAEBAQEAAAABAQABAQAAAAEB
AAEBAAAAAAEBAQEBAAAAAAAAAAAAAAEBAQE BAQAAAAAAAAAAAAAAAAAAAAAAAAABAQEAAAAAAQEA
AQEAAAABAQABAQAAAAABAQEAAAAAAAAAAAAAA AABAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAEB
AAAAAAAAAAAAAAAAAAABAQAAAAAAAAEBAAAAAAA BAQAAAAAAAQEAAAABAQAAAQEBAQEAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBA QEBAAEBAAAAAAAAAQEAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAQEBAQEBAQAAAAAAAAEBAAAAAAAAAQEA
AAAAAAAAAAAAAAAAAAAAAAABAQAAAAEBAQEBAAABAQAAA QEAAQEAAAABAQEBAQEAAAABAQAAAQEA
AQEAAAEBAAEBAAABAQAAAAAAAAEBAQEAAQEAAAABAQEBAQA AAQEAAAEBAAEBAAAAAQEBAQABAAAA
AQEAAQEAAAEBAAEAAQABAQABAQEBAQAAAAAAAQEAAAAAAQEAA AAAAAAAAAAAAAAAAAEBAAAAAAAA
AQEAAAAAAAEBAQEAAAAAAQEBAQAAAAAAAQEAAAAAAAAAAAAAAAA AAAAAAAAAAAABAQAAAQEAAQEA
AAEBAAEBAAABAQAAAAEBAAABAQAAAAEBAAABAQAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAEBAAAB
AQAAAAEBAAABAQAAAAEBAAABAQABAQAAAQEAAQEAAAEBAAAAAAAAAAA AAAAAAAAAAAAAAAABAAAA
AQABAAAAAQAAAAAAAQAAAAEAAQAAAAEAAAAAAAEAAAABAAEAAAABAAAAA AABAAAAAQABAAAAAQAA
AAABAAEAAQABAQABAAEAAQAAAQABAAEAAQEAAQABAAEAAAEAAQABAAEBAAE AAQABAAABAAEAAQAB
AQABAAEAAQAAAQEBAAEBAQEBAAEBAQABAAEBAQABAQEBAQABAQEAAQABAQEAA QEBAQEAAQEBAAEA
AQEBAAEBAQEBAAEBAQABAAAAAQEAAAAAAAABAQAAAAAAAAEBAAAAAAAAAQEAAAA AAAABAQAAAAAA
AAEBAAAAAAAAAQEAAAAAAAABAQAAAAAAAAEBAAAAAAAAAQEAAAAAAAABAQAAAAAAA AEBAAAAAQEB
AQEAAAAAAAABAQAAAAAAAAEBAAAAAAAAAQEAAAAAAAABAQAAAAAAAAEBAAAAAQEBAQE AAAAAAAAB
AQAAAAEBAQEBAAAAAAAAAQEAAAAAAAABAQAAAAAAAAEBAAAAAAABAQABAQAAAAEBAAEBA AAAAQEA
AQEAAAABAQABAQABAQEBAAEBAAAAAQEAAQEAAAABAQABAQAAAAEBAAEBAAAAAAAAAAAAAAA AAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQAAAAEBAAEBAAAAAQEAAQEAAAABAQABAQAAAAAAA AAA
AAAAAAAAAAAAAQEBAQEAAAAAAAABAQAAAAEBAQEBAAAAAAAAAQEAAAAAAAABAQAAAAAAAAEBAAA A
AAABAQABAQAAAAEBAAEBAAEBAQEAAQEAAAAAAAABAQABAQEBAAEBAAAAAQEAAQEAAAABAQABAQAA
AAEBAAEBAAAAAQEAAQEAAAABAQABAQAAAAEBAAEBAAAAAQEAAQEAAAABAQABAQAAAAEBAAEBAAAA
A QEAAQEAAAABAQABAQAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQAAAAAAAAEBAAEBAQEAAQEAAAAB
AQA BAQAAAAEBAAEBAAAAAQEAAQEAAAABAQABAQAAAAEBAAEBAAEBAQEAAQEAAAAAAAABAQABAQEB
AQEBA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEAAQEAAAABAQABAQAAAAEBAAEBAAAAAQEA
AQEAAQE BAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQAAAAAAAAEBAAAAAQEBAQEA
AAAAAAABA QAAAAEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAA AAAAAAAAAAAABAQEBAQAAAAAAAAEBAAAAAAAAAQEAAAAAAAABAQAAAAAAAAEBAAAA
AAAAAQEAAAAAA AABAQAAAAAAAAEBAAAAAAAAAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAABAQAAAAAAAAE BAAAAAAAAAQEAAAAAAAABAQAAAAEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQAAAAEBAAAAAAAA
AQEAAAAAAAABAQAAAAA AAAEBAAAAAAAAAQEAAAAAAAABAQAAAAAAAAEBAAAAAAAAAQEBAQEAAAAB
AQAAAAAAAAEBAAAAAAAAA QEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEB
AQEBAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAQEAAAAAAAABAQAAAAAAAAEBAAAAAAAAAQEA
AAABAQEBAQEBAQAAAAEBAAAAA AAAAQEAAAAAAAABAQAAAAAAAAEBAAAAAAAAAQEAAAAAAAABAQEB
AQAAAAEBAAAAAAAAAQEBAQEAAAA BAQAAAAAAAAEBAAAAAAAAAQEAAAAAAAEBAAEBAAAAAQEAAQEA
AAABAQABAQAAAAEBAAEBAAAAAQEAA QEBAAABAQABAQAAAAEBAAEBAAAAAQEAAQEAAAABAQABAQAA
AAEBAAEBAAAAAQEAAQEBAAABAQAAAAA AAAEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAEBAQEBAQAAA QEAAAAAAAABAQABAQEAAAEBAAEBAAAAAQEAAQEAAAAB
AQABAQAAAAEBAAEBAAAAAQEAAQEAAQEBAQA BAQEAAAAAAAAAAAEBAQEBAQEBAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AEBAQEBAQEBAAAAAAAAAAABAQEBAAEBAQAAAQEA
AQEAAAABAQABAQAAAAEBAAEBAAAAAQEAAQEAAAA BAQABAQAAAAEBAAEBAQAAAQEAAAAAAAABAQAB
AQEAAAEBAAEBAAAAAQEAAQEAAAABAQABAQAAAAAAA AAAAAAAAAAAAAAAAQEBAQEBAQEAAAAAAAAA
AAEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAABAQABAQAAAAEBAAEBAAEBAQEAAQEB
AAAAAAAAAAABAQEBAAEBAQAAAQEAAQEAAAABAQABAQAAA AEBAAEBAAAAAAEBAAAAAAAAAQEAAAAB
AQEBAQEBAQAAAAAAAAAAAQEBAQEBAQEAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAEBAAEBAAAA
AQEAAQEAAAABAQABAQAAAAEBAAEBAAEBAQEBAQEBAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAEBAQEBAQEBAAAAAAAAAAABAQEBAQEBAQA AAAEBAAAAAAAAAQEAAAAAAAAB
AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBA QEBAQEAAAEBAAEBAAAAAQEA
AQEAAAABAQABAQAAAAEBAAEBAAAAAQEAAQEAAAABAQABAQAAAAEBAAE BAAAAAQEBAQEBAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEAAAAAAAABAQAAAAAAAAEBAQEBA AAAAQEAAAAAAAABAQEB
AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BAQEBAQAAAAEBAAAA
AAAAAQEBAQEAAAABAQAAAAAAAAEBAAAAAAAAAQEAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAA
AAAAAAAAAAAAAQEBAQEBAAABAQABAQAAAAEBAAEBAAAAAQEAAQEAAAABAQABAQA AAAEBAAEBAAAA
AQEAAQEAAAABAQABAQABAQEBAQEBAQAAAQEAAQEAAAABAQABAQAAAAEBAAEBAAAAA AEBAAAAAAAA
AQEAAAABAQEBAQEBAQAAAAEBAAAAAQEBAQEBAQEAAAABAQAAAAAAAAEBAAAAAAAAAQE AAAAAAAAB
AQAAAAAAAAEBAAAAAAAAAQEAAAAAAAABAQAAAAEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQAAAAEBAAAAAAA AAQEA
AAAAAAABAQAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA QEB
AQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQE B
AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAABAQEBAAAAAAEBAQEAAAAAAQEBAQAAAAAB
AQEBAAAAAAEBAQEAAAAAAQEBAQAAAAABAQEBAAAAAAAAAAABAQEBAAAAAAEBAQEAAAAAAQEBAQAA
A AABAQEBAAAAAAEBAQEAAAAAAQEBAQAAAAABAQEBAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB
AQE BAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAA AABAQEAAQEAAQEAAQEBAAABAQAAAQAAAAEBAAEBAQAAAAEBAQABAQAAAAAAAAAAAAABAQEB
AAAAAQE AAAEBAAABAQAAAQEAAAEBAAEBAAAAAQEAAAEBAAABAQAAAAEBAAEBAAABAQAAAAAAAAAA
AAABAQEBA QEBAAEBAAAAAQEAAQEAAAAAAAABAQAAAAAAAAEBAAAAAAAAAQEAAAAAAAABAQAAAAAA
AAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEAAAEBAAEBAAAAAQEAAQEAAAABAQABAQAA
AAEBAAEBAAAAA AAAAAAAAAEBAQEBAQEAAQEAAAABAQAAAQEAAAAAAAAAAQEAAAAAAAEBAAAAAAAB
AQAAAAEBAAEBAQE BAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQABAQABAQAAAAEB
AAEBAAAAAQEAAQEAA AAAAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQAAAQEAAAEB
AAABAQAAAQEAAAEBAAA BAQAAAQEAAAEBAQEBAAABAQAAAAAAAAAAAAAAAAAAAAEBAQABAQABAQAB
AQEAAAAAAAEBAAAAAAAAA QEAAAAAAAABAQAAAAAAAAEBAAAAAAAAAAAAAAAAAQEBAQEBAAAAAAEB
AAAAAAABAQEBAAAAAQEAAAE BAAABAQAAAQEAAAABAQEBAAAAAAABAQAAAAABAQEBAQEAAAABAQEA
AAAAAQEAAQEAAAEBAAAAAQEAA QEBAQEBAQABAQAAAAEBAAABAQABAQAAAAABAQEAAAAAAAAAAAAA
AAAAAQEBAAAAAAEBAAEBAAABAQA AAAEBAAEBAAAAAQEAAAEBAAEBAAAAAQEAAQEAAAEBAQABAQEA
AAAAAAAAAAAAAAAAAQEBAAAAAAEBA AAAAAAAAAEBAAAAAAEBAQEBAAABAQAAAQEAAAEBAAABAQAA
AAEBAQEAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAABAQEBAQEAAQEAAQEAAQEBAQABAQABAQAB
AQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAQEAAAAAAAEBAAAAAQEBAQEBAAEBAAEBAAEBAQEA
AQEAAQEAAQEBAQEBAAABAQAAAAAAAQEAAAA AAAAAAAABAQEBAAAAAQEAAAAAAAEBAAAAAAAAAQEB
AQEBAAABAQAAAAAAAAABAQAAAAAAAAABAQEBA AAAAAAAAAAAAAAAAAAAAAAAAQEBAQEAAAEBAAAA
AQEAAQEAAAABAQABAQAAAAEBAAEBAAAAAQEAAQE AAAABAQAAAAAAAAAAAAAAAAAAAAAAAQEBAQEB
AQAAAAAAAAAAAAEBAQEBAQEAAAAAAAAAAAABAQEBA QEBAAAAAAAAAAAAAAAAAAAAAAAAAAABAQAA
AAAAAAEBAAAAAAEBAQEBAQAAAAABAQAAAAAAAAEBAAA AAAAAAAAAAAAAAQEBAQEBAAAAAAAAAAAA
AAABAQAAAAAAAAABAQAAAAAAAAABAQAAAAAAAQEAAAAAA AEBAAAAAAAAAAAAAAAAAAEBAQEBAQAA
AAAAAAAAAAAAAAABAQAAAAAAAQEAAAAAAAEBAAAAAAAAAAE BAAAAAAAAAAEBAAAAAAAAAAAAAAAB
AQEBAQEAAAAAAAAAAAAAAAAAAQEBAAAAAAEBAAEBAAAAAQEAA QEAAAABAQAAAAAAAAEBAAAAAAAA
AQEAAAAAAAABAQAAAAAAAAEBAAAAAAAAAQEAAAAAAAABAQAAAAA AAAEBAAAAAAAAAQEAAAAAAAAB
AQAAAAEBAAEBAAAAAQEAAQEAAAAAAQEBAAAAAAAAAAAAAAAAAAAAA QEAAAAAAAAAAAAAAAABAQEB
AQEAAAAAAAAAAAAAAAABAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAABAQEAAQEAAQEAAQEB
AAAAAAAAAAAAAAABAQEAAQEAAQEAAQEBAAAAAAAAAAAAAAAAAAAAAAAAA AABAQEAAAAAAQEAAQEA
AAABAQABAQAAAAABAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAEBAAAAAAAAAQEAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAA
AAAAAAAAAAAAAAEBAQEAAAAAAQEAAAAAAAABAQAAAAAAAAEBAAABAQEAAQEAAAABA QABAQAAAAAB
AQEBAAAAAAABAQEAAAABAQABAQAAAAABAQABAQAAAAEBAAEBAAAAAQEAAQEAAAABAQA BAQAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQAAAAAAAAABAQAAAAAAAQEAAAAAAAEBAAAAA AABAQEB
AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQAAAAA BAQEB
AAAAAAEBAQEAAAAAAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=

How-to: Use Html 4.01 in CakePHP 1.2

CakePHP : Posted by Felix Geisendörfer at February 21, 2007 05:20 PM

Hi folks,

this is just a little tip for those of you who dislike their xhtml to end up as tag soup and still prefer to send out Html 4.01 strict. Here is how to make all helper functions in Cake 1.2 output Html 4.01 compliant markup instead of xhtml:

PHP:
  1. class AppHelper extends Helper
  2. {
  3.     /**
  4.      * This is the constructor for our AppHelper class that we use to make all helper output html 4.01
  5.      * compatible markup.
  6.      *
  7.      * @return AppHelper
  8.      */
  9.     function AppHelper()
  10.     {
  11.         // Loop through all tags in this helper
  12.         foreach ($this->tags as $tag => $html)
  13.         {
  14.             // Replace all xhtml style tag closings with html 4.01 strict compatible ones
  15.             $this->tags[$tag] = r('/>', '>', $html);
  16.         }
  17.     }
  18. }

If you wonder why somebody would prefer the good old html over the all-so-hyped xhtml, read a little bit about it here.

-- Felix Geisendörfer aka the_undefined

Referencing Javascript files

CakePHP : Posted by cakebaker at February 21, 2007 04:52 PM

If you want to use a Javascript file, you usually reference it in the head section of your layout:

<head>
    <?php echo $javascript->link('script.js'); ?>
</head>

But sometimes you want to use a certain Javascript file only in one view. Then it doesn’t make sense to reference it in the layout. You could take the PHP statement from above and place it in your view, that will work. But it is not a clean solution as the Javascript reference is placed somewhere in the middle of the generated HTML code.

Fortunately, CakePHP 1.2 allows you to define a reference to a Javascript file which is then added to the head section of the generated HTML code. For this purpose the variable $scripts_for_layout has to be added to the layout:

<head>
    <?php echo $scripts_for_layout; ?>
</head>

In the view you can now link to the Javascript file with:

$javascript->link('script.js', false);

That’s it.

Ecosystem Navigation and Tiny Visualizations

RubyOnRails : Posted on Max Kiesler at February 21, 2007 03:58 PM


Many community websites have adopted a new form of navigation which works more like an ecosystem and less like a static list. The definition of an ecosystem is "a system formed by the interaction of a community of organisms with their environment". Ecosystem navigation relies on community input including, recency, popularity, and activity. As an avid reader blogs I've always been struck by the similarity in the way their information is presented and navigated. In a standard blog paradigm the reader is presented with a list of blog posts sorted by date, and a list of comments, categories, tags. By scanning this list of information the reader is given a good idea of what the blog is focused on and what topics the author posts about. However, this is less of a ecosystem approach and more of a linear presentation of the authors content. Tags and categories do give blog readers a non-liner way to explore a website and and this was the first area I decided to focus on.

The first tiny visualization that I saw on blogs that addressed the linear nature of the date driven layout was the tag cloud. This construct shows a list of tags used by the author in a weighted list which shows the most popular tags used as larger text and the lesser used tags as smaller text. This meta layer lets the website reader see graphically and quickly the most popular tags used by the author or the community. While tag clouds shows the authors interest, it does not show the readers interests.

Most blog authors have a statistical program that shows them readers interest by tracking incoming and outgoing links to their website. This data shows what the popularity is of an article or category of at any given time. Many of the newer stats programs are also enhanced with real-time charts and graphs which give you an easy way to comprehend all of the data. Whether this information is presented by month, day or even in real-time it shows the author what their readers are interested in. I have found over the years that this data does not always correlate with my blogs date driven navigational hierarchy . Many times my most popular posts on a given day is not the most resent. So I started wondering about the idea of showing blog data by popularity by both myself and my readers.

So was born my category pie chart. I know what you're thinking, "why an imaged mapped graphic -are we living in 1997"? In a work no, this graphic was produced serverside on the fly by real time database information and the image map that controls it is completely accessible. It was built with a software app named ChartDirector which I combined it with my blog application. What's missing at this point is the popularity of category by readers. Once I've created this chart, myself, and my readers can see what categories we are both interested in. This is more of an eco-friendly navigational system. Next steps will also include developing other tiny visualizations of blog posts, comments and archives.

Ecosystem navigation is a convergence of both the authors and readers interest presented by recency, popularity, activity. The next step for me will be to find a real-time convergence in categories, tags and blog posts presented in an easy to understand graphical manner. Consider this a starting point for the discussion. Please let me know what you think about these ideas in the comment section of this post.

Seattle in late March

RubyOnRails : Posted by Robby Russell at February 21, 2007 12:35 AM

I’m going to be hanging out in Redmond, WA. late next month… why? That… I’ll explain at a later date. ;-)

What I can say is that I’ll be available on a few evenings if anybody is interested in meeting up to talk shop, which can include anything from d3, ruby on rails, bdd, agile interaction design... to BBC comedy shows. :-)

I’ll be flying up from Portland to Seattle on Saturday, March 24th. I’m going to try and stay downtown for that night… and then will be staying at Sheraton Bellevue until Tuesday night. So… Saturday-Monday nights are currently open.

I’m also planning to head to the monthly Seattle.rb meeting on Tuesday, March 27th.

If you’re interested in meeting up, drop me a line.

UPDATE If you’re taking the kinky aspect of BDD too serious... please don’t email me. ;-)