Help using named scope with many-to-many relationship

I have three tables that look something like this:

USER

user_id

TRIP_USER

user_id

trip_id

f_user_admin

TRIP

trip_id

What I want to do, in my view index for Trips, is to show only (1) the trips for which the logged-in user has access too (if user and trip are in TRIP_USER), and (2), whether the user is an administrator for each trip (f_user_admin = 1) that is listed.

From what I can see in my research of Relational Active Record in the documentation, Named Scopes could be my answer(see http://www.yiiframework.com/doc/guide/1.1/en/database.ar#named-scopes). I have to admit that I am overwhelmed as to how I will implement this in my application.

Should I create the scope in the TripUser model and then access it through relations in Trip? Or should the scope be defined in the Trip model, by referencing fields from TripUser?

Any guidance, including code to point me in the right direction would be appreciated.

Nothing?

For the first part it should be as simple as:




$user = User::model()->findByPk(Yii::app()->user->getId());

$trips = $user->userTrips->trips;



Now for the admins, you can define a relation in the User model as follow:


'ownTrips' => array(self::HAS_MANY, 'TripUser', 'condition' => 'f_user_admin = 1')

and then just




$user = User::model()->findByPk(Yii::app()->user->getId());

if($user->isAdmin){

    $trips = $user->userTrips->trips;

}else{

    $trips = $user->ownTrips->trips;

}



Not sure if thats what you want exactly, but it should give you an idea.

Named scopes are not necesary as they are simple model relations. but you can offcourse use them if you feel more confortable that way.

Thanks for replying…

I am not dead set on using named scopes. You suggested the following code:




$user = User::model()->findByPk(Yii::app()->user->getId());

$trips = $user->userTrips->trips;



I assume “userTrips” and “trips” are relations? If so, I’ll use the code you suggested, but with my actual relation names. And how exactly would I incorporate your suggestion into the the following functions of my TripController:




public function actionIndex()

{

  $dataProvider=new CActiveDataProvider('Trip');

  $this->render('index',array(

    'dataProvider'=>$dataProvider,

  ));

}



and




public function actionUpdate($id)

{

	$model=$this->loadModel($id);


	// Uncomment the following line if AJAX validation is needed

	// $this->performAjaxValidation($model);


	if(isset($_POST['Trip']))

	{

		$model->attributes=$_POST['Trip'];

		if($model->save())

			$this->redirect(array('view','id'=>$model->trip_id));

	}


	$this->render('update',array(

		'model'=>$model,

	));

}



In the last one, I want to make sure that the user has access to the specific trip he is trying to view.

For the first part you might want to use CArrayDataProvider instead of CActiveDataProvider.


public function actionIndex()

{

  $user = User::model()->findByPk(Yii::app()->user->getId());

  $dataProvider=new CArrayDataProvider($user->userTrips->trips);

  $this->render('index',array(

    'dataProvider'=>$dataProvider,

  ));

}



As for the second you might wanna take a look at this:

http://www.yiiframework.com/doc/guide/1.1/en/topics.auth

I implemented the following code based on your suggestion:


	public function actionIndex()

	{

		//$dataProvider=new CActiveDataProvider('Trip');

		$user = User::model()->findByPk(Yii::app()->user->getId());

		$dataProvider=new CArrayDataProvider($user->tblTrips);

		$this->render('index',array(

			'dataProvider'=>$dataProvider,

		));

	}



based on the relation in my User model:


'tblTrips' => array(self::MANY_MANY, 'Trip', '{{trip_user}}(trip_id, user_id)'),



However, I am getting the following CException: Property “Trip.id” is not defined. What’s weird is that the primary key for my Trip table is not id, but in fact trip_id. So I’m not sure why it’s looking for Trip.id.

I know I’m close, because if I do


print_r($user->tblTrips);

The resulting array contains all the data that I’m looking for. Can you shed some light on what I’m missing?

Id bet is this:

http://www.yiiframework.com/doc/api/1.1/CArrayDataProvider#keyField-detail

That’s exactly what it was. Thanks!

One last thing. I want to make sure that a User can only get to the Update screen if he has access to the Trip he is trying to modify. Essentially, he should only be able to do so if there is a record with his user_id and the trip_id in the TRIP_USER table. I read the documentation on RBAC, but it seems to be overkill for what I am trying to do. I tried modifying the loadModel function as below, but I’m getting a PHP Error of “Object of class User could not be converted to string”:


	public function loadModel($id)

	{

		$user = User::model()->findByPk(Yii::app()->user->getId());

		$model=Trip::model()->$user->tblTrips->findByPk((int)$id);

		if($model===null)

			throw new CHttpException(404,'The requested page does not exist.');

		return $model;

	}



Should the loadModel be modified for what I am attempting to do? Or should the change instead occur in actionUpdate (see current code bewlow)?


	public function actionUpdate($id)

	{

		$model=$this->loadModel($id);


		// Uncomment the following line if AJAX validation is needed

		// $this->performAjaxValidation($model);


		if(isset($_POST['Trip']))

		{

			$model->attributes=$_POST['Trip'];

			if($model->save())

				$this->redirect(array('view','id'=>$model->trip_id));

		}


		$this->render('update',array(

			'model'=>$model,

		));

	}



I like what you suggested earlier in your post #3, but that would get ALL trips for the user. In this case, I still have the user_id, but I also have a trip_id that I need to incorporate. I am unsure how to do that.

Thanks again for your help!

This code does not make sense at all:


$model=Trip::model()->$user->tblTrips->findByPk((int)$id);



mainly because of the model()->$user part.

You should be doing something like:




$model = TripUser::model()->findBypk(array("user_id" => Yii::app()->user->getId(), "trip_id" => $id));

The problem with that is that it returns the TripUser model, when in fact I am looking for the Trip model according to specific trip_id (from $_GET) and user_id (from Yii::app()->user->getId()).

Maybe I’m going about this all wrong. In actionUpdate(), before calling loadModel(), maybe I should call a custom function to see if there is a record with user_id and trip_id in the TRIP_USER table, meaning that the user has action to the trip? What do you think?

Like with so many things in Yii, I just want to avoid taking an alternate route when there is possibly already built-in functionality that can accomplish what I am trying to do.

Well you have … logic problems, please try to analyse a little more what you are doing and what you need, this is very simple.

what about?




        public function loadModel($id)

        {

                $model = TripUser::model()->findBypk(array("user_id" => Yii::app()->user->getId(), "trip_id" => $id));

                if($model===null)

                        throw new CHttpException(404,'The requested page does not exist.');

                return $model->trip;

        }



That worked beautifully. I wouldn’t say I had logic problems. I knew exactly what I wanted to do. I was just struggling on how to get it done within Yii.

Thanks for your patience and for giving me a hand when, obviously, others chose not to! I have a better understanding on how to apply relations in Yii.

Don’t give us that bullshit.

Asgaroth rocks - and why meddle into your affairs when it seems to be handled just fine. ;)

