DEV Community

tony
tony

Posted on • Originally published at Medium on

10 2

Part 3: Testing Model Relationships in Laravel — POLYMORPHIC

Part 3: Testing Model Relationships in Laravel — POLYMORPHIC

Source: Better Through Code

This part deals with more complex model relationships, the polymorphic type. Just like before, we shall stick to the official documentation examples and work out befitting tests. To have a low down on the earlier parts see link directly below 👇

POLYMORPHIC RELATIONSHIPS

A polymorphic relationship allows a target model to belong to more than one type of model using a single association.  — Laravel.com

1. One-to-One Polymorphic Relationship

A one-to-one polymorphic relation is similar to a simple one-to-one relation; however, the target model can belong to more than one type of model on a single association.

Scenario:

A blog Post and a User may share a polymorphic relation to an Image model. Using a one-to-one polymorphic relation allows you to have a single list of unique images that are used for both blog post s and user accounts

morphTo()

— An image table can be morph ed in to any model (i.e serve many different models), eg User or Post model in our case.



// App/Image.php
...
public function imageable() 
{ 
 return $this->morphTo(); 
}


Enter fullscreen mode Exit fullscreen mode
<?php
namespace Tests\Unit;
use App\Country;
use App\Image;
use App\Post;
use App\Supplier;
use App\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Support\Facades\Schema;
use Tests\TestCase;
class ImagesTest extends TestCase
{
use RefreshDatabase, WithFaker;
public function setUp() :void
{
parent::setUp();
$this->country = factory(Country::class)->create();
$this->supplier = factory(Supplier::class)->create();
$this->user = factory(User::class)->create();
$this->post = factory(Post::class)->create();
$this->image = factory(Image::class)->create();
}
/** @test */
public function images_database_has_expected_columns()
{
$this->assertTrue(Schema::hasColumns('images', [
'id', "url", "imageable_id", "imageable_type"
]), 1);
}
/** @test */
public function an_image_can_be_uploaded_by_a_user() // morphedTo a USER
{
$image = factory(Image::class)->create([
"imageable_id" => $this->user->id,
"imageable_type" => "App\User",
]);
$this->assertInstanceOf(User::class, $image->imageable);
}
/** @test */
public function an_image_can_be_uploaded_for_a_post() // morphedTo a POST
{
$image = factory(Image::class)->create([
"imageable_id" => $this->post->id,
"imageable_type" => "App\Post",
]);
$this->assertInstanceOf(Post::class, $image->imageable);
}
}

morphOne()

  • — A user/post model can morph one instance of the same image model table (i.e make use of same image table but one record per user/post).
  • — INVERSE of morphTo relationship.


// App/User.php
...
public function image() 
{ 
 return $this->morphOne(Image::class, 'imageable'); 
}


Enter fullscreen mode Exit fullscreen mode
<?php
namespace Tests\Unit;
use App\Country;
use App\Image;
use App\Post;
use App\Supplier;
use App\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Support\Facades\Schema;
use Tests\TestCase;
class UserTest extends TestCase
{
use RefreshDatabase, WithFaker;
public function setUp() :void
{
parent::setUp();
$this->country = factory(Country::class)->create();
$this->supplier = factory(Supplier::class)->create();
$this->user = factory(User::class)->create();
$this->post = factory(Post::class)->create();
$this->image = factory(Image::class)->create([
"imageable_id" => $this->user->id,
"imageable_type" => "App\User",
]);
}
// ...
/** @test */
public function a_user_morphs_one_image()
{
$this->assertInstanceOf(Image::class, $this->user->image);
}
}

Files relevant to this test 👇



- **MODEL FILES(App/)
 -- User.php 
 -- Post.php
 -- Image.php

- **MIGRATION FILES(database/migrations/)
 -- 2014\10\12\000000\create\users\table.php
 -- 2019\09\27\100604\create\posts\table.php
 -- 2019\09\28\143143\create\images\table.php

- **MODEL FACTORY FILES(database/factories/)
 -- UserFactory.php 
 -- PostFactory.php
 -- ImageFactory.php

- **UNIT TEST FILES(tests/Units/)
 -- UserTest.php 
 -- PostsTest.php
 -- ImagesTest.php


Enter fullscreen mode Exit fullscreen mode

2. One-to-Many (Polymorphic)

