I have a Members table:
Members(id, name, gender, head_id)
What I want is to make family relationships based around the head of household (who is a member).
So its like: One member belongs to one Family (defined by a member)
Maybe I could split it into 2 tables, a Members and Families table:
Families(id, head_id)
and the Members table would have a family_id instead of head_id.
The main problem would be on adding new members and modifying the relationships.
EDIT:
All the answers were great.
I ended up doing it manually. Dave's solution is what I was looking for, but didn't workout exactly the way I was hoping.
See "Multiple Relations to the Same Model"
"It is also possible to create self associations as shown below:"
class Post extends AppModel {
public $belongsTo = array(
'Parent' => array(
'className' => 'Post',
'foreignKey' => 'parent_id'
)
);
public $hasMany = array(
'Children' => array(
'className' => 'Post',
'foreignKey' => 'parent_id'
)
);
}
From a Database point of view, I consider you should have 3 tables:
persons (id, passport_number, name, dob, ...)
families (id, father_id, mother_id, surname, number_of_members,... )
families_persons (family_id, person_id)
A family is defined as the union of two persons and might have some other common fields such as surname.
Anyway, if you do it in your way, you can do it with one only table. (father with head_id set to 0, and the rest of the family members with head_id referring to his id.
In case you want to use two tables, controllers can use more than one Model, so it is not a problem to deal with more table in the save action.
You should have two tables. That is the best way, and will offer you the most flexibility and ease of use within your application.
Database Tables
// families
id | name
------------------------------
1 | Smith
2 | Jones
3 | Davis
// members table
id | family_id | name | gender
-----------------------------------
1 | 2 | James | M
2 | 3 | Christine | F
3 | 1 | David | M
4 | 2 | Mark | M
5 | 1 | Simon | M
6 | 1 | Lucy | F
CakePHP Models
Then you just need to define your models so they have the correct relationships.
// app/Model/Family.php
class Family extends AppModel {
public $hasMany = array('Member');
}
// app/Model/Member.php
class Member extends AppModel {
public $belongsTo = array('Family');
}
Then you can retrieve your families like this:
CakePHP Controller
// Find all members that belong to Family 1
$family = $this->Member->find('all', array(
'conditions' => array('family_id' => 1)
));
OR
// Find Family 1 and get all its members
$family = $this->Family->find('first', array(
'conditions' => array('family_id' => 1),
'contain' => array('Member')
));
You shouldn't have any problems with adding new members or modifying the relationships, like you are worried about, but if you run into any specific problems we can likely help you. This kind of model relationship is extremely common.
Related
I have an array, $submenus, in my app that I implode to a delimited string:
$subs = implode(',', $submenus);
The string will look something like this: 'ml_,nc_,msr_'. These values are stored in a field called group_prefix in my submenus table. Each submenu row has a unique group_prefix.
The following code builds menus and submenus from a database:
$menus = $this->Menus->find('all', [
'order' => ['Menus.display_order ASC'],
'conditions' => $conditions,
'contain' => [
'Submenus' => [
'conditions' => [
'Submenus.status' => 1,
'FIND_IN_SET("' . $subs . '", Submenus.group_prefix)'
],
]
]
]);
$this->set('menus', $menus);
It works fine until I add the FIND_IN_SET condition on Submenus. When I do, I get no submenus returned, just the main menus. Debug confirms that the string is formatted propery. Doesn't error out, I just get no resultset.
When I run the submenus query in MySQL, it works.
set #prefixes = 'ml_,nc_,msr_';
SELECT `id`, `name` FROM `submenus` WHERE `status` = 1 AND FIND_IN_SET(`submenus`.`group_prefix`, #prefixes);
+----+---------------------------+
| id | name |
+----+---------------------------+
| 4 | Mission Lessons Module |
| 5 | MSR Module |
| 8 | Work Authorization Module |
+----+---------------------------+
What am I missing?
Answer was to reverse the order of arguments in FIND_IN_SET.
I'm using CakePHP v3
I have a table that looks like this:
Document:
id | section | paragraph
-------------------------
1 2 4
Text:
id | section | paragraph | theText
---------------------------------------
12 2 4 Blah blah
So in SQL I could do something like this;
SELECT * FROM document
INNER JOIN text
ON document.section=text.section
AND document.paragraph=text.paragraph
How can I do something like this in CakePHP using the ORM? The Primary key in both tables is set up to be the id column.
I've looked into foreignKey and binidingKey in Cake's docs, but I can't see how to use multiple columns in those.
http://book.cakephp.org/3.0/en/orm/associations.html.
FWIW, here is a sample of code that shows how I want to access them.
$cond = [
'contain' => ['text']
];
$docs = $this->Documents->find('all',$cond);
Yes, it is possible. Just use arrays to express the columns that should be matched:
$this->belongsTo('Things', [
'bindingKey' => ['key1', 'ke2'],
'foreignKey' => ['fk1', 'fk2']
]);
That will match key1 = fk1 and key2 = fk2
I have access to a database similar to this:
users:
id | name
====|========
1 | Tom
2 | Dick
3 | Harry
4 | Sally
exlusions:
user_id | exclusion
========|==============
1 | French only
3 | No magazine
4 | English only
1 | No magazine
Is it possible to query the database to get a result like this?
$users = [
[
'id' => 1,
'name' => 'Tom',
'english_only' => false, // unset or null would be okay
'french_only' => true,
'no_magazine' => true,
],
// . . .
];
I've been playing around with things like GROUP_CONCAT, PIVOT, and other examples and can't figure out how or if any of it applies.
SQL Fiddle -- thought I could modify this to match my scenario. I didn't get all that far, and as you can see I have no idea what I am doing...
You can use IF in your select to make your 3 columns out of the 1 based on their values.
SELECT id, name,
IF(exclusion='English only',true,false) as english_only
IF(exclusion='French only',true,false) as french_only
IF(exclusion='No magazine',true,false) as no_magazine
FROM users, exclusions
WHERE users.id=exclusions.user_id
I started with #RightClick's answer, but had to tweak it a bit for my SQL server.
SELECT User.id, User.name,
CASE WHEN Ex.exclusion = 'English Only' THEN 1 ELSE 0 END as english_only,
CASE WHEN Ex.exclusion = 'French Only' THEN 1 ELSE 0 END as french_only,
CASE WHEN Ex.exclusion = 'No magazine' THEN 1 ELSE 0 END as no_magazine
FROM users as User
LEFT JOIN exclusions Ex on User.id = Ex.user_id;
So much simpler than what I thought it was going to be after googling and searching SO all day...
I'm new to CakePHP and having a bit of trouble figuring out how to set up model associations.
Say I have 3 tables: payments, reservations and reservation_details with the following data
table reservations
id | confirmation_number | guest_id
1 123 1
table reservation_details -a reservation can have multiple entries (multiple rooms)
id | reservation_id | date | time | room_id | rate
2 1 2014-18-04 13:00 1 9.99
3 1 2014-18-04 13:00 2 4.99
table payments - many payments for one reservation can be made
id | reservation_id | payment_amount | payment_type | guest_id
4 1 14.98 Cash 1
Here are my model Associations
//Reservation model
public $hasMany = array('ReservationDetail', 'Payment');
//ReservationDetail model
public $belongsTo = array('Reservation');
//Payment model
public $belongsTo = array('Reservation');
public $hasMany = array('ReservationDetail' => array('foreignKey' => 'reservation_id'));
What I'm trying to do is be able to search for a payment and it would return the corresponding reservation and reservation_details for that payment. So it would grab any records from reservation_details that share the same reservation_id. Right now the reservation is returned, but the reservation_details is returned empty
The following search returns information from payments and reservations, but an empty array from reservation_details.
$payment = $this->Payment->find('all',array(
'conditions' => array(
'Payment.guest_id' => '1'
)
));
I'm almost positive it's joining the reservation_details table on payments.id = payments.reservation_id rather than payments.reservation_id = reservation_details.reservation_id. When I manually change payments.id to 1 (the reservation_id value), then the reservation_details are returned.
I believe the MySQL query that I'm trying to achieve would be something like
SELECT reservations.*, reservation_details.*, payments.* from payments
INNER JOIN reservations on reservations.id = payments.reservation_id
INNER JOIN reservation_details on reservation_details.reservation_id = payments.reservation_ID
WHERE payments.guest_id = '1'
Payment.php
add containable behavior as-
public $actsAs = array('Containable');
PaymentsController.php
$payments = $this->Payment->find('all', array(
'conditions' => array(
'Payment.guest_id' => '1'
),
'contain' => array(
'Reservation' => array(
'ReservationDetail'
)
)
));
debug($payments);
I have a HABTM relationship between two tables: items and locations, using the table items_locations to join them.
items_locations also stores a bit more information. Here's the schema
items_locations(id, location_id, item_id, quantity)
I'm trying to build a page which shows all the items in one location and lets the user, through a datagrid style interface, edit multiple fields at once:
Location: Factory XYZ
___________________________
|___Item____|___Quantity___|
| Widget | 3 |
| Sprocket | 1 |
| Doohickey | 15 |
----------------------------
To help with this, I have a controller called InventoryController which has:
var $uses = array('Item', 'Location'); // should I add 'ItemsLocation' ?
How do I build a multidimensional form to edit this data?
Edit:
I'm trying to get my data to look like how Deceze described it below but I'm having problems again...
// inventory_controller.php
function edit($locationId) {
$this->data = $this->Item->ItemsLocation->find(
'all',
array(
"conditions" => array("location_id" => $locationId)
)
);
when I do that, $this->data comes out like this:
Array (
[0] => Array (
[ItemsLocation] => Array (
[id] => 16
[location_id] => 1
[item_id] => 1
[quantity] => 5
)
)
[1] => Array (
[ItemsLocation] => Array (/* .. etc .. */)
)
)
If you're not going to edit data in the Item model, it probably makes most sense to work only on the join model. As such, your form to edit the quantity of each item would look like this:
echo $form->create('ItemsLocation');
// foreach Item at Location:
echo $form->input('ItemsLocation.0.id'); // automatically hidden
echo $form->input('ItemsLocation.0.quantity');
Increase the counter (.0., .1., ...) for each record. What you should be receiving in your controllers $this->data should look like this:
array(
'ItemsLocation' => array(
0 => array(
'id' => 1,
'quantity' => 42
),
1 => array(
...
You can then simply save this like any other model record: $this->Item->ItemsLocation->saveAll($this->data). Adding an Item to a Location is not much different, you just leave off the id and let the user select the item_id.
array(
'location_id' => 42, // prepopulated by hidden field
'item_id' => 1 // user selected
'quantity' => 242
)
If you want to edit the data of the Item model and save it with a corresponding ItemsLocation record at the same time, dive into the Saving Related Model Data (HABTM) chapter. Be careful of this:
By default when saving a HasAndBelongsToMany relationship, Cake will delete all rows on the join table before saving new ones. For example if you have a Club that has 10 Children associated. You then update the Club with 2 children. The Club will only have 2 Children, not 12.
And:
3.7.6.5 hasAndBelongsToMany (HABTM)
unique: If true (default value) cake will first delete existing relationship records in the foreign keys table before inserting new ones, when updating a record. So existing associations need to be passed again when updating.
Re: Comments/Edit
I don't know off the top of my head if the FormHelper is intelligent enough to autofill Model.0.field fields from a [0][Model][field] structured array. If not, you could easily manipulate the results yourself:
foreach ($this->data as &$data) {
$data = $data['ItemsLocation'];
}
$this->data = array('ItemsLocation' => $this->data);
That would give you the right structure, but it's not very nice admittedly. If anybody has a more Cakey way to do it, I'm all ears. :)