CodeIgniter Forums
DataMapper 1.6.0 - Printable Version

+- CodeIgniter Forums (https://forum.codeigniter.com)
+-- Forum: Archived Discussions (https://forum.codeigniter.com/forumdisplay.php?fid=20)
+--- Forum: Archived Libraries & Helpers (https://forum.codeigniter.com/forumdisplay.php?fid=22)
+--- Thread: DataMapper 1.6.0 (/showthread.php?tid=11358)



DataMapper 1.6.0 - El Forum - 10-06-2008

[eluser]stensi[/eluser]
@Isuka: Yep, at the moment the save() method, if not passed any parameters, will insert or update a record. If it's passed a parameter (object) then it will insert or update a relationship record.

The worry I have about setting it up to do both in one go, is if you're doing an update to a record, you probably don't want to re-save all the existing relationships that haven't changed. I could try setting it up to save relationships only on new records that have the relationship ID specified but then that means the behavior is slightly different between insert and update. I'll see what I can come up with.

On being able to delete in the way you mentioned, yep, you're right that it would be much easier. I'll see how much of a change would be required for that type of functionality.

@Boyz26: When setting up a relationship between models, you need to specify the relationship in both models, not just one of them. It's looking like you want a Many to Many relationship between Person and Book so modify the Person's $has_many var to be like so:

Code:
var $has_many = array("set" => "sets", "book" => "books");

Also, I expect in your Book model that you should set the $table to be all in lowercase ( "books" ). But, it's not necessary to specify the $table if the plural of the singular model name is simply a matter of adding the letter "s" on the end (like in the case of book to books).

Give those changes a try and let me know how it goes.

UPDATE

Ah, I just noticed that the Constructor of your Book model is "Set". The Constructor must be named the same as the class name (Book). You should change the Book model to be:

Code:
<?php
class Book extends DataMapper {

    var $has_many = array("person" => "people");
    
    function Book()
    {
        parent::DataMapper();
    }    
}

And your Person model to be:

Code:
<?php
class Person extends DataMapper {

    var $table = 'people';

    var $has_many = array("book" => "books");
    
    function Person()
    {
        parent::DataMapper();
    }
}

You should have the following tables:

people
books
books_people


DataMapper 1.6.0 - El Forum - 10-06-2008

[eluser]Paul Apostol[/eluser]
Excellent work and thank you for this very useful library.
Because I've tried it on my application which has 60 tables (with normalization will be more) I got some minor problems and I discovered some things (correct me if I'm wrong):
1. The models must be in the same folder with datamapper.
2. The tables can't have a prefix
3. The ID must be named 'id' for the tables and for joining table 'id_tablename' (sg)

I'll make a lot of models and I prefer to put them in different folders. A solution (maybe) is to have a datamapper class in each folder and to load it in the controller and not in autoloader.

Having so many tables I prefixed them with a keyword (prefix_tablename) and it's a problem with joining tables. Maybe a solution is to have a variable which set a prefix for each model. Also, for the related tables it search for plurals, even when it's not applied.

The last problem is not so important. I prefer a naming convention like 'idTablename' for index and 'tablename_idTablename' for joining tables (redundancy?). With such normalization maybe is better (for me) to use same name of the ID in the joining tables like in the normal tables.

All the best.


PS: I forgot, sometimes I need to extract users related to books, other times books related to users. What is the naming convention for the joining tables?


DataMapper 1.6.0 - El Forum - 10-06-2008

[eluser]Boyz26[/eluser]
Yup I made sure all of those are the as you said. It still gave me all the authors.

I really appreciate your willingness to help.

EDIT:
I started again from scratch and just made the very simple program:

In the controller i have:
Code:
function index()
    {
        $student = new Student();
        $student->where('name','Andrew')->get();

        $book = new Book();
        $book->where('id',1)->get();
        
        $student->save($book);
        
        foreach($student->book->get()->all as $b):
            echo $b->author.'<br />';
        endforeach;        
    }

models/student.php
Code:
&lt;?php
class Student extends DataMapper {

    var $has_many = array("book" => "books");
    
    function Student()
    {
        parent::DataMapper();
    }
    
    
}
?&gt;

models/book.php
Code:
&lt;?php
class Book extends DataMapper {

    var $has_many = array("student" => "students");
    
    function Book()
    {
        parent::DataMapper();
    }
    
    
}
?&gt;

The database tables are
students: id, name
books: id, title, author
books_students: id, book_id, student_id

I populated students and books, and ran index.php, but it still gave me all the authors.

Is there something that I am doing wrong?


DataMapper 1.6.0 - El Forum - 10-06-2008

[eluser]Paul Apostol[/eluser]
@Boyz26: I had the same problem and I fixed like I said. Maybe the joining table is not named as it should

And some changes for the previous point (changes proposals), for the DataMapper class:
1.
Code:
var $table_prefix;

2. in DataMapper function
Code:
// Determine table name
if (empty($this->table))
{
    $this->table = plural(get_class($this));
}
//adding table prefix - added by me
if (!empty($this->table_prefix))
{
    $this->table = $this->table_prefix . $this->table;
}
// end addition

3. changes in _related function
Code:
function _related($table_in, $model, $id)
    {
        // No related items
        if (empty($table_in) || empty($model) || empty($id))
        {
            return;
        }
        if(!is_array($table_in)){
            $table = plural($table_in);

                
        } else {
            $table = $table_in['prefix'] . $table_in['table'];
                
        }
        if (empty($model))
        {
            $model = singular($table);
        }
        $this->model = strtolower($this->model);

        // Determine relationship table name
        $relationship_table = $this->_get_relationship_table($table, $model);

        // Retrieve related records
        if (empty($this->db->ar_select))
        {
            $this->db->select($this->table . '.*');
        }
        
        
        if(is_array($table_in) && !empty($table_in['id'])){
            if ($this->table == $table)
            {
                $this->db->from($this->table);
                $this->db->join($relationship_table, $table . '.'.$table_in['id'].' = ' . $this->model . '_'.$table_in['id'], 'left');
                $this->db->where($relationship_table . '.' . $model . '_'.$table_in['id'].' = ' . $id);
            }
            else
            {
                $this->db->from($this->table);
                $this->db->join($relationship_table, $this->table . '.'.$table_in['id'].' = ' . $this->model . '_'.$table_in['id'], 'left');
                $this->db->join($table, $table . '.'.$table_in['id'].' = ' . $model . '_'.$table_in['id'], 'left');
                $this->db->where($table . '.'.$table_in['id'].' = ' . $id);
            }
        } else {
            // Check if self referencing
            if ($this->table == $table)
            {
                $this->db->from($this->table);
                $this->db->join($relationship_table, $table . '.id = ' . $this->model . '_id', 'left');
                $this->db->where($relationship_table . '.' . $model . '_id = ' . $id);
            }
            else
            {
                $this->db->from($this->table);
                $this->db->join($relationship_table, $this->table . '.id = ' . $this->model . '_id', 'left');
                $this->db->join($table, $table . '.id = ' . $model . '_id', 'left');
                $this->db->where($table . '.id = ' . $id);
            }
        }
        $query = $this->db->get();

        $this->model = ucfirst($this->model);

        // Clear this object to make way for new data
        $this->_clear(TRUE);

        if ($query->num_rows() > 0)
        {
            // Populate all with records as objects
            $this->all = $this->_to_object($query->result(), $this->model, $this->fields);

            // Populate this object with values from first record
            foreach ($query->row() as $key => $value)
            {
                $this->{$key} = $value;
            }
        }

    }

In the model you can declare like this:
Code:
var $table = "tablename";
    var $table_prefix = "prefix";
    var $has_many = array('modelname' => Array('table'=>'relatedtablename', 'prefix'=>'joiningtableprefix', 'id'=> 'tableid'));

PS: no, it's not good Sad I have to test it more and add some other changes. Sorry, tomorrow I'll make the corrections if looks interesting for you.


DataMapper 1.6.0 - El Forum - 10-06-2008

[eluser]stensi[/eluser]
@paul Apostol: Wow, 60 tables! I can see why you prefer to group them into sub-directories, lol. It wouldn't be too difficult for me to modify the __autoload function to search sub-directories within the models folder, if the class isn't found in the same folder as DataMapper. I'll add that to the Road Map.

I'm looking at including a table prefix, which will apply to both the normal and joining tables. Do you have different prefix's for your tables? Using the prefix as a way to group them? I guess this could be accommodated when I introduce prefixes, but in the case of having different prefixes for different models, you would end up having to specify that setting in each of the models, rather than just the main DataMapper model.

The id does have to be "id" and to clarify for the joining tables, it's the singular of the table followed by "_id". I don't plan on changing this naming convention.

When setting up a relationship, you need to specify the $has_many or $has_one in both models. Lets say we have Author, Book and Genre objects, with a Many to Many relationship between the Author and Book and a One to Many between the Book and Genre. You would have the following tables:

Normal
authors
books
genres

Joining
authors_books
books_genres

And the models setup like so:

Author model
Code:
&lt;?php

class Author extends DataMapper
{
    $has_many = array("book" => "books");

    class Author()
    {
        parent::DataMapper();
    }
}

Book model
Code:
&lt;?php

class Book extends DataMapper
{
    $has_many = array("author" => "authors");
    $has_one = array("genre" => "genres");

    class Book()
    {
        parent::DataMapper();
    }
}

Genre model
Code:
&lt;?php

class Genre extends DataMapper
{
    $has_many = array("book" => "books");

    class Genre()
    {
        parent::DataMapper();
    }
}

With that done, you can access the authors from a book, and the books from an author, and onto the genres from there etc.

Code:
$a = new Author();
$a->get();

echo $a->name . ' has written these books:<br />';

$a->book->get();

foreach ($a->book->all as $b)
{
    $b->genre->get();

    echo $b->title . ' (' . $b->genre->name . ')<br />';
}

Going the reverse way:

Code:
$g = new Genre();
$g->get();

echo 'The following books are in the ' . $g->name . ' genre:<br />';

$g->book->get();

foreach ($g->book->all as $b)
{
    echo $b->title . ' was written by:<br />';

    $b->author->get();

    foreach ($b->author->all as $a)
    {
        echo $a->name . '<br />';
    }
}

@Boyz26: I'll setup the same stuff you've mentioned and see what happens for me. Stay tuned.


DataMapper 1.6.0 - El Forum - 10-06-2008

[eluser]Boyz26[/eluser]
Alright. Thanks!


DataMapper 1.6.0 - El Forum - 10-06-2008

[eluser]stensi[/eluser]
Ok, when I ran it without any relationships saved, no relationships were returned (ie, no authors echo'd). After saving one relationship, I got that one relationship back (not all of them like you were getting).

Can you make sure you have the latest version of datamapper.php (1.3.3) in your models folder?

I've attached a zip that has the files I tested with, as well as a SQL script you can run to recreate the tables I used (with some pre-populated data).

Note
Running the SQL script in the zip will DROP the existing books, students, and books_students tables so backup any data in those you want to keep before running the SQL script. Also, replace all occurrances of `datamapper` in the SQL script with the name of your Database so it will work.


DataMapper 1.6.0 - El Forum - 10-06-2008

[eluser]Boyz26[/eluser]
Wow.. I'll check it out. But thanks a lot.


DataMapper 1.6.0 - El Forum - 10-06-2008

[eluser]Boyz26[/eluser]
I tested your file, but it is still the same. So I guess something is wrong with my computer..
I have attached the whole file. I think the only difference that could be, is the autoload part:
Code:
$autoload['libraries'] = array('database');
$autoload['helper'] = array('url', 'form');
$autoload['model'] = array('datamapper','student','book');


Other than that, I really don't know why it is not working for me.

(I got this from the examples controller)
Code:
Showing the students related books.

alpha
id: 1
title: titleOne
author: authorOne
id: 2
title: titleTwo
author: authorTwo
id: 3
title: titleThree
author: authorThree
id: 4
title: titleFour
author: authorFour



DataMapper 1.6.0 - El Forum - 10-06-2008

[eluser]stensi[/eluser]
Ah, that might be it. You shouldn't autoload your models that extend DataMapper as it autoloads them all for you. Change it to:

Code:
$autoload['libraries'] = array('database');
$autoload['helper'] = array('url', 'form');
$autoload['model'] = array('datamapper');

Note that DataMapper is written for PHP5 so if you're running it in PHP4, it might not work correctly.