Is Codeigniter Datamapper other_field must append _id automatically? - php

I have a problem with Codeigniter Datamapper when Joining table. I have 2 table:
Purchase
purchase_id, ref ...
Stock
stock_id, purchase_ref...
I created a has_many relation :
For purchase:
public $has_many = array(
'stock' => array(
'join_table' => 'stocks',
'other_field' => 'purchase_ref',
'cascade_delete' => TRUE
)
);
For stock:
public $has_one = array(
'purchase'=>array(
'join_other_as' => 'purchase_ref',
'join_table' => 'purchases',
'cascade_delete' => FALSE
)
);
But datamapper seems append _id to the column that I wanna join and it be comes purchase_ref_id
and the sql statement becomes something like:
...LEFT OUTER JOIN "stocks" stocks ON "purchases"."id" = "stocks"."purchase_ref_id"...
Is that anyway to avoid appending _id when joining table in datamapper? thx a lot.

This is a hard coded behaviour (for example here, but in many places in the code) and you can't change it with the current version. I'm afraid you would either have to patch the library's code or change your db schema.

Related

Cakephp count containable records

Cakephp 2.6
Schools hasMany Pupils and I am running a containable query from the Schools model. In this query I want to count the number of pupils each school has but I can't get it to work.
I've tried using the following virtual field, but it doesn't work
$this->virtualFields['total'] = 'COUNT(Pupil.id)';
and the following is giving inaccurate counts
$contain = array(
'Pupil' => array(
'fields' => array(
'CONT(Pupil.id) AS total'
)
),
);
I don't want to use counterCache as data can be mass imported to both tables outside of the framework so I can't rely on it to update the count field.
How can I get this count to run correctly?
Try to add grouping by Schools:
$contain = array(
'Pupil' => array(
'fields' => array(
'CONT(Pupil.id) AS total'
),
'group' => array('Pupil.school_id') // doesn't really know how you called `school` relation column...
),
);

Multiple M2M in DataMapper ORM issue

I am building a small core piece for my DataMapper ORM library in CodeIgniter to control user's access/edit rights (CRUD) over DataMapper objects itself. For this I want to link a DataMapper Entity, Userrole and Userright together.
The documentation is here http://datamapper.wanwizard.eu/pages/advancedrelations.html (Under head; Multi-table Relationships).
So if I fill in the join table by hand I can just get the values without any problems. The only problem is saving the relationship.
Here are my Model-rules
Userrole
var $has_many = array(
'userright' => array(
'join_table' => 'dm_entities_userrights_userroles',
),
'dm_entity' => array(
'join_table' => 'dm_entities_userrights_userroles',
),
);
Userright
var $has_many = array(
'dm_entity' => array(
'join_table' => 'dm_entities_userrights_userroles',
),
'userrole' => array(
'join_table' => 'dm_entities_userrights_userroles',
),
);
Dm_entity
var $has_many = array(
'userrole' => array(
'join_table' => 'dm_entities_userrights_userroles',
),
'userright' => array(
'join_table' => 'dm_entities_userrights_userroles',
),
);
It seems odd I declared the table twice constantly, but it seems not to work if I don't.
So here's my retrieval code for this relation form the perspective of the Userright;
$userrole = new Userrole;
$userrole->get_where(array('name' => 'Administrator'));
$dm_entity = new Dm_entity;
$dm_entity->get_where(array('name' => 'User'));
$userrights = new Userright;
$userrights->where_related($userrole);
$userrights->where_related($dm_entity)->get();
$output = '';
foreach ($userrights as $userright) {
$output .= '<i><b>'.$userright->name.'</b></i> and ';
}
$output = substr($output, 0, -5);
echo '<b>'.$userrole->name.'</b> has the right to '.$output.' the DataMapper entity <b>'.$dm_entity->name.'</b>.'.br();
So this works without any problems either. But now the saving part:
$new_userright = new Userright;
$new_userright->get_where(array('name' => 'Update'));
$new_userright->save(array($userrole, $dm_entity));
Which results in two entries in the dm_entities_userrights_userroles table where 1 entry has dm_entity_id empty and the other the userrole_id empty.
I hope to avoid making a separate Model for the join table to solve this.
Does anyone know how I can get this to work so it makes one correct entry, instead of two scattered ones?
This is not going to work (as you have noticed).
A relation is between two models, and for each Many to Many relation you need a separate join table.
Although technically you can create a join table with 10 FK's to 10 different models, Datamapper will not be aware of that, and will treat the table as different for every relation, causing duplicates to appear.
The only way to make this work, is to define a model for the join table too, and give that one-to-many relations to each of the other models. You can then use that model to manually add relations by assigning or updating the FK values, and still use the many-to-many relations for retrieval and update.

