CakePHP counterCache associated table field won't update on delete() - cakephp

I have the following tables: causes, users, transactions
Cause hasMany Transaction and User hasMany Transaction
Transaction belongsTo Cause and Transaction belongsTo User
Both my causes and users tables have transaction_count fields and my belongsTo has counterCache => true on both associations.
Both of my hasMany clauses have dependent => true and my foreignKeys are set up correctly in that if I delete a Cause, all of it's associated Transactions are deleted as well.
It is also working, in that when I create a Transaction, the transaction_count field in both my users and causes tables updates correctly. Here is the breakdown:
1.) Create a cause
2.) Create a transaction
RESULT: causes[ 15 ][ 'transaction_count' ] increases by 1 correctly
RESULT: users[ 1 ][ 'transaction_count' ] increases by 1 correctly
3.) Manually delete a transaction ( $transaction->delete( $id ) )
RESULT: causes[ 15 ][ 'transaction_count' ] decreases by 1 correctly
RESULT: users[ 1 ][ 'transaction_count' ] decreases by 1 correctly
4.) FAILURE:
If I create the Cause and Transaction and then delete the Cause using $this->Cause->delete( $id ), even though all of the transactions are deleted in the database (along with the Cause), the transaction_count in my users table is not updated to reflect the decrease.
I imagine this is because $this->Transaction->delete() is not getting called explicitly, but it is instead getting deleted due to its association with Cause?
Is there a workaround for this?

Tables
CREATE TABLE `causes` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`transaction_count` int(10) unsigned NOT NULL DEFAULT '0'
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `transactions` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`cause_id` int(10) unsigned NOT NULL DEFAULT '0',
`user_id` int(10) unsigned NOT NULL DEFAULT '0',
`name` varchar(255) NOT NULL
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `users` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`transaction_count` int(10) unsigned NOT NULL DEFAULT '0'
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
User.php
App::uses('AppModel', 'Model');
class User extends AppModel {
public $hasMany = array(
'Transaction' => array(
'dependent' => true
)
);
}
Cause.php
App::uses('AppModel', 'Model');
class Cause extends AppModel {
public $hasMany = array(
'Transaction' => array(
'dependent' => true
)
);
}
Transaction.php
App::uses('AppModel', 'Model');
class Transaction extends AppModel {
public $belongsTo = array(
'User' => array(
'counterCache' => true
),
'Cause' => array(
'counterCache' => true
)
);
}
Controller
$this->Cause->query('TRUNCATE TABLE users');
$user['User'] = array(
'name' => 'aaa'
);
$this->Cause->Transaction->User->create($user);
$this->Cause->Transaction->User->save(null, false);
$userId = $this->Cause->Transaction->User->getLastInsertID();
$this->Cause->query('TRUNCATE TABLE causes');
$cause['Cause'] = array(
'name' => 'aaa'
);
$this->Cause->create($cause);
$this->Cause->save(null, false);
$causeId = $this->Cause->getLastInsertID();
$this->Cause->query('TRUNCATE TABLE transactions');
$transaction['Transaction'] = array(
'name' => 'aaa',
'user_id' => $userId,
'cause_id' => $causeId
);
$this->Cause->Transaction->create($transaction);
$this->Cause->Transaction->save(null, false);
$this->Cause->Transaction->create($transaction);
$this->Cause->Transaction->save(null, false);
$transaction['Transaction'] = array(
'name' => 'aaa',
'user_id' => $userId,
'cause_id' => 99
);
$this->Cause->Transaction->create($transaction);
$this->Cause->Transaction->save(null, false);
$this->Cause->delete($causeId);
$cause = $this->Cause->find('first', array(
'conditions' => array(
'Cause.id' => $causeId
)
));
$transactions = $this->Cause->Transaction->find('all', array(
'conditions' => array(
'Transaction.cause_id' => $causeId
)
));
$user = $this->Cause->Transaction->User->find('first', array(
'conditions' => array(
'User.id' => $userId
)
));
pr($cause);
pr($transactions);
pr($user);
if (is_a($this->Cause, 'Cause')) {
echo 'Cause';
}
if (is_a($this->Cause->Transaction, 'Transaction')) {
echo 'Transaction';
}
if (is_a($this->Cause->Transaction->User, 'User')) {
echo 'User';
}
Result
Array
(
)
Array
(
)
Array
(
[User] => Array
(
[id] => 1
[name] => aaa
[transaction_count] => 1
)
)
CauseTransactionUser
It works ok.
Double check for typos in models and check if cake is using your models and not the ones that are created on the fly if some model is missing.

