outl1ne / nova-sortable

This Laravel Nova package allows you to reorder models in a Nova resource's index view using drag & drop.
MIT License
283 stars 120 forks source link

Cannot Seed Database correctly: This package overrides the "sort order" database field when seeding the database #177

Closed connecteev closed 1 year ago

connecteev commented 1 year ago

Thanks for this package, because I really need a way to sort resource rows in Nova using drag-and-drop. However, here is an issue that I can't seem to fix.

Problem

This package overrides the "sort order" database field (specified by order_column_name) when seeding the database. As a result, I cannot seed the database table correctly.

My goal is simple:

To allow CourseSections to be sortable using drag-and-drop on the Course Detail page in Nova.

My Model relationships:

Here is my code for the models and Nova resources:

+++ b/app/Models/CourseContent/CourseSection.php
@@ -8,10 +8,19 @@
 use Illuminate\Database\Eloquent\Model;
 use Illuminate\Database\Eloquent\Relations\BelongsTo;
 use Illuminate\Database\Eloquent\Relations\HasMany;
+use Spatie\EloquentSortable\Sortable;
+use Spatie\EloquentSortable\SortableTrait;

-class CourseSection extends Model
+class CourseSection extends Model implements Sortable
 {
-    use HasFactory;
+    use HasFactory, SortableTrait;
+
+    public array $sortable = [
+        'order_column_name' => 'section_order',
+        'sort_when_creating' => true,
+        'sort_on_has_many' => true,
+        'nova_order_by' => 'ASC',
+    ];

     public function course(): BelongsTo
     {
diff --git a/app/Nova/CourseSection.php b/app/Nova/CourseSection.php
index cc2c8fa..c5cace3 100644
--- a/app/Nova/CourseSection.php
+++ b/app/Nova/CourseSection.php
@@ -11,9 +11,21 @@
 use Laravel\Nova\Fields\Slug;
 use Laravel\Nova\Fields\Text;
 use Laravel\Nova\Http\Requests\NovaRequest;
+use Outl1ne\NovaSortable\Traits\HasSortableRows;

 class CourseSection extends Resource
 {
+    use HasSortableRows;
+
+    // You can disable sorting on a per-request or per-resource basis by overriding the canSort() on the Resource method
+    public static function canSort(NovaRequest $request, $resource)
+    {
+        return true;
+    }
+
     /**
      * The model the resource corresponds to.
      *
@@ -66,10 +78,11 @@ public function fields(NovaRequest $request)
                 ->hideFromIndex()
             ,

-            Number::make('Order')
+            Number::make('Order', 'section_order')
                 ->min(1)
                 ->max(20)
                 ->step(1)
+                ->sortable()
                 ->showWhenPeeking()
                 ->rules('required')
diff --git a/composer.json b/composer.json
index e49599e..0345389 100644
--- a/composer.json
+++ b/composer.json
@@ -18,6 +18,7 @@
         "laravel/tinker": "^2.8",
         "manogi/nova-tiptap": "^3.2",
         "meilisearch/meilisearch-php": "^1.1",
+        "outl1ne/nova-sortable": "^3.4",
         "spatie/laravel-markdown": "^2.3",
         "spatie/shiki-php": "^1.3",
         "stripe/stripe-php": "^10.16",

Database Table, Factory, Seeder etc:

diff --git a/database/factories/CourseContent/CourseSectionFactory.php b/database/factories/CourseContent/CourseSectionFactory.php
index d358f7d..fee1af4 100644
--- a/database/factories/CourseContent/CourseSectionFactory.php
+++ b/database/factories/CourseContent/CourseSectionFactory.php
@@ -22,6 +22,7 @@ public function definition(): array
         return [
             'course_id' => Course::factory(),
+            'section_order' => $faker->numberBetween(1,20),
             'title' => Str::title(fake()->words(4, true)),
             'is_visible' => true,
         ];
diff --git a/database/migrations/13_content_topics_articles_courses_tags/0013_01_01_000003_create_course_sections_table.php b/database/migrations/13_content_topics_articles_courses_tags/0013_01_01_000003_create_course_sections_table.php
index 91d1190..3e12f68 100644
--- a/database/migrations/13_content_topics_articles_courses_tags/0013_01_01_000003_create_course_sections_table.php
+++ b/database/migrations/13_content_topics_articles_courses_tags/0013_01_01_000003_create_course_sections_table.php
@@ -16,6 +16,7 @@ public function up(): void
             $table->id();
             $table->foreignIdFor(Course::class)->constrained()->cascadeOnDelete();
+            $table->unsignedInteger('section_order');
             $table->string('title');
             $table->string('slug')->unique();
             $table->boolean('is_visible');

diff --git a/database/seeders/CourseContentSeeder.php b/database/seeders/CourseContentSeeder.php
index 4bef395..fbd85ae 100644
--- a/database/seeders/CourseContentSeeder.php
+++ b/database/seeders/CourseContentSeeder.php
@@ -18,25 +18,31 @@ class CourseContentSeeder extends Seeder
     public function run(): void
     {
         $courses = Course::factory()
             ->count(2)
             ->create();

         // Create CourseSections
         $courses->each(function ($course) {
             for ($sectionNum=1; $sectionNum <= 4; $sectionNum++) {
+                $section_title = "course_" . $course->id . "_section_" . $sectionNum;
+                echo "creating section for course ID: $course->id with section number: $sectionNum , section name: $section_title\n";
                 CourseSection::factory()
                     ->withSlug()
                     ->create([
+                        'title' => $section_title,
                         'course_id' => $course->id,
+                        'section_order' => $sectionNum,
                     ]);
             }
         });

Output when I run CourseContentSeeder:

  Database\Seeders\CourseContentSeeder ..................................................................................................... RUNNING
creating section for course ID: 1 with section number: 1 , section name: course_1_section_1
creating section for course ID: 1 with section number: 2 , section name: course_1_section_2
creating section for course ID: 1 with section number: 3 , section name: course_1_section_3
creating section for course ID: 1 with section number: 4 , section name: course_1_section_4
creating section for course ID: 2 with section number: 1 , section name: course_2_section_1
creating section for course ID: 2 with section number: 2 , section name: course_2_section_2
creating section for course ID: 2 with section number: 3 , section name: course_2_section_3
creating section for course ID: 2 with section number: 4 , section name: course_2_section_4
  Database\Seeders\CourseContentSeeder ............................................................................................... 10.83 ms DONE

As you can see, the seeder tries to create the section with the correct section_order (in the $sectionNum variable), and what I want is for the section_order database field to be 1, 2, 3, 4 for course 1, and then 1, 2, 3 4 again for course 2, and so on.

However, what I see in reality is that this package is overriding the section_order database field when the course_sections table is being seeded. The section orders do not reset from course 1 to course 2, and go from 1....8, as you can see below.

image

Root cause??

I was able to isolate this to the order_column_name column in the model. If I change order_column_name to any other value, say order, then the seeding works fine (section_order is 1, 2, 3, 4 for course 1; and section order is 1, 2, 3, 4 for course 2).

public array $sortable = [
        'order_column_name' => 'section_order',
        'sort_when_creating' => true,
        'sort_on_has_many' => true,
        'nova_order_by' => 'ASC',
    ];

How do I fix this? Without resolving this, I cannot seed the database correctly.

connecteev commented 1 year ago

Found a temporary fix for this. sort_when_creating should be set to false. If set to true, the seeding gets affected.

In CourseSection model:

class CourseSection extends Model implements Sortable
{
    use HasFactory, SortableTrait;

    public array $sortable = [
        'order_column_name' => 'section_order',

        // sort_when_creating should be false (at least during seeding), otherwise the order_column_name field gets messed up in the database during seeding
        'sort_when_creating' => false,

        'sort_on_has_many' => true,
        'nova_order_by' => 'ASC',
    ];

However, if we need sort_when_creating to be set to true, then the following approach could work during seeding. We have to reset the section_order for each section within a course:

In CourseContentSeeder:

        // Create CourseSections with default sorting
        $courses->each(function ($course) {
            CourseSection::factory()
                ->count(4)
                ->withSlug()
                ->create([
                    'course_id' => $course->id,
                ]);

            // If sort_when_creating (under the model's sortable property) is not false during seeding, the order_column_name field (mapped to section_order) gets messed up in the database during seeding
            // We have to reset the section_order for each section within a course
            $course->courseSections->each(function ($section, $index) {
                $section->update([
                    'section_order' => $index + 1,
                ]);
            });
        });