cakephp model associated with many models - cakephp

I am using CakePHP 2.4 and I need to have a model called Files to store file information for Users, Cases, and Trucks. Now a file can be associated with many other models (Users, Cases, Trucks, etc). In other words I want Users to have Files, Cases to have Files, and Trucks to have Files.
Do I need to create UserFiles model, CaseFiles model, and TruckFiles or can I have a table entry that flags in Files that it is a User record, Case record, or Truck record? I also want to do the same for Notes and Tasks, so there would either be UserNotes, CaseNotes, and TruckNotes or can I have Notes (id, foreign_id, associatedModel(would be Truck, User, or Case), etc..). So that when I create my Users, I have Files that tie to my User.id and have a condition that the associatedModel is User.
I think I will need to create the UserFiles, UserNotes, and UserTasks. But if there is a good design and better way to do this I am open to suggestions.

CakePHP already has a Class named File. See: http://api.cakephp.org/2.4/class-File.html
For this its better to use other name, for example Attach.
User hasMany Attach
Case hasMany Attach
Truck hasMany Attach
Instead of create three foreign keys on 'attaches' table (like user_id, case_id, and truck_id), in this case i prefer create a model_name and model_id fields as foreign key.
schema table attaches:
- id
- model_name
- model_id
- ...
model Attach:
public $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'model_id',
'conditions' => array('Attach.model_name' => 'User'),
),
'Case' => array(
'className' => 'Case',
'foreignKey' => 'model_id',
'conditions' => array('Attach.model_name' => 'Case'),
),
'Truck' => array(
'className' => 'Truck',
'foreignKey' => 'model_id',
'conditions' => array('Attach.model_name' => 'Truck'),
),
);
model User, Case, Truck:
public $hasMany = array(
'Attach' => array(
'className' => 'Attach',
'foreignKey' => 'model_id',
'dependent' => true,
'conditions' => array('Attach.model_name' => 'User'), // Change the condition on Case and Truck model
),
);

You would need to create these models
User
File
Case
Truck
Then, you define model associations as you need.
For example: User hasMany File, Case hasMany File, Truck hasMany File, File belongsTo User, Case and Truck

One way to think about it is if the relationship is one to many (hasMany), many to one (belongsTo), one to one (hasOne), or many to many (hasAndBelongsToMany). With the first three mentioned, you can store the foreign key in one of the models because at least one of the models being associated is associated with only one entry in the other table. In the many to many relationship, you need a join table to store the pairs of foreign keys representing the associations.
In this case, if you're talking about a file that can belong to many users, and users can have many files, then that is a many to many or hasAndBelongsToMany relationship, and then yes, you need a UsersFile join table. If however, a file can only belong to one user, or a user can only have one file, then you're talking about one of the other relationships, and you do not need the join table. The same can be said for files/cases and files/trucks.

Related

How to order the primary model by associated data?

Situation
using Cake 3.2.6
What I want
To display a list of PaymentsByMembers by the members' role_id in ascending order followed by their name
In other words I want to order the primary models by the associated data fields.
What I tried
$this->paginate = [
'contain' => [
'Issuers',
'Recipients', 'PaymentTypes'
],
'conditions' => $conditions,
'order' => ['Recipients.role_id' => 'ASC', 'Recipients.name' => 'ASC']
];
what's the relations?
A Payments by Member belongsTo a Recipient
What did you find?
I can only find a section in the cookbook about filtering by associated data.
There is no equivalent about ordering by associated data.
I will be happy to write that in.

CakePHP 2 Models hasMany as hasOne

I am trying to do this as MVC / CakePHP 2 as possible so if my approach is the incorrect, I would love to know (still learning). I feel like what I am doing should happen in the model and less so in the controller (to follow the fat model skinny controller principals).
I have a hasMany relationship between two tables:
trainings hasMany days.
If I want all the days in a training, this setup works as expected.
But I want (in every instance of training) the first day in a training. My thought process was to setup a hasOne relationship in the Training model as follows:
public $hasOne = array(
...
'FirstDay' => array(
'className' => 'Day',
'foreignKey' => 'training_id',
'fields' => 'FirstDay.training_date',
'order' => array('FirstDay.training_date ASC'),
)
);
In essence training hasOne days as FirstDay.
I assumed that with this setup, if I call a Training object I will get the associated FirstDay.
Instead I get multiple entries for Training -- one for each instance of days for a given training. The SQL that gets output is as follows:
SELECT `Training`.`id`, `Training`.`course_id`, `Course`.`name`, ... `FirstDay`.`training_date`
FROM `tst`.`trainings` AS `Training`
LEFT JOIN `tst`.`courses` AS `Course` ON (`Training`.`course_id` = `Course`.`id`)
...
shortened for your benefit
...
LEFT JOIN `tst`.`days` AS `FirstDay` ON (`FirstDay`.`training_id` = `Training`.`id`)
WHERE 1 = 1 ORDER BY `FirstDay`.`training_date` ASC LIMIT 20
I was assuming that the hasOne would put a limit 1 instead of 20 in the above clause. Since it did not, I tried adding a 'limit' => 1 but that didn't work and the documentation does not mention that as an option in a hasOne relationship. I also do not understand why WHERE 1 = 1 is there but I figure it does not matter since it is a true statement that does not limit anything -- just seems like unnecessary lifting.
The relationship type hasOne is implemented with a LEFT JOIN, and therefore can't support LIMIT as an option, as it would affect the whole query (limiting not only Day but Training too).
There are several approaches to your problem. The most simple is to define your association as hasMany, but setting 'limit'=>1.
public $hasMany = array(
'FirstDay' => array(
'className' => 'Day',
'foreignKey' => 'training_id',
'fields' => 'FirstDay.training_date',
'order' => array('FirstDay.training_date ASC'),
'limit' => 1
)
);
Optionally, you then get rid of the extra numerical index [0] by using Hash::map() after the find():
$this->Training->contain('FirstDay');
$trainings=$this->Training->find('all');
$trainings = Hash::map($trainings, "{n}", function($arr){
$arr['FirstDay']=$arr['FirstDay'][0];
return $arr;
});
For other possible options, see:
Last x blog entries - but only once per user
Method for defining simultaneous has-many and has-one associations between two models in CakePHP?

