Laravel Many To Many (Polymorphic) issue - laravel

I've been trying to create a Many To Many (Polymorphic) system that saves the state of every update done by a specific user on specific Models (for instance: Company, Address, Language models) with a note field and the updated_by. The goal is to keep track of who updated the model and when, and the note field is a text field that states where in the system the model was updated.
Below is what I created, but I'm open to getting a different solution that, in the end, allows me to accomplish the goal described above.
I've created the model Update (php artisan make:model Update -m) with the migration:
/**
* Run the migrations.
*
* #access public
* #return void
* #since
*/
public function up()
{
Schema::create('updates', function (Blueprint $table) {
$table->id()->comment('The record ID');
$table->bigInteger('updatable_id')->unsigned()->comment('THe model record id');
$table->string('updatable_type')->comment('THe model name');
$table->string('note')->nullable()->comment('The record note');
$table->integer('updated_by')->unsigned()->nullable()->comment('The user ID which updated the record');
$table->timestamps();
$table->softDeletes();
});
}
On the model Update all the $fillable, $dates properties are standard, and the method of the Update model:
class Update extends Model
{
/**
* Method to morph the records
*
* #access public
*/
public function updatable()
{
return $this->morphTo();
}
}
After trying several ways on the different models, my difficulty is getting the relation because when I save to the updates table, it saves correctly. For instance, in the Company model: Company::where('id', 1)->with('updates')->get(); as in the model Company I have the method:
public function updates()
{
return $this->morphToMany(Update::class, 'updatable', 'updates')->withPivot(['note', 'updated_by'])->withTimestamps();
}
Most certainly, I'm doing something wrong because when I call Company::where('id', 1)->with('updates')->get(); it throws an SQL error "Not unique table/alias".
Thanks in advance for any help.

The problem I see here is using morphToMany instead of morphMany.
return $this->morphToMany(Update::class, 'updateable', 'updates'); will use the intermediate table (and alias) updates (3rd argument) instead of using the default table name updateables. Here it will clash with the table (model) updates, so it will produce the error you are receiving.
return $this->morphMany(Update::class, 'updateable'); will use the table updates and should work with your setup.
Do notice that morphMany does not work with collecting pivot fields (e.g. withPivot([..]), it's not an intermediate table. Only morphToMany does.

Related

Laravel Mutator to add predefined values into database

I'm new into Laravel and I'm trying to store the user's company id on a column of the products table each time a user creates a new product. The company's id it's retrieved from the user's session. I'm trying it with Laravel's Mutator:
public function setFirstNameAttribute($value) {
$this->attributes['company_id'] = session()->get('company.id');
}
But each time I create a new Product the company id stored it's null. Seems like the function it's never executing. Is there any other resource to perform actions like this?
You must use model events - this will be executed on model creation before saving. Or you can use another events depends on you logic - see docs.
class YourModel extends Model
{
/**
* The "booted" method of the model.
*
* #return void
*/
protected static function booted()
{
static::creating(function (YourModel $model) {
$model->company_id = session()->get('company.id');
});
}
}
Mutators only works when you change mutating field directly:
$model->first_name = 'new_name'
And with your code - you will lost "new_name".
I noticed that the function name is incorrect, since the accessors use "studly" cased name of the column you wish to access, it may be as simple as to change
public function setFirstNameAttribute($value)
to
public function setCompanyIdAttribute($value)

How to normalize a table when I have a key pair type of data

I am making a platform where a trainer will be able to add courses to his profile. But before he adds course there needs to be an application acceptance that needs to be done.
So, I had earlier divided my database into three parts:
users | trainer | course
After a little bit of research, I found that I will have to do database normalization but I am having issues with setting it up.
Now I have trainer table trainer_experience table and trainer_achievements table. Inside trainer experience table I have to save data of the position they wore in and the company name.
My trainer table is this:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateTrainersTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('trainers', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('user_id');
$table->date('date_of_birth');
$table->integer('mobile_number');
$table->longText('address');
$table->string('id_proof');
$table->string('id_registration_number');
$table->string('high_school_name');
$table->string('graduation_college_name');
$table->string('graduation_course_name');
$table->string('post_graduation_college_name');
$table->string('post_graduation_course_name');
$table->string('phd_field_of_study');
$table->string('total_duration_of_training');
$table->integer('no_of_people_trained');
$table->boolean('paid_training');
$table->string('training_category');
$table->string('course_material_ready');
$table->string('youtube_link')->nullable();
$table->string('twitter_link')->nullable();
$table->string('instagram_link')->nullable();
$table->string('facebook_link')->nullable();
$table->string('linkedin_link')->nullable();
$table->string('blog_link')->nullable();
$table->string('website_link')->nullable();
$table->string('meetup_group_link')->nullable();
$table->timestamps();
$table->index('user_id');
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('trainers');
}
}
How do I store the data so, that I can at least retrieve it in the order they have entered? Do I need to make two different tables? What is the best option that I have?
You have to figure out in the data in trainer_experience and trainer_achievements is reusable or not ?
trainer_experience and trainer_achievements are dependent on each other?
Use Polymorphic relationship for best use.

