My relationships are as follows:
User hasMany Item, Offer, Watcher
Item BelongsTo User
Item hasMany Offer, Watcher
Offer/Watcher BelongTo Item & User
When I try to get the Users Items using this:
$this->loadModel( 'User', $this->Auth->user( 'id' ) );
$this->set( 'user', $this->User->read() );
It gets the users record, the items they're watching and items they've made offers on which is fine.
However on the users items that they're selling it wont get the watchers/offers correctly.
if I use: $this->User->recursive = 2; Then it only gets the user_id but doesn't associate it to the users profile at all!
Example:
http://pastebin.com/9A8k7rDs
I can get this information by including the models individually and getting the data that way, but that's such a hack.
I was wondering if anyone knew any better ways?! or even if my relationships are right :z?
Do you have the relationships set up in all the models? Meaning not only do you have to have the relationship in the User model, but also in the Item, Watcher and Offer models as well.
I got it!
I used $this->User->Item->find( ... ) with $this->Item->recursive = 2;
It returned the data I wanted and a bit extra so I controlled that with the containable behaviour.
Thanks!
Related
I have a DB, "views," with many, many entries. I also have a "Courses" table, which these views are one-many related to. In Laravel Nova, I can get a metric of all views over time for a course with some code like this:
public function calculate(Request $request)
{
return $this->countByDays($request, view::where('viewable_id', $request->resourceId));
}
In this case, viewable_id is the id of the course, and $request->resourceId gives the ID of the course to sort by. Pretty simple.
However, now things get a little difficult. I have another model called Teachers. Each Teacher can have many courses, also in a one-many relationship. How do I get a metric of views over time for all the courses that teacher teaches?
I assumed the simplest way to do this would be to create a Laravel Collection with all courses the Teacher teaches (not exactly efficient), and then select all views in the database where viewable_id matches one of the courses in that list. Of course, by posting this, I couldn't figure out how to do that.
Of course, once this is figured out, I'd love to do the same thing for Categories (though that should function in a very identical manner to Teachers, so I don't need to ask that question).
How do I get a metric of views over time for all the courses that teacher teaches?
This should be the "countByDays" of views where the viewable_id is in the list of course ids that the teacher teaches.
An SQL query statement to achieve that is given below:
select * from "views"
where "viewable_id" in (select "id" from "courses" where "teacher_id" = ?)
The Eloquent query should be similar to:
$this->countByDays($request,
view::whereIn(
'viewable_id',
Course::with('teacher')
->select('id')
->where('teacher_id', $request->resourceId)
)
);
I have three tables: users, purchase_orders and approvals.
One purchase_order has to be approved by multiple users.
When a new purchase_order gets created, I also create 3 pending approvals belonging to that PO.
The approvals table has a field allowed_user_type that determines who can approve it.
I can't figure out, what is the Eloquent way of selecting the pending purchase orders that can be approved by a specific user, as these are determined from the approvals table.
So far I can pull the pending approvals from the approvals table for a user with the following in the User model.
public function approvals_pending()
{
return $this->hasMany('App\Approval', 'allowed_user_type', 'user_type')
->where('approved', '=', 0);
}
The question is, how do I combine this with a theoretical filter?
I mean ideally, I would love to write:
return $this->hasMany('App\PO')->whereIn('id', '=', $this->approvals_pending()->get()->po_id);
Or something like that...
Any ideas would be greatly appreciated.
OK, for anyone interested I found a solution:
It's very close to what I thought I would have to write.
The lists method basically creates a single array out of the selected field, so it can be plugged-in directly to a whereIn method like so:
return \App\PO::whereIn('id', $this->approvals_pending()->lists('po_id'));
I don't know if this is the most Eloquent way of doing this but it does work.
Say I have an orders table and a users table and a guests table, for a company who accept orders from both users and guests.
I also have a is_guest boolean on the orders table which lets me know quickly whether the order has been made by a guest or a registered user.
I have a model method called customer which returns the eloquent relation like so:
public function customer()
{
return $this->is_guest ? $this->belongsTo('Guest', 'user_id') : $this->belongsTo('User', 'user_id');
}
This works very well when I pass orders to the view and access the customer's data like so:
// Maps to either $order->user->firstname OR $order->guest->firstname
echo $order->customer->firstname;
But when I try to use this functionality when using eager loading it doesn't quite work:
$orders = Order::with('customer')->get();
return Response::make($orders);
It only returns the eager loaded customer data for either the user or the guest and never both.
I'm wondering why this is and how to get round it?
Is it because of the way the eager loading works - it only calls the customer() method once just as it only hits the database once, so whatever the first result of the query is, it will use that to determine the rest of the query too?
If I am trying to get a JSON response of all the order data with all of the customer's details too, how would I do that?
Thanks.
UPDATE
Yes, turns out I was abusing the Laravel relationship method by adding a condition.
Using the polymorphic relations is a much better approach as suggested by #lukasgeiter.
http://laravel.com/docs/4.2/eloquent#polymorphic-relations
I did have trouble loading other relations based on the polymorphic relation. For example if both User and Guest had corresponding tables UserDetail and GuestDetail then I couldn't get the eager loading to work. So I added this to both the User and Guest models:
protected $with = ['detail'];
This means that whenever the customer polymorphic relation is eager loaded, then both User and Guest will load their relationships automatically. I think I could have also used the 'lazy loading' functionality $order->load('customer') but I didn't try it out.
public function detail()
{
return $this->hasOne('UserDetail');
}
Thanks guys.
In an ActiveRecord (CakePHP flavored) setup I have a HasAndBelongsToMany association setup on Videos and Bins: A Bin can store n references to Videos, and Videos can belong to n Bins.
I need to be able to manually set and store the display order of the Videos within a particular Bin (so the client can have his Videos in a particular order within the Bin.) My initial thought is to create a field in Bin that stores an array of Video IDs in the order they need to appear. Is this the most efficient way to handle this?
If so, when I then get the HABTM query result, what is the cleanest/most efficient way to re-order the returned query to match the sorted array of ID's?
The Videos associated with a Bin are returned as an array:
[Video] => Array
(
[0] => Array
(
[id] => 49b2de95-0338-48b9-bc88-01ba35664358
...
)
[1] => Array
(
[id] => 49b1ccad-b770-410e-be46-03a035664358
...
)
Or is there a better way to achieve what I'm trying to do without using HABTM associations?
Thanks in advance -
What to do when HABTM becomes complicated?
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.
Also note that if you want to add more fields to the join (when it was created or meta information) this is possible with HABTM join tables, but it is important to understand that you have an easy option.
HasAndBelongsToMany between two models is in reality shorthand for three models associated through both a hasMany and a belongsTo association.
Consider this example:
Child hasAndBelongsToMany Club
Another way to look at this is adding a Membership model
Child hasMany Membership
Membership belongsTo Child,
ClubClub hasMany Membership.
These two examples are almost the exact same. They use the same amount and named fields in the database and the same amount of models. The important differences are that the "join" model is named differently and it's behavior is more predictable.
In your example, you need a way to add and remove without editing other users Video links, therefore standard habtm will not suit you very well. Create a model for this "join" similar to the Membership model described above. Further, if you added a weight field, you could use the ordered behavior (that I wrote) to order each set of videos per bin. Then you would use the following fields
id, bin_id, video_id, weight
And set up bin_id as the 'foreign_key' in the behavior configuartion. Good luck
Well I tried to solve just this problem and think I found the simplest solution possible:
When saving a new order of the related models you delete the existing relations and add the new ones in the desired order. This means the ids created run in your sort order and you can simply sort by id to display the order.
public $hasAndBelongsToMany = array(
'Item' => array(
'order' => 'ItemFoldersItem.id ASC, Item.name DESC',
)
);
Pretty simple isn't it?
Don't know about "most efficient" way to implement what you want, that depends on your whole application and what you want to accomplish. I'd just keep in mind that the most simple solution is often the best.
However, I can give you a tip for sorting your Video array! It is fairly easy:
$yourData = Set::sort($yourData, '/Video/id', 'asc');
Take a look at the Set class for more candy. :)
So I have a User table and a History table with User hasMany Histories, and I'm trying to implement pagination on the user table.
My problem is that I have search, and some of the things one can search by are things in the History table. Is there a way to filter pagination results based on data in a table associated by hasMany? Containable, which initially seemed like a solution, allows such filtering but only in the retrieval of associated data, not the records themselves (unless I'm missing something?)
Has anyone had to solve this before?
Since it's a hasMany relationship, that means Cake will need to make 2 separate queries: 1 on the users table, and one on the histories table to retrieve all the associations. Since the History data isn't being retrieved until the 2nd query, then your 1st query cannot be filtered via WHERE conditions for fields found in the History model.
To resolve this, you can do one of two things:
Perform pagination on History using Containable (since History belongsTo User, meaning only 1 query will be performed).
Perform pagination on User the way you're already doing, except perform an ad-hoc join to History such that it's no longer a hasMany relationship.
e.g.:
$this->User->bindModel(array('hasOne' => array('History')));
$this->paginate['User']['contain'][] = 'History';
$this->paginate('User', array('History.some_field' => 'some_value'));