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.
Related
Below are the lines from cakephp documentation which does not work.
Changing Fetching Strategies
As you may know already, belongsTo and hasOne associations are loaded using a JOIN in the main finder query. While this improves query and fetching speed and allows for creating more expressive conditions when retrieving data, this may be a problem when you want to apply certain clauses to the finder query for the association, such as order() or limit().
For example, if you wanted to get the first comment of an article as an association:
$articles->hasOne('FirstComment', [
'className' => 'Comments',
'foreignKey' => 'article_id'
]);
In order to correctly fetch the data from this association, we will need to tell the query to use the select strategy, since we want order by a particular column:
$query = $articles->find()->contain([
'FirstComment' => [
'strategy' => 'select',
'queryBuilder' => function ($q) {
return $q->order(['FirstComment.created' =>'ASC'])->limit(1);
}
]
]);
THanks
When working with hasOne note that CakePHP will strip the ORDER BY clause from the query after the queryBuilder is called. The queryBuilder is used to create the joining conditions for the JOIN clause. There is no SQL syntax that allows a ORDER BY clause inside an ON (expression) for a join.
You also have to use a SELECT strategy for hasOne if you want to use ORDER BY.
You can get around this issue by using a custom finder.
$articles->hasOne('FirstComment', [
'className' => 'Comments',
'foreignKey' => 'article_id',
'strategy' => Association::STRATEGY_SELECT,
'finder' => 'firstComment'
]);
In your CommentsTable class define a custom finder which sets the order.
public function findFirstComment($q) {
return $q->order([$this->aliasField('created') =>'ASC']);
}
CakePHP won't strip the ORDER BY clauses for hasOne when added by custom finders.
Note: The custom finder has to be in the association's target, not the source table.
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.
I have these relations:
Attendance belongs to Employee
Schedule belongs to Employee
This find() works just fine to get all Attendance records in a date range.
$data = $this->Attendance->find('all', array(
'contain' => array(
'Employee' => array(
'Schedule' => array(
'conditions' => array('sche_status' => 1),
'order' => 'sche_valid DESC',
'limit' => 1
))
),
'conditions' => $conditions,
'order' => array('employee_id', 'att_date')
)
);
However, this Schedule condition
'conditions' => array('sche_status' => 1),
gets the "current" Schedule. Problem is when Employee has more than one Schedule in the date range.
In order to get the relevant Schedule for it's parent Attendance record, I need to also
condition on Attendance.att_date
Tried
'conditions' => array('sche_status' => 1, 'ho_valid <=' => Attendance.att_date)
and
'conditions' => array('sche_status' => 1, 'ho_valid <=' => $this->Attendance.att_date)
How can I reference Attendance.att_date from the contan Schedule conditions? Is that possible?
My current datasets are pretty clean and my find() is dry, wouldn't want to make a mess here.
Any help greatly appreciated.
When you use contain(), it does individual queries. In your example, it will actually do three queries individually that then get combined by CakePHP into the returned data array.
So, you cannot use fields from a different model to condition against a contained model.
Your best bet is to either query the field first, then use it's value in this find, or use joins(), which makes all the results return from a single query instead of three separate queries.
Side note - it appears you're building this query in a Controller - following the MVC structure, best practice is to keep ALL queries in your Model, not your Controller - then just call the Model's method (which has the find()) from the Controller to retrieve the results.
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.
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?