[Extension] Mongoyii

It is definitely doing something wrong, I’ll look into it more.

This might take some time, I have done somethng to my dev envo which means it is performing every action twice even connecting directly to MongoDB with:




	$m=new MongoClient();

	$db=$m->super_test;

	$db->user->insert(array('d'=>'e'));



So yeah, this will take some time to figure out how and why I have broken my dev envo, I will get back to you once I figure out the problem.

Ok gone through this and I can’t reproduce on thne latest code. I believe I may have had some cache problems within Linux/Apache/Whatever that caused the buggy behaviour before but now that I have gone through each part and got it to start fresh it is working perfect.

Can you show your full code?

Hello, I have a question.

In my model there is a subdocument of class "File". When I retrieve a document from db, I get this subdocument as an array. What do you recommend to do to transform this subdocument into object, because I need the data as an object each time I use it. I thought of using afterFind(), but maybe there is a better way in MongoYii extension?

Best way currently is to actually cast it to a new of its class:




$file=new File();

$file->setAttributes($doc->file);



If you are up for some extra weight and slowness in return for easy then there is a pull request on the github repo that could help you here: https://github.com/Sammaye/MongoYii/pull/38

It should be instantly mergable.

There is a couple of reasons why this hasn’t actually been implemented; mian being that it is quite a weight

Oh, great! thank you. I’ll look at it.

And another question. Maybe there is another discussion about it somewhere :)

I try to use subdocument validator, but I cannot figure out how to get an error for a specific subfield when I use CActiveForm methods.

My application is multilingual and I want to use subdocument for each language.

In the model the rules look like this:




public function rules()

{

    return array(

        array('ru,en,es,ar', 'subdocument', 'type' => 'one', 'rules' => array(

            array('question', 'length', 'max' => 100),

            // more fields, I omitted them for clarity

        )),

    );

}



Next to the form (where $form is CActiveForm and $lang is a language code):




<?=$form->labelEx($model, $lang.'[question]')?>

<?=$form->textArea($model, $lang.'[question]')?>

<?=$form->error($model, $lang.'[question]')?>



I see an error in a summary block


<?=$form->errorSummary($model)?>

but instead of a message I see "Array". And I do not see an error near the field. How should I access the validation errors?

I tried to rewrite method ESubdocumentValidator::setAttributeErrors() and it seems to work like this for my case:




protected function setAttributeErrors($object,$attribute,$messages)

{

    foreach ($messages as $subAttr => $errors) {

        $subAttr = $attribute."[$subAttr]";

        foreach ($errors as $error) {

            $object->addError($subAttr, $error);

        }

    }

}



But anyway there is still the problem of labels, because they are not resolved in error messages for subdocuments. :huh:

Yeah labels and errors is a problem area, very hard to sovle this.

There was a discussion about it earlier in the thread but I remember that nothing was done about it in the end. I could make a change to the getError() function (which is used in CHtml) that would enable perline errors however it was found out that the summry function required a bit more…

I will look into that but I think there is a way to change getError, I will look into that first

Ok I have a solution for inline:




	public function getErrors($attribute=null)

	{

		if($attribute===null)

			return $this->_errors;

		else{

			$attribute=trim(strtr($attribute,'][','['),']');

			if(strpos($attribute,'[')!==false){

				$prev=null;

				foreach(explode('[',$attribute) as $piece){

					if($prev===null&&isset($this->errors[$piece]))

						$prev=&$this->_errors[$piece];

					elseif(isset($prev[$piece]))

						$prev=is_array($prev)?$prev[$piece]:$prev->$piece;

				}	

				return $prev===null?array():$prev;

			}

			return isset($this->_errors[$attribute]) ? $this->_errors[$attribute] : array();

		}

	}



I will test this and push it when I have proven it works. It means that to get subdocuments you now do the dot notation.

I am still unsure how the labels will work is at all, they may have to stay manual.

I have used the bracket notation, i.e.: s[5][d] but I am unsure if that is best, it might be best since dot notation is relation in Yii.

Mind you I could get the summary working if I changed the way that errors were done, made them flat instead of nesting errors in the fields but it is so rare for subdocument errors to be in the summary since they normally denote iterative detail.

