WhoDidIt behavior: automagic created_by and modified_by fields
WhoDidIt behavior is useful for tracking who has created and modified records: automagically!
Handles created_by, modified_by fields for a given Model, if they exist in the Model DB table.
It's similar to the created, modified automagic, but it stores the logged User id
in the models that actsAs = array('WhoDidIt')
This is useful to track who created records, and the last user that has changed them.
The DB table must have a created_by or a modified_by field (the names of these fields can be overriden during behavihor inizialisation).
more info: 4webby.com
Git repository: http://github.com/danfreak/4cakephp/
Thanks to the guys of the #IRC channel (poLK, AD7six, etc) for their help!
Download code
It's similar to the created, modified automagic, but it stores the logged User id
in the models that actsAs = array('WhoDidIt')
This is useful to track who created records, and the last user that has changed them.
The DB table must have a created_by or a modified_by field (the names of these fields can be overriden during behavihor inizialisation).
more info: 4webby.com
Git repository: http://github.com/danfreak/4cakephp/
Thanks to the guys of the #IRC channel (poLK, AD7six, etc) for their help!
Download code
<?php
/**
* WhoDidIt Model Behavior for CakePHP
*
* Handles created_by, modified_by fields for a given Model, if they exist in the Model DB table.
* It's similar to the created, modified automagic, but it stores the logged User id
* in the models that actsAs = array('WhoDidIt')
*
* This is useful to track who created records, and the last user that has changed them
*
* @package behaviors
* @author Daniel Vecchiato
* @version 1.2
* @date 01/03/2009
* @copyright http://www.4webby.com
* @licence MIT
* @repository https://github.com/danfreak/4cakephp/tree
**/
class WhoDidItBehavior extends ModelBehavior {
/**
* Default settings for a model that has this behavior attached.
*
* @var array
* @access protected
*/
protected $_defaults = array(
'auth_session' => 'Auth', //name of Auth session key
'user_model' => 'User', //name of User model
'created_by_field' => 'created_by', //the name of the "created_by" field in DB (default 'created_by')
'modified_by_field' => 'modified_by', //the name of the "modified_by" field in DB (default 'modified_by')
'auto_bind' => true //automatically bind the model to the User model (default true)
);
/**
* Initiate WhoMadeIt Behavior
*
* @param object $model
* @param array $config behavior settings you would like to override
* @return void
* @access public
*/
function setup(&$model, $config = array()) {
//assigne default settings
$this->settings[$model->alias] = $this->_defaults;
//merge custom config with default settings
$this->settings[$model->alias] = array_merge($this->settings[$model->alias], (array)$config);
$hasFieldCreatedBy = $model->hasField($this->settings[$model->alias]['created_by_field']);
$hasFieldModifiedBy = $model->hasField($this->settings[$model->alias]['modified_by_field']);
$this->settings[$model->alias]['has_created_by'] = $hasFieldCreatedBy;
$this->settings[$model->alias]['has_modified_by'] = $hasFieldModifiedBy;
//handles model binding to the User model
//according to the auto_bind settings (default true)
if($this->settings[$model->alias]['auto_bind'])
{
if ($hasFieldCreatedBy) {
$commonBelongsTo = array(
'CreatedBy' => array('className' => $this->settings[$model->alias]['user_model'],
'foreignKey' => $this->settings[$model->alias]['created_by_field'])
);
$model->bindModel(array('belongsTo' => $commonBelongsTo), false);
}
if ($hasFieldModifiedBy) {
$commonBelongsTo = array(
'ModifiedBy' => array('className' => $this->settings[$model->alias]['user_model'],
'foreignKey' => $this->settings[$model->alias]['modified_by_field']));
$model->bindModel(array('belongsTo' => $commonBelongsTo), false);
}
}
}
/**
* Before save callback
*
* @param object $model Model using this behavior
* @return boolean True if the operation should continue, false if it should abort
* @access public
*/
function beforeSave(&$model) {
if ($this->settings[$model->alias]['has_created_by'] || $this->settings[$model->alias]['has_modified_by']) {
$AuthSession = $this->settings[$model->alias]['auth_session'];
$UserSession = $this->settings[$model->alias]['user_model'];
$userId = Set::extract($_SESSION, $AuthSession.'.'.$UserSession.'.'.'id');
if ($userId) {
$data = array($this->settings[$model->alias]['modified_by_field'] => $userId);
if (!$model->exists()) {
$data[$this->settings[$model->alias]['created_by_field']] = $userId;
}
$model->set($data);
}
}
return true;
}
}
?>
Comments
Comment
1 more info?
First, I did not find any more info at 4webby.com
Secondly, it seems to me that binding associations to the model goes beyond the scope of this behavior, also I do not see any configuration for it.
Comment
2 Replay to Alexander
Sorry I was in late with publishing, check this out
Well you might be right, even if I find it handy to retrieve the username, for example of you has saved/modified a record in an admin_index view for example. But I added a further config variable
class Post extends AppModel {
var $name = 'Post';
var $actsAs = array('WhoDidIt'=>array('auto_bind'=>false));
}
So that it is now possible to disable autobinding
Here they are:
And you can use them as follows:
class Post extends AppModel {
var $name = 'Post';
var $actsAs = array('WhoDidIt'=>array('user_model'=>'MyUser',
'created_by_field'=>'built_by',
'modified_by_field'=>'affected_by'));
}
Comment
3 Useful
code really useful.
I will give it a try in one of my projects.
Comment
4 ;o)
Comment
5 Very nice!
Controller Class:
<?phpE.g.: http://code.google.com/p/chieftain/source/browse/trunk/chieftain/controllers/books_controller.php#35$this->Model->trace($this->Auth->user('id'));
?>
+1 on my references for behaviors.
Thanks for your work. :)
Question
6 Doesn't seem to be working.
I updated my database to include the two fields of created_by and modified_by however if I do a
debug($this->Dealership->save($this->data));I should be able to see the created_by and modified_by added to the save function however I don't see anything extra being added. I also it doesn't appear that any value is being passed into the database.Just to make sure I have everything set up correctly, I want to add
to my model for the controller I am working with correct?var $actAs = array('WhoDidIt');
Then I also want to save the Who Did It Behavior to the app/models/behavior folder as well?
Sorry for all the questions but this is the first behavior that I have made use of.
Comment
7 Answer to Jon
you should use
var $actsAs = array('WhoDidIt');
and not
var $actAs = array('WhoDidIt');
Dan
Comment
8 Whodunit
See also: http://en.wikipedia.org/wiki/Whodunit
It's like Sherlock Holmes. Murder mysteries. Like when good database table rows are bludgeoned to death by ill-mannered users. This behavior helps us solve the case.
Question
9 Can you use UUID?
I'm new to Cake and this is my first app using Cake. This is one part of my app that I have been trying to figure out over the last couple of weeks. If this works I will be in hog heaven! My question is that I believe you said that the created_by field has to be int. Is that correct? I'm using cakes UUID and wondering will I still be able to use the behavior.
Bug
10 Problem with whodidit and joiner tables
Thanks for sharing this useful behaviour. I'm having trouble using it. If anyone could help, I'd appreciate it. I have a hABTM relationship using a joiner table, and for some reason, whodidit tries to look for a created_by column in that table, even though it doesn't have its own model. It's trying a left join with this non-existant column when CakePHP tries to delete from the joiner table, causing the deletes to fail, giving me duplicate rows.
Is there a way around this? Is it a bug in whodidit, or am I just using it incorrectly somehow?
Thank you for your time,
Zoe.
Comment
11 Re: Whodunit
Cheers for the suggestion Mike! I'm not a native English speaker.... therefore I didn't have probably the best naming idea!
Comment
12 Re: Can you use UUID?
I didn't try to use UUID, but I guess it should work! Let me know!
Dan
Comment
13 Re: Problem with whodidit and joiner tables
Hey Zoe,
which version of CakePHP are you using?
Can you post your model relationship?
Do you use custom queries?
Dan
Comment
14 Re: Problem with whodidit and joiner tables
Thanks for the reply! I'm using CakePHP version 1.2.3.8166.
I'm having this problem on pages which involve has-and-belongs-to-many relationships. For example, each product hABTM categories. In the back office, I have the following in the products' form so it draws a tickbox for each category:
echo $form->input('Category.Category', array(
'multiple' => 'checkbox'
));
Using CakePHP's standard save() method on Product, it tries to delete and insert any rows into the joiner table as necessary. The products and categories both act as WhoDidIt. CakePHP seems to be trying to link in the users table to the actual joiner table itself, namely categories_products. It tries to perform a left join using a non-existent created_by column while performing a delete:
DELETE `CategoriesProduct` FROM `categories_products` AS `CategoriesProduct` LEFT JOIN `users` AS `CreatedBy` ON (`CategoriesProduct`.`created_by` = `CreatedBy`.`id`) LEFT JOIN `users` AS `ModifiedBy` ON (`CategoriesProduct`.`modified_by` = `ModifiedBy`.`id`) WHERE `CategoriesProduct`.`product_id` = 56 AND `CategoriesProduct`.`category_id` IN (30, 31, 30)
This gives an error, so the delete isn't executed, resulting in duplicate rows in the joiner table.
Thank you for your help,
Zoe.
Comment
15 Bug not WhoDidIt's fault
My apologies. I took out WhoDidIt from my app and wrote similar code in app_model's beforeSave, and experienced the exact same problem still. It turns out the culprit was that I had the belongsTo in app_model. This still shouldn't have affected the joiner table, but whatever's to blame, it's clearly not WhoDidIt, which seems to be working just fine.
Thanks again for your help!
Zoe.
Comment
16 Awesome!
A note to beginners: Remember that the file name convention is underscored lowercase. That is, the file needs to be named who_did_it.php and be placed in the /app/models/behaviors folder.
Thanks!
H