how to set a relation with empty value?

I have 2 tables namely Equipment and Supply.
I have used array_merge in yii php to serve as union for two different tables for the purpose of diplaying them in a single grid.
Everything works fine with the fields that is common with the two tables. The problem is when I try to display a field that is only existing in one of these two tables. It says Property "Supply.equipType" is not defined" because only equipment has the equipType relation.
in my gridView:
array(
'name'=>'equipment_type',
'value'=>'$data->equipType->name',
),
in my controller where I did the merging:
$prov1 = new CActiveDataProvider('BaseEiEquipItem', array(
'criteria' => array(
'condition' => 'id>0'
)));
$prov2 = new CActiveDataProvider('BaseSiReceivedItem', array(
'criteria' => array(
'condition' => 'id>0'
)));
$records=array_merge($prov1->data , $prov2->data);
$provAll = new CArrayDataProvider($records,
array(
'sort' => array( //optional and sortring
'keyField'=>false,
'attributes' => array(
'id', 'description',),
),
'pagination' => array('pageSize' => 10) //optional add a pagination
)
);
$this->render('create',array(
'model'=>$model,
'searchModel'=>$searchModel,
'modelGrid'=>$modelGrid,
'provAll' => $provAll,
));
in my equipment model :
public function relations() {
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'equipType' => array(self::BELONGS_TO, 'BaseRefEquipmentType', 'equipment_type'),
);
}
Any idea on how to solve this? Is there any way to fake a relation or something?
thanks in advance
Just add condition to check if equipType is there:
'value'=>'isset($data->equipType) ? $data->equipType->name : ""'
Using sql to merge results would be better, though.
Can't you just use plain SQL union?
If Equipment has 2 fields (a, b) and Supply has 2 fields (b, c), you can do union like this:
SELECT a, b, null FROM Equipment
UNION ALL
SELECT null, b, c FROM Supply
This problem shows, that it's probably not a good idea to mix different model types in one gridview. If you have a model that has no equipType then it's pretty obvious, that you can't show this column in a gridview. So what would you expect?
As a (dirty) workaround you can add all missing columns as pseudo attributes to the models where they are missing:
public $equipType;

CakePHP: Using multilevel Containable Behaviour