Not everyone has the time and patience all the time.

Understandably so. I just wanted to make sure to thank Asgaroth for his patience. I didn’t mean to rub someone the wrong way in the process…

Then why append "when, obviously, others chose not to" to your thank-you sentence?

Not all of us are glued to the keyboard viewing the Yii forum 24/7.

I am slightly offended. ;)

Well the problem is solved, I think its better if we just leave it that way. no need more replies to this topic, as far as I can think of.

I find that offending and far from the gentle conversation standard we happily have been used to in this forum since Yii was born.

And even if we are, it’s on our own discretion to answer or not.

Personally I use to drop an answer from the top of my memory if I think it will be meaningful and I’m convinced to some extent it would be correct (which it may not).

I don’t think it’s meaningful to answer a person that registered five minutes ago, asking the same question that already was answered in a different thread a couple of hours earlier. Also, there’s most probably some not so fresh newcomers that experienced the same thing just recently and can give a working solution without spending any time at all on research/searching older information somewhere. And very often the person asking the question immediately finds out the answer him/herself.

Sometimes I locate older information if I find it meaningful to refresh my understanding of the topic.

On other occasions I choose to dig into some problem for the reason of educating myself by finding a working solution (not necessarily the best one).

Less often I choose to post an untested answer, pointing that out by adding “not tested”, “IIRC” etc. Especially in those cases it’s even more valuable to get feedback. If feedback is not given, it’s totally meaningless from my point of view to post hints that’s not relying on my own recent testing. Sooner or later somebody that now the working solution most probably will come by.

I hope you now got a better understanding of why you sometimes get an immediate answer and sometimes have to wait, or even refresh the thread if it’s been forgotten of. And if this isn’t acceptable for you, at least you have the definitive guide and api reference to rely on. ;)

/Tommy

I agree, which is why I had to comment.

In all fairness, I want to mention that he messaged me an apology, so I’m cool with it.

I can relate to the feeling of exasperation when you’re banging your head against a wall, and help only seems to arrive too slowly…

But that’s reality - Tommys post explains the mechanics of that in detail.

To those moments I recommend the IRC instant help in there :D