views:

32

answers:

3

I am wondering whether it is possible to create a hasMany Relationship in a Model which makes use of the ID of the logged in user.

Example: One tip has many votings (from different users). This is easy:

public $hasMany = array(
   'TipVoting' => array(
        'className' => 'TipVoting',
        'foreignKey' => 'tip_id',
        'dependent' => false,
        'conditions' => '',
        'fields' => '',
        'order' => '',
        'limit' => '',
        'offset' => '',
        'exclusive' => '',
        'finderQuery' => '',
        'counterQuery' => ''
    ));

Now I want to get all Tips but want to know whether the logged in user hast voted this Tip already. So I would like to create a Realtionship which looks like this:

public $hasMany = array(
   'MyTipVoting' => array(
        'className' => 'TipVoting',
        'foreignKey' => 'tip_id',
        'dependent' => false,
        'conditions' => array('TipVoting.user_id' => $loggedinUser_id),
        'fields' => '',
        'order' => '',
        'limit' => '',
        'offset' => '',
        'exclusive' => '',
        'finderQuery' => '',
        'counterQuery' => ''
    )
);

How do I get the $loggedinUser_id into this Model? Or is this a bad way of implementing this issue? Should I rather go for a seperate find on the TipVoting Model?

Any ideas?

Thanks!

A: 

Use the containable behavior. http://book.cakephp.org/view/474/Containable

You don't want to define a different relationship, you just want to be able to filter by associated criteria. Containable will allow you to do this.

With just the TipVoting relationship, in your model do this:

var $actsAs = array( 'Containable' );

in your controller action do this:

$this->TipVoting->find( 'all', array( 
 'contain' => array( 
   'Vote' => array( 
    'conditions' => array( 
      'TipVoting.user_id' => $this->Auth->User('id') ), 
    'order' => 'TipVoting.id DESC' ) ) );
Travis Leleu
man, you made my day! Thanks for pointing this out. This is exactly what I was looking for!This is my solution on my problem: $this->Tip->find('all', array( 'contain' => array('TipVoting' => array( 'conditions' => array('TipVoting.user_id' => $this->Auth->user('id'))) ) ));Thanks again!
chris
No problem. The Containable behavior is really crucial to using CakePHP, it should be emphasized a little more in the manual. Another Cake class that's awesome is Set -- it allows you to merge a couple of Cake-style arrays together, merging based on different keys.
Travis Leleu
A: 

If I understand the question, what you're really after is a way to define the condition once rather than a way to define a join-specific condition. The Containable behavior would handle the latter, but not the former. To that end...

I'm not aware of any way to use variable values in your join definition. Since these are class variables, I suspect that it can't be done (disclaimer: I haven't tried). You also have the added complexity of trying to access what is normally a session value (the authorized user) at the model level--something of a MVC no-no in the strictest sense.

What I've done to meet similar needs is a combination of things is the following:

I use my AppController to forward authenticated user info:

# AppController
# You may or may not want to use the conditional
if( !empty( $this->data ) ) {
  $this->data['User'] = $this->Auth->user();
}

In your model, create a beforeFind() callback that will modify the query structure:

# Your model, e.g. TipVoting
# The data[] array will probably be keyed differently
public function beforeFind( $query ) {
  if( array_key_exists( 'active', $this->_schema ) ) {
    $conditions = !empty( $query['conditions'] ) ? $query['conditions'] : array();

    if( !is_array( $conditions ) ) {
      $conditions = array( $conditions );
    }

    if( !isset ( $conditions['user_id'] ) && !isset( $conditions[$this->alias . '.user_id'] ) ) {
      $conditions[$this->alias . '.user_id'] = $this->data['User']['Administrator'];
    }

    $query['conditions'] = $conditions;
  }

  return $query;
}

This code is just a snippet that meets a need similar to yours so it may not work directly, but it should give you an idea of what's possible and I think it may even be pretty close to what you're after. Just be aware that you probably will have to make a few changes here and there.

The key is that you're retaining the "proper" MVC structure by forwarding session information to the model explicitly (rather than asking the model to access it directly) while setting your query condition to use a session value every time records from the model are retrieved unless a user-centric condition is already involved.

It's not a perfect fit for what you're after, but it shouldn't take too long to tailor it to your needs.

Rob Wilkerson
A: 

Try using bindModel from the appControllers beforeFilter.

...
public function beforeFilter( ){
    parent::beforeFilter( );
    if( $this->Auth->user( )){
        $this->User->bindModel(
            'hasMany' => array(
                'MyTipVoting' => array(
                    'className' => 'TipVoting',
                    'foreignKey' => 'tip_id',
                    'conditions' => array(
                        'MyTipVoting.user_id' => $this->Auth->user( 'id' ),
                        //note the use of the aliased name here, not TipVoting but MyTipVoting
                        ...
                    ),
                    ...
                ),
                ...
            ),
        );
    }
}
Abba Bryant