Related Search Behavior

hi le_top

I’m so sorry, it was my fault. I included an old (ancient :D) version of Yii!

thanks for your reply!

Here is a way to use the ‘autoScope’ feature now available in RelatedSearchBehavior.

AutoScope automagically adds scopes to your CActiveRecord for each field. If you have a field ‘author’ then you’ll get a scope like this for free:


public function author(mixed $value, boolean $partialMatch=false, string $operator='AND', boolean $escape=true)

Why do you need this: well, to speed up development and avoid writing ‘condition’.

Autoscope provides:

  • Automatic specification of the table alias for the field;

  • Same functionnality as provided by CDbCriteria::compare();

  • Runtime checking that the used field/scope exists (instead of getting a failed SQL command);

  • The opportunity to write less code.

How do I use this?

To understand, have a look at the "manual" scope definition below where I[size=2]select Devices with a given protocol.[/size]

[size=2]It requires that the CActiveRecord referenced in the relation ‘deviceType’ also has a scope called ‘device_type_protocol’:[/size]




class Device extends CActiveRecord {

/** Scope to select devices with specific protocol. */

public function protocol($protocol) {

        $this->getDbCriteria()->mergeWith(

                array(

                        'with'=>array(

                                'deviceType'=>array(

                                        'scopes'=>array('device_type_protocol'=>$protocol),

                                ),

                        ),

                )

        );

        return $this;

    }

}



In the case where the scope ‘DeviceType::device_type_protocol’ does not exist, I’ld have to use ‘condition’ to filter on it:




class Device extends CActiveRecord {

/** Scope to select devices with specific protocol. */

public function protocol($protocol) {

        $this->getDbCriteria()->mergeWith(

                array(

                        'with'=>array(

                                'deviceType'=>array(

                                      'condition'=>"deviceType.device_type_protocol=$protocol",

                                ),[

                        ),

                )

        );

        return $this;

    }

}



That is the code when I am ‘lazy’: no quoting for the alias, duplication of the alias name (‘deviceType’ is written twice), non escaping or parameter for ‘$protocol’. And to do better, I’ld have to create a lot of stupid scopes.

Too many things I do not like, so I developed ‘autoScope’ which provides the scope ‘device_type_protocol()’ automagically.

Unfortunately, I could not put everything in the extension, and you must extend the ‘__call’ method of the ActiveRecord. I’ve automated that by updating the ‘Gii’ generator template for the model.




class DeviceType extends CActiveRecord {


