I'm building a CakePHP powered website, where users can refer each other, i was thinking about a "Has and Belongs to many" relationship (A user can have multiple referrals, but can be referred only by one)
is this the right way or it's better with another association / road ?
Thanks in advance!
If I'm correct in assuming that a User can refer many, but may only be referred by one, then something like this should suffice for what you're doing:
//User model
$belongsTo = array(
'Referrer' => array(
'className' => 'User',
'foreignKey' = > 'referrer_id'
)
);
$hasMany = array(
'Referree' => array(
'className' => 'User',
'foreignKey' => 'referrer_id'
)
);
Details about Multiple relations to the same model.
An example of how to retrieve the data:
//User model
public $actsAs = array('Containable');
public $recursive = -1; //better to set in AppModel IMO
public function getUser($userId = null) {
if(empty($userId)) return false;
return $this->find('first', array(
'conditions' => array(
$this->alias . '.' . $this->primaryKey => $userId
),
'contain' => array(
'Referrer',
'Referee'
)
));
}
Related
I have HABTM associations between two models but can only get find to return one level. I can return several levels with other associations but think I must be missing something with HABTM.
Controller/SchedulesController.php
$this->Schedule->find('first', array(
'contain' => array(
'Association' => array(
'Schedule'
)
)
));
Model/Schedule.php
public $actsAs = array('Containable');
public $hasAndBelongToMany = array(
'Association'
);
Model/Association.php
public $actsAs = array('Containable');
public $hasAndBelongsToMany = array(
'Schedule'
);
At the moment I only get...
array(
'Schedule' => array(
...
),
'Association' => array(
(int) 0 => array(
...
'AssociationsSchedule' => array(
...
)
)
)
...but I would like Schedule -> Association -> Schedule
While contain() should work, the other option is to use the recursive option before the find like this:
$this->Schedule->recursive = 3; //2 might work, but I think you need 3 levels
$this->Schedule->find('first');
Its also worth mentioning that a similar question was asked here.
I have question in cakephp model,
I want to add dynamic condition in var $hasMany keyword
I want to add condition like current user_id, i got user Id after my login.
var $hasMany = array(
"AskComment"=>array('limit'=>3),
'AskStatistic',
'AskContactsLink',
'AskStatistic',
'AskObject',
'AskLikes'
);
If you want to add dynamic condition in your model, then you might have to bind the model association-ship dynamically into your controller's code. Write the following code into your controller's method for which you want to impose some new condition on the existing/new associated models.
$this->PrimaryModel->bindModel(array('hasMany' => array(
'AskComment' => array(
'className' => 'AskComment',
'foreignKey' => 'primary_id',
'conditions' => array('AskComment.user_id' => $user_id)
)
)
));
Take a look at this link: Creating and destroying associations on the fly. This will surely help you to achieve the same.
I think its better to put your association in the construct function of your Model.
like this:
/**
* #see Model::__construct
*/
public function __construct($id = false, $table = null, $ds = null) {
public $hasMany = array(
'AskComment' => array(
'className' => 'AskComment',
'foreignKey' => 'primary_id',
'conditions' => array(
'AskComment.user_id' => $user_id,
),
),
);
}
I am trying to do a search, using pagination for posts which have a specific tag or tags (for example, if a user was to select two tags, then posts containing either tag would be returned).
I have the relationship defined in my Posts table
public $hasAndBelongsToMany = array('Tags' => array(
'className' => 'Tags',
'joinTable' => 'posts_tags',
'foreignKey' => 'post_id',
'associationForeignKey' => 'tag_id',
'unique' => 'keepExisting'));
How do I use Find to retrieve rows with a given tag (name or ID would be fine)
Trying:
// other pagination settings goes here
$this->paginate['conditions']['Tags.id'] = 13;
gives me an error that the relationship does not exist.
Looking at the debug info it appears that the tables are not joining the Posts_Tags and Tags table, however, when I debug the data making it to the view, the Posts objects contain the tags data.
Most of the documentation I can find for this seems to revolve around earlier versions of CakePHP, any help would be appreciated.
Could not find a satisfying solution myself.
I created a behavior to take care of this.
Create a file called HabtmBehavior.php and put it in your app/Model/Behavior folder.
Put the block of code in there and save file.
Add the behavior to your model: eg public $actsAs = array('Habtm');
Here is a usage example with find.
<?php $this->Entry->find('all', array('habtm'=>array('Tag'=>array('Tag.title'=>'value to find'))) ?>
Paginate would look something like this:
$this->paginate['Entry']['habtm']['Tag'] = array('Tag.title'=>'value to find');
You are free to add as many relations as you want by adding additional Model Names in the habtm array.
(Just be careful not to make it to complex since this could start slowing down your find results.)
<?php
class HabtmBehavior extends ModelBehavior {
public function beforeFind(Model $model, $options) {
if (!isset($options['joins'])) {
$options['joins'] = array();
}
if (!isset($options['habtm'])) {
return $options;
}
$habtm = $options['habtm'];
unset($options['habtm']);
foreach($habtm as $m => $scope){
$assoc = $model->hasAndBelongsToMany[$m];
$bind = "{$assoc['with']}.{$assoc['foreignKey']} = {$model->alias}.{$model->primaryKey}";
$options['joins'][] = array(
'table' => $assoc['joinTable'],
'alias' => $assoc['with'],
'type' => 'inner',
'foreignKey' => false,
'conditions'=> array($bind)
);
$bind = $m.'.'.$model->{$m}->primaryKey.' = ';
$bind .= "{$assoc['with']}.{$assoc['associationForeignKey']}";
$options['joins'][] = array(
'table' => $model->{$m}->table,
'alias' => $m,
'type' => 'inner',
'foreignKey' => false,
'conditions'=> array($bind) + (array)$scope,
);
}
return $options;
}
}
Hope this helps.
Happy baking.
I think the best solution is apply find function on join table Model. I try this before and it's work fine.
in your PostTag model :
/**
* #see Model::$actsAs
*/
public $actsAs = array(
'Containable',
);
/**
* #see Model::$belongsTo
*/
public $belongsTo = array(
'Post' => array(
'className' => 'Post',
'foreignKey' => 'post_id',
),
'Tags' => array(
'className' => 'Tag',
'foreignKey' => 'tag_id',
),
);
in your controller :
// $tagsId = tags ids
$posts = $this->PostTag->find('all', array('conditions' => array('PostTag.tag_id' => $tagsId),'contain' => array('Post')));
also is better follow cake naming convention, if you have tags(plural), post_tags(first singular second plural),posts(plural) tables you must have Tag,PostTag,Post Models.
I know the solutions is simple and might have something to do with the Containable behavior but I can't get it working. Without all the tries
This is the case. I'd like to display the Event details of an Event (eg. a conference). Each event takes place in a EventVenue and each EventVenue is located in a Country.
So in the Country Model the following is present:
public $hasMany = array(
'EventVenue' => array(
'className' => 'EventVenue',
'foreignKey' => 'country_id'
))
In the EventVenue model a BelongsTo association is made
public $belongsTo = array(
'Country' => array(
'className' => 'Country',
'foreignKey' => 'country_id'
))
And in the Event model a hasOne association is made
public $hasOne = array(
'EventVenue' => array(
'className' => 'EventVenue',
'foreignKey' => 'event_id',
))
What I want is to display the country name on the page that is renderd in the EventsController. I do get all the Event and EventVenue data but the associated Country for the venue is not retrieved.
The data is retrieved in the following way
$item = $this->Event->findBySlug($slug);
How can I also get the country name (eg. Netherlands) retrieved from the database? I tried something like this but that did not work:
$item = $this->Event->findBySlug($slug,array(
'contain' => array(
'Event' => array(
'EventVenue' => array(
'Country'
)
)
)
)
Try this:
$item = $this->Event->findBySlug($slug,array(
'contain' => array(
'EventVenue' => array(
'Country'
)
)
);
Update
Turns out findBy does not support Containable. You could use this to get the desired result:
$item = $this->Event->find('first',array(
'conditions' => array(
'Event.slug' => $slug
),
'contain' => array(
'EventVenue' => array(
'Country'
)
)
);
Oh and make sure you have this in the model: public $actsAs = array('Containable');
try this method:
$this->Event->recusive = 2;
$item = $this->Event->findBySlug($slug);
You need to set the recursive to 2 before you make the find, something like this
$this->Event->recursive = 2;
with this, you'll get the Event, the EventVenue and the Country on one shot
Hope it helps
For some reason i'm really having a hard time wrapping my head around HABTM associations. I learn best by watching someone do something and explaining why. Anyways, I have 2 tables I want associated, Drugs, and SideEffects. I've created the intermediate table drugs_side_effects(has no data right now). Does cake put the data in that automatically or do I need to do something? The 3.7.6.5 hasAndBelongsToMany (HABTM) from the book didn't specify.
I've set up the models correctly(I think) and am not sure how to proceed at this point. It seems pretty simple. I need to display side_effect from the SideEffects table in a Drug view. I think in the edit_french controller function i'll need something like
$side_effect = $this->Drug->SideEffect->read(
array('SideEffect.id','SideEffect.side_effect'), $id);
$this->set('side_effect',$side_effect);
but I feel like that won't work as expected. Or maybe there's a more efficient way? Any advice or help is appreciated.
Drug Model:
var $hasAndBelongsToMany = array(
'SideEffect' => array(
'className' => 'SideEffect',
'joinTable' => 'drug_side_effects',
'foreignKey' => 'drug_id',
'associationForeignKey' => 'side_effect_id'
)
);
}
?>
SideEffect Model:
var $hasAndBelongsToMany = array(
'Drug' => array(
'className' => 'Drug',
'joinTable' => 'drug_side_effects',
'foreignKey' => 'side_effect_id',
'associationForeignKey' => 'drug_id'
)
);
}
?>
Drugs Controller:
<?php
class DrugsController extends AppController {
var $name = 'Drugs';
var $helpers = array('Html','Form','Paginator');
var $paginate = array(
//'contain' => array('SideEffect'),
//'fields' => array('Drug.id', 'Drug.generic'),
'fields' => array('Drug.id', 'Drug.generic','Drug.date_altered'),
'limit' => 50,
'order' => array(
'Drug.generic' => 'asc'
)
);
function index() {
$data = $this->paginate('Drug');
$alldrugs = $this->set('drugs', $this->Drug->find('all'));
$this->set('drugs', $data);
$this->set('alldrugs', $data);
//$this->set('lessdrugs', $this->paginate());
$this->set('title_for_layout','List of all current drugs');
}
function edit_french($id = null) {
$this->Drug->id = $id;
$drug = $this->Drug->read(
array(
'Drug.id','Drug.generic','Drug.ahl','Drug.aap','Drug.rid','Drug.oral','Drug.mw','Drug.clinical_recommendations'
),
$id
);
$this->set('title_for_layout', 'Translate clinical recommendations - ' . $drug['Drug']['generic']);
$this->set('drug',$drug);
if (empty($this->data)) {
$this->data = $this->Drug->read();
} else {
if ($this->Drug->save($this->data)) {
$this->Session->setFlash('The drug has been updated.');
$this->redirect(array('action' => 'index'));
}
}
}
}
?>
You can pull the related data using ContainableBehavior. To do so, simply run a find on the Drug model and tell it to contain the associated SideEffect data.
$drug = $this->Drug->find('first', array(
'conditions' => array(
'Drug.id' => $id
),
'contain' => array(
'SideEffect'
)
));
You can also set the contain before using read() if you prefer.
$this->Drug->contain(array('SideEffect'));
$drug = $this->Drug->read(null, $id);
Using Containable allows you to gather all associated data in a single find() request.