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