Updating a Laravel pivot table when the pivot has a unique field

I have two models, User and Service, with a ManyToMany relationship. The pivot table has two additional fields, id_code and alias. There may be many relationships between User and Service, but each one is uniquely identified by the field id_code. I need to retrieve a specific record in the pivot table by the id_code, and update the alias for that record only.
My models:
User:
class User extends Authenticatable
{
//All attributes & other functions here...
public function linked_services(){
return $this->belongsToMany(Service::class)
->withPivot('alias', 'id_code')
->withTimestamps();
}
}
Service:
class Service extends Model
{
//All attributes & other functions here...
public function linked_users(){
return $this->belongsToMany(User::class)
->withPivot('alias', 'id_code')
->withTimestamps();
}
}
Service-User migration:
Schema::create('service_user', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('user_id')->nullable();
$table->unsignedBigInteger('service_id');
$table->string('alias', 50)->nullable();
$table->string('id_code', 50);
$table->foreign('user_id')->references('id')->on('users');
$table->foreign('service_id')->references('id')->on('services');
$table->timestamps();
});
In the update function, I get the specific $id_code from a view, but I don't know how to update the "alias" for that specific "id_code".
What I already tried and is not working:
public function update(Request $request, String $id_code){
foreach(Auth::user()->linked_services as $service){
if($service->pivot->id_code === $id_code){
$service->pivot->alias = $request->alias;
$service->pivot->save();
}
}
return redirect()->route('services_user.index');
}
This function updates ALL the existing pivot records for the service_user, I need to update only the specific record for an given "id_code".
I solved it!!! I tell you how:
Auth::user()->linked_services()
->wherePivot('id_code', $id_code)
->update(['alias' => $request->alias]);
I learned that $user->linked_services() (unlike $user->linked_services) returns a relationship object (in this case, belongsToMany), which contains all the relationship data (intermediate table, pivots, FKs, parent / related objects, etc), and the desired pivot can be accessed on that object with ->wherePivot().

Laravel 5.2 How laravel treat this belongsto relationship?

