KoolReport's Forum

Official Support Area, Q&As, Discussions, Suggestions and Bug reports.
Forum's Guidelines

Many to many relations #3371

Open Eugene opened this topic on on Nov 7 - 25 comments

Eugene commented on Nov 7

Hi,

do you have an example of the realization with edit possibility.

This relation use the 3rd table and I am a bit stuck how to correct make possibility to create and edit tables with these relations

KoolReport commented on Nov 8

Do you mean using the admin panel of dashboard framework? Could you please describe more details of your case.

Eugene commented on Nov 8

I mean Admin panel The case is pretty simple 2 tables with many to many relation via 3rd table like student, courses and junction table student-courses

if I edit or create a record in the student table I want to choose 1or more courses - actually it means new records in the junction table but it should work from the student table resource ... etc

KoolReport commented on Nov 11

In this case, you need to create a column "Courses" inside Student resource which has the Select2 multiple selection as input. This column is only available in Update and Create screen. In this Select2 input will be provided list of courses inside dataSource(). When the UpdateScreen/CreateScreen of student open, The Select2 will allow you to select one or more courses. When click Save, list of course id will be sent to server in format of array. Admin panel will help to convey those data to methods called updateRecord($conditions, $data) and createRecord($data) depend on screens.

These methods can be overwritten inside Student resource. So what you do inside that updateRecord() and createRecord() is to create/update student, and insert/update the list of course to the StudentCourse (linked table).

Eugene commented on Nov 11

Could you give more detailed answer maybe with example? I am not sure I understood what do I have to do

KoolReport commented on Nov 12

Ok, please give me some time, I will write some pseudo code for you.

Eugene commented on Nov 12

thank you I will wait

KoolReport commented on Nov 13

Hi Eugene, the code will be something like this:

You need first to create a CourseField, a virtual field to mange the list of courses in student table:

<?php

use demo\AdminAutoMaker;
use koolreport\dashboard\Field;

class CourseField extends Field
{
    protected $oriValue = null; // Use this value to avoid multiple query to database
    protected function onCreated()
    {
        $this->valueType("array");
    }

    public function orginalValue()
    {
        if($this->oriValue === null)
        {
            // Make query to database to get list of couses of particular student
            $arrayStudentCourseIds = AdminAutoMaker::table("student_course")
                    ->select("course_id")
                    ->where("student_id",$this->row["student_id"])
                    ->run()
                    ->pluck("course_id");
            //Save to oriValue
            $this->oriValue = $arrayStudentCourseIds;
        }
        return $this->oriValue;
    }

    public function defaultFormatValue($value, $row = null)
    {
        // To show list of student courses in array formats
        return json_encode($value);
    }
}

Now in the Student resource, you do this:

<?php

use koolreport\dashboard\admin\Resource;
use demo\AdminAutoMaker;
use koolreport\dashboard\fields\ID;
use koolreport\dashboard\fields\Text;
use koolreport\dashboard\inputs\Select2;

class Student extends Resource
{
    protected function onCreated()
    {
        $this->manageTable("students")->inSource(AdminAutoMaker::class);
    }

    protected function fields()
    {
        return [
            ID::create("student_id"),
            Text::create("student_name"),
            Text::create("student_email"),
            Text::create("student_phone"),
            Text::create("student_address"),
            CourseField::create("courses")
                ->inputWidget(
                    Select2::create()
                        ->dataSource(function(){
                            return AdminAutoMaker::table("courses")
                                ->select("course_id","course_name");
                        })
                        ->multiple(true)
                        ->value("course_id")
                        ->text("course_name")
                )
                ->showOnAll(false)
                ->showOnCreate(true)
                ->showOnUpdate(true)
        ];
    }

    public function createRecord($data)
    {

        $couses = null;
        if(isset($data["courses"])) {
            $courses = $data["courses"];
            unset($data["courses"]);
        }
        $result = parent::createRecord($data);
        if($result === false) {
            return false;
        }
        //Now we will insert student courses into student_course table
        if(is_array($courses)) {
            foreach($courses as $course_id) {
                AdminAutoMaker::table("student_course")->insert([
                    "student_id"=>$data["student_id"],
                    "course_id"=>$course_id
                ])->run();
            }
        }
        return true;
    }

    public function updateRecord($ids,$data)
    {
        $couses = null;
        if(isset($data["courses"])) {
            $courses = $data["courses"];
            unset($data["courses"]);
        }

        $result = parent::updateRecord($ids,$data);
        if($result === false) {
            return false;
        }

        //Now we delete all records in student_course table
        AdminAutoMaker::table("student_course")
            ->where("student_id",$data["student_id"])
            ->delete()
            ->run();

        //And then insert again
        if(is_array($courses)) {
            foreach($courses as $course_id) {
                AdminAutoMaker::table("student_course")->insert([
                    "student_id"=>$data["student_id"],
                    "course_id"=>$course_id
                ])->run();
            }
        }
        return true;
    }

}

Hope that helps.

Eugene commented on Nov 13

Thank you, but I met with some issues

  1. CourseField::create("courses") - table Students does not have any column courses because the relations is organized via 3rd table student_courses - it generate sql error Column not found: 1054 Unknown column 'students.courses' in 'field list'

  2. the following generate error that method text is not exist

->value("course_id")
->text("course_name")

I changed it like this but I cannot be sureis it correct or not coz control does not appear

->fields(function () {
    return [
        Number::create('course_id'),
        Text::create('course_name')
    ];

})
  1. If I set $this->autoColName = false; in CourseField and don't use any clolumn name in CourseField::create() I get the error that Call undefined showOnCreate() method....

If I delete all that showOn methods maximum what I got is the column CourseField at the list screen - just array value and absolutely nothing in the Create and Update screens.

I really do not know where to go now.

