Disable the default scope of a model

Hi,

this tip is emerged from this topic with Mikes great idea to put it in a simple behavior.

So, if you have a defaultScope (e.g. for hiding records with a soft deletion) and you want to disable this default scope in some particular cases (e.g. an admin page where soft deleted records could be undeleted) you may add a behavior like the following:


<?php

class DisableDefaultScopeBehavior extends CActiveRecordBehavior

{

    private $_defaultScopeDisabled = false; // Flag - whether defaultScope is disabled or not


    public function disableDefaultScope()

    {

          $this->_defaultScopeDisabled = true;

          return $this->Owner;

    }


    public function getDefaultScopeDisabled() {

        return $this->_defaultScopeDisabled;

    }


}

?>

inside your model override the defaultScope() function like this:


    public function defaultScope()

    {

        return $this->getDefaultScopeDisabled() ?

            array() :

            array( 

                // Your default scope goes here e.g.

                // 'condition' => 'deleted=0' 

            ); 

    }

now you can call your model like this to disable the default scope:


MyModel::model()->disableDefaultScope()->findAll();

Have fun and thanks to Mike!

Cheers!

TAKE CARE: I don’t know why, but this editor seems to make the ‘behavior’-part of the classnames lowercase… of course they start with an uppercase ‘B’.

how to attach this behavior to a model? where to put this behavior file? thank you

This is realy useful if you call $model->find() method. But did anybody known solution if you need same behavior on related models? Example:




class Category extends CActiveRecord{

...

public function relations(){

    return array(

        ...

        'users'=>array(sefl::HAS_MANY, 'User', 'category_id'),

        ...

    );

} 

...

}


class User extends CActiveRecord{

...

    public function defaultBehavior(){

        ... some condition ...

    }

...

}


// Need get users without default scope of User class

$category->users



Thanks Yoshi, I needed that for the very purpose you mentioned: soft deletes and the possibility to watch the "archive".

I didn’t put it in a behaviour, though, but in the myProjectActiveRecord class which instances all my models and which I also use for the (soft) global beforeDelete(). That’s partly because I’m not too familiar with behaviours and partly because it saves me from adding the behaviour to all my models. It works fine, but I might be doing things “against good practices”.

Anyway, this is how I put it in. Any (mild) critisism is welcome.




abstract class MyProjectActiveRecord extends CActiveRecord

{

	/**

	* Switch off the default scope

	*/

	private $_defaultScopeDisabled = false; // Flag - whether defaultScope is disabled or not

	public function disableDefaultScope()

	{

		$this->_defaultScopeDisabled = true;

		return $this;    // not $this->Owner

	}

	public function getDefaultScopeDisabled() 

        {

		return $this->_defaultScopeDisabled;

	}

        etc...



I noticed an issue (bug?) with using this implementation and wanted to bring it up for discussion.

If you use the above and perform a find, like this:


MyModel::model()->disableDefaultScope()->find($condition);

Then, elsewhere you do the following:


MyModel::model()->find($condition);

The default scope will STILL be disabled.

I am not sure, but think this is because it (find) shares an instance.

I thought I saw somewhere that this is not the case of findAll.

The solution I found was to reset the flag to false, prior to returning the default scope.

Comments and feedback welcome.

Just a note for future searchers that there is now a resetScope() function.

So one can just do:


MyModel::model()->resetScope()->findAll();

This will reset all scopes including the default (use resetScope(FALSE) to leave the default scope in place.)

Docs:

http://www.yiiframework.com/doc/api/1.1/CActiveRecord#resetScope-detail

The solution is to fix disableDefaultScope() method.

Let’s rename it to withoutDefaultScope()




public function withoutDefaultScope()

{

    $obj = clone $this;

    $obj->setDefaultScopeDisabled(true); // Need to add setter


    return $obj;

}



Now you can use it this way: MyModel::model()->withoutDefaultScope()->find($condition);

And after if you try to execute MyModel::model()->find($condition); - it will work as expected (with default scope enabled)