Having a problem with an AR-angement

Hey everyone.

I have been using Yii for over five years now, but the site I had was simple enough to at most require a 1:n relation between tables. Now, when I wanted to implement private messaging into my site, I quickly realized that implementing actual conversations would require an n:m relation. This looks like that:


ONE User

HAS MANY Conversations


ONE Conversation

HAS MANY Members (= Users that see the messages)

HAS MANY Messages


ONE Message

HAS ONE Conversation

Okay, so this is a typic n:m relation and requires a link between the Users and Conversations, since one convo can have many members, while one user can have many convos.

So I built this lil’ SQL:


CREATE TABLE IF NOT EXISTS `tbl_user_pm_conv_members` (

    `user_id` int(11),

    `conv_id` int(11)

);

CREATE TABLE IF NOT EXISTS `tbl_user_pm_conv` (

    `id` int(11) NOT NULL AUTO_INCREMENT,

    `owner_id` int(11) NOT NULL,

    `subject` varchar(255) NOT NULL,

    PRIMARY KEY (`id`)

);

CREATE TABLE IF NOT EXISTS `tbl_user_pm_msg` (

    `id` int(11) NOT NULL AUTO_INCREMENT,

    `conv_id` int(11) NOT NULL,

    `from_id` int(11) NOT NULL,

    `body` text NOT NULL,

    `sent` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,

    PRIMARY KEY (`id`)

);

(I saw a note about constraints, i dont have them added cause I dont understand why I even need them…)

So far, so good. "tbl_user_pm_conv_members" points towards a conversation and a user. It does not need a primary key, therefore.

And now the bummer. I tried EVERY possible variation that I could find, and this is how I started out.

User.php:


<?php class User extends CActiveRecord {

// ...

public function relations() {

    return [

        "convos"=>[

            self::MANY_MANY,

            "PrivateConversation",

            "tbl_user_pm_conv_members(id,convo_id)"

        ]

    ];

}

// ...

} ?>

PrivateConversation.php:


<?php class PrivateConversation extends CActiveRecord {

// ...

    public function relations() {

        return [

            "messages"=>array(self::HAS_MANY, "PrivateMessage", "conv_id"),

            "owner"=>array(self::BELONGS_TO, "User", "id"),

            'members'=>array(

                self::MANY_MANY,

                "User",

                "tbl_user_pm_conv_members(id,conv_id)",

            ),

        ];

    }

// ...

} ?>

PrivateConversationMembers.php:


<?php class PrivateConversationMembers extends CActiveRecord {


    public function relations() {

        return [

            "member"=>array(self::BELONGS_TO, "User", "user_id"),

            "convo"=>array(self::BELONGS_TO, "PrivateConversation", "conv_id")

        ];

    }


    public function tableName() {

        return "{{user_pm_conv_members}}";

    }


} ?>

PrivateMessage should be obvious.

So…how exactly do I tell my models to work? Because I always get an "unknown column in OR clause" error. Horray, very informative. So it would be awesome if someonje could help me to deal with this, thanks!

Kind regards, Ingwie!

If you have your keys setup right, yii (trhough gii) would have created the appropriate relations for you. Your tbl_user_pm_conv_members, acting as a link between your user table and conversation table actually needs a key, and that would be a compound key consisting of both columns. If you use Mysql Workbench for example, its as simple as as clicking the PK checkbox for both columns. Then you would need to setup foreign keys linking both columns to their respective tables. Gii should then be able to detect these and figure out that you need a MANY:MANY relationship between User and Conversation. You will be able then to jump straight from a User model to all Conversation models using that relation, just as how you would use a HAS MANY relation.

Keys are important for integrity purposes on the database side. Yii then uses them to figure out relations for you

Hey.

I don’t usually use Gii - and for now, i actually can’t either. The system that my Yii app is running on is not supporting HTTP Basic Auth. I am only guessing this method, since whwnever I open Gii up, I get nothing but a blank page…so I guess that is a problem. :)

In the end, I figured out my issue and I was…well, upset. I had the FKs named wrongly. I can now see all conversations that a user has, yay! However, this happyness did not last long.

I related a conversation with id=1 to a user of id=1, and got them related. Then I connected user of id=2 to conversation with id=1 via the many_many relation. To visualize this:


{ // PrivateConversation

    "id": 1,

    "owner_id": 1,

    "subject": "..."

}


{ // PrivateConversation members

    "user_id": 1,

    "conv_id": 1

}

{

    "user_id": 2,

    "conv_id": 1

}

But when print_r()'ing over the members relation, I only see one entry - mine. The other user is not loaded, which leads me to the conclusion that I have a faulty setup still…

In User.php, this connects to the conversations:


            "convos"=>array(

                self::MANY_MANY,

                "PrivateConversation",

                "tbl_user_pm_conv_members(user_id,conv_id)"

            ),

            "my_convos"=>array(

                self::HAS_MANY,

                "PrivateConversation",

                "owner_id"

            ),

(Obviously, this is just a snippet.)

And this is supposed to connect all members of a conversation:


            "members"=>array(

                self::MANY_MANY,

                "User",

                "tbl_user_pm_conv_members(user_id,conv_id)"

            ),

I looked into the docs again and it mentiones customized PK<->FK pairing. I am going to try this one, but I am not expecting much of a change. :)

About the constraitns. I use phpMyAdmin, and the mysql command line. So how would I apply a compound index?

Here’s some code to work with, hopefully it helps you out:

Not sure about phpmyadmin, but you can use the following SQL code and just adjust the table and column names to fit your needs:




ALTER TABLE `user_conversation` 

CHANGE COLUMN `user_id` `user_id` INT(11) NOT NULL ,

CHANGE COLUMN `conversation_id` `conversation_id` INT(11) NOT NULL ,

ADD PRIMARY KEY (`user_id`, `conversation_id`);

ALTER TABLE `user_conversation` 

ADD CONSTRAINT `user_fk`

  FOREIGN KEY (`user_id`)

  REFERENCES `user` (`id`)

  ON DELETE CASCADE

  ON UPDATE NO ACTION,

ADD CONSTRAINT `conversation_fk`

  FOREIGN KEY (`conversation_id`)

  REFERENCES `conversation` (`id`)

  ON DELETE CASCADE

  ON UPDATE NO ACTION;



I have 3 tables there that should look fairly close to what you have: user, conversation, and the table in between user_conversation.

Next, your User and Conversation model should have the appropriate MANY:MANY relation

User.php




public function relations()

{

    return array(

        'conversations' => array(self::MANY_MANY, 'Conversation', 'user_conversation(user_id, conversation_id)'),

    );

}



Conversation.php




public function relations()

{

    return array(

        'users' => array(self::MANY_MANY, 'User', 'user_conversation(conversation_id, user_id)'),

    );

}



Hopefully this will now allow you to grab all conversations a specific user has, and all users tied to a specific conversation. The following is an example for both




// Get all conversations User 1 is involved in

$user = User::model()->findByPk(1);

foreach ($user->conversations as $conversation) {

    echo $conversation->subject . '<br />';

}


// Get all users involved in Conversation 1

$conversation = Conversation::model()->findByPk(1);

foreach ($conversation->users as $user) {

    echo $user->username . '<br />';

}



EDIT:

I just looked over what you have again and it seems like your code should have worked both ways.

This silly constraint did it. I dont know which part especially, but i fixed the sql to fit my scheme and appended it into my base SQL. Phew. Thank you a LOT! :D

…and I shall go back and learn some more ERM/ORM stuff. :)