I've been quite some time trying to use the Containable Behavior in CakePHP but I can't get to make it work as I expected.
My application is different, but to simplify I'll put this example. Let's say I have a forum with threads and activities, and the activities can be rated. The general relations would be:
Forum: hasMany [Thread]
Thread: belongsTo [Forum], hasMany [Activity]
Activity: belongsTo [Thread], hasMany [Rating]
Rating: belongsTo [Activity]
What I want to achieve is, using the find method, get all the ratings performed on a certain forum. What I suppose should be done is the following:
$this->Rating->find('count', array(
'contain' => array(
'Activity' => array(
'Thread'
)
),
'conditions' => array(
'Thread.forum_id' => 1
)
));
But the result query is:
SELECT COUNT(*) AS `count` FROM `ratings` AS `Rating` LEFT JOIN `activities` AS `Activity` ON (`Rating`.`activity_id` = `Activity`.`id`) WHERE `Thread`.`forum_id` = 1;
I've accomplished this using the 'joins' option, but it's more complex and I have to use this kinda action in many situations.
All the files related with the example can be found here: http://dl.dropbox.com/u/3285746/StackOverflow-ContainableBehavior.rar
Thanks
Update 23/11/2011
After investigating the framework and thanks to the answers of Moz Morris and api55 I found the source of the problem.
The basic problem was that, as I understood CakePHP, I thought it was querying using joins each time. The thing it that it doesn't do that, the real operation it would perform to obtain the result I was looking for would be something like this:
SELECT * FROM Rating JOIN Activity...
SELECT * FROM Activity JOIN Thread...
SELECT * FROM Activity JOIN Thread...
...
Meaning that it would do a query to get all the activities and then, for each activity, perform a query to get the Threads... My approach was failing not because of the Containable Behaviour being used wrong, but because the 'conditions' option was applied to all queries and, on the first one, it crashed because of the absence of the Thread table. After finding this out, there are two possible solutions:
As api55 said, using the conditions inside the 'contain' array it would apply them only to the queries using the Thread table. But doing this the problem persists, because we have way too many queries.
As Moz Morris said, binding the Thread model to Rating would also work, and it would perform a single query, which is what we want. The problem is that I see that as a patch that skips the relations betweem models and doesn't follow CakePHP philosophy.
I marked api55 solution as the correct because It solves the concrete problem I had, but both give a solution to the problem.
First of all, have you put the actAs containable variable in the appModel?? without it this beahaviour won't work at all (i see it is not working correctly since it didn't join with Thread table)
I would do it from the top, i mean from forum, so you choose your forum (im not sure you want forum or thread) and get all its rating, if theres no rating you will end up with the rating key empty.
something like this
appModel
public $actsAs = array('Containable');
rating controller
$this->Rating->Activity->Thread->Forum->find('count', array(
'contain' => array(
'Thread' => array(
'Activity' => array(
'Rating' => array (
'fields' => array ( 'Rating.*' )
)
)
)
),
'conditions' => array(
'Forum.id' => 1
)
));
Then if you need only a value in rating table just use Set:extract to get an array of this value.
As you did it IT SHOULD work anyways but i sugest not to use forum_id there, but in conditions inside contain like this
'contain' => array(
'Activity' => array(
'Thread' => array(
'conditions' => array('Thread.forum_id' => 1)
)
)
),
Also, never forget the actsAs variable in the model using the containable behaviuor (or in app model)
Whist I like api55's solution, I think the results are a little messy - depends on what you intend to do with the data I guess.
I assume that when you said using the 'joins' method you were talking about using this method:
$this->Rating->bindModel(array(
'belongsTo' => array(
'Thread' => array(
'foreignKey' => false,
'conditions' => 'Thread.id = Activity.thread_id',
),
'Forum' => array(
'foreignKey' => false,
'conditions' => 'Forum.id = Thread.forum_id'
)
)
));
$ratings = $this->Rating->find('all', array(
'conditions' => array(
'Forum.id' => 1 // insert forum id here
)
));
This just seems a little cleaner to me, and you don't have to worry about using the containable behaviour in your AppModel. Worth considering.

Model Associations and Data Modelling

I am developing a web app for an art gallery, and I want to check I have set up the data model correctly.
I have a people table and an artists table. Some of the people are artists and some are not.
I also have a products table and each product belongs to one artist.
Below is a simplified version of the tables:
products
********
id
title
artist_id
artists
*******
id
profile
rating
person_id
people
******
id
first_name
last_name
I need to be able to retrieve the name of the artist when performing the find() method on the Product model.
Have I set up the data model correctly and if so what is the best way to retrieve the artist's name without getting lots of unwanted data?
It's possible to use CakePHP's bindModel & unbindModel to build up relations that don't rely on Cake's automagic goodness.
Since you were interested in doing the find on the Product model, I'll set out an example that can be called from the Products controller:
// unbind Artist
$this->Product->unbindModel(array(
'belongsTo' => array('Artist')
));
// bind Artist & Person using explicit conditions
$this->Product->bindModel(array(
'hasOne' => array(
'Artist' => array(
'foreignKey' => false,
'conditions' => array('Artist.id = Product.artist_id')
),
'Person' => array(
'foreignKey' => false,
'conditions' => array('Person.id = Artist.person_id')
),
)
));
// return the person
$person = $this->Product->find('first', array(
'conditions' => array(
'Product.id' => 1 // or any other condition
),
'fields' => array(
'Person.first_name',
'Person.last_name'
)
));
What's happening here?
Firstly, we unbind the Product model's relationship with the Artist model.
Secondly, we bind the Artist and Person models by explicitly defining the relationships and disabling Cake's automatic forigenKey connections.
Lastly, we do a 'first' find on the Product model and only request the 'first_name' & 'last_name' of the Person.
Each 'Product' belongs to an 'Artist' and each 'Artist' hasAndBelongsToMany 'Product'
You will also need the following table:
artists_products
****************
id
product_id
artist_id
http://book.cakephp.org/#!/view/1044/hasAndBelongsToMany-HABTM

Categories