After RuPy’08

RuPy logoRuby and Python Conference (RuPy) took place in Poznań, Poland this weekend. It was second edition of the RuPy and I must say that the event was great.

I have not written a single line of code in any of those languages before, but I was seriously considering switching to Ruby (and Rails) soon. I am fortunate enough that next project I will be working on will be written in Ruby on Rails, so this will be even bigger motivation to learn it. And I must say that after the conference my motivation is sky high.

There were many interesting talks, for me, rails-developer-wannabe however the most interesting was live-coding session given by Rida Al Barazi, who was trying to answer the question “Is Rails as agile as advertised”. I saw him in action for about two hours as he was developing simple movie database web application and I admit, it’s very agile indeed. At least when you compare it to Cake, which I like very much when I develop in PHP.

Other lectures was I liked was : “Test driven developement in Rails” by Andrzej Krzywda, “Correlations and Conclusions” by Zed Shaw, “Business Natural Languages” by Jay Fields and “Caching in Rails” by Wiktor Schmidt.

Python part of the conference was taking place at the same time in another room. I didn’t attend it as I decided to focus on Ruby. Maybe next year.

Videos and other materials from RuPy should be available soon on conference’s website.

Surely I will be posting here about Ruby too in future. Of course, I am not going to give up Cake completely anytime soon. There are several Cake-powered projects I am still involved in.

Switching to App::import() may cause problems

Some time ago importing 3rd party libraries through vendor() method became deprecated and now usage of App::import(‘Vendor’, ‘…’) is suggested. I decided to walk through my application files and switch to new version. It was not that easy however… In my app I use Swift Mailer library for sending e-mails.

// I changed this...
vendor('Swiftlib/Swift/Connection/SMTP');
// to this...
App::import('Vendor', 'Swiftlib/Swift/Connection/SMTP');

It didn’t work, because Cake’s Inflector translated the path to swiftlib/swift/connection/s_m_t_p.php. Of course such file does not exist. Changing vendor’s file names wouldn’t be a good idea. After checking the API I found that App::import() also takes $file parameter if you want to explicitly specify which file to include. So the working solution looks like this:

App::import('Vendor', null, true, array(), APP.'vendors/Swiftlib/Swift/Connection/SMTP.php');

Remember that when you use 3rd party libraries and they do not follow Cake’s conventions in naming files.

CakePHP – Some good practices

When you write small applications, you don’t really need to care about quality of your code. However this is a must in medium or large ones. If you don’t spend some time on planning, there is a reasonable chance that you’ll end up with an unfinished project. After writing some in Cake, I have some views on how to write good (or not bad, at least!). Big part of what you’ll read below is related to software engineering in general.

1. Plan the structure of your application

This is the most important. Analyze requirements, identify objects. It’s good to divide application to separable modules and implement and test them one at the time. Connect them after they’re tested. Advantages of such approach are obvious – high cohesion, easier implementation and testing, maintainability. It’s also easy to assign tasks to programmers, because they can work independently.

2. Implement functionalities with abstraction in mind

Try to write each module of the application in a way that it could be reused in another projects without much changes (preferrably only configuration ones). For example let’s consider following situation: you want users of your site to be able to rate objects (photos, comments, whatever…).

database table ratings:

  • id – primary key
  • user_id – id of user that rates an object
  • model – name of the model of an object (Photo, Comment, etc)
  • foreign_key – id of an object
  • rating – actual rating chosed by an user

Such structure lets users rate any object in the system. I’d suggest writing a Behavior that shares a method to write ratings in database. Then to make an object ratable you’d only need to add this behavior to $actsAs property of object’s model. Simple and generic.

3. Keep controllers independent of user interface

Controller’s action should be implemented in a way, that there is no difference whether they’re accessed via AJAX or usual request. It’s views (and layouts) that are responsible for user interface. If you want to create rich AJAX application, alter the views. Controllers and their action, if correctly written, may remain unchanged.

4. Move the heart of your application to models

You should place most of business logic of your app in models as the concept “Fat model, skinny controller” suggests. Often many controllers share models beetwen them and perform the same actions on them. Placing these actions inside models makes it easier to test, debug and change them, because all the changes need to be made in one place only. Similarly, implementation of such mechanisms as caching or logging is much easier to do and maintain. It’s less error-prone.

5. Use elements extensively

Many pages of the site display the same elements – nav bars, breadcrumbs, menus, etc. Define them in elements. You can pass variables with renderElement() method so you can customize them anytime. For example: create and edit form are almost the same, the only difference is probably the value of action attribute of form tag. So it’s good to make them an element.

Remember that it’s the first controllers and models you write that determine your application’s structure and the way it develops. In the middle of the work it’s hard to change the approach, so you should invest some time in analyzing and planning before you start coding. It will pay off.

Read-only model fields

How many times you found yourself in situation when you were trying to prevent certain fields from being edited by users? I’ve done it myself and I’ve seen others doing it. The most hardcore method is to unset() long list of fields in $this->data variable in controller’s action. It can get difficult to maintain if you change fields’ values from many places – if you forget to unset a sensitive field somewhere this could happen in serious security flaw in your application.

