CakePHP pagination with HABTM models

I’m having some problems with creating pagination with a HABTM relationship. First, the tables and relationships:

requests (id, to_location_id, from_location_id)
locations (id, name)
items_locations (id, item_id, location_id)
items (id, name)

So, a Request has a Location the request is coming from and a Location the Request is going to. For this question, I’m only concerned about the “to” location.

Request --belongsTo--> Location* --hasAndBelongsToMany--> Item

(* as "ToLocation")

In my RequestController, I want to paginate all the Items in a Request’s ToLocation.

// RequestsController
var $paginate = array(
    'Item' => array(
        'limit' => 5,
        'contain' => array(
            "Location"
        )
    )
);

// RequestController::add()
$locationId = 21;
$items = $this->paginate('Item', array(
    "Location.id" => $locationId
));

And this is failing, because it is generating this SQL:

SELECT COUNT(*) AS count FROM items Item   WHERE Location.id = 21

I can’t figure out how to make it actually use the “contain” argument of $paginate

Any ideas?

Here is Solutions:

We have many solutions to this problem, But we recommend you to use the first solution because it is tested & true solution that will 100% work for you.

Solution 1

after 3 days searching, I found the way

var $paginate = array('Post'=>array('group'=>'Post.id'));

It’s recomended to add group, because sometimes we will get duplicte posts in different categories

$this->Post->bindModel(array('hasOne'=>array('CategoriesPost')), false);
$out = $this->paginate('Post', array('CategoriesPost.category_id'=>array(1,4,7,6)));

Add false to use bind model to all queries, not only to the following

Solution 2

To paginate HABTM, you need to temporarily bind ‘hasOne’ join model to model which you paginate:

// prepare to paginate Item
$this->Item->bindModel(array('hasOne'=>array('ItemsLocation')));
$contain['ItemsLocation']=array();
$conditions[]=array('ItemsLocation.location_id'=>$locationId);
$order = array('Item.created' => 'desc'); // set order
...
$items = $this->paginate('Item', compact('conditions','contain','order'));

Solution 3

I’ve been able to get it working somewhat, but the solution doesn’t feel very cakey at all.

$items = $this->paginate(
    $this->Request->ToLocation->Item,
    array(
        "Item.id IN ("
        . "SELECT item_id FROM items_locations "
        . "WHERE location_id = " . $locationId
        . ")"
    )
);

Note: Use and implement solution 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply