I have a Lesson model with below fields :
lesson_id
title
start_date
end_date
And a Content model that have these fields :
content_id
lesson_id
contentable_id
contentable_type
order
A OneToMany relationship is between Lesson and Content.
In addition, I have two another models named Unit with these fields :
unit_id
title
time
And Test by these :
test_id
title
description
Content contentable_type and contentable_id attributes holds unit (or test) data related to a specific Lesson.
For example in contentable_type field of Content can insert only App\Unit or App\Test string and contentable_id holds ID of that Unit or Test.
(Be careful that Content Model is not to create a morph relations between these tables and I created it beacause I want to give Ordering capability to units and test of a Lesson)
Now, Suppose I want to delete a specific Content and related Units (or Tests).
Deleting Content model instance is easy, but to remove related Unit (or Test), I must fetch appropriate model name from contentable_type and it's ID then select and delete it.
For that I wrote this :
public function destroy ($course_id, $lesson_id, $content_id)
{
$content = Content::findOrFail($content_id);
$modelName = $content->contentable_type;
$modelName::find($content->contentable_id)->delete();
$content->delete();
}
Despite working properly ,But I think that is not convenient.
I search for a way that when delete a Content model , this automatically found related model and remove it too.
What is best and Proper solution?
In this case I just do these changes :
public function destroy ($course_id, $lesson_id, $content_id)
{
$content = Content::findOrFail($content_id);
$content->delete();
}
And in the Content Model :
public static function boot()
{
static::deleting(function ($content) {
$modelName = $content->contentable_type;
$modelName::find($content->contentable_id)->delete();
});
}
This works fine.
But do this best and Most correct solution?
Related
Laravel rookie here. I've got titles and reviews. A title can have many reviews, a review belongs to a title.
So far, my tables look unsurprisingly as follows:
titles:
- id (pk)
...
reviews:
- id (pk)
- title_id (fk)
...
Now, I want a title to have zero or one (so-called) top reviews.
My first instinct was to add a nullable top_review_id column to the titles table, but I wanted to avoid a cycle of foreign keys. So instead, I created a third table called top_reviews as follows:
top_reviews:
- id
- title_id (fk, unique)
- review_id (fk, unique)
That way a title is guaranteed to have at most one top review and a review cannot be the top review for multiple titles. (I do realise that it is still possible to have a top review entry where the review actually belongs to a different title, but that's okay.)
My question is how do I wire that up cleanly in Laravel (7.x) ideally using Eloquent ORM relationships and following the framework's best practices?
So far I've got this:
class Title extends Model {
public function reviews() { return $this->hasMany(Review::class); }
public function topReview() { /* ??? */ }
}
class Review extends Model {
public function title() { return $this->belongsTo(Title::class); }
}
I've considered the following:
I could manually build something ugly like return Review::find(DB::table('top_reviews')->select('review_id')->where('title_id', $this->id)->get());, but I suspect there is a nicer Laravelesque way for these trivial relationships.
Simply using hasOne() doesn't seem to be the solution either since it will assume a different table name (namely reviews instead of top_reviews) and there is no way to specify a custom table.
Defining a model TopReview seems clumsy, but perhaps it is my best bet. I suppose that would allow me to define topReview() as hasOneThrough(Review, TopReview).
Feel free to correct me if I'm on the wrong track.
Thanks.
With belongsToMany relationship
class Title extends Model
{
public function reviews()
{
return $this->hasMany(Review::class);
}
public function topReview()
{
return $this->belongsToMany(Review::class, 'top_reviews', 'title_id', 'review_id');
}
}
Anyways you can skip that top_reviews table and just save top_review_id into your titles table and I think that's more efficient
Depend on you requirement
You one only one top reviews so I think you should Has One Through but if you want to have multi top reviewer on 1 title you should use Many To Many.
Has One Through
I will remove title_id if i use it
top_reviews:
- id
- review_id (fk, unique)
class Title extends Model {
public function topReview() {
return $this->hasOneThrough(TopReview::Class, Review::class);
}
}
Many To Many
top_reviews:
- id
- title_id (fk, unique)
- review_id (fk, unique)
class Title extends Model
{
public function topReview()
{
return $this->belongsToMany(Review::class, 'top_reviews', 'title_id', 'review_id');
}
}
but as you describe above. I think I will use has one through.
I have 3 tables:
customer: fields: id, name
item: fields: id, name
customer_items: fields: customer_id, item_id, qty
Customer and Item have their separate Models as we would expect.
Question: How would I relate these two(Customer and Item) without having a pivot model.
I want to directly get customer items using $customer->items instead of doing $customer->customerItem->items which I find unnecessary since I don't want to track customerItems & customer by item.
Also, I cannot directly use customer_items table for Item model as I might need to retrieve all items in its controller.
As pointed out by #kerbholz (but they didn't create an answer so here it is), in your customer model you need the following function:
public function items()
{
return $this->belongsToMany('App\Item');
}
Which assumes your Item model class sits within App. You may also do the reverse in your Item model.
Now you should be able to do $customer->items and get a collection of items.
If you want to include the qty field then you need:
public function items()
{
return $this->belongsToMany('App\Item')->withPivot('qty');
}
Note that you still need the pivot table, you can't escape that, but you can now navigate it in a more elegant manner.
creat customer_items table and include custmer_item_id and user_id . in User model include this function
public function basket() // example
{
return $this->belongsToMany(User::class, 'customer_items', 'user_id', 'custmer_item_id');
}
I have a Course Model that have many fields like this :
course_id
title
description
creator
start_date
end_date
reg_start_date
reg_end_date
picture
lesson_count
cost
status
active
teacher
created_at
updated_at
deleted_at
And I have a Form to edit a specified Model. action attribute of the edit form tag is referenced to course.update route.
In the edit Form,in addition to fields with same names of above Model properties, there are many other form fields that not related to Course Model (and used for manyTomany relations or other operations)
Now in public update method , when I want to use Eloquent update() method , Since the number of irrelevant field names are many, I must to use except() method for incoming request. like this :
public
function update (StoreCourseRequest $request, $id)
{
$data = $request->except(['search_node', '_token', 'start_date_picker', 'end_date_picker', 'reg_start_date_picker', 'reg_end_date_picker', 'orgLevels', 'courseCats','allLessonsTable_length']);
$course = Course::findOrFail($id);
$course->update($data);
$course->org_levels()->sync($request->get('orgLevels'));
$course->course_categories()->sync($request->get('courseCats'));
$result = ['success' => true];
return $result;
}
As you see on usage of $request->except() method, I passed many field names to it to filter only proper attributes for use in $course->update($data);.
Now my Question is that Are there any way that we can get only same name model attributes from a field name?
If I understand your question correctly you are trying to avoid having to use the except() method for incoming requests, correct?
If that is the case, you can just skip it altogether and pass the entire request to the update() method as it will only update matching fields (provided they are listed as "fillable" in the method class). This process is called "mass-assignment".
Pseudo code - say I have the models Author, Document, Revisions, Editor.
Author hasMany Document
Document hasMany Revisions
Document hasMany Editors (which are stored in the revision table)
But the following table structure:
Author Model: id, name, email
Document Model: id, author_id, title
Revisions Model: id, document_id, editor_id, text, saved_at
Editor Model: id, name, email
First question - to store the revision history (including which editor changed the text at which time); is this an ideal structure? I want to be able to do $author->documents->where('title', 'Some title')->editor->name;
To access the Editor from the Document - is it worth setting attributes directly in the Document constructor:
public function __construct(array $attributes = [] ){
$this->setRawAttributes(
array_merge($this->attributes,
$this->revisions()->orderBy('saved_at', 'desc')->first()->attributesToArray()
)
);
}
Or use mutators in the model:
public function getEditorIdAttribute($value){
return $this->revisions()->orderBy('saved_at', 'desc')->first()->editor_id;
}
Or is there a better way of handling revisions that's more Laravel/Eloquent-like?
For anyone who comes down this path - I wasn't able to set attributes in the constructor and have them available in the Model so I resorted to using mutators.
To prevent a new query every time a mutator was called (which adds up if you have a handful of mutators) - I used a simple workaround:
// Document Model
class Document extends Eloquent{
$this->latest = ''
// relations etc here
public function getSomeValueAttribute{
$this->getLatest('some_value');
}
public function getAnotherValueAttribute{
$this->getLatest('another_value');
}
public function getLatest($attr){
if(empty($this->latest)) $this->latest = $this->revisions->last();
return $this->latest->getAttribute($attr);
}
}
I'm sure I can extend the getValueAttribute() mutator to keep things DRY, but the above works for me for now, and mutators are called before relations are setup so it works quite well. I'm also able to see all my revisions via $document->revisions->get() or just the latest values via $document->text.
Details
I have 3 tables :
catalog_downloads
export_frequencies
export_frequencies_catalog_downloads (Pivot Table)
Diagram
I am not sure if I set the relation between them correctly.
Please correct me if I am wrong.
Here is what I did
In CatalogDownload.php
public function export_frequencies(){
return $this->belongsToMany('ExportFrequency','export_frequencies_catalog_downloads','export_frequency_id','catalog_download_id');
}
In ExportFrequency.php
public function catalog_downloads(){
return $this->belongsToMany('CatalogDownload','export_frequencies_catalog_downloads','export_frequency_id','catalog_download_id');
}
Questions
According to my diagram - Did I assign the relationship correctly ?
I hope I didn't mix up between hasMany and belongsTo
Will I need a class or a model for a Pivot Table ?
Thanks
Since export_frequencies is in the CatalogDownload model you have to invert the ID's because the parameters of belongsToMany are as follows:
1. Name of the referenced (target) Model (ExportFrequency)
2. Name of the Pivot table
3. Name of the id colum of the referencing (local) Model (CatalogDownload in this case)
4. Name of the id colum of the referenced (target) Model (ExportFrequency in this case)
what leads to this function:
public function export_frequencies(){
return $this->belongsToMany('ExportFrequency','export_frequencies_catalog_downloads','export_frequency_id','catalog_download_id');
}
The other function was correct.
If you had some data in your pivot table, for instance a colum with the name someCounter then you will have to tell the relation to load that column when creating the pivot object like this:
public function export_frequencies(){
return $this->belongsToMany('ExportFrequency','export_frequencies_catalog_downloads','export_frequency_id','catalog_download_id')->withPivot('someCounter');
}
That will load the column and make it avalible like this:
$catalogDownload->export_frequencies()->first()->pivot->someCounter;
You will need a separate Pivot Model if you need to do some special handling for the fields or if that pivot itself has a relation of its own but then you might consider using a full blown model instead of a pure Pivot Model.
As an added note to the accepted answer, you are able to set up your many to many relationships without referencing the pivot table and the relevant id's as long as you follow a specific convention.
You can name your pivot table using singular references to the related tables, like 'catalog_download_export_frequency'. Notice the alphabetic order of the singular references.
Then you can simply do:
// CatalogDownload Model
public function exportFrequencies()
{
return $this->belongsToMany('ExportFrequency');
}
// ExportFrequency Model
public function catalogDownloads()
{
return $this->belongsToMany('CatalogDownload');
}
This will then allow you to run queries using the query builder or Eloquent like:
$catalogDownload->exportFrequencies()->get(); // Get all export frequencies for a specific CatalogDownload.
Or
$this->catalogDownload->with('exportFrequencies')->find($id); // Using eager loading and dependency injection, when CatalogDownload is assigned to $this->catalogDownload
Hope this helps!