I have 3 tables, with the fields listed below:
Pricings
id
route_id
Routes
id
from_city_id
to_city_id
Cities
id
name
So far the relationships of fields are: pricings belong to a route, and routes belongs to city.
But I'm not sure about these relationships, since from_city_id and to_city_id are foreign keys relating to id on cities.
Maybe I'm wrong designing my table or something else.
route_id is a foreign key to id on the Routes table.
from_city_id and to_city_id are foreign keys to id on the Cities table.
How I can define relationships of these 3 tables so I can get from city name and to city name from the pricings model, like $pricing->from_city->name and $pricing->to_city->name?
Any help appreciated.
UPDATE:
My Pricing Model:
public function route()
{
return $this->belongsTo(Route::class);
}
My Route Model:
public function pricing(){
return $this->hasOne(Pricing::class);
}
public function zone(){
return $this->belongsTo(Zone::class);
}
public function city(){
return $this->belongsTo(City::class);
}
public function from_city(){
return $this->belongsTo(City::class);
}
public function to_city(){
return $this->belongsTo(City::class);
}
Now I can use $pricing->route->from_city->name and $pricing->route->to_city->name
It shows the correct result, but how can this be achieved using Laravel?
Does this mean Laravel will assume that the route table has fields to_city_id and from_city_id, since the method in the route model is to_city() and from_city()?
Thanks
One solution may be to make a migration (new table or to change to existing table).
https://laravel.com/docs/5.3/migrations
Laravel's schema build is super handy:
https://laravel.com/docs/5.0/schema
An example of the routes migration would be:
Make the migration:
php artisan make:migration routes
The migration would look something like:
```
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateUserRole extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('routes', function (Blueprint $table) {
$table->increments('id');
$table->foreign('id')->references('route_id')->on('pricings')->onDelete('cascade');
$table->integer('from_city_id')->unsigned()->index();
$table->foreign('from_city_id')->references('id')->on('cities')->onDelete('no action');
$table->integer('to_city_id')->unsigned()->index();
$table->foreign('to_city_id')->references('id')->on('cities')->onDelete('no action');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::drop('routes');
}
}
```
The above for some reason will not show correctly on here, so here is a cleaned up view link: http://viper-7.com/kfgUjt
Try this one:
In Pricing Model:
//if pricing and route have one to one(hasOne) relationship or you may change it to one to many(hasMany)
public function routes(){
return $this->hasOne('App\Routes','id','route_id');
}
and in Route Model:
public function from_city(){
return $this->hasOne('App\Cities','id','from_city_id');
}
public function to_city(){
return $this->hasOne('App\Cities','id','to_city_id');
}

Laravel Eloquent Model Unit testing