	public function __call($name,$parameters) {

    	try {

        	return parent::__call($name,$parameters);

    	} catch (CException $e) {

        	if(preg_match('/ and its behaviors do not have a method or closure named /',$e->getMessage())) {

                 return $this->autoScope($name, $parameters);

        	} else {

            	throw $e;

        	}

    	}

	}

}



If you look closer you’ll see that there is a call to ‘$this->autoScope()’. Yii will not find ‘autoScope’ in the model and will go look for it in the behaviors and should find it in the the RelatedSearchBehavior.

AutoScope checks if the attribute exists and if it does applies a scope on it using ‘compare’ as the method, here is the line of code to the call of ‘compare’ in autoScope:




[size=2]           $owner->getDbCriteria()->compare($column, $value,$partialMatch,$operator,$escape);[/size]



Your extension saves so much time, i really appreciate your work.

Is it possible for you to add the possibility to order by 2 or more columns by default,

or do you know another way to get this to work properly?

Clearly a great timesaver and cleaner code resulting from it.

There is another way, but then why would there be the extension?

I updated the extension to add support for multiple columns. I also updated the demo to use multiple columns.

Wow that was fast :blink:

Thank you very much!

In reply to:

and

There is only one order by clause at a time because only one column is sorted at a time.

The sort order is determined by a get variable which is typically ‘sort’:

RESTOFURL&sort=start_date

The above example would sort by ‘start_date’. ‘start_date’ must correspond to the virtual attribute if it is a column in a relation for ‘RelatedSearchBehavior’ to work. And of course the column should be sortable according to the grid view (but I guess that this is the case).

Check the URL that is called when you try to do a sort - an easy way to do so is ‘click right’/‘open link in new window’ - the Url will appear in the navigator’s location bar and you can even manually play with it.

Example with the demo: http://relatedsearchbehavior.ynamics.com/index.php?r=site/index&Invoiceline_page=7&ajax=yw0&sort=SupportEmail.desc

How to setup a MANY MANY relation?

If set it up the same way, as with simple relations there is no data in the column and no error :(

I have not really thought of many-many relations and it is a topic the regularly pops up in the forum.

Could you setup a demo for that which could be integrated in the demo for this extension and which can at the same time be used as a test case to check what the solution is?

I am thinking about how to integrate it into your demo,

but i currently have no idea how to do it on a sensemaking basis.

In my application I have these 3 tables

User

PK - [color="#FF0000"]id[/color]

AuthAssignment

PK1,[color="#0000FF"]FK1[/color] - itemname

PK2,[color="#FF0000"]FK2[/color] - userid

AuthItem

PK - [color="#0000FF"]name[/color]

I want to display the name of the assigned item in my User gridview


'roles' => array(self::HAS_ONE,'AuthAssignment','userid'),

'items' => array(self::HAS_ONE,'AuthItem',array('itemname'=>'name'),through'=>'roles')


'role' => 'items.description',

It works, just a workaround for MANY MANY relation :P

Hi

To set up within the demo, you can see that a track can appear on more than one invoice and that an invoice can have more than one Track.

I added the following relations the Track (untested):




   'invoice_lines'=>array(self::HAS_MANY,'InvoiceLine',array('TrackId'=>'TrackId')),

   'invoices'=>array(self::HAS_MANY,'Invoice',array('InvoiceId'=>'InvoiceId'),'through'=>'invoice_lines'),



However, these relations do not seem usefull for your purpose.

The demo uses ‘InvoiceLines’ as the base model class (equivalent to AuthAssignment) and therefore any existing combination of Track/Invoice appears in the table.

Hence, in a way, the demo already demonstrates how you can use ‘many_many’.

Knowning this, you might be able to demonstrate through the demo the situation where you said that there is no data in the column and no error.

First of all, great extension, I love it :)

But I seems to have a problem with the autoscope.

Among other things I added the code below to my model, I also adjust to search function as mentioned in the install guide.




public function __call($name,$parameters) {

		try {

			return parent::__call($name,$parameters);

		} catch (CException $e) {

			if(preg_match('/ en zijn behaviors hebben geen method of closure met naam /',$e->getMessage())) {

				return $this->autoScope($name, $parameters);

			} else {

				throw $e;

			}

		}

	}

	

	function behaviors() 

	{

		return array(

			'relatedsearch'=>array(

				 'class'=>'RelatedSearchBehavior',

				 'relations'=>array(

					  'appProcesId'=>'applicationProces.proces_id',

				 ),

			 ),

		);

	}



In a gridview searching and displaying the ‘appProcesId’ works great. But when I want to do something like this:




ApplicationProcesActivity::model()->appProcesId(2)->findAll();



I get an error:




Call to a member function findAll() on a non-object



While:




ApplicationProcesActivity::model()->id(40)->findAll();



works fine. the autoscope does work, but only with properties from that model, not the ones defined in the behaviour. Am I doing something wrong?

Hi Johannn

Autoscope is not currently implemented for related fields.

It is possible to do so and would require either duplicating some of the code or refactoring it to avoid code duplication.

Basically, the code of the ‘relatedSearch’ method is needed without the ‘sort’ logic and dataProvider creation - we just need to update the criteria.

For performance, the analysis of the relations would better be cached, but to start we could ignore caching.

The autoScope method then has to call on this new method in case the field does not exist as a regular field of the table, but when it exists as a relation.

I do not have much time currently, so on my end this implementation has to wait.

You can still do something like indicated in the documentation (I haven’t checked the code precisely, but it gives an idea):




...->findAll(array(

'with'=>array('applicationProcess'=>array('scopes'=>array('id'=>2))))



I notice that there also seems to be an internationalisation issue which I hadn’t notices (even though my application language is in French).

Which is logical because there is no translation for french for this message.

To resolve that, we have to modify _call:


/**

     * Add automatic scopes for attributes (uses RelatedSearchBehavior).

     */

	public function __call($name,$parameters) {

    	try {

        	return parent::__call($name,$parameters);

    	} catch (CException $e) {

        	if(preg_match(

                	'/'.Yii::t(

                        	'yii',

                        	quotemeta(

                                	Yii::t(

                                        	'yii',

                                        	'{class} and its behaviors do not have a method or closure named "{name}".'

                                        	)

                                	),

                                	array('{class}'=>'.*','{name}'=>'.*')

                        	)

                	.'/',$e->getMessage())) {

            	return $this->autoScope($name, $parameters);

        	} else {

            	throw $e;

        	}

    	}



P.S: (in Dutch): nog veel plezier met de extensie!

Thank you for the quick reply.

I’ll use the solution provided in the documentation by using the scope in the findAll()

Again thanks for this great extension :)

Hello out there,

I do use this extension quite a lot, but I run right now into an incompatibility with another extension.

Perhaps someone solved this already…

I am using the "yii-remember-filters-gridview" component which stores the filter, the pagination and the sorting in a session. When combing back to the grid (for example after updating one record) all three above mentioned parameters are active again. Nice.

But… as there is quite some logic in RelatedSearchBehaviour about Sorting, it breaks the function of the other extension. Does anybody see a way to solve this?

The "yii-remember-filters-gridview" is a small component, so I pasted the function for the storage of sorting in the session below.

Any ideas how to solve this? Could we add a check for the session into RSB?


* http://www.yiiframework.com/extension/remember-filters-gridview

*/




    private function doReadSave() {

     if ($this->owner->scenario == 'search' || $this->owner->scenario == $this->rememberScenario ) {

        $this->owner->unsetAttributes();


        // store also sorting order

        $key = get_class($this->owner).'_sort';

        if(!empty($_GET[$key])){

          Yii::app()->user->setState($this->getStatePrefix() . 'sort', $_GET[$key]);

        }else {

          $val = Yii::app()->user->getState($this->getStatePrefix() . 'sort');

          if(!empty($val))

            $_GET[$key] = $val;

        }

.......

        

There is actually a function in Yii for it (http://www.yiiframework.com/doc/api/1.1/CGridView#enableHistory-detail ) with limitations.

HOwever, I am using the same[size=2] ‘ERememberFiltersBehavior’ which I add automatically in my models using gii. [/size]

To fix the other extension, we need to consider that the sort key is in ‘sortVar’ . You are correct: sorting does not work correctly. I don’t see it at first sight - I think that RSB is doing it the right way, and the way RFB works needs some investigation.

I had version 1.2 of ERememberFiltersBehavior, but apparently that did not have the sort functionnality which explains why I couldn’t see it. The latest (same) 1.2 version does have it.

Basically ERememberFiltersBehavior relies on the name of the sort variable as provided by CActiveDataProvider which relies on CDataProvider where it is a combination of the DataProvider’s id which by default is the model class name.

So ideally, ERemberFiltersBehavior should get the $_GET key in the same way, but I can’t figure a way to do so at this time.

So, I modified RelatedSearchBehavior to rely on CActiveDataProvider and hence provide the expected sortVar in most cases.

The update is on the extension page.

Wowwwwww!

Awesome. It works very nice now. Thanks so much for changing this.

gb5256

Thanks for sharing this extension which seems very awesome. However did someone try it with postgresql ?? cause I never succeeded to use it…

EDIT:

get working with postgresql finally, However still in trouble with MANY_MANY relationships.

It doesn’t work when I set in behavior function :

‘my_name’=>‘myMANY_MANYrelation.anyField’

it displays nothing.

Moreover, I got problem with sorting my column through HAS_MANY relations, I have an error ’ missing FROM-clause entry for table “myHAS_MANYrelationship” ’ with the request send :

SELECT "t"."id" AS "t0_c0" FROM "mainTable" "t" GROUP BY t.id ORDER BY "myHAS_MANYrelation"."field" LIMIT 10;

The extension is awesome ;-), and it should work with postgresql which you ended up doing.

Is ‘myHAS_MANYrelation’ an existing relation in the active record? That is, ‘myHAS_MANYrelation’ must be the name of the relation defined for ‘mainTable’.

You write ‘myHAS_MANYrelationship’ and ‘myHAS_MANYrelation’. The latter is without ‘ship’ for instance so the name is not the same.