Find record with at least one HABTM related record in CakePHP

I have two models in my CakePHP application: NewsArticle and Image. A news article can have and belong to many images, but an image is not necessary.
How can I find the first news article in my application that has at least one associated image record, so as any news articles with no related image records are discarded?
You'll have to use an INNER JOIN (see Joins in CakePHP) to limit the find() based on the existence of an associated HABTM item.
Using join is quiet easy, just join the HABTM table using RIGHT JOIN, so only rows with HABTM records are gathered, this is my example for https://www.flylogs.com, i just wanted aircraft that was being used by at least on pilot and their names:
$this->paginate = array(
'contain' => array('Pilot.name'),
'group' => 'Aircraft.id',
'joins' => array(
array(
'table' => 'pilots_aircraft',
'alias' => 'PilotAircraft',
'type' => 'right',
'conditions' => 'Aircraft.id = PilotAircraft.aircraft_id'
)
),
);
$aircraft = $this->paginate('Aircraft');
Important the group clausule, if not stated, it will gather a row for each pilot repeating aircraft!

Associations in data model, how one row can point to different foreign keys?

The Case:
I'm really stuck in thinking about the proper way for linking two tables in my data model, consider this example (which is abstracted from the problem)
Say that I have:
some model called 'Attachment' which contains 'attachment_id','attachment_title',.... etc,
and another model called 'Question' which contains 'question_id', 'attachment_id' (foreign) , 'question_option1', 'question_option2','question_option2','correct_answer', etc.
I already know that each "question" may have an attachment with the question, so we have these relationships so far:
in question model:
var $belongsTo = array(
'Attachment' => array(
'className' => 'Attachment',
'foreignKey' => 'attachment_id',
'conditions' => '',
'fields' => '',
'order' => '',
),
and in attachment model I have :
var $hasOne = array(
'Question' => array(
'className' => 'Question',
'foreignKey' => 'attachment_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
The Problem :
Then a new requirement has arisen : put images for the every option in 'Question' , so instead we can show images not the text, which means that we add new rows in 'Question' table (at least what I did) that called 'question_option1_attachment', 'question_option2_attachment', 'question_option3_attachment' , and these cells will contain a foreign key that points to an attachment_id.
surely this didn't work for me :
'foreignKey' => array('attachment_id', 'question_option1_attachment', 'question_option2_attachment', 'question_option3_attachment') ...
The Question :
How to do the association between One question that has many rows point to different foreign keys ?
what do you suggest, do I need to redefine the whole association somehow ?
As the case specified above, I think so you will be required to create another table that hold the data that defines the mapping between different questions and foreign keys.
This table will only have the ids mapped with the questions and while making a call to the model you will be required to take the table into consideration so check what are the keys associated with the question. These will have multiple entries for a question so you will have to use the JOINS to get the appropriate data.

CakePHP: Unsure how to handle a semi difficult relationship

I'm working on my first CakePHP app, an order/invoice system. The order-part are coming along nicely, but for the invoices I kinda need some help.
The database structure I'm using; A delivery note consists of multiple products, which in turn exist of multiple re-usable elements (like a number of trays and the product itself). Now because some customer order larger quantities, they get lower rates, which are time-based (from week 1 to 9 €1 for element y, and from week 10 to 8 €1.20 for the same element). Of course some customers just have to use the daily prices, which will be stored the same way, just witha nulled customer_id.
Now my problem; I have absolutely no idea how I should tackle the invoice view, or more specifically; what the best way is to get the data, or if I should just go and practice my SQL-writing skills.
I'm not seeing any major problems with your schema. The Containable behavior should make things easy here. Add it to your Invoice model:
var $actsAs = array('Containable');
Make your life easier by adding a DefaultPrice relationship for each element. In your Element model:
var $hasOne = array(
'DefaultPrice' => array(
'className' => 'Price',
'foreignKey' => 'element_id',
'conditions' => array('DefaultPrice.customer_id IS NULL')
)
);
Now to build your invoice, set up and pass a contain parameter to your find operation. (I assume you want to show a breakdown of element costs, right?)
$contain = array(
'DeliveryNote' => array(
'Product' => array(
'Element' => array(
'DefaultPrice',
'Price' => array(
'conditions' => array('Price.customer_id' => $customer_id)
)
)
)
)
);
$this->Invoice->find('first', array('conditions' => ..., 'contain' => $contain));
This should result in each Element record including a DefaultPrice, and if the customer is receiving special pricing, the Price record will also be included.
Note: You may want to consider including a default_price field in the Element field, and avoid having to do the additional join above. Every Element is going to have a default price, right?

Resources