A “one-to-many polymorphic” relation is similar to a simple one-to-many relation; however, the target model can belong to more than one type of model on a single association. — Laravel.com

Scenario:

Users of your application can “comment” on both posts and videos. Using polymorphic relationships, you may use a single comments table for both of these scenarios.

morphTo()

— A comment table can be morph ed to any model (i.e serve many different models), eg Video or Post model in our case.



// App/Comment.php
...
public function commentable() 
{ 
 return $this->morphTo(); 
}


Enter fullscreen mode Exit fullscreen mode
<?php
namespace Tests\Unit;
use App\Comment;
use App\Country;
use App\Post;
use App\Supplier;
use App\User;
use App\Video;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Support\Facades\Schema;
use Tests\TestCase;
class CommentsTest extends TestCase
{
use RefreshDatabase, WithFaker;
public function setUp() :void
{
parent::setUp();
$this->country = factory(Country::class)->create();
$this->supplier = factory(Supplier::class)->create();
$this->user = factory(User::class)->create();
$this->post = factory(Post::class)->create();
$this->video = factory(Video::class)->create();
$this->comment = factory(Comment::class)->create();
}
// ...
/** @test */
public function a_comment_can_be_morphed_to_a_video_model()
{
$comment = factory(Comment::class)->create([
"commentable_id" => $this->video->id,
"commentable_type" => "App\Video",
]);
$this->assertInstanceOf(Video::class, $comment->commentable);
}
/** @test */
public function a_comment_can_be_morphed_to_a_post_model()
{
$comment = factory(Comment::class)->create([
"commentable_id" => $this->post->id,
"commentable_type" => "App\Post",
]);
$this->assertInstanceOf(Post::class, $comment->commentable);
}
}

morphMany()

  • — A video/post model can morph many instances of the same comment model table (i.e make use of same comment table for many records).
  • — INVERSE of morphTo relationship.


// App/Video.php
...
public function comments() 
{ 
 return $this->morphMany(Comment::class, 'commentable'); 
}


Enter fullscreen mode Exit fullscreen mode
<?php
namespace Tests\Unit;
use App\Comment;
use App\Country;
use App\Post;
use App\Supplier;
use App\User;
use App\Video;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Schema;
class VideosTest extends TestCase
{
use RefreshDatabase, WithFaker;
public function setUp() :void
{
parent::setUp();
$this->country = factory(Country::class)->create();
$this->supplier = factory(Supplier::class)->create();
$this->user = factory(User::class)->create();
$this->post = factory(Post::class)->create(['user_id' => $this->user->id]);
$this->video = factory(Video::class)->create(['user_id' => $this->user->id]);
$this->comment = factory(Comment::class)->create([
"commentable_id" => $this->video->id,
"commentable_type" => "App\Video",
]);
}
/** @test */
public function videos_database_has_expected_columns()
{
$this->assertTrue(
Schema::hasColumns('videos', [
'id','user_id', 'title', 'description', 'size', 'length'
]), 1);
}
/** @test */
public function a_video_morphs_many_comments()
{
$this->assertInstanceOf('Illuminate\Database\Eloquent\Collection', $this->video->comments);
}
}

Files relevant to this test 👇



  • **MODEL FILES(App/)
    -- User.php
    -- Post.php
    -- Video.php
    -- Comment.php

  • **MIGRATION FILES(database/migrations/)
    -- 2019\09\28\211240\create\videos\table.php
    -- 2019\09\28\221627\add\commentable\id\and\commentable\type\to\comments\table.php

  • **MODEL FACTORY FILES(database/factories/)
    -- VideoFactory.php

  • **UNIT TEST FILES(tests/Unit/)
    -- PostsTest.php
    -- VideosTest.php
    -- CommentsTest.php

Enter fullscreen mode Exit fullscreen mode



  1. Many-to-Many (Polymorphic)

In progress…

Kindly check back soon

Image of Datadog

The Essential Toolkit for Front-end Developers

Take a user-centric approach to front-end monitoring that evolves alongside increasingly complex frameworks and single-page applications.

Get The Kit

Top comments (1)

Collapse
 
robencom profile image
robencom

Hello, many thanks for these articles!
I appreciate it!
Waiting for the next one: "Many-to-Many (Polymorphic)".

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay