[Solved] Sort by related AR's attribute?

So the questions I have are

A)  is this expected behavior

B)  should it do what I want

Right now if you are sorting from columns of multiple tables, it is virtually impossible to take use of attributeLabels(), yet if you are only sorting by columns of one table, you can configure it to take use of attributeLabels().  Does not make sense to me

Yes, CSort was not originally designed for multiple tables. Please create a ticket for this feature. Thanks.

Qiang, i see this is fixed already. Thats cool, thanks!

Could you give a short summary, how to use it now? With all the discussion, it’s often not really clear in the end ;).

The API for CSort is updated with more information.

The "fix" allows you to not specify a label for related attributes.

Ok, thanks. Will have a look.

Hmm. It still doesn't take the labels from the related tables for me.

Lets say i have models Customer (id, name) and Project (id,name).

Project:

<?php


  function attributeLabels() {


    return array(


      'name'=>'Project'


      // doesn't help either:


      'customer.name'=>'Customer'


    );


  }


Customer:

<?php


  function attributeLabels() {


    return array(


      'name'=>'Customer'


    );


  }


<?php


$sort->attributes=array(


    'name'=>'project',


    'customer.name'=>'customer'


)
<?php


  $sort->link('customer.name');

Produces ‘Name’ as header label. I guess, i missed the point :)

Is 'customer' the name of the related object?

Yes, forgot that. Project BELONGS_TO Customer. And i do a query on Projects with('customer').

Well i think

<?php


$sort->attributes=array(


    'name'=>'project',


    'customer.name'=>'customer'


)

should be

<?php


$sort->attributes=array(


    'project.name'=>'project',


    'customer.name'=>'customer'


)

Hmm. Nope. Doesn't make a difference. Must have overlooked something. How about the attributeLabel() above? Is that correct?

Edit:

In fact i already hat the table prefixed and get an error without, because of the ambiguous column names.

Yes, it all looks fine to me.

Here's my code, which seems the same:

<?php


//user model


	public function attributeLabels() {


		return array(


			'about'=>'About Yourself',


			'group_id'=>'User Level',


			'email_confirmed'=>'Activated?',


			'created'=>'Date Registered',


			'email_visible'=>'Show Email',


			'notify_comments'=>'Notify me for comments',


			'notify_messages'=>'Notify me for messages',


		);


	}





//user controller list action


		$sort = new CSort('user');


		$sort->attributes = array(


			'user.username'=>'username',


			'user.group_id'=>'group_id',


			'user.email'=>'email',


			'user.created'=>'created',


			'user.email_confirmed'=>'email_confirmed',


		);


		$sort->applyOrder($criteria);


		


		$users=User::model()->with('group')->findAll($criteria);





//view


		<th><?php echo $sort->link('user.username'); ?></th>


		<th><?php echo $sort->link('user.group_id'); ?></th>


		<th><?php echo $sort->link('user.email'); ?></th>


		<th><?php echo $sort->link('user.created'); ?></th>


		<th><?php echo $sort->link('user.email_confirmed'); ?></th>


It successfully pulls all the labels from the user model.  I guess I have no group sorting however even though the user.group_id column actually displays a value from the groups table.

Forget it, everything’s fine. Had a typo in my attributeLabels().  ::)

I must have been tired yesterday: It still doesn’t work.  :P

Jonah, in your example you don't use a related table. That's where i have the problem. It doesn't take the labels from the related table. The labels for the main table work. So i still have to use this, otherwhise the column header is 'Name':

<?php echo $sort->link('customer.name','Customer'); ?>

All this was quite chaotic, but finally i found the reason for the confusion: This stuff is very picky about upper-/lowercase. I had to change mostly everything to lowercase to make it work.

That means, that i also have to use lowercase aliases for my related tables, even if my table names are usually uppercase. Not really nice, as i now have to be very careful about table names and aliases in my SQL statements.

[code=Project.php]<?php

    public function relations()

    {

        return array(

            // alias was 'Customer' before

            'customer'  => array(self::BELONGS_TO,'Customer','customerID','alias'=>'customer'),

        );

    }

[/code]

'customer' is more reasonable than 'Customer' since it can be used like a variable.

Well, not always. As i said, my table names are all uppercase (as supposed in the guide). So to order by two columns i now have to be very careful:

<?php


$list=Project::model()->with('Customer')->findAll(array(


                'condition'=>'active=1',


                // It's lowercase 'customer' but uppercase 'Project'...


                'order'=>'customer.shortname, Project.name'


            ));


Would have been nice, if it would work with uppercase aliases, too.

I see the potential problem. Maybe you can come up with some suggestion? ;)

Hmm, i guess not solveable, as it is now. In applyOrder(), CSort should use the alias defined in the related AR's relations() for related objects. But it doesn't know, if the current attribute is for the main table ('project.name') or a related table('customer.name').

Edit:

Not really correct. We could do the same as in resolveLabel() to find out, if we have an attribute of a related object and then use it's defined alias. Not sure, if having many things like that affect performance too much.

I have now the same problem, when extending my model classes with relations.

My problem is that 4 or more columns have the same name.

Is there really need to add the table name to the columns of the selecting model class?

I mean everything worked with



<th><?php echo $sort->link('ID'); ?></th>


<th><?php echo $sort->link('Phone'); ?></th>


etc. if no related table has the same column names.

But if I add a relation to my model where the othertable has also the columns ID and Phone I have to write:



<th><?php echo $sort->link('table1.ID'); ?></th>


<th><?php echo $sort->link('table1.Phone'); ?></th>


And add the attributes to the CSORT object



$sort->attributes=array(


     'table1.ID'=>'ID',


     'table1.Phone'=>'Phone',


     'othertable.somecolumn'=>'somecolumn'


  ); 


I understand that if I want a specific column of a related table that I have to add the alias which is set in the relation function of the model.

But I don't understand why its needed to add the tablename for the own tablecolumns of the model (e.g. if ID is also a column of the releated table).

Why is there no "default" alias used in the query (which introduce the  Column &#039;ID&#039; in order clause is ambiguous error).

I mean it should be easy to change the sql to 



select * from table1 as default LEFT OUTER JOIN `othertable` othertablealias ON (`table1`.`foreignkey`=othertablealias.`ID`) ORDER BY default.`ID` LIMIT 10


or either the model table name itself if nothing else is set e.g.



 ORDER BY `table1`.`ID`


Cause I have created all the sites with the crud command and extending now model after model with relations.

And therefore I have to add the "tablename" for the columns of the modelclass itself, which have worked before a relation has been added to the model.

this e.g. would do this mentioned above

public function applyOrder($criteria) in CSort



...


$default_table=CActiveRecord::model($this->modelClass)->tableName(); // add


foreach($directions as $attribute=>$descending)


{


		if(($pos=strpos($attribute,'.'))!==false)


			$attribute=$schema->quoteTableName(substr($attribute,0,$pos)).'.'.$schema->quoteColumnName(substr($attribute,$pos+1));


		else


			$attribute=$schema->quoteTableName($default_table).'.'.$schema->quoteColumnName($attribute); // change


			//$attribute=$schema->quoteColumnName($attribute); // original 


		$orders[]=$descending?$attribute.' DESC':$attribute;


}


...


But I am not sure if everything is working as expected in every case.

Any Opinions?

I think the most convenient solution at the moment is to copy CSort from Yii 1.1 beta and using extended attributes http://www.yiiframework.com/doc/api/CSort#attributes-detail

I changed the name of the class of NewCSort and copied it to the extensions directory, so now I use


Yii::import('ext.NewCSort');

$sort = new NewCSort('Smth');

instead of


$sort = new CSort('Smth');