Welcome Guest, Not a member yet? Register   Sign In
How to have multiple relationships to same model when HAS_ONE in both directions with DMZ?
#1

[eluser]godunov[/eluser]
I'm using DataMapper DMZ, trying to make a flashcard here and I have a Card class and a Card_Side class. Each instance of Card_Side has a relationship with exactly one Card, and each instance of Card has exactly one relationship with two different instances of Card_Side, one called front and the other back. SInce these are one-to-one in both directions, I am not using a join table (for the record using a join table doesn't seem to change anything).

My tables (table_name => attributes):

cards => id, front_id, back_id
card_sides => id, card_id

My Relationships:

Card:
Code:
var $has_one = array(
    'front' => array(
        'class' => 'card_side'
    ),
    'back' => array(
        'class' => 'card_side'
    )
);


Card_Side: *I've tried many things here, nothing works for me
Code:
var $has_one = array(
    'card' => array(
        'other_field' => 'back'
    ),
    'card' => array(
        'other_field' => 'front'
    )
);
// Also Tried:
var $has_one = array('card');

Main Script to Create Card and Card_Sides
Here I create a Card, two Card_Sides, and relate them to each other:

Code:
$this->load->model('card', 'card_model');
$this->load->model('card_side', 'side_model');
$empty_card = $this->card_model->create();
if ($empty_card != NULL)
{
    $front = $this->side_model->create();
    $front->save($empty_card);
    
    $back = $this->side_model->create();
    $back->save($empty_card);
    
    echo '$front->id = '.$front->id.'<br>';
    echo '$back->id = '.$back->id.'<br>';
    
    $empty_card->save(
        array(
            'back' => $back,
            'front' => $front
        ));
    
    echo '$empty_card->front->get()->id = '.$empty_card->front->get()->id.'<br>';
    echo '$empty_card->back->get()->id = '.$empty_card->back->get()->id.'<br>';
    echo '$front->card->get()->id = '.$front->card->get()->id.'<br>';
    echo '$back->card->get()->id = '.$back->card->get()->id;
}

Tables After This Code Runs:

cards:
+------+----------+---------+
| id | front_id | back_id |
+------+----------+---------+
| 8239 | 85 | 86 |
+------+----------+---------+

card_sides
+----+---------+
| id | card_id |
+----+---------+
| 85 | NULL |
| 86 | 8239 |
+----+---------+



Here's my problem. Athough the card's "back_id" and "front_id" fields are filled with the correct ids of the two new Card_Sides, the above code doesn't relate the objects together properly. Also the first Card_Side is getting a NULL for its card_id. The echo statements in the code above print the following:

$front->id = 85
$back->id = 86
$empty_card->front->get()->id = 86 :down:
$empty_card->back->get()->id = 86
$front->card->get()->id = 8239
$back->card->get()->id = :down:

So the Card is only associated with the second Card_Side ("back"), and only the first Card_Side ("front") is associated with the Card.

How do I set up my relationships for Card_Sides here? All the examples I've seen of multiple references to the same class have has_many in at least one direction. How do we do has_one in both directions?

Many thanks for any input.
#2

[eluser]WanWizard[/eluser]
You can't do this without a relationship table.

The problem is that when you do $cardside->save($card), Datamapper is going to try to find the relationship, and the keys that define this relationship. In your setup, it finds two. Which one to use?
#3

[eluser]godunov[/eluser]
WanWizard, many thanks for your reply. I have tried a join table but I still need to define the relationships in the models and I don't know how to do that. The problem I have is the one you cited: a card_side is referenced by both a card->front and card->back attribute. How would having a join table solve this? I see a join having the following attributes: id, card_id, front_id, back_id. These are already represented in the cards and card_sides table though, so what does the join table change? My issue is with the relationship definitions in the models.

Basic restating of my simple problem: How would one do what seems like a very common relationship where a Manager, say, has one Secretary and one Chauffeur, and the Secretary and Chauffeur are both Employees, and each has just one Manager (who is not an Employee in my silly example). One-to-one in both directions, but the Manager cites 2 Employees, each with a different name, while each Employee only cites a single Manager. That's the issue, that the Employee (or Card_Side in my problem above) only cites one Manager (or Card). Can anyone show me how the relationships would be defined in the model, whether using a join table or not?
#4

[eluser]WanWizard[/eluser]
I don't see an easy way to solve this.

I tried faking it by creating a Frontcard and a Backcard model, alias both to the card_side table ($this->table = 'card_sides'), and then define a one-to-one between card and frontcard, and between card and backcard. In terms of relationships, that works, but you can't set automatic relations either, as Datamapper will set the ITFK to NULL before assigning a new one, to make sure that in a has_one you can only have one record having a relation with in this case card_id (and not two, as in your case).

Normally you would just create a one-to-many relation, and take care of the limitations of the relation in your code.
#5

[eluser]godunov[/eluser]
I certainly appreciate your looking into this for me, thank you very much (that's an interesting solution proposal, the aliasing of models to the same table; that was helpful to think about). You must be correct though, and my viewing this as an "obvious and simple" relationship evinces my modest understanding of db relationships. I solved the problem with a hack, not something I'm too happy with but it does work (I may just take your advice and build a one-to-many relationship and handle limitations with the code). My "solution" is to use 2 different foreign keys for Card_Side, one to front_parent and one to back_parent. Now the "front" card_side stores the parent card_id in "front_parent" and similarly for the back. This allows me to get $card->front and $card->back just as I want, but going backwards, from $card_side->card is ugly (set a bool etc, so: if ($card_side->is_front) $parent_card = $card_side->front_parent; else $parent_card = $card_side->back_parent).

It's still odd to me that this seemingly trivial relationship is so complicated, but I suspect that this is one of those questions that will seem absurd to me after a few more months of learning. It just doesn't seem like an obviously backwards way to build the relationships yet.

Thank you again for your help, I've really enjoyed reading and thinking about your comments.




Theme © iAndrew 2016 - Forum software by © MyBB