How To Cascade Table Relationships?

Hi:

I’ve searched quite a bit for an answer but am still confused about how to cascade table relationships. Let’s say in an automobile context:

Make(HAS_MANY)->(BELONGS_TO)Model(HAS_MANY)->(BELONGS_TO)Trim4cyl,2dr->Vehicle[VIN]

I want to be able to get a list of all vehicles from a specific make; I also want a view of a vehicle with its Trim, Model and Make.

The basic HAS_MANY<->BELONGS_TO relations() work fine between adjacent entities so I can view a Vehicle’s Trim or a Model’s Make, but I can’t traverse beyond their direct relationship. I tried using ‘through’=>‘relation’ but I’m still confused.

Any ideas will be greatly appreciated.

As I haven’t received any replies I kept digging and sort of solved the problem, but not in an elegant way IMHO.

What I wanted in vehicle’s view.php




<?php $this->widget('bootstrap.widgets.TbDetailView',array(

	'data'=>$model,

	'type'=>'striped bordered condensed',

	'attributes'=>array(

		'id',

		'vin',

		'vehmake.name',

		'vehmodel.name',

		'vehtrim.description',

		'license_tag',

...



where ‘vehtrim’ is a parent, ‘vehmodel’ is a grandparent and ‘vehmake’ is a great-grandparent of ‘vehicle’. I though I could access the last two directly by defining a relations() using ‘through’, but it appears that ‘through’ only works forward (HAS_ONE/HAS_MANY instead of BELONGS_TO).

What I ended up doing in the vehicle’s view.php:




<?php $this->widget('bootstrap.widgets.TbDetailView',array(

	'data'=>$model,

	'type'=>'striped bordered condensed',

	'attributes'=>array(

		'id',

		'vin',

		array(

			'label' => 'Make',

			'value' => $model->VehicleMake,

		),

		array(

			'label' => 'Model',

			'value' => $model->VehicleModel,

		),

		'vehtrim.description',

		'license_tag',

...



which required changes to the vehicle model:




...

/**

	 * Gets this vehicle's make

	 * @return string vehicle make.

	 */

	public function getVehicleMake()

	{

		return TVehmake::model()->findByPk(TVehmodel::model()->findByPk(TVehtrim::model()->findByPk($this->vehtrim_id)->vehmodel_id)->vehmake_id)->name;

	}

	/**

	 * Gets this vehicle's model

	 * @return string vehicle model.

	 */

	public function getVehicleModel()

	{

		return TVehmodel::model()->findByPk(TVehtrim::model()->findByPk($this->vehtrim_id)->vehmodel_id)->name;

	}



Is there a better way? Suggestions are appreciated.

Thanks,

You have to define a ‘HAS_MANY’ in one model and ‘BELONGS_TO’ in the other model to have maximum flexibility when addressing the relations. A ‘HAS_MANY’ relation does not automatically work as a ‘BELONGS_TO’ relation from the other model.

You should be able to convert:


[color=#000088][size=2]return[/size][/color][size=2] [/size][color=#660066][size=2]TVehmake[/size][/color][color=#666600][size=2]::[/size][/color][size=2]model[/size][color=#666600][size=2]()->[/size][/color][size=2]findByPk[/size][color=#666600][size=2]([/size][/color][color=#660066][size=2]TVehmodel[/size][/color][color=#666600][size=2]::[/size][/color][size=2]model[/size][color=#666600][size=2]()->[/size][/color][size=2]findByPk[/size][color=#666600][size=2]([/size][/color][color=#660066][size=2]TVehtrim[/size][/color][color=#666600][size=2]::[/size][/color][size=2]model[/size][color=#666600][size=2]()->[/size][/color][size=2]findByPk[/size][color=#666600][size=2]([/size][/color][size=2]$this[/size][color=#666600][size=2]->[/size][/color][size=2]vehtrim_id[/size][color=#666600][size=2])->[/size][/color][size=2]vehmodel_id[/size][color=#666600][size=2])->[/size][/color][size=2]vehmake_id[/size][color=#666600][size=2])->[/size][/color][size=2]name[/size][color=#666600][size=2];[/size][/color][size=2]

[/size]

into


return $this->veh->make->name;

where ‘veh’ and ‘make’ are relations of TVehtrima nd TVehmake respectively.

Thanks for your input. My models already have their corresponding HAS_MANY<->BELONGS_TO relations defined throughout the whole chain, but when I previously tried your suggestion return $this->veh->make->name; I get the error Property “TVehicle.make” is not defined. As I mentioned, it appears that it doesn’t work in reverse (from child to parent) beyond the immediate BELONGS_TO ancestor.

Any more ideas are certainly welcome. Thanks!

Does your ‘TVehicle’ have the ‘make’ relation? What is the definition of it?

No it doesn’t as TVehicle is not directly related to TVehmake, because it inherits the relation from its ancestors. This is so to maintain the database normalized properly. The relationship structure is as follows:




TVehmake(HAS_MANY)->

     (BELONGS_TO thru vehmake_id)TVehmodel(HAS_MANY)->

          (BELONGS_TO through vehmodel_id)TVehtrim(HAS_MANY)->

               (BELONGS_TO through vehtrim_id)TVehicle[VIN]



Basically:

  1. a make has many models

  2. a model belongs to 1 make only

  3. a model has many trims (e.g. 4cyl, 2door, etc.)

  4. a trim belongs to 1 model only

  5. a trim has many vehicles (different VINs)

  6. a vehicle (specific VIN) belongs to 1 trim only.

I am reluctant to violate normalization by linking tables that don’t follow this schema, at least at this point in the project.

Please keep the ideas coming, thanks!

Hi

Your schema is ok, I was talking about the relations in the CActiveRecord model for the table.

For instance, in one of my models, ‘AlertListener’, I have this:


[size=2]    /**[/size]

     * @return array relational rules.

     */

    public function relations()

    {

        // NOTE: you may need to adjust the relation name and the related

        // class name for the relations automatically generated below.

        return array(

			'entityAlert' => array(self::BELONGS_TO, 'EntityAlert', 'entity_alert_id'),

        );

    }



And in the ‘EntityAlert’ model, I have this (only the relevant relation is shown):


[size=2]    /**[/size]

     * @return array relational rules.

     */

    public function relations()

    {

        // NOTE: you may need to adjust the relation name and the related

        // class name for the relations automatically generated below.

        return array(

[size=2]                'alertListeners' => array(self::HAS_MANY, 'AlertListener', 'entity_alert_id'),[/size]

[size=2]        );[/size]

    }



So I can use ‘$record->entityAlert’ for instances of the first class, and ‘$record->alertListeners’ for instances of the second class.

You just have to get the right relationship definitions in your models and you should be able to access the data as indicated. Yii does the magic.

The relationships are correct in the models:

TVehmake.php




return array(

     'vehmodels' => array(self::HAS_MANY, 'TVehmodel', 'vehmake_id'),



TVehmodel.php




return array(

	'vehmake' => array(self::BELONGS_TO, 'TVehmake', 'vehmake_id'),

	'vehtrims' => array(self::HAS_MANY, 'TVehtrim', 'vehmodel_id'),



TVehtrim.php




return array(

	'vehmodel' => array(self::BELONGS_TO, 'TVehmodel', 'vehmodel_id'),

	'vehicles' => array(self::HAS_MANY, 'TVehicle', 'vehtrim_id'),



TVehicle.php




return array(

	'vehtrim' => array(self::BELONGS_TO, 'TVehtrim', 'vehtrim_id'),



So the problem is not there. I suspect but haven’t found any confirmation that the relations don’t work backward beyond the immediate parent<-child BELONGS_TO. A single pair of HAS_MANY<->BELONGS_TO works fine, just not beyond that when going backwards (finding a grandparent or more).

Hi

You indicate that the error is that ‘TVehicle.make is not defined’. Unless there a typo somewhere, the relation’s name is ‘vehmake’, so the request should read:


return $this->vehtrim->vehmodel->vehmake->name;

Sorry for the confusion: after my original post I refactored the Make table and everything related to it to TVehmake (notice the date difference between my first 2 posts). I haven’t found any typos yet, but the problem persists.

Ok, in those cases I do a CVarDumper::dump($this->vehtrim,10,true) and make sure that I get the expected type of object.

Thanks for your help!