best way to get published posts only

hi,

let’s assume we have a forum web application with a Thread model that HAS_MANY posts, and a post can be published or unpublished according to publish_status column (1=published)

for most users they should only see published, and in some rare cases we might want to display even unpublished (eg. admin)

we faced the case of many developers forget to check for publish status,

and we want to force that.

we thought about using a scoops like the sample code in reference (Post::model()->published()->findAll();)

but in our case we are in the middle of relation (I’m not sure if getTableAlias would work in an early stage as the scope)

Our case looks like this

Thread::model()->with(‘posts’)->publishedPosts()->findByPk($thread_id);

or

Thread::model()->publishedPosts()->with(‘posts’)->findByPk($thread_id);

I thought about a solution, I call it subrelation, it used like this

$thread=Thread::model()->with(‘all_posts’)->findByPk($thread_id);

$thread->posts // will be publish posts only

the code is like this




// in models/Thread.php

	public function relations()

	{

		return array(

					'posts' => array(self::HAS_MANY, 'ThreadPost', 'thread_id', 'on'=>'posts.publish_status=1'),

					'all_posts' => array(self::HAS_MANY, 'ThreadPost', 'thread_id'),

		);

	}




	public function subrelations() {

		return array('posts'=>array('all_posts', 'publishedFilter'));

	}

	

	protected static function publishedFilter($superset) {

		$subset=array();

		foreach($superset as $e) {

			if ($e->publish_status==1) $subset[]=$e;

		}

		return $subset;

	}

	

	public function getRelated($name, $refresh=false, $params=array()) {

		$sub=$this->subrelations();

		if (!$refresh && isset($sub[$name])) {

				list($other_name,$filter)=$sub[$name];

				if($this->hasRelated($other_name))

					return $this->_related[$name]=call_user_func(array(get_class($this),$filter), $this->_related[$other_name]);

		}

		return parent::getRelated($name, $refresh, $params);

	}



maybe I did not show the context of the problem write,

if we use the suggested method in Yii’s guide

http://www.yiiframework.com/doc/guide/1.1/en/database.ar#named-scopes




   // in Post.php // or ThreadPost.php

    public function scopes()

    {

        return array(

            'published'=>array(

                'condition'=>'status=1',

            ),

            'recently'=>array(

                'order'=>'create_time DESC',

                'limit'=>5,

            ),

        );

    }



now if you try




Thread::model()->with('posts:published')->FindByPk($id);



and you will get




exception 'CDbException' with message 'CDbCommand failed to execute the SQL statement: SQLSTATE[23000]: Integrity constraint violation: 1052 Column 'status' in on clause is ambiguous' in ...

Stack trace:



this is because Thread also got status too

You can use getTableAlias to disambiguate columns.

For example:




    public function defaultScope() 

    {

        $a = $this->getTableAlias(false, false);

            return array(

                'condition' => $a . ".status = 1",

            );

    }



If I used default scope I’ll run into the case when I need to deactivate the default scopes

http://www.yiiframework.com/forum/index.php?/topic/4781-disable-the-default-scope-of-a-model/

Yii 1.1.9 got a new feature, which is that I can use a named scope in relation




        public function relations()

        {

                return array(

                                        'posts' => array(self::HAS_MANY, 'ThreadPost', 'thread_id', 'scopes'=>'published'),

                                        'all_posts' => array(self::HAS_MANY, 'ThreadPost', 'thread_id'),

                );

        }




    public function scopes()

    {

        $t=$this->getTableAlias(false, false);

        return array(

            'published'=>array(

                'condition'=>"$t.publish_status=1",

            ),

        );

    }



but again, If I do this then when using all_posts (including unpublished eg. for some admin page) it would make another join or (another query)

also think about the case of with(‘posts.user’, ‘all_posts.users’) …etc.

so I blieve the subrelation concept I suggested works better for such cases