Imagine that your user model has such fields: id, username, password, group_id. Of course you let users change their username and password, but it’s critical to keep them away from group_id field that assigns them to certain user group to make sure they don’t make themselves Administrators!

My proposal is to control these unwanted changes from model level. Let’s call these read-only fields ‘locked fields’. We are going to define which field to lock in our model’s definition and the beforeSave() callback method will take care of the rest. Here is the AppModel:

class AppModel extends Model {
  var $lockedFields = array();
 
  function beforeSave() {
    if (!empty($this->id)) {
      foreach ($this->lockedFields as $field) {
        unset($this->data[$this->name][$field]);
      }
    }
    return true;
  }
 
  function lockFields($fields = array()) {
    if (!is_array($fields)) {
      $fields = array($fields);
    }
    foreach ($fields as $field) {
      if (!in_array($field, $this->lockedFields)) {
        $this->lockedFields[] = $field;
      }
    }
  }
 
  function unlockFields($fields = array()) {
    if (!is_array($fields)) {
      $fields = array($fields);
    }
    foreach ($this->lockedFields as $k => $field) {
      if (in_array($field, $fields)) {
        unset($this->lockedFields[$k]);
      }
    }
  }
}

As you see beforeSave() does it’s job only when a record is being edited ($this->id is set) as it should. UserModel looks like this:

// user.php
class UserModel extends AppModel {
  var $lockedFields = array('group_id');
}

We defined group_id as locked field in model’s definition but thanks to lockFields() and unlockFields() methods defined in AppModel we are able to alter this dinamically. There may be occasion when administrator actually wants to move an user to another group. You can achieve it through a simple method in UserModel.

// user.php
function moveToAdmins($userId) {
  $this->unlockFields('group_id');
  $this->id = $userId;
  $this->saveField('group_id', 1);
  $this->lockFields('group_id');
}

Simple. You also can lock/unlock many fields at once passing them in array.

As always I hope this helps someone :)

Hidden model fields, revisited

Two days ago I wrote a post about hidden model fields. That solution had a drawback – you couldn’t read a field once it was hidden. There was also a bug – hidden fields were shown when their values was null. Here’s upgraded version:

class AppModel extends Model {
  var $hiddenFields = array();
 
  /**
   * Sets given fields as hidden.
   *
   * @param array $fields List of fields
   * @return void
   */
  function hideFields($fields = array()) {
    foreach ($fields as $field)
      if (!in_array($field, $this->hiddenFields))
        $this->hiddenFields[] = $field;
  }
 
  /**
   * Sets given fields as visible.
   *
   * @param array $fields List of fields
   * @return void
   */
  function showFields($fields = array()) {
    foreach ($fields as $field)
      foreach ($this->hiddenFields as $k => $hiddenField)
        if ($hiddenField == $field)
          unset($this->hiddenFields[$k];
  }
 
  /**
   * Callback function.
   * Removes fields marked as hidden from the results of find operation.
   *
   * @param array $result Results of find operation
   * @param bool $primary Whether model is being queried directly
   */
  function afterSave($result, $primary = false) {
    foreach ($result as &$row)
      foreach ($this->hiddenFields as $field)
        if (array_key_exists($field, $row[$this->name]))
          unset($row[$this->name][$field]);
  }
}

The usage is pretty simple and intuitive. You can mark certain fields as hidden in your model definition, as shown in original post, but you can also show and hide them from wherever you like. For example in controller (assuming that ‘password’ field is marked as hidden in User model):

function show_me_my_password() {
  $this->User->showField(array('password'));
  $this->User->read(null, 1);
  // do something...
}

It’s just example. Generally you should avoid showing passwords as plain text for security reasons. You should always even keep them in encrypted form, ie. hashed. You won’t need to ever decrypt them. Instead of retrieving password from database for comparision during logging in, hash it and compare hashes. Instead of retrieving it to remind it to the user, generate new one.

Hidden model fields

Probably there are some fields in your models, usually holding sensitive data, that you do not want to display to the end users. In my case these are passwords, activation codes, etc. I want to ensure that values stored in these fields will be never exposed, neither intentionally nor by accident. So I came up with this little solution.

This is implementation of afterFind() callback function that should be placed inside of app_model.php file.

// app_model.php
class AppModel extends Model {
  var $hiddenFields = array();
 
  function afterFind($result, $primary = false) {
    foreach ($result as &$row)
      foreach ($this->hiddenFields as $field)
        if (isset($row[$this->name][$field]))
          unset($row[$this->name][$field]);
    return $result;
  }
}

The last thing to do is to define $hiddenFields variable in your model.

var $hiddenFields = array('password', 'activation_code');

As you may have noticed, values of these fields are retrieved from the database and later unset. If you don’t want them to be retrieved at all (this would be more secure), you should take care of filtering this fields out in beforeFind() callback method, however this could be quite cumbersome. For me it was OK enough to just unset them after retrieval, so I even didn’t try to do it through beforeFind().

There are also drawbacks. For example you can’t retrieve someone’s password and send it by e-mail when someone forgets it (in fact you can still get it with model’s query() method, but it’s not a ‘clean’ way). In such situations you should generate new random password instead of reminding the forgotten one.

If you have any views on this, please leave a comment.