I am new to CakePHP and have a fairly basic question.
I have two tables : books and users. books and users have a habtm relationship. I have created the MVC for the above.
Now when a user logs into the system, I want the user to be able to reserve a book (ie an entry in books_users), by looking at the results of the 'index' action. What is the API to be used?
$this->Book->save() does not seem appropriate as we aren't creating a book. We only want an association between an existing book and the logged-in user.
I am trying to avoid, retrieving $this->Book, iterating manually through the sub-array User, creating a new sub-array and saving the whole thing back. I am sure there must be a simpler way.
Adapted from Chuck's answer, unsure why edit was pushed back.
In app/Model/Book.php
class Book extends AppModel {
/************************************************************************
* If you want your multiple assoc. to work you must set unique to *
* false, otherwise when you save an entry it will enforce unique *
* on book ID and subsequently your associations will delete previously *
* saved associations, acting more like "User HasMany Books". *
************************************************************************/
var $hasAndBelongsToMany = array(
'User' => array(
'className' => 'User',
'unique' => false
));
public function addUser($bid, $uid) {
$this->data['User']['id'] = $uid;
$this->data['Book']['id'] = $bid;
$this->save($this->data);
}
}
In app/Controller/BooksController.php (or UsersController)
$this->Book->addUser($bid, $uid);
Fat Models / Skinny Controllers. Allows duplicate entries (you need to constrain limits and check for duplicates, otherwise default behaviour makes HMBTM difficult). Does exactly what you want it to, you just need to supply book and user id.
CakePHP doesn't tend to encourage complex associations, and the reason this is because HMBTM is just a convenience and care should be taken when mixing it with other associations, as per the link provided below, self defined associations are more predictable than HMBTM in CakePHP
http://book.cakephp.org/2.0/en/models/saving-your-data.html#what-to-do-when-habtm-becomes-complicated
You simply need to save a record to Book or User that contains the ids of both and it will insert it into the HABTM table.
$this->data['User']['id'] = {USER_ID};
$this->data['Book']['id'] = {BOOK_ID};
$this->Book->save($this->data);
Look at the HABTM table and you will find the record.
did you bake your application? This (basic) functionality will be provided to you for you to adapt.
In short - take the id of the book and the id of the user. Save it to your books_users table.
id | book_id | user_id
1 1 1
2 2 2
3 2 3
If you have set your associations up correctly, when you request a user, all their books will be returned from the join table.
Your logic will need to deal with - number of books available,if a person can reserve more than one book at once...
http://book.cakephp.org/2.0/en/models/saving-your-data.html#saving-related-model-data-habtm
Has an example.
Related
After struggling with this inconvenience for a couple of weeks now, I've decided to open en now topic here. A topic that can help me, but I'm sure it will help some others with this same problem too!
I wonder how I should name the tables, controllers and models of a hasMany through table (thus with additional fields) and it's coupled tables. I tried to stick on the CakePHP naming conventions as discribed in it's cookbook, but this constantly gave me some "Object not found" errors. For practical reason, I'll show you my problem with a multiple-words named table. Perhaps that could be the reason of the problem? ;)
Situation
I have a fansite of a themepark and as you now, a themepark has many attractions. To ride an attraction, you must have a minimal height. Sometimes, small people can only ride it with an adult companion. But most of the time: you are allowed to ride the attraction because you just are tall enough ;)
Now I want to show the information of a specific attraction on my website. Name, content, photos, and so on. In addition to that information, I want to display my guests if they (or their kids) are tall enough to ride that attraction. It should appear like this way:
0m00 -> 1m00: not allowed
1m00 -> 1m30: allowed with an adult companion
1m30 -> 1m90: allowed
Database
I have two tables that are representing two objects: "attractions" & "attraction_accessibilities". In this case, I'm 100% sure the database names are correct.
Table "attraction_accessibilities" (id - name):
1 - Not allowed
2 - Allowed with an adult companion
3 - Allowed
Table "attractions" (id - name):
1 - Boomerang
2 - Huracan
3 - Los Piratas
4 - El Volador
...
Secondly, I should have another table between "attractions" and "attraction_accessibilities". This table should contain:
an id specific for each record
a link to the id of the "attractions" table (attraction_id)
a link to the id of the "attraction_accessibilities" table
(attraction_accessibility_id)
the additional information like "min-height" and "max-height"
I think I should name that table "attraction_accessibilities_attractions". It's a constriction of the two other tables, and I did it that way because CakePHP proposed it when you're making a HABTM association (http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#hasandbelongstomany-habtm).
But unfortunately, when I do call it that way, I've never succeeded to link those models in my application together.
Question
Is there anybody who've had the same problem but found a solution for it? If "yes": how should I name my database tables then and also important: how should I name my controller and model .php files?
Many thanks for the one who could help me and some other hopeless programmers ;)
If you use the HABTM relationship with unique set to keepExisting then you can name the table as you like and set the joinTable parameter according to he name you choosed
i.e. in your Attraction model
public $hasAndBelongsToMany = array(
'AttractionAccessibility' =>
array(
'joinTable' => 'attraction_accessibilities_attractions',
)
);
Instead if you're going to use the hasMany through relation then you can name the table as you like. In fact the so called "hasMany through" is just the concatenation of two hasMany relationships
so. If you name your table restrictions then
class Attraction extends AppModel {
public $hasMany = array(
'Restriction'
);
}
class AttractionAccessibility extends AppModel {
public $hasMany = array(
'Restriction'
);
}
class Restrictionextends AppModel {
public $belongsTo = array(
'Attraction ', 'AttractionAccessibility '
);
}
I tried to find this everywhere but I cant. Here is my problem:
Lets say I have models associated like this:
Student (has many Memberships)
Membership (belongs to Student and TeacherCourse)
TeacherCourse (belongs to Teacher and Course)
Teacher (has many TeacherCourses)
Course (has many TeacherCourses)
When I use membershipsController to send all membership to the view, I get only for example $membership['Membership']['id'] or $membership['TeacherCourse']['id'] BUT NOT $membership['TeacherCourse']['Teacher']['id']...
Which means I get info on Memberships and TeacherCourse because they are directly associated. My question is how can I also get info about Teachers or Courses directly?
You could increase the model's recursion level to obtain deeper associations (set $this->Membership->recursive to 2 or 3), but in general it's better to use the Containable behavior so you can choose which associations you want to retrieve. For example this would return memberships with associated TeacherCourse and Teacher models:
$membership = $this->Membership->find('all', array(
'contain'=>array(
'TeacherCourse'=>array(
'Teacher'=>array()
),
));
See also the full documentation of the Containable behavior at http://book.cakephp.org/2.0/en/core-libraries/behaviors/containable.html
Scratching my head for a few minutes on this one when I realised you need to add the following to the Model first:
public $actsAs = array('Containable');
Just adding the contain array in the controller may not return the variables you're looking for in the view without the above.
I am sure there must be a simple way to do this, but I have been combing the manual and can't find it.
A user creates a new "course" model and in the same form adds a dynamic number of "student" models. Each student belongs to one course. So I receive the data to the controller roughly like this:
Array
(
[Course] => Array(name, etc. etc.),
[Student] => Array(
[0] => Array(student details, etc.),
[1] => Array(student 2 details, etc.))
)
Once the controller receives this, I save the Course data to create the new course. I now have the course unique ID from the database...how do I easily save the student data, adding the course_id automatically to each Student, without having to loop through all of them and adding the course_id manually?
Thanks!
If your model relationships are setup correctly, you can use saveAll().
Check the book on Saving your Data.
I have a table/model of users, and a certain subset of those users have an entry in a "roles" table.
What is the standard way to create a model that has access to all the users who have one or more entries/rows in the roles table?
Thanks!
Welcome to Cake!
One of the coolest and most powerful aspects of Cake is their very capable ORM. Once you figure out Cake's associations and the way they work with databases it's hard to go back to something else. But, there's a couple things we need to make sure we do with our actual physical database schema before we can do this.
So, let's work with some "sample" data.
users
-------------------------------------------------------
| id (PK) | role_id (FK) | username | password |
-------------------------------------------------------
| 1 | 1 | cspray | ad64675 |
-------------------------------------------------------
...and so on and so on.
roles
--------------------------------------------
| id (PK) | name |
--------------------------------------------
| 1 | Benevolent Dictator for Life |
--------------------------------------------
...and so on and so on.
Note the name of the tables, users and roles, and the field names, specifically id and role_id. Those are important. id is normally an auto increment, int field in your database. There's other ways to store your id key but let's not pile too much on at once.
Now, all this and we haven't answered your question yet! Don't worry we're about to get to that. But, first double check your database schema and make sure those table names and columns are named according to Cake convention. Don't worry I'll wait...
...
Ok! Now open up your favorite text editor and navigate to install_dir/app/models/ (where install_dir is the name of the folder you have the framework installed in). Once there create a new file and name it user.php. Inside this file we're gonna use some code that looks like this...
class User extends AppModel {
public $belongsTo = array('Role');
}
And, well, there you have it! You're now free to query the roles table from within your User class by calling methods through Cake's "association chain", $this->User->Role->find(). You can, and probably should, create a Role model just like you created User, sans the $belongsTo. Inside it you can define your own methods to interact with the roles table and give the information you need to User.
I don't want to go too much into how Cake's ORM works though, mostly because if you read through the Cake Cookbook and the Cake API you will find a wealth of knowledge ready to be plucked.
Enjoy your ventures into Cake!
So, your model associations are all setup and you're ready to go. But, where do you go? Well, you'll want to check out Cake's Model behaviors, particularly one of the most important core behaviors, Containable. This will allow you to do exactly what you want to do.
Let's just take a look at some code, I'm going to assume that you're already familiar with the Model::find() method. If not I'd go check that out real quick so you don't get too confused. It's fairly straight-forward though.
class User extends AppModel {
// provides $this->User->Role
public $belongsTo = array('Role');
// gives access to various behaviors
public $actsAs = array('Containable');
// custom function
public function get_users_with_roles() {
return $this->User->find(
'contain' => array(
'Role' => array( // tells ORM to query `roles` table
'Hour', // <-- tells ORM to query `hours` table
'conditions' => array(
'Role.id' => '<> NULL'
)
)
),
'fields' => array(
'User.username'
)
);
}
}
This should return all user's usernames (and possibly their id and role_id as well) that have an entry in the roles table. Note the addition of the new property and the way the "query" is laid out in Cake's ORM. One thing I could suggest getting comfortable with is multi-dimensional associative arrays, Cake uses a lot of them.
This is my code:
$charities = explode(',',$this->data['Charity']['charities']);
foreach ($charities as $key=>$charity){
$data['Charity'][$key] = $charity;
}
$this->Grouping->id = $this->data['Charity']['grouping_id'];
if ($this->Grouping->save($data)){
//Great!
}else{
//Oh dear
}
The code is from an action that gets called by AJAX. $this->data['Charity']['charities'] is a comma separated list of ids for the Charity model, which HABTM Grouping.
The code works fine, except that Cake doesn't seem to check whether that charity is already associated with that grouping, so I end up with lots of duplicate entries in the join table. I can see that this will be a pain later on so I'd like to get it right now.
What am I doing wrong?
Thanks
A few things I spotted:
A. Is $this->data['Charity']['charities'] an array with named keys corresponding to the table columns? Because inside the foreach() loop, you are taking the key and putting it in the $data['Charity'] array. The result could be a wrong format. The $data array for Model saves is commonly formatted as $data['Charity']['NAME_OF_COLUMN1], $data['Charity']['NAME_OF_COLUM2], and so on for each column that wants to be saved. So, if the keys are numbers, then you could be having something like: $data['Charity'][0], $data['Charity'][1], which is wrong.
B. HABTM Associations are saved differently. What I do recommend, is that you take a look at the book's section on saving HABTM. I save a HABTM relation like this (supposing a relation between users and coupons):
$this->data['Coupon'] = array('id' => 1, 'code' = 'BlaBla');
$this->data['User'] = array('id' => 33);
$this->Coupon->save($this->data, false);
So, as you can see, the $data array has a subarray named after the foreign model ('User') with the columns+values that I want to save. Then I save this array to the Coupon model.
C. Be aware that CakePHP will always delete old records and insert new ones within the join table when it is 'updating' HABTM relations. To avoid this, you set the 'unique' field to false in the Model's $HABTM configuration.