Simple(or so I thought): relational tags

Hi, I have been trying to wrap my brain around this for the past few days, but for some reason I just can’t grasp it… All I am doing is inputting tags into a database. It should be easy… Anyway, this is what I am doing:

  • Accepts the input "blog,Yii"

  • Add the tags to the database IF the tag is not currently in it

  • Whether it is TRUE or FALSE add the news_ID and tags_ID to the tagsMatch table

This is my basic way of thinking, correct me if I’m wrong.

  • Grab the user tag inputs from the POST: "Yii,blog"

  • Explode the tags by the "," delimiter and clean the tags(cut out the whitespace and convert the tags to lowercase)

  • Store the tags in an Array

  • Loop through the tags

  • Check to see if the tag is in the tags database or not

  • IF TRUE - don’t add to database

  • IF FALSE - add to database

  • While in the for loop, create a newrecord for the tagsMatch that includes the news_ID and tags_ID(add to tagsMatch)

  • End

Here is my current database layout:

Since I can’t embed links, it is like the Relational Database example tutorial(the tbl_category, tbl_category_post, post) relationship

[b]TagsMatch to Tags are actually a MANY TO MANY relationship.

[/b]

This is what I could have problems with:

Why can’t I do … in the foreach loop? (I get the error: “Illegal offset type in isset or empty”)


		$tagModel = new Tag(array('name'=>$tagged));

    		$tagModel->save();

I get weird results with this code:


	        $tagModel->isNewRecord = true;

                $tagModel->primaryKey = NULL;

                $tagModel->name = $tagged;

                $tagModel->save();

News model:


	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(

			'comments' => array(self::HAS_MANY, 'Comments', 'news_ID'),

			'tagsMatch' => array(self::MANY_MANY, 'TagsMatch', 'news_ID'),

			'tags' => array(self::HAS_MANY, 'Tag',array('id'=>'tags_ID'),'through'=>'tagsMatch'),

		);

	}

Tags model:


	public function normalizeTags($attribute)

	{

		$tags = explode(',',$this->name);


		foreach($tags as $value)

		{

			$value = trim($value);

			$value = strtolower($value);

			$tagsArray[] = $value;

		}


		return $tagsArray;

	}

News Controller:


$model=new News;

        $tagModel=new Tag;

        $tagMatcher=new TagsMatch;

        //$tagsMatch=new TagsMatch;


		// Uncomment the following line if AJAX validation is needed

		// $this->performAjaxValidation($model);


		if(isset($_POST['News']))

		{

			$model->attributes=$_POST['News'];

            //if(isset($_POST['previewPost']))

            $model->validate();

            //$model->save();

                                //$this->redirect(array('show','id'=>$post->id))

			//$model->attributes=$_POST['News'];


			if($model->save()) {


				$tagModel->attributes=$_POST['Tag'];


				echo $tagModel->name . "=>";


				$tags = $tagModel->normalizeTags($tagModel->name);

				

				print_r($tags);

				foreach($tags as $tagged)

				{

					if(Tag::model()->findByAttributes(array('name'=>$tagged))===null);

					{

						//$tagModel->isNewRecord = true;

                        //$tagModel->primaryKey = NULL;

                        //$tagModel->name = $tagged;

                        //$tagModel->save();

						$tagModel = new Tag(array('name'=>$tagged));

						echo "     =     " . $tagged . "     =     ";

    					$tagModel->save();

    				}


    				$tagModel->save(false);

    				$tagMatcher->isNewRecord=true;

    				$tagMatcher->news_ID = $model->news_ID;

    				$tagMatcher->tags_ID = $tagModel->tags_ID;

    				$tagMatcher->save();


    				//$this->dbConnection->createCommand("INSERT INTO TagsMatch (news_ID, tags_ID) VALUES ({$this->id}, {$tag->id})")->execute();

						//$tagModel = Tag::model()->findByAttributes(array('name'=>$tagged));


				}


				//$this->redirect(array('view','id'=>$model->news_ID));

			}

				

		}


		$this->render('create',array(

			'model'=>$model,

            'tagModel'=>$tagModel,

		));






I noticed that in the Yii blog demo and the "enhanced" Yii blog demo they have a tags field in their blog posts table. Is this a hack or is there a reason to do this? Thanks for any clarifications!!

Aaron

There is something strange in your controller: you wrote


$model->attributes=$_POST['News'];

where "$model" is a object of class "Tag". So I doubt his attributes should come from a "News" form.

Another important error is with the constructor of an AR record. You can’t give it an array of attributes. So your line


$tagModel = new Tag(array('name'=>$tagged));

should be replaced by


$tagModel = new Tag();

$tagModel->name = $tagged; // or ->attributes = array("name" => $tagged);

Then, I suggest you remove some fat from your controller. You should build a function that takes an array of tags and returns an array of ids (old or just created). It will be easier to test and read your code.

A side-note on perfermance: instead of a foreach, this function could use a single query. Something like


Tag::model()->findByAttributes(array("name" => $tags));

(if the value of an attribute is an array, it gets converted into an SQL "IN").

I’d also recommend to never simply write


$smthing->save();

If there is an error, it will silently fail. You could write instead


if (!$smthing->save()) {

    Yii::log(print_r($smthing->errors, true), "error");

    throw new CHttpException(500, "...");

}

BTW, you don’t need to write


$tagMatcher->isNewRecord=true;

The default scenario, when you call "new" on an AR class, is already a "create" scenario.