Related

MySQL Error with straightforward polymorphic association in CakePHP

I have 4 models, Application , Accommodation, Invoice and InvoiceItem
Invoice, Application and Accommodation all hasMany InvoiceItem
InvoiceItem belongsTo Application, Accommodation and Invoice
The purpose of all this is so that the line items of an Invoice (which come from the InvoiceItem model) can be associated with either an Accommodation or an Application via the InvoiceItem.foreign_id depending on whether InvoiceItem.class is set as Accommodation or Application
From what I understand, this is known as a polymorphic association.
Unfortunately, when I do a $this->Invoice->find('all'); with recursive set to 3 (or set to -1 with the appropriate fields specified in containable) I get this MySQL error:
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column
'InvoiceItem.class' in 'where clause'
SQL Query: SELECT `Application`.`id`, `Application`.`name`,
`Application`.`created`, `Application`.`updated` FROM `applications`
AS `Application` WHERE `Application`.`id` = 3786 AND
`InvoiceItem`.`class` = 'Application'
I don't understand why Cake would try to add InvoiceItem.class as a condition without creating a join for the InvoiceItem model.
Here are my models (note: I've cut down the number of fields in each for readability -- the Application and Accommodation models share very few similar fields in reality):
Accommodation Model
CREATE TABLE `accommodations` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`created` datetime NOT NULL,
`updated` datetime NOT NULL,
PRIMARY KEY (`id`)
)
<?php
class Accommodation extends AppModel {
var $name = 'Accommodation';
var $hasMany = array(
'InvoiceItem' => array(
'className' => 'InvoiceItem',
'foreignKey' => 'foreign_id',
'conditions' => array('InvoiceItem.class' => 'Accommodation'),
)
);
}
?>
Application Model
CREATE TABLE `applications` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`created` datetime NOT NULL,
`updated` datetime NOT NULL,
PRIMARY KEY (`id`)
)
<?php
class Application extends AppModel {
var $name = 'Application';
var $hasMany = array(
'InvoiceItem' => array(
'className' => 'InvoiceItem',
'foreignKey' => 'foreign_id',
'conditions' => array('InvoiceItem.class' => 'Application'),
)
);
}
?>
InvoiceItem model
CREATE TABLE IF NOT EXISTS `invoice_items` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`class` varchar(30) COLLATE utf8_unicode_ci NOT NULL,
`foreign_id` int(10) unsigned NOT NULL,
`name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
)
<?php
class InvoiceItem extends AppModel {
var $name = 'InvoiceItem';
var $belongsTo = array(
'Accommodation' => array(
'foreignKey' => 'foreign_id',
'conditions' => array('InvoiceItem.class' => 'Accommodation')
),
'Application' => array(
'foreignKey' => 'foreign_id',
'conditions' => array('InvoiceItem.class' => 'Application')
),
'Invoice' => array(
'className' => 'Invoice',
'foreignKey' => 'invoice_id',
),
);
}
?>
Invoice model
CREATE TABLE IF NOT EXISTS `invoices` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
)
<?php
class Invoice extends AppModel {
var $name = 'Invoice';
var $hasMany = array(
'InvoiceItem' => array(
'className' => 'InvoiceItem'
'foreignKey' => 'invoice_id'
)
);
}
?>
I'm using CakePHP 2.4.0
class InvoiceItem extends AppModel,and bind no belongsTo.
class InvoiceItem extends AppModel {
var $name = 'InvoiceItem';
var $belongsTo = null
}
you can assign a suitable belongsTo to InvoiceItem at Runtime depend on content
$this->InvoiceItem->bindModel(array( 'belongsTo' => array(...) ) )

CakePHP model linking, belongsTo, hasOne

I read the CakePHP book but couldn't make it.
I have a table named frendslists. Every user (owner_user_id) has too many friends and I add the friends to friend_id column. (Model name is Friendslist)
CREATE TABLE IF NOT EXISTS `friendslists` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`owner_user_id` int(20) unsigned NOT NULL,
`friend_id` int(20) NOT NULL COMMENT 'id of the friend',
PRIMARY KEY (`id`),
KEY `friend_id` (`friend_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
--id-------owner_user_id-----friend_id
--------------------------------------
--1--------1234--------------9200-----
--2--------1234--------------3210-----
--3--------1234--------------7600-----
I also have a profiles table. Every unique person have a profile there. One person can have only one profile. (Model name is Profile)
CREATE TABLE IF NOT EXISTS `profiles` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`profile_user_id` int(20) NOT NULL,
`name` varchar(50) NOT NULL,
`location` varchar(50) NOT NULL,
PRIMARY KEY (`id`),
KEY `profile_user_id` (`profile_user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
--id------profile_user_id---name------location-
-----------------------------------------------
--1-------9200--------------michael----usa-----
--2-------3210--------------john-------uk------
--3-------7600--------------danny------denmark-
I want to link friendslists table to profiles table. Is this one to one (hasOne) or many to one (belongsTo) type relationship ?
When I query friendslists table I want to get profiles data of the friends. What should I do inside CakePHP models and tables?
I created a foreign key like this:
ALTER TABLE `friendslists`
ADD CONSTRAINT `friendslists_ibfk_1`
FOREIGN KEY (`friend_id`)
REFERENCES `profiles` (`profile_user_id`)
ON DELETE CASCADE ON UPDATE CASCADE;
I changed model file to this:
class Friendslist extends AppModel {
var $name = 'Friendslist';
var $useTable = 'friendslists';
public $belongsTo = array(
'Profile' => array(
'className' => 'Profile',
'foreignKey' => 'friend_id'
)
)
function getAll(){
return $this->find('all');
}
}
At last when I do this:
$records=$this->Friendslist->find('all', array('conditions' => array(
'Friendslist.owner_user_id' => 1234)
));
I get these result:
[Friendslist] => Array
(
[id] => 1
[owner_user_id] => 1234
[friend_id] => 9200
)
[Profile] => Array
(
[id] =>
[profile_user_id] =>
[name] =>
[location] =>
)
)
I'm sure that profiles table has a record with profile_user_id=9200. But Profile records comes empty.
I'm a little confused why you're linking friendslists to profiles. Wouldn't it make more sense to link people with people and then get the profile therefrom?
In any case, what you're describing is a HasAndBelongsToMany(HABTM)
So in your Person model you want to specify that they have many other people (or in your case Profiles) that they're associated with and specify the lookup table.
Something like...
public $hasAndBelongsToMany = array(
'Person' => array(
'className' => 'Profile',
'joinTable' => 'friendslists',
'foreignKey' => 'owner_id',
'associationForeignKey' => 'friend_id'
)
);
And then within the friendslists model I'd describe it as a belongsTo listing both the owner and the profile.
Something like:
public $belongsTo = array(
'Person' => array(
'className' => 'Person'
,'foreignKey' => 'owner_user_id'
),
'Profile' => array(
'className' => 'Profile'
,'foreignKey' => 'friend_id'
)
);
You may need to tweak the names as I had a hard time digesting exactly what entities are at play, but that should give you an idea at least.

saveAll() Failing with CakePHP 2.3.1

I am building a CakePHP 2.3 application, and can't for the life of me figure out why saveAll keeps failing.
I have the following models:
Artist hasMany ArtistPreview
ArtistPreview belongsTo Artist
I have no problem saving my Artist model with the save() function, however as soon as I try to use the saveAll method, Cake seems to fail validation - which is crazy because I have even totally removed all my validation rules from my models.
Here's my view, nothing crazy:
<?php
echo $this->Form->create('Artist', array('inputDefaults' => array('class' => 'input-block-level')));
echo $this->Form->input('Artist.name', array('label' => __('Name')));
echo $this->Form->input('Artist.artist_section_id', array('label' => __('Section'), 'empty' => true));
?>
<label>Tags</label>
<div class="checkboxes">
<?php echo $this->Form->input('Tag', array('label' => false, 'multiple' => 'checkbox', 'class' => false));; ?>
</div>
<?php
echo $this->Form->input('Artist.website', array('label' => __('Website')));
echo $this->Form->input('ArtistPreview.0.url', array('label' => __('Artist Preview')));
echo $this->Form->input('Artist.description', array('label' => __('Description')));
?>
<?php
echo $this->Form->submit(__('Add'), array('class' => 'btn btn-primary btn-large', 'div' => false)) . ' ';
echo $this->Form->button(__('Cancel'), array('type' => 'button', 'class' => 'btn btn-large', 'div' => false, 'onclick' => 'closeDialog()'));
echo $this->Form->end();
?>
And here's my add controller method:
public function add() {
$this->layout = 'form';
if (!$this->request->is('ajax')) {
throw new ForbiddenException();
}
if ($this->request->is('post')) {
$this->setData($this->request->data['Artist'], array(
'user_id' => AuthComponent::user('id'),
'date' => $this->Date->gmt()
));
if ($this->Artist->saveAll($this->request->data)) {
$this->redirectAjax('/artists/view/' . $this->Artist->id);
}
}
$this->set(array(
'title_for_layout' => 'Add Artist',
'artistSections' => $this->Artist->ArtistSection->find('list', array('order' => 'name')),
'tags' => $this->Artist->Tag->find('list', array('order' => 'name'))
));
}
As I mentioned above, the saveAll method fails everytime. I have inspected what is getting output and Cake is outputting a bunch of blank <div>s with the error-message class, but there is no message. Every field on the form gets one of these divs.
Now, when I change my code to use the save function instead of saveAll, everything gets saved properly except the ArtistPreview data, which is to be expected. Even my HABTM Tags are being saved properly, and nothing fails validation.
I have been using Cake since the 1.3 days so I'm quite familiar with it, and have even done exactly what I'm asking in previous projects. I'm leaning towards this being a bug, unless I'm missing something here. Any ideas?
EDIT
I have also tried the saveAssociated method, along with setting the deep key to true when trying to save. This will let me save the artist, but it will not save the related data (ArtistPreview).
EDIT 2
As requested, here are the two tables:
-- -----------------------------------------------------
-- Table `artists`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `artists` ;
CREATE TABLE IF NOT EXISTS `artists` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT ,
`user_id` INT UNSIGNED NULL ,
`artist_section_id` INT UNSIGNED NULL ,
`artist_status_id` INT UNSIGNED NULL ,
`name` VARCHAR(200) NULL ,
`website` VARCHAR(500) NULL ,
`description` TEXT NULL ,
`date` DATETIME NULL ,
`comment_count` INT UNSIGNED NOT NULL DEFAULT 0 ,
PRIMARY KEY (`id`) ,
INDEX `FK_artists_users_idx` (`user_id` ASC) ,
INDEX `FK_artists__artist_sections_idx` (`artist_section_id` ASC) ,
INDEX `FK_artists__artist_statuses_idx` (`artist_status_id` ASC) ,
CONSTRAINT `FK_artists__users`
FOREIGN KEY (`user_id` )
REFERENCES `users` (`id` )
ON DELETE SET NULL
ON UPDATE CASCADE,
CONSTRAINT `FK_artists__artist_sections`
FOREIGN KEY (`artist_section_id` )
REFERENCES `artist_sections` (`id` )
ON DELETE SET NULL
ON UPDATE CASCADE,
CONSTRAINT `FK_artists__artist_statuses`
FOREIGN KEY (`artist_status_id` )
REFERENCES `artist_statuses` (`id` )
ON DELETE SET NULL
ON UPDATE CASCADE)
ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `artist_previews`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `artist_previews` ;
CREATE TABLE IF NOT EXISTS `artist_previews` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT ,
`artist_id` INT UNSIGNED NULL ,
`url` VARCHAR(500) NULL ,
`date` DATETIME NULL ,
PRIMARY KEY (`id`) ,
INDEX `FK_artist_previews__artists_idx` (`artist_id` ASC) ,
CONSTRAINT `FK_artist_previews__artists`
FOREIGN KEY (`artist_id` )
REFERENCES `artists` (`id` )
ON DELETE CASCADE
ON UPDATE CASCADE)
ENGINE = InnoDB;
Here is my setData function, which just merges two arrays. Instead of using hidden fields, I set fields in the controller which aren't populated by user input:
public function setData(&$original, $new) {
$original = array_merge($original, $new);
}
So I finally figured this out, and wow was it a rookie mistake. I think I must've been half asleep yesterday when I was coding. The problem was in my model:
class Artist extends AppModel {
public $belongsTo = array(
'User',
'ArtistSection',
'ArtistStatus'
);
public $hasMany = array(
'ArtistPicture',
'Artist' // <--- problem was here, had 'Artist' instead of 'ArtistPreview'
);
public $hasAndBelongsToMany = array(
'Tag'
);
public function __construct($id = false, $table = null, $ds = null) {
parent::__construct($id, $table, $ds);
$this->validate = array(
'name' => array(
'rule' => 'notEmpty',
'message' => __('Artist name cannot be blank.')
),
'website' => array(
'rule' => 'url',
'message' => __('Must be a valid URL.'),
'allowEmpty' => true
),
'artist_section_id' => array(
'rule' => 'notEmpty',
'message' => __('Section is required.')
)
);
}
}
Thank you to those of you who took the time to look at this question, even though I posted the wrong information.

Polymorphic associations in CakePHP2

I have 3 models, Page , Course and Content
Page and Course contain meta data and Content contains HTML content.
Page and Course both hasMany Content
Content belongsTo Page and Course
To avoid having page_id and course_id fields in Content (because I want this to scale to more than just 2 models) I am looking at using Polymorphic Associations. I started by using the Polymorphic Behavior in the Bakery but it is generating waaay too many SQL queries for my liking and it's also throwing an "Illegal Offset" error which I don't know how to fix (it was written in 2008 and nobody seems to have referred to it recently so perhaps the error is due to it not having been designed for Cake 2?)
Anyway, I've found that I can almost do everything I need by hardcoding the associations in the models as such:
Page Model
CREATE TABLE `pages` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`slug` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`created` datetime NOT NULL,
`updated` datetime NOT NULL,
PRIMARY KEY (`id`)
)
<?php
class Page extends AppModel {
var $name = 'Page';
var $hasMany = array(
'Content' => array(
'className' => 'Content',
'foreignKey' => 'foreign_id',
'conditions' => array('Content.class' => 'Page'),
)
);
}
?>
Course Model
CREATE TABLE `courses` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`slug` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`created` datetime NOT NULL,
`updated` datetime NOT NULL,
PRIMARY KEY (`id`)
)
<?php
class Course extends AppModel {
var $name = 'Course';
var $hasMany = array(
'Content' => array(
'className' => 'Content',
'foreignKey' => 'foreign_id',
'conditions' => array('Content.class' => 'Course'),
)
);
}
?>
Content model
CREATE TABLE IF NOT EXISTS `contents` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`class` varchar(30) COLLATE utf8_unicode_ci NOT NULL,
`foreign_id` int(11) unsigned NOT NULL,
`title` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`content` text COLLATE utf8_unicode_ci NOT NULL,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
)
<?php
class Content extends AppModel {
var $name = 'Content';
var $belongsTo = array(
'Page' => array(
'foreignKey' => 'foreign_id',
'conditions' => array('Content.class' => 'Page')
),
'Course' => array(
'foreignKey' => 'foreign_id',
'conditions' => array('Content.class' => 'Course')
)
);
}
?>
The good thing is that $this->Content->find('first') only generates a single SQL query instead of 3 (as was the case with the Polymorphic Behavior) but the problem is that the dataset returned includes both of the belongsTo models, whereas it should only really return the one that exists. Here's how the returned data looks:
array(
'Content' => array(
'id' => '1',
'class' => 'Course',
'foreign_id' => '1',
'title' => 'something about this course',
'content' => 'The content here',
'created' => null,
'modified' => null
),
'Page' => array(
'id' => null,
'title' => null,
'slug' => null,
'created' => null,
'updated' => null
),
'Course' => array(
'id' => '1',
'title' => 'Course name',
'slug' => 'name-of-the-course',
'created' => '2012-10-11 00:00:00',
'updated' => '2012-10-11 00:00:00'
)
)
I only want it to return one of either Page or Course depending on which one is specified in Content.class
UPDATE: Combining the Page and Course models would seem like the obvious solution to this problem but the schemas I have shown above are just shown for the purpose of this question. The actual schemas are actually very different in terms of their fields and the each have a different number of associations with other models too.
UPDATE 2
Here is the query that results from running $this->Content->find('first'); :
SELECT `Content`.`id`, `Content`.`class`, `Content`.`foreign_id`, `Content`.`title`,
`Content`.`slug`, `Content`.`content`, `Content`.`created`, `Content`.`modified`,
`Page`.`id`, `Page`.`title`, `Page`.`slug`, `Page`.`created`, `Page`.`updated`,
`Course`.`id`, `Course`.`title`, `Course`.`slug`, `Course`.`created`,
`Course`.`updated` FROM `cakedb`.`contents` AS `Content`
LEFT JOIN `cakedb`.`pages` AS `Page` ON
(`Content`.`foreign_id` = `Page`.`id` AND `Content`.`class` = 'Page')
LEFT JOIN `cakedb`.`courses` AS `Course` ON (`Content`.`foreign_id` = `Course`.`id`
AND `Content`.`class` = 'Course') WHERE 1 = 1 LIMIT 1
Your query is okay. Just filter empty values after find:
public function afterFind($results, $primary = false) {
return Set::filter($results, true);
}
Take a look at this question.
You can also try to provide conditions to you find query that Page.id in not null or Course.id is not null.

Selecting the newest record of a hasMany relationship

I have 2 basic models in my CakePHP application: User and Login. A user has a hasMany relation with Logins (i.e., a new login record is created everytime the user logs in).
Now, I want to make 2 relations from the User model to the Login model:
User hasMany Login
and
User hasOne lastLogin
This last relation should only include the last record of the Login model for the selected user.
I tried this as follows:
var $belongsTo = array
(
'LastLogin' => array
(
'className' => 'Login',
'order' => 'LastLogin.created DESC',
'limit' => 1
)
);
However, this doesn't work. Any ideas on how to get this working?
UPDATED ANSWER IN RESPONSE TO COMMENTS
With a belongsTo relationship, the foreign key should be in the current model.
This means that if you want to have a relationship where User belongsTo LastLogin, the users table should have a last_login_id field.
In your case you probably want to use a hasOne relationship instead, and you're going to have to use the MAX() SQL function in the fields key. Note that getting the last_login works completely independently of your User hasMany Login relationship. So if all you want is the last login you can remove the hasMany relationship and just leave the hasOne.
With the example code below you'll get this:
Output of /users/index:
Array
(
[User] => Array
(
[id] => 1
[name] => user1
[last_login] => 2011-05-01 14:00:00
)
[Login] => Array
(
[0] => Array
(
[id] => 1
[user_id] => 1
[created] => 2011-05-01 12:00:00
)
[1] => Array
(
[id] => 2
[user_id] => 1
[created] => 2011-05-01 13:00:00
)
[2] => Array
(
[id] => 3
[user_id] => 1
[created] => 2011-05-01 14:00:00
)
)
)
If you don't use the Model::afterFind() callback your results will look more like this (Login array snipped to save space):
Array
(
[User] => Array
(
[id] => 1
[name] => user1
)
[0] => Array
(
[last_login] => 2011-05-01 14:00:00
)
)
Example code:
users table:
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
)
logins table:
CREATE TABLE `logins` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`created` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
)
User model:
class User extends AppModel {
var $name = 'User';
var $hasMany = array('Login');
var $hasOne = array(
'LastLogin' => array(
'className' => 'Login',
'fields' => array('MAX(LastLogin.created) as last_login')
)
);
// This takes the last_login field from the [0] keyed array and puts it into
// [User]. You could also put this into your AppModel and it would work for
// all find operations where you use an SQL function in the 'fields' key.
function afterFind($results, $primary=false) {
if (!empty($results)) {
foreach ($results as $i => $result) {
if (!empty($result[0])) { // If the [0] key exists in a result...
foreach ($result[0] as $key => $value) { // ...cycle through all its fields...
$results[$i][$this->alias][$key] = $value; // ...move them to the main result...
}
unset($results[$i][0]); // ...and finally remove the [0] array
}
}
}
return parent::afterFind($results, $primary=false); // Don't forget to call the parent::afterFind()
}
}
Users controller:
class UsersController extends AppController {
var $name = 'Users';
function index() {
$this->autoRender = false;
pr($this->User->find('all'));
}
}
I had a similar situation but mine was
Product hasMany Price
I wanted a Product hasOne CurrentPrice with the CurrentPrice defined as the top most record found if sorted by created in desc order.
I solved mine this way. But I am going to use your User and LastLogin instead.
class User extends AppModel {
var $name = 'User';
var $hasMany = array('Login');
var $hasOne = array(
'LastLogin' => array(
'className' => 'Login',
'order' => 'LastLogin.created DESC'
)
);
If you think about it, created and id has the same meaning. so you could also use id in descending order.
I am assuming that your table schema is similar to the one suggested by mtnorthrop.
i.e.
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
)
logins table:
CREATE TABLE `logins` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`created` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
)

Resources