KoolReport commented on Nov 14

Yeah, you are right. Let me a little time consulting with dev.team to find solution.

Eugene commented on Nov 26

Hi, Koolreport, do you have any ideas about the solution?

The many-to-many relationship is one of the standard ones in relational databases, so I believe the admin panel should support such tables by default.

KoolReport commented on Dec 2

I am thinking of a solution that's possible work for Create New record. You can create an your own action and add to Action Group Box. In action, there is a form in which you can add your own inputs. Let take example for Student resource, you can add inputs for "name", "age" ... and extra multiple select2 box for "courses".

After user click submit form, you can get user's inputs in the handle() method through parameters called $form. You get values from form and insert into database by your own including the update to "student_course" table.

How do you think of this solution.

Eugene commented on Dec 2

Hmm... it means I have to do everything from the scratch.

I thought Admin panel has already something for many to many relationship.

Eugene commented on Dec 2

I thought you created your viaTable for something like this

Sebastian Morales commented on Dec 5

Eugene, we will think about an automatic update solution for many to many relations in AdminTable so that users won't have to write the update mechanism themselves. We will keep you updated in this topic.

Eugene commented on Dec 5

Thank you Sebastian, I will wait but also try to do something myself - I have to finish this app hehe

Sebastian Morales commented on Dec 11

Eugene, I think KoolReport's previous answer could work if we build a "courses" data column for the Student table by using both sql for joining tables and some PHP processes to merge rows of the same student with different courses into one row.

Then you could display the "courses" column, edit it with Select2 and save it into database with custom createRecord and updateRecord methods.

Another solution is to use a separate Resource and AdminTable to manage the StudentsCourses table itself with multiple rows of student id, student name (RelationalLink), course id, course name (RelationalLink).

Eugene commented on Dec 11

Sebastian, thank you so much.

Could you please make for me a bit more clear this idea: "we build a "courses" data column for the Student table by using both sql for joining tables and some PHP processes to merge rows of the same student with different courses into one row."

Regarding the second solution with a separate Resource and AdminTable to manage the StudentsCourses, I have already thought about it like about the last choice option. Sure it will work but I think you agree that this solution is very far from user friendly approach. I hope I can realize something where the connection table is hidden from the user using your first idea.

KoolReport commented on Dec 12

Another idea: Make a custom form class which can be added into UpdateScreen and CreateScreen. This custom form will be appended to main form, has its own update/create mechanism. Something like this:

class MyCoursesForm extends ExtraForm
{
    protected function form($row)
    {
        $defaultValue = null;
        if(is_array($row)) {
            // If student data is available, get the courses list and put into $defaultValue
            $studentID = $row["student_id"];
        }

        return [
            "Courses"=>Select2::create("courses")
                                    ->defaultValue($defaultValue)
                                    ->dataSource(function(){
                                            return DB::table("courses")->select("id","name");
                                    })
        ];
    }

    protected function handleForm($row)
    {
        $form = $this->getForm();
        $courses = $form->input("courses")->value();
        $studentID = $row["student_id"];
        //Do anything here with db
        
        return true; // Successfully
    }
}

In the Resource we do:

class Student extends Resource
{
    protected function onCreated()
    {
        $this->updateScreen()->extraForm(MyCoursesForm::create());

        $this->createScreen()->extraForm(MyCoursesForm::create());

    }
}

So the Student resource are appeared normal like now. Just that, when update or create, there is extra form appeared together that allow user to input courses.

Eugene commented on Dec 13

Thank you Koolreport,

but it looks like that ExtraForm is not a part of koolreport... I was not able to find this class

KoolReport commented on Dec 13

What i mean is that do you think this idea a good solution? then I we will add this capability.

Eugene commented on Dec 13

It looks to be pretty easy to use. So I vote for this. :-) I hope you can add it soon.

KoolReport commented on Dec 13

Awesome, I will tell dev.team to explore this direction.

Sebastian Morales commented 6 days ago

You can group concat all the courses of each student with this sql query:

SELECT s.studentName, GROUP_CONCAT(c.courseName SEPARATOR ',') as `courses` FROM `students` s join `student_course` sc o on s.studentID = sc.studentID join `courses` c on sc.courseID = c.courseID group by s.studentName;  

Use this courses column for displaying and use custom updateRecord, createRecord when updating it. Let us know if there's any issue with this approach.

Eugene commented 5 days ago

Thank you Sebastian, but I think there is an issue on this way. I add your sql query to the query function of my resource class I did it like this:

protected function query($query)
    {
  return Database::rawSQL("
SELECT s.id, s.studentName, GROUP_CONCAT(c.courseName SEPARATOR ',') as `courses` FROM `students` s join `student_course` sc on s.studentID = sc.studentID join `courses` c on sc.courseID = c.courseID group by s.studentName;  
"
                );
    }

It works for the list view but does not work for the detail or edit view. It looks like somewhere the query with 'where students.id=... is executed (for the details view) but my query returns columns name without table prefix and I get the error SQLSTATE[42S22]: Column not found: 1054 Unknown column 'students.id' in 'where clause'

and for edit view I get error

SQLSTATE[42S22]: Column not found: 1054 Unknown column 'students.courses' in 'field list' it is also understandable – I think the edit form tries to get all fields from the students table but it does not know that courses column does not exist in this table...

I am sorry I do not have knowledges about how koolreport works internally to understand how to avoid this errors in these cases.

Sebastian Morales commented 4 days ago

This additional column probably needs some small changes in Dashboard's Admin code. Pls send us an email to support@koolreport.com and mention this topic. We will send you an updated version of Dashboard to solve this issue.

Build Your Excellent Data Report

Let KoolReport help you to make great reports. It's free & open-source released under MIT license.

Download KoolReport View demo
help needed
suggestion

Dashboard