But then flattening the errors out could also brings other problems, especially with keep track of those errors through the validator, atm I have the advantage that the validator populates the errors array as it moves through nested classes…hmmm

Yeah I remember now why I didn’t fix the summary function.

Imagine you have a subdocument with a field of addresses. The user gets the name wrong is two addresses suddenly you have two errors in the summary of:

"name was an invalid value"

Found no good way of solving that without the user coding their own custom tailored summary function, just for them.

If there was a way to specify labels for the fields of subdocuments (for those that don’t use classes), the error summary wouldn’t have duplicate/unmeaning messages (if not for arrays, and summary is not probably used with arrays).

As for labels, maybe to add the labels from the main object to the EMongoModel object, which is created during subdocument validation? Make a rule how to name labels which will be passed to subdocuments: subdoc[field] or subdoc.field and use a way of adding label similar to CValidator::addError(), which will take labels from the resulted object.

Hope you understand what I mean, it’s a bit difficult to write in English. :)

Hmm the only way I see is the ability to say:

"The second title was wrong"

and by your answer I would say you wish to be able to specify in the label which position the field is in, i.e. as a label:

"Second Title"

Any other method would result in the same problems with duplicate error message messing up UX.

That could work for the validator and I will look into it but within the HTML form itself when you go to print out the label it will still have to be done manually under that fix.

For error summary I mean subdocuments without multidimensional arrays.

In my application I have subdocuments for languages (example):


news: { en: {title: '', text: ''}, ar: {title: '', text: ''}}

I would like to be able to set labels in the News model like this:




public function attributeLabels()

{

    return array(

        'en[title]' => Yii::t('model_news', 'Title in English'),

        'ar[title]' => Yii::t('model_news', 'Title in Arabic'),

        'en[text]'  => Yii::t('model_news', 'Text in English'),

        'ar[text]'  => Yii::t('model_news', 'Text in Arabic'),

    );

}



When I validate subdocuments, in summary will be:

"Title in English is too long (maximum is 100 characters)."

Ok I will look into that one, hmm I would have to try and define…well actually I do already define the difference between multi or not.

I’ll take a look at this

Hello again! :)

As I continue to use your extension (thanks for it by the way!), I find new aspects which work not like I expect. So there is the situation…

When I delete subdocument from the main document (by unsetting corresponding attribute) and save the model (using $model->save()), the subdocument is not deleted from db. I looked inside the EMongoDocument::update() method and now I know that the model is not replaced but only defined attributes are updated (by $set operator). It seems logical, but in the end the result is wrong. The resulted document in db is different from my model.

Do you think it is possible to change this behaviour, or maybe introduce some parameter to save() method, which if set would mean document replacing, not attributes updating?

Can you show your code for this?

Do you mean that the field is turning null or is not emptying at all?




class NewsController extends Controller

{

    public function actionEdit($id)

    {

        $model = $this->loadModel($id);


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

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


            try {

                $saved = $model->save();

            } catch (FlashException $e) {}


            if (!empty($saved)) {

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

            }

        }


        $this->render('edit', compact('model'));

    }

...


class News extends EMongoDocument

{

    public function beforeSave()

    {

        foreach ($this->getLanguageCodes() as $lang) {

            if ($this->{$lang}['title'] === '') {

                unset($this->$lang);

            }

        }


        return parent::beforeSave();

    }

...



I have subdocuments corresponding to languages and I want to delete subdocument if no title was provided for that language. But when I save document, the field in db is not emptying. I cat set it to null instead of unsetting the attribute, but the problem is that old attributes remain in db after document saving and there is no way to remove them without using mongodb driver directly ((

Heh you are using unset this would also be a complication in normal Yii.

It actually deletes the property making the variable no longer exist, if you nulled it instead that would produce a null version. It is still not brilliant and I will look into changing update to not use $set, I just remember a reason why I used $set there, it was based upon a common need of others if I remember right.

Yeah, I wouldn’t do it in MySQL, because there is pre-defined structure for each table of course, but MongoDB is structureless and there should be a way to remove old attributes, don’t you think? :)

Now I use null for that purpose, like you suggested.