Ok I found the problem. If it was a straight forward User / Post table, most of us would have declared Post with a "id" Primary Key and this HAS_MANY loop problem will never appear.
But no, I was using User/Post as an example of my actual application Site / SiteToModuleMap table. And the old SiteToModuleMap table DO NOT have a Primary Key declared.
So @phtamas is right.
The problem is caused by the lack of primary key, hence my HAS_MANY loop only show 1 result. And the workaround is indeed adding this to the model
public function primaryKey()
// For composite primary key, return an array like the following
// return array('pk1', 'pk2');
This is definitely a beginner problem. I will update the title to help others.
Thanks for the effort ScallioXTX and softark!!
from The doc
2. Performing Relational Query
The simplest way of performing relational query is by reading a relational property of an AR instance. If the property is not accessed previously, a relational query will be initiated, which joins the two related tables and filters with the primary key of the current AR instance
. The query result will be saved to the property as instance(s) of the related AR class. This is known as the lazy loading approach, i.e., the relational query is performed only when the related objects are initially accessed. The example below shows how to use this approach: