Using both CFormModel & CModel to validate and insert data

I have a simple database which contains questions. The fields of table questions contains the fields:

  • question_id

  • question

  • answer

But in the view, i have organized it in a way that you have to add 3 questions at the same time. I found it hard to combine this with the validate() and error functions in the CModel so i was thinking of doing it this way:

Make a CFormModel, get all the data, validate it, then when ok, do a foreach on each question where i populate a CModel of each question, and then save() it.

How does that sound? (or is there a better way to evaluate my 3 questions in one go without using CFormModel?)

I can tell you probably want to populate an CActiveRecord object (CModel is the abstract base class). I can’t tell if it’s the best solution, it may be.

/Tommy

well it sounds like CModel (actuall CActiveRecord as tri mentioned above) would be the ideal way to go, but I’m not sure what validations you’re attempting so I can’t be 100% sure, maybe by posting your issues with it we might be able to fix the work you’ve already done.

If you’re going to use CFormModel, then you would just do all the validations and works in that model and create manual SQL statements to save to the database and that. There would be no point making both a CFormModel and CModel to do this as CFormModel extends CModel at some point.

You’re looking for CActiveRecord which also extends CModel.

What I can see is this:

set your rules

-> maybe the length of the questions that are being submitted

-> check if the values submitted are valid characters or so on.

(I’m not sure what you want to do but if you specify we’d be more than happy to help :) )

Then with your validation it should process just what you set in the rules. Assuming all your rules are valid uses, this should work no problem. If you want to make custom rules, you can do this. I have and would be more than welcome to show you how. For example, lets say you want to make sure that no letter ‘e’ are used in the question or something. You just design your own personal function to do this and set it up to be used as a rule validation.

Then you can call the save function and everything will work great.

So just give a little more information on what it is you’re trying to do and we’ll be able to help more in depth.

@Tri: Thanks for the correction. Yes, I was talking about CActiveRecord

@Whoopass: Thanks a lot for your long answer and sorry for lack of info, I will fill in some more now.

There is also a group_id field in my table, dont know why i forgot that, so the table is like this:

table: Questions

  • question_id (auto increment)

  • group_id

  • question

  • answer

What I want to do is have a view that shows input fields for a group of 3 questions. They will typically share the same group_id.

My only rule is (for now) that the user cannot enter an empty question or answer, so that is what i have to validate. What I know about validation I learned from the "Creating First Yii Application" tutorial. Problem is that they use CFormModel instead of CActiveRecord.

What I want to achieve is that when for example a user forgets to enter the answer to question 2, then after posting the answer, this field will be red with a warning, and all the other fields will still be filled, not blanked out. In the login example from the documentation CHtml::errorSummary($model) is used.

The problem i ran into while trying to achieve this is that i have 3 question each being a model of the db table Questions. I just couldn’t get this working…

Hope this is sufficient info, and thanks a lot for any suggestions :)

My point is maybe easier to understand if you think of an even more complex setup. Lets say you have 10 db-tables, and make one complex html-table to collect all the data at once. In that case it would seem more easy to make a CFormModel that maches the html-form to evaluate all the user-entry-data, and after put it into the db via SQL directly or CActiveRecord.

Am I making my example clear? :) Hoping for an elegant solution that i can put in my blog that i just started…

Sorry I’ve been very busy, I’ll get around to writing a sample for you tonight to see if we can make some progress for you :)

Why not use tabular input?

@whoopass: no sorry no worry ;) im gratefull for any input at any time

@waylex: this is probably what i want to do in my initial example! thanks for pointing that out :)

i am still curious though, as how to do it in the situation where we have a complex form that receives information about several different models from different db-tables?!

also i didnt see <?php echo CHtml::errorSummary($user); ?> used in the example explaining tabular input, but i will look into it now to see if i can make it work with tabular input

Edit: Finally I got it working! The solution is to pass an array to errorSummary with all the model-objects I want it to summarize in the error message.

Things are so easy when you know how to do it :P

Haha that’s great! I’m truly sorry, I’ve been so busy that I just haven’t had a chance, that now 6 am an hour before I’m leaving for school I mocked up a quick setup for you. Even though yours works, I’ll post this for your reference and anyone else’s later. Truly sorry I couldn’t help you sooner but so glad you figured it out :)




<?php


class Questions extends CActiveRecord

{

    /**

     * The followings are the available columns in table 'User':

     * @var integer $question_id

     * @var integer $group_id

     * @var string $question

     * @var string $answer

     */

                

    /**

     * Returns the static model of the specified AR class.

     * @return CActiveRecord the static model class

     */

    public static function model($className=__CLASS__)

    {

        return parent::model($className);

    }


    /**

     * @return string the associated database table name

     */

    public function tableName()

    {

        return 'Questions';

    }


    /**

     * @return array validation rules for model attributes.

     */

    public function rules()

    {

        return array(

            // These are just some examples of what rules you can use for your variables

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

            // convert question to lower case

            array('question', 'filter', 'filter'=>'strtolower'),

            array('answer','length','max'=>128),

            // convert answer to lower case

            array('answer', 'filter', 'filter'=>'strtolower'),

            // This is the important one, this will mention that you require those fields to be inputted (thus not empty)

            array('question, answer', 'required'),

        );

    }

    

    /**

     * @return actions to perform before validating

     */

    public function beforeValidate()

    {

        // if you need to change something to your variables before validating, do it here:        

        return true;

    }

    

    /**

     * @return actions to perform before saving 

     */

    public function beforeSave()

    {

        // if you need to do something to the variables after validating but before you save, do it here.

          return true;

    }

    

    /**

     * @return actions to perform after saving ie: hash link and validate account

     */

    public function afterSave()

    {

        // if you want to do something after the questions save do it here.    

          return true;

    }


    /**

     * @return array relational rules.

     */

    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(

            /*

             * Sample relations 

             

                 'posts' => array(self::HAS_MANY, 'Post', 'authorId'),


                'author'=>array(self::BELONGS_TO, 'User', 'authorId'),


                'comments'=>array(self::HAS_MANY, 'Comment', 'postId',

                    'order'=>'??.createTime'),

                

                'tagFilter'=>array(self::MANY_MANY, 'Tag', 'PostTag(postId, tagId)',

                    'together'=>true, 

                    'joinType'=>'INNER JOIN', 

                    'condition'=>'??.name=:tag'),     

             */

        );

    }


    /**

     * @return array customized attribute labels (name=>label)

     */

    public function attributeLabels()

    {

        return array(

            'group_id'=>'Group ID',

            'question_id'=>'Question ID',

            'question'=>'Question',

            'answer'=>'Answer',

        );

    }


    public function safeAttributes()

    {

        return array(

            // what's safe for the user to enter

            'question, answer',

        );

    }

}



That’s a simple mock up of how to use the CActiveRecord style model for this. Then all you would have to do is create 3 instances of the class (for your three questions) and then just assign them all the same group_id. This can be done using a loop and then storing information in an array like you have.

The next one I’ll get on top of for you for sure ;) (haha that sounded really bad… I’ll just stop talking now lol).