I am trying to write a testcase which tests the association and detachment of the relationship between two Eloquent models in Laravel 4.2
Here's my test case:
class BookingStatusSchemaTest extends TestCase
{
private $statusText = "Confirmed";
private $bookingStub;
private $statusStub;
public function testMigrateService()
{
$this->createTestData();
$booking = $this->bookingStub;
$status = $this->statusStub;
/**
* Check that the booking has no status. OK
*/
$this->assertNull($booking->status);
/**
* Check that status has no booking. OK
*/
$this->assertEquals(count($status->bookings), 0);
/**
* Add a status to the booking. OK
*/
$booking->status()->associate($this->statusStub);
/**
* Check that status has a booking. NOT OK - This gives error
*/
$this->assertEquals(count($status->bookings), 1);
/**
* Check that the booking has a status. OK
*/
$this->assertNotNull($booking->status);
/**
* Do NOT delete the status, just set the reference
* to it to null.
*/
$booking->status = null;
/**
* And check again. OK
*/
$this->assertNull($booking->status);
}
private function createTestData()
{
$bookingStatus = BookingStatus::create([
'status' => $this->statusText
]);
$booking = Booking::create([ ]);
$this->bookingStub = $booking;
$this->statusStub = $bookingStatus;
}
}
When I execute it I get:
There was 1 failure:
1) BookingStatusSchemaTest::testMigrateService
Failed asserting that 1 matches expected 0.
Booking model:
class Booking extends Eloquent {
/**
* A booking have a status
*/
public function status()
{
return $this->belongsTo('BookingStatus');
}
}
BookingStatus Model:
class BookingStatus extends Eloquent
{
protected $table = 'booking_statuses';
protected $guarded = [ 'id' ];
protected $fillable = ['status'];
/**
* A booking status belongs to a booking
*/
public function bookings()
{
return $this->hasMany('Booking');
}
}
Here's the migration Schema for bookingstatus:
Schema::create('booking_statuses', function(Blueprint $table)
{
$table->increments('id');
$table->string('status');
$table->timestamps();
});
And heres for booking:
Schema::create('bookings', function(Blueprint $table)
{
$table->increments('id');
$table->unsignedInteger('booking_status_id')->nullable();
$table->timestamps();
});
What do I have to add / change to be able to verify the relationship in my test case?
It's been a while and I had totally forgotten about this question.
Since OP still sems interested in it, I'll try to answer the question
in some way.
So I assume the actual task is: How to test the correct relationship between two Eloquent models?
I think it was Adam Wathan who first suggested abandoning terms like "Unit Tests" and "Functional Tests" and "I-have-no-idea-what-this-means Tests" and just separate tests into two concerns/concepts: Features and Units, where Features simply describe features of the app, like "A logged in user can book a flight ticket", and Units describe the lower level Units of it and the functionality they expose, like "A booking has a status".
I like this approach a lot, and with that in mind, I'd like to refactor your test:
class BookingStatusSchemaTest extends TestCase
{
/** #test */
public function a_booking_has_a_status()
{
// Create the world: there is a booking with an associated status
$bookingStatus = BookingStatus::create(['status' => 'confirmed']);
$booking = Booking::create(['booking_status_id' => $bookingStatus->id]);
// Act: get the status of a booking
$actualStatus = $booking->status;
// Assert: Is the status I got the one I expected to get?
$this->assertEquals($actualStatus->id, $bookingStatus->id);
}
/** #test */
public function the_status_of_a_booking_can_be_revoked()
{
// Create the world: there is a booking with an associated status
$bookingStatus = BookingStatus::create(['status' => 'confirmed']);
$booking = Booking::create(['booking_status_id' => $bookingStatus->id]);
// Act: Revoke the status of a booking, e.g. set it to null
$booking->revokeStatus();
// Assert: The Status should be null now
$this->assertNull($booking->status);
}
}
This code is not tested!
Note how the function names read like a description of a Booking and its functionality. You don't really care about the implementation, you don't have to know where or how the Booking gets its BookingStatus - you just want to make sure that if there is Booking with a BookingStatus, you can get that BookingStatus. Or revoke it. Or maybe change it. Or do whatever. Your test shows how you'd like to interact with this Unit. So write the test and then try to make it pass.
The main flaw in your test is probably that you're kind of "afraid" of some magic to happen. Instead, think of your models as Plain Old PHP Objects - because that's what they are! And you wouldn't run a test like this on a POPO:
/**
* Do NOT delete the status, just set the reference
* to it to null.
*/
$booking->status = null;
/**
* And check again. OK
*/
$this->assertNull($booking->status);
It's a really broad topic and every statement about it inevitably opinioted. There are some guidelines that help you get along, like "only test your own code", but it's really hard to put all the peaces together. Luckily, the aforementioned Adam Wathan has a really excellent video course named "Test Driven Laravel" where he test-drives a whole real-world Laravel application. It may be a bit costly, but it's worth every penny and helps you understand testing way more than some random dude on StackOverflow :)
To test you're setting the correct Eloquent relationship, you have to run assertions against the relationship class ($model->relation()).
You can assert
It's the correct relationship type by asserting $model->relation() is an instance of HasMany, BelongsTo, HasManyThrough... etc
It's relating to the correct model by using $model->relation()->getRelated()
It's using the correct foreign key by using $model->relation()->getForeignKey()
The foreign key exists as a column in the table by using Schema::getColumListing($table) (Here, $table is either $model->relation()->getRelated()->getTable() if it's a HasMany relationship or $model->relation()->getParent()->getTable() if it's a BelongsTo relationship)
For example. Let's say you've got a Parent and a Child model where a Parent has many Child through the children() method using parent_id as foreign key. Parent maps the parents table and Child maps the children table.
$parent = new Parent;
# App\Parent
$parent->children()
# Illuminate\Database\Eloquent\Relations\HasMany
$parent->children()->getRelated()
# App\Child
$parent->children()->getForeignKey()
# 'parent_id'
$parent->children()->getRelated()->getTable()
# 'children'
Schema::getColumnListing($parent->children()->getRelated()->getTable())
# ['id', 'parent_id', 'col1', 'col2', ...]
EDIT
Also, this does not touch the database since we're never saving anything. However, the database needs to be migrated or the models will not be associated with any tables.

Resources