jonathangeiger / kohana-jelly

See the link below for the most up-to-date code
https://github.com/creatoro/jelly
MIT License
146 stars 34 forks source link

Hierarchial data model #102

Closed paulchubatyy closed 14 years ago

paulchubatyy commented 14 years ago

Hey,

I was trying to implement the Hierarchical model with Jelly, but I've got the issue, when parent record didn't have the ID loaded.

Here is the model:

class Model_Category extends Jelly_Model {

public static function initialize(Jelly_Meta $meta)
{
    $meta->table('categories')
        ->name_key('title')
        ->sorting(array('title' => 'asc'))
        ->fields(array(
            'id' => new Field_Primary,
            'title' => new Field_String(array(
                'rules' => array(
                    'not_empty' => array(TRUE),
                ),
            )),
            'level' => new Field_Integer(array(
                'rules' => array(
                    'not_empty' => array(TRUE),
                ),
            )),
            'materialized_path' => new Field_String(array(
                'unique' => TRUE,
                'rules' => array(
                    'not_empty' => array(TRUE),
                ),
            )),
            'parent' => new Field_HasOne(array(
                'foreign' => 'category.id',
                'column' => 'parent_id',
            )),
            'children' => new Field_HasMany(array(
                'foreign' => 'category.parent',
            )),
            'created_at' => new Field_Timestamp(array(
                'auto_now_create' => TRUE,
            )),
            'updated_at' => new Field_Timestamp(array(
                'auto_now_update' => TRUE,
            )),
        ));
}

public function save($key = NULL)
{
    // Calculating the level
    $this->level = $this->parent->level + 1;
    // Calculating the path
    $this->set_materialized_path($this->get_materialized_path_array());

    parent::save($key);
}

public function get_materialized_path_array(array $path = array())
{
    $path[] = urlencode($this->title);

    if ($this->parent->id > 0) {
        $path = $this->parent->get_materialized_path($path);
    }

    return $path;

}

protected function set_materialized_path(array $array)
{
    // the argument is a reverse array
    $this->materialized_path = implode('/', array_reverse($array));
}

}

So I'm posting:

title: 'test',
parent: 1

Please note that category with id 1 is already in the database. So I set model values procedure:

    $this->entity->set(arr::extract($_POST, array('parent', 'title'));

but than I get the following when echo Kohana::debug($this->entity->as_array()):

array(9) (
"id" => NULL
"title" => string(4) "test"
"level" => NULL
"materialized_path" => NULL
"parent" => object Model_Category(9) {
    protected _original => array(9) (
        "id" => NULL
        "title" => string(8) "Bamboola"
        "level" => integer 1
        "materialized_path" => string(8) "Bamboola"
        "parent" => NULL
        "children" => NULL
        "created_at" => integer 1271169097
        "updated_at" => integer 1271169097
    )
 ..... and so on

Please note that parent id is NULL while it is present in the database and was set to 1.

Any ideas how can that be fixed?

paulchubatyy commented 14 years ago

Basically calling save leads to the data integrity corruption. The parent is saved with id 0 for the first time, but afterwards other parent's cannot be saved. Though the parent id should be as passed to set method.

banks commented 14 years ago

I'm struggling to understand your problem here.

I think you have this right but for clarity: an ID in a foreign key should never be set to 0 that is bad practice. At the top level of the hierarchy (or anywhere alse a relation doesn't exist) the foreign key value should be NULL. I think that is what you are trying but it is a little unclear from your explanation exactly where you think the problem is occuring.

Are you saying the read from the DB is wrong? Or is the problem when setting the relationship up? or is it when saving something?

paulchubatyy commented 14 years ago

The problem is when saving category with the parent. Follow this logic:

I've created the category A with no parent. Everything works fine. I've creating another category B and setting the parent A. Before saving the category B the result is as shown here:

array(9) (
"id" => NULL
"title" => string(4) "test"
"level" => NULL
"materialized_path" => NULL
"parent" => object Model_Category(9) {
    protected _original => array(9) (
        "id" => NULL
        "title" => string(8) "Bamboola"
        "level" => integer 1
        "materialized_path" => string(8) "Bamboola"
        "parent" => NULL
        "children" => NULL
        "created_at" => integer 1271169097
        "updated_at" => integer 1271169097
    )
 ..... and so on

After saving the category A is updated (!!!!) and it's Id is set to 0.

I hope I was more clear this time.

banks commented 14 years ago

OK, thanks for that. It looks like the problem is in the set() logic. Can I just confirm how you were setting the parent of B?

Options are:

//1
$b->parent = $a; // Set an instance

//2
$b->parent = 1; // Set by key
paulchubatyy commented 14 years ago

Option 2: Set the key.

jerfowler commented 14 years ago

Since parent is acting as a foreign key field, shouldn't it be a BelongsTo?

        'parent' => new Field_BelongsTo(array(
            'foreign' => 'category.id',
            'column' => 'parent_id',
        )),
paulchubatyy commented 14 years ago

Tried that one either. Didn't work for me.

banks commented 14 years ago

Parent needs to be BelongsTo I didn't spot that. HasOne and HasMany won't work as there is no foreign key between them! Hasmany always relates to belongsTo and HasOne also always relates to BelongsTo.

If the belongsto change doesn't fix it there is probably still an issue in set() but you won't get far with a HasOne field!

paulchubatyy commented 14 years ago

I've changed the model to use BelongsTo. This shouldn't affect the logic setting the parent, right? I'll test with BelongsTo and post my results in a while.

paulchubatyy commented 14 years ago

Didn't solved the issue. Seems there is really a bug in set method.

paulchubatyy commented 14 years ago

Sorry guys, that's not your bug. I've figured out the bug in my code. Sorry for taking your time.