I have 2 models: Attorney and Powers.
Where: Attorney HABTM Powers
When I add a new Attorney, I select many checkboxes that correspond to the Powers.
My question is: When I edit an Attorney, how do make the checkboxes appear selected?
Sorry my bad english.
You need to actually retrieve the HABTM data.
So, for instance, you can use CakePHP's Containable Behavior and get them like this:
$attorney = $this->Attorney->find('first', array(
'conditions' => array(
'id' => $id
),
'contain' => array(
'Power'
)
));
Then, assuming you name your form-field correctly, they'll populate checked or unchecked automatically.
Without that, you would just be receiving the Attorney data, and the View would have no idea which to check or not.
Related
I am currently working on an application in CakePHP to display (but not modify) the contents of a legacy database. I am not able to change the database structure in any way.
There are six tables I am working with and the relationships are outlined in this awful paint diagram:
I have currently defined the models as follows:
http://pastebin.com/4aEaSnQp
(I decided to put it in a PasteBin as it is a bit of a wall of text, I am happy to edit it into the post if preferred).
What I would like to do is use one controller to pass relevant linked data from my controller to the view from essentially all of these tables. However, even if I set recursion to 2 the links only seem to go so far - for example if I try to find data from the 'Ctit' table with code like this:
$this->set('contracts',$this->Ctit->find('all',
array(
'recursion' => 2,
'conditions' => array('Title.TITNO' => $id),
'fields' => array('Contract.CONNO','Title.TITLE','Contract.CONDATE','Territory.DESCRIPTION')
)
));
The resulting query does not include the "Territory" table and so throws back an error.
What am I doing wrong here? Is it possible to retrieve the data in this way or might I have to run multiple finds or write my own queries (something I am hoping to avoid)?
Thanks in advance,
Kez
So... condensing the comments.
Basically it's a Containable problem. To get everything related to a single model (meaning, the 5 other models), a way to do it would be like
$this->Ctit->find('all', array(
'contain' => array(
'Royalty',
'Title',
'Contract' => array('Publisher', 'Territory')
)
You can also add options to that array, like any other query, for example order, fields, conditions, etc.
Example:
$this->Ctit->find('all', array(
'contain' => array(
'Royalty' => array('order' => 'id DESC'),
'Title' => array('fields' => array('id', 'name'),
'Contract' => array('Publisher',
'Territory' => array('conditions' => array('name' => 'terr'))
)
One thing to have in mind is to always use
public $actsAs = array('Containable');
(mind that s in actsAs, some people have had trouble because of that) otherwise your models won't behave containabl-y and you'd think this whole thing doesn't work.
Containable behaviour is useful for this, because you don't have to do a lot of finds, but keep in mind that cake does a lot of queries behind scenes to use containable. If you want this to be just one big query, use joins (found it!)
Also, be aware that this query might get big real quick. You are basically asking for all records with all 5 associations, when you reach a memory limit notice you are going to remember me. I doubt you want to show all data for every record on one view, so reconsider, maybe a simple paginate first and then a single view for each single record (in that case, the find will be a find('first') and that's lot better than find('all').
I have a database that uses the Party analysis pattern to simplify associations (as it has to store info on both people and organisations, both of which have common attributes). Therefore my associated models can get quite deep. My question is what is the best way to save my data? Take for example saving an employee, the Employee table is not directly connected to Party table, however if I nest my models in an array like so:
array(
'Party' => array(
'Email' => array(...),
'Address' => array(
'Country' => array(...),
'Location' => array(
'State' => array(...)
)
),
'PhoneNumber' => array(...),
'Person' => array(
'Language' => array(...),
'Skill' => array(...),
'Employee' => array(
'WorkHistory' => array(...),
'Qualification' => array(...)
)
)
)
)
I can use saveAll, and it will save everything. That means on my forms I have field names such as Party.Person.Employee.start_time for example. However I was told that doing it this way is not really following CakePHP conventions. The other alternative is to have my array like this:
array(
'Party' => array(...)
'Email' => array(...),
'Address' => array(...),
'Country' => array(...),
'Location' => array(...),
'PhoneNumber' => array(...),
'Person' => array(...),
'Language' => array(...),
'Skill' => array(...),
'Employee' => array(...),
'WorkHistory' => array(...),
'Qualification' => array(...)
)
However if I call saveAll on this, then not everything will be saved as some of the tables/models are not directly connected to Party. The only way to save such a structure, that I know of is, to use Transactions.
So I was wondering what the CakePHP experts think is the best way to approach a situation where you need to save very deep associated models?
Edit:
From what I can gather based on the responses I have had, for the model you are calling saveAll on, those other models that are directly associated to it do not need to be nested in that models array. However if you also want to save models that are not directly associated to it then you need to nest those inside a model that is directly associated to the model you are calling saveAll on. Sorry I know that's confusing so here is my revised array structure in light of this information:
array(
'Party' => array(...),
'Email' => array(...),
'Address' => array(
'Country' => 'attribute',
'Location' => 'attribute',
'State' => 'attribute'
),
'PhoneNumber' => array(...),
'Person' => array(
'Language' => array(...),
'Skill' => array(...),
'Employee' => array(
'WorkHistory' => array(...),
'Qualification' => array(...)
)
)
)
Person is directly associated to Party and so does not need to be nested within Party. However Employee (a type of Person) is not directly associated to Party, and so for it to save the Employee data when calling saveAll on Party, it needs to be nested within Person because the Person model is directly associated to Party. (FYI: Party hasOne Person and Person hasOne Employee. This is the best way I could figure out to structure the inheritance in my database). By structuring it this way, the application is both conforming to CakePHP standards and saving the data with minimal effort.
Well, the explanation kinda was there, it's simply the CakePHP conventions, and we all know what can happen when not following conventions, right?
As I said, following the conventions helps CakePHP to glue everything together automatically, and using the format shown in your question would require to manually restructure the data received by your Model::find() calls.
So while the structure shown in your question may work for saving (at least for now), it will at least cause you some extra work that isn't actually necessary.
I admit that I could have been a little more clear as you seem to have misunderstood me. I didn't wanted to say that the array should be flat altogether, it was just the Person entry that was wrongly placed, and so I only mentioned that one.
Of course in the end it all depends on the type of association, and there are examples for all that in the linked docs: Cookbook > Models > Saving Your Data
Generally you should be able to save as deep as you want using saveMany() or saveAssociated() as long as there aren't any HABTM associations where you create new records instead of assigning existing ones.
You can get started by referring to the structure returned by Model::find(), make sure you are saving on the main model (in this case Party), and depending on the associations/number of records use
save() (single record, no associations (except for HABTM))
saveAll() (wrapper for saveMany and saveAssociated)
saveMany() (multiple main model records, possible associations (require using the deep options))
or saveAssociated() (single main model record, including associations (may require using the deep option)
However this may not apply to all situations, as mentioned you may for example run into problems with HABTM associations, depending on whether you wan to create new records you may need to save them separately, but this isn't really the place to explain every possible situation, especially because the basics are already covered in the Cookbook.
I'd suggest that you study the docs, stick to the conventions, learn by trying, and check out the core source and tests, that's how everyone of us learned it. And when you have a problem with a specific situation that hasn't already been covered, just create a new question
I have three models, Users, Comments and Pages.
Users has many Comments, and Comments belong to Pages.
All models use the containable behavior, and default to recursive -1.
If I call a find() query on Comments, with the contain request including the Page model's field, this correctly returns the results using a single query, automagically joining the Page table to the user.
If I call a similar query from the User model (containing Comment and Comment.Page), the result is a query to source the Comments, followed by a query per comment to source the relevant Page.
Is there a way to configure the models to maintain the JOIN optimisation? I assumed the belongsTo declaration on the related model (Comments) would follow through to the host model (Users).
UPDATE
I should clarify, my question used a simplified version of my actual case study. Although the minimal solution I require would include this initial Model hasMany Model belongsTo Model structure, I am also looking for the solution at one or more additional belongsTo Models down the chain (which, I though, would automagically use LEFT JOINs, as this would be feasible).
Hmm that's interesting. That's a sort of optimization that should be implemented in the core :)
At any rate, I think you could get the same results (perhaps formatted differently) by building the query a little differently:
$this->User->Comment->find('all', array(
'conditions' => array(
'Comment.user_id' => $userId
),
'contain' => array(
'User',
'Page'
)
));
By searching from the Comment model, it should use two left joins to join the data since they are both 1:1 relationships. Note: The results array may look a little different than from when you search from the User model.
So are you asking if there is an easier way to just contain all your queries? If you want to contain everything within the current controller. You could do the contain in the beforeFilter() callback and it would apply to all your queries within that controller.
I am not quite sure if I understand your question, but I think you have a problem with the many sql-calls for the Comment -> Page linkage? If that is correct, then
try linkable behaviour which reduces sql calls and works almost as contain does
or if its pretty much the same data you want, then create a function in a specific model from where you are happy with hte sql calls (for example the Comment-Model) and call it from the user model by $this->Comment->myFindFct($params);
hope that helps
EDIT: one thing that comes to my mind. You were able to change the join type in the association array to inner, which made cake to single call the associated model as well
I find a good way to do this is to create a custom find method.
As a for instance I'd create a method inside your User model say called _findUserComments(). You'd then do all the joins, contains, etc.. inside this method. Then in your controllers, wherever you need to get all of your user's comments you would call it thusly:
$this->User->find('UserComments', array(
"conditions" => array(
'User.id' => $userId
)
));
I hope this helps.
If model definition like bellow:
Comment model belongs to Page and User.
Page belongs to User and has many Comment.
User has many Page and Comment
code bellow will return one joined query:
$this->loadModel('Comment');
$this->Comment->Behaviors->attach('Containable');
$queryResult = $this->Comment->find('all', array(
'contain' => array(
'User',
'Page'
)
));
The code bellow will return two query. Page and User joined into one query and all comment in another query
$this->loadModel('Page');
$this->Page->Behaviors->attach('Containable');
$queryResult = $this->Page->find('all', array(
'contain' => array(
'User',
'Comment'
)
));
and also bellow code will return three query, one for each model:
$this->loadModel('User');
$this->User->Behaviors->attach('Containable');
$queryResult = $this->User->find('all', array(
'contain' => array(
'Page',
'Comment'
)
));
I have a 'Club' HABTM 'Member' relationship.
From the ClubsController::View($id) view. I wish to show ALL the Members that belong to that ONE Club in a paginating table. I have tried many things but all were not exactly what I was looking for. The most common, seemingly related solution looked like CakePHP multi-HABTM associations with pagination but this would give me ALL the 'Club' that belong to ONE 'Member' from the ClubsController. As apposed to ALL the 'Member' from the ONE 'Club' from the ClubsController. I feel this should be an obvious task.
Im answering because I can't comment on Meibooms answer.
It's wrong, because there shouldn't be a Member.club_id field as its a HABTM relation.
Instead, what you should have is a join table: ClubsMembers.club_id ClubsMembers.member_id
As explained in the answer to the related question you mention, what you need to do is bind a fake hasOne relation to be able to set the conditions you want.
In your particular case:
$this->Club->Member->bindModel(array(
'hasOne' => array('ClubsMember')
), false);
// Set up the paginate options array
$this->paginate['Member'] = array(
'contain' => array(
'ClubsMember',
),
'conditions' => array(
'ClubsMember.club_id' => $club_id,
),
//Group since the HasOne may return multiple rows for each
//(if $club_id is an array of IDs and a Member is in more than one of those).
'group' => 'Member.id',
'order' => 'Member.name',
);
$this->set('members', $this->paginate('Member'));
Ignore the contain part if you don't use ContainableBehavior. (Cake will ignore it anyway.)
It sounds to me like a simple paginate on Member with the conditions of 'Member.club_id' => $id should do the trick for you.
ps. What version of CakePHP are you using? Newer versions are a bit more sophisticated with pagination afaik.
I am new to CakePHP, and while I really enjoy the ease of being able to just select a model and all its associated models, I am trying to figure out if there is an easier way to not just have all the fields selected from each model.
For example, instead of just automatically selecting all fields when I grab the model data, and without having to laboriously specify a fields=>array(...) every time, is there some way I can specify which fields are selected by default?
You can also create your own find method in the model:
function findSelected($options = array()) {
$options['fields'] = array('id','name');
return $this->find('all', $options);
}
in controller:
$this->Model->findSelected(array('order' => 'id ASC'));
Even nicer to merge the options array in the findSelected method, then you can even provide additional fields on the fly.
yes, you can check in beforeFind of that model if the 'fields' key is set, if not, you can set it there. But I would say, other than making the debug looks neater, there's almost no performance gain in doing that. And it's one more thing to keep in mind if you have to make changes to the model.
You want to have a look at the Containable behaviour - http://book.cakephp.org/view/1323/Containable
It's very well documented, but to give you a brief overview
$this->Article->find('all', array(
'contain' => array(
'Author.name',
'Category' => array(
'name',
'icon'
)
)
));
would return all your Articles data, along with just the three other fields.
I have Containable added to my app_model definition because I use it on all but the simplest of finds.