1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Go言語】WebフレームワークBeegoでTodoタスクを登録する方法(1対多のER編)

Posted at

サンプル

登録画面から入力項目を入力して登録ボタンをクリックします。

Beegoの登録画面.png

正常に登録できると、PostgreSQLに登録されます。
登録後のPostgre.png

上記の機能を作っていきます。

ER図

モデルクラスはRegisterTaskの2種類です。
Registerモデルには、ユーザ情報を、Taskモデルにはタスク情報を入れています。

登録済みのユーザのみがタスクを登録できるので、RegisterモデルとTaskモデルは1対多の関係となります。

ER図.png

ディレクトリ構成

MY-FIRST-BEEGO-PROJECT
├─ conf
│  └─ app.conf
├─ controllers
│  └─ default.go
├─ crypt
│  └─ crypt.go
├─ models
│  └─ todo.go
├─ routers
│  └─ router.go
└─ static
   ├─ css
   ├─ img
   ├─ js
   │  ├─ form.js
   │  ├─ login.js
   │  ├─ reload.min.js
   │  ├─ todoCreate.js
   │  ├─ todoCard.js
   │  └─ todoList.js
   ├─ lib
   │  └─ bootstrap
   │     ├─ css
   │     │  ├─ bootstrap-grid.min.css
   │     │  └─ bootstrap.min.css
   │     ├─ js
   │     │  ├─ bootstrap-bundle.min.js
   │     │  └─ bootstrap.min.js
   │     ├─ jquery
   │     │  └─ jquery.js
   │     └─ upload
   ├─ views
   │  ├─ mytodo
   │  │  ├─ addTodo.tpl
   │  │  └─ myList.tpl
   │  ├─ index.tpl
   │  ├─ subIndex.tpl
   │  └─ thirdIndex.tpl
   └─ main.go

モデルクラスの修正

リレーショナルが1対多になるように、Taskモデルの中にRegisterモデルをポインタ型で保持しておくようにしました。

on_delete(do_nothing)は、登録されたユーザー(Register)が削除された場合にそのユーザーに関連するタスクが削除されないように指定しています。

models/todo.go
package models // ここでパッケージ名を指定

import (
	"time"

	"github.com/beego/beego/v2/client/orm"
	_ "github.com/lib/pq" // PostgreSQL ドライバをインポート
)

type Register struct {
	Id        int64     `orm:"auto"`
	Name      string    `orm:"size(20)"`
	Email     string    `orm:"size(50)"`
	Password  string    `orm:"size(100)"`
	CreatedAt time.Time `orm:"auto_now;type(datetime)"`
}

// 20250211追加
type Task struct {
	TaskId          int64     `orm:"auto"`
	TaskName        string    `orm:"size(20)"`
	TaskDescription string    `orm:"size(100)"`
	TaskPriority    string    `orm:"size(5)"`
	TaskImage       string    `orm:"size(100)"`
	Register        *Register `orm:"rel(fk);on_delete(do_nothing)"` // 外部キーリレーション
}

func init() {
	// PostgreSQLのドライバ登録
	orm.RegisterDriver("postgres", orm.DRPostgres)

	// データベース接続設定
	orm.RegisterDataBase("default", "postgres", "user=postgres password=postgres host=localhost port=5432 dbname=beegoDb sslmode=disable")

	// モデルの登録
	orm.RegisterModel(new(Register)) // Todo

	// Taskモデルの登録
	orm.RegisterModel(new(Task))

	// テーブルが存在しない場合に作成(強制的にマイグレーション)
	orm.RunSyncdb("default", false, true)
}

フロント側のコード

views/mytodo/addTodo.tpl
<!DOCTYPE>
<html>
    <head>
        <title>Todo登録</title>
        <!--共通-->
        <link rel="stylesheet" type="text/css" href="/static/lib/bootstrap/css/bootstrap.min.css"/>
        <script type="text/javascript" src="/static/lib/jquery/jquery.js"></script>
        <!--個別-->
        <script type="text/javascript" src="/static/js/taskCreate.js"></script>        
    </head>
    <body>
        <h1>Todo登録</h1>
        <div class="container">
            サンプル
            <form enctype="multipart/form-data">
                <div>
                    <input type="hidden" id="userId" name="userId" value=""/>
                </div>
                <div class="form-group mb-3">
                    <label>タスク名</label>
                    <input type="text" id="myTask" name="myTask" class="form-control border border-dark" required/>
                </div>
                <div class="form-group mb-3">
                    <label for="exampleFormControlTextarea1" class="form-label">タスク詳細</label>
                    <textarea class="form-control" id="myTaskDescription" name="myTaskDescription" rows="3"></textarea>
                </div>
                <div class="form-group mb-3">
                    <label>優先度</label>
                    <select id="myTaskPriority" name="myTaskPriority" class="form-select form-select-lg mb-3" aria-label=".form-select-lg">
                        <option>-- 選択してください --</option>
                        <option value="1"></option>
                        <option value="2"></option>
                        <option value="3"></option>
                    </select>
                </div>
                <div class="form-group mb-3">
                    <label>タスク画像</label>
                    <input type="file" id="myTaskImage" name="myTaskImage" class="form-control border border-dark"/>
                </div>
                <div class="form-group mb-3">
                    <button id="todoCreate" class="btn btn-primary">
                        登録<br/><small>Create</small>
                    </button>
                    <button id="backToTopPage" class="btn btn-secondary">
                        一覧に戻る<br/><small>Back To TopPage</small>
                    </button>
                </div>
            </form>
        </div>
    </body>
</html>
static/js/taskCreate.js
$(document).ready(function(){
    //Session StorageからユーザIDを取得する
    const userId = sessionStorage.getItem("userId");
    let inputValueInUserId = document.getElementById("userId");
    inputValueInUserId.value = userId;

    //一覧に戻るボタンをクリックしたときの処理
    $('#backToTopPage').on('click',function(){
        location.replace("/sub");
    });

    //登録ボタンをクリックしたときの処理
    $('#todoCreate').on('click',function(e){
        //POST通信するための設定
        e.preventDefault();

        //オプションタグの選択項目を取得
        const selectedTaskPriority = document.getElementById("myTaskPriority");

        //フォームデータの作成
        let formData = new FormData();
        formData.append('userId',$("#userId").val());
        formData.append('myTask',$("#myTask").val());
        formData.append('myTaskDescription',$("#myTaskDescription").val());
        formData.append('myTaskPriority',$("#myTaskPriority").val());   //  $("#myTaskPriority").val()
        formData.append('myTaskImage',$("#myTaskImage")[0].files[0]);
        const fileInfo = $("#myTaskImage").val();
        console.log(fileInfo);

        $.ajax({
            url:"/controllers/todoAdd",
            method:"POST",
            data:formData,
            processData:false,
            contentType:false,
            success:function(response){
                alert("タスクの登録に成功しました。");
            },
            error:function(response){
                alert("タスクの登録に失敗しました。");
            }
        });
    });

});

サーバ側のコード

targetTask.Registermodels.Register型のフィールドですが、これはポインタ型であり、初期化されていない状態ではnilになります。
nilの状態でtargetTask.Register.Idにアクセスしようとすると、nilポインタの参照エラーが発生します。

解決方法:

targetTask.Registerを初期化する必要があります。
以下のように、targetTask.Registerに新しいmodels.Registerのインスタンスを代入してください。

// Registerを初期化
targetTask.Register = &models.Register{}

以上を踏まえたコードがこちらです↓

controllers/default.go
// タスク登録用コントローラ
func (c *TodoAddController) Post() {
	//フォームデータから入力情報を取得する
	userId := c.GetString("userId") //20250211
	task := c.GetString("myTask")
	taskDescription := c.GetString("myTaskDescription")
	taskPriority := c.GetString("myTaskPriority")
	fmt.Println("フォーム画面から取得したユーザIDは、", userId) //20250211
	fmt.Println("フォーム画面から取得したタスク名は、", task)
	fmt.Println("フォーム画面から取得したタスク説明は、", taskDescription)
	fmt.Println("フォーム画面から取得したタスク画像は、", taskPriority)

	//ファイルをアップロード
	f, h, err := c.GetFile("myTaskImage")
	if err != nil {
		fmt.Println("ファイルエラー", err)
		c.Ctx.ResponseWriter.WriteHeader(500)
		c.Ctx.WriteString("画像アップロード失敗")
		return
	}

	defer f.Close()
	// 画像を保存するパスを指定
	uploadPath := "static/upload/"
	err = os.MkdirAll(uploadPath, os.ModePerm) // ディレクトリがない場合に作成

	if err != nil {
		fmt.Println("ファイル保存エラー", err)
		c.Ctx.ResponseWriter.WriteHeader(500)
		c.Ctx.WriteString("画像保存失敗")
		return
	}

	// ファイル名を表示
	//fmt.Println("ファイル名は", fileName)

	// 画像ファイル名を取得して保存
	fileName := h.Filename
	savePath := filepath.Join(uploadPath, fileName)

	// ファイルを保存
	outFile, err := os.Create(savePath)
	if err != nil {
		fmt.Println("ファイル保存エラー", err)
		c.Ctx.ResponseWriter.WriteHeader(500)
		c.Ctx.WriteString("画像保存に失敗しました")
		return
	}

	defer outFile.Close()

	// アップロードされたファイルの内容を保存
	_, err = f.Seek(0, 0) // ファイルの最初に戻す
	if err != nil {
		fmt.Println("ファイル読み込みエラー", err)
		c.Ctx.ResponseWriter.WriteHeader(500)
		c.Ctx.WriteString("ファイル読み込みに失敗しました")
		return
	}

	_, err = outFile.ReadFrom(f)
	if err != nil {
		fmt.Println("ファイル書き込みエラー", err)
		c.Ctx.ResponseWriter.WriteHeader(500)
		c.Ctx.WriteString("ファイル書き込みに失敗しました")
		return
	}

	// ファイル名を表示
	fmt.Println("ファイル名は", fileName)

	//----Taskテーブルへ登録する処理----

	//ORMを取得する
	o := orm.NewOrm()

	//Taskモデルのインスタンスを生成する
	var targetTask models.Task
	// Registerを初期化
	targetTask.Register = &models.Register{}

	//トランザクション開始
	tx, err := o.Begin()

	if err != nil {
		fmt.Println("トランザクション開始エラー", err)
		c.Data["json"] = map[string]string{
			"status":  "失敗",
			"message": "トランザクションの開始に失敗しました。",
		}
	}
	//ユーザIDをStringからInt64型へ変換する
	numUserId, _ := strconv.ParseInt(userId, 10, 64)
	fmt.Println("整数型に変換したIDは、", numUserId)
	targetTask.Register.Id = numUserId //ユーザID
	fmt.Println("登録したRegisterId", targetTask.Register.Id)

	targetTask.TaskName = task
	fmt.Println("登録したTaskName", targetTask.TaskName)
	targetTask.TaskDescription = taskDescription
	fmt.Println("登録したTaskName", targetTask.TaskDescription)

	targetTask.TaskPriority = taskPriority
	fmt.Println("登録したTaskName", targetTask.TaskPriority)              //taskPriority
	targetTask.TaskImage = "http://localhost:8080/upload/" + fileName //fileName

	fmt.Println("登録したRegisterId", targetTask.Register.Id)
	fmt.Println("登録したTaskName", targetTask.TaskName)
	fmt.Println("登録したTaskName", targetTask.TaskDescription)
	fmt.Println("登録したTaskName", targetTask.TaskPriority)
	fmt.Println("登録したTaskName", targetTask.TaskImage)

	_, err = tx.Insert(&targetTask)

	if err != nil {
		tx.Rollback()
		fmt.Println("エラー発生", err)
		c.Data["json"] = map[string]string{
			"status":  "失敗",
			"message": "タスク登録に失敗しました。",
		}
		c.ServeJSON()
		return
	}

	//成功した場合コミットする
	err = tx.Commit()

	if err != nil {
		fmt.Println("トランザクションコミットに失敗しました。")
		c.Data["json"] = map[string]string{
			"status":  "失敗",
			"message": "トランザクションコミットに失敗しました。",
		}
		c.ServeJSON()
		return
	}

	// 成功レスポンスを返す
	c.Ctx.ResponseWriter.WriteHeader(200)
	c.Ctx.WriteString("タスク登録が成功しました")
	//c.SaveFile("uploadname", "static/upload/"+h.Filename)
}

全体のコードはこちら↓

controllers/default.go
package controllers

import (
	"fmt"
	"time"

	"my-first-beego-project/models"
	_ "my-first-beego-project/models"

	"github.com/astaxie/beego"
	"github.com/beego/beego/v2/client/orm"

	"my-first-beego-project/crypto"

	"os"
	"path/filepath"

	"strconv"
)

type MainController struct {
	beego.Controller
}

func (c *MainController) Get() {
	c.Data["Website"] = "beego.me"
	c.Data["Email"] = "astaxie@gmail.com"
	c.TplName = "index.tpl"
}

// 20250205追加
type SubController struct {
	beego.Controller
}

type ThirdController struct {
	beego.Controller
}

type UserController struct {
	beego.Controller
}

// ログイン画面
type LoginController struct {
	beego.Controller
}

// タスクの登録画面
type TodoCreateController struct {
	beego.Controller
}

// タスク登録
type TodoAddController struct {
	beego.Controller
}

// 20250205
func (c *SubController) Get() {
	c.Data["Page"] = "SubPage"
	c.TplName = "subIndex.tpl"
}

// @router :id[get]
func (this *ThirdController) Get() {
	this.Data["ID"] = this.Ctx.Input.Param(":id")
	this.TplName = "thirdIndex.tpl"
}

// ログイン画面コントローラ
func (c *LoginController) Get() {
	c.TplName = "login.tpl"
}

func (c *UserController) Post() {
	//フォームデータを取得する
	name := c.GetString("name")
	email := c.GetString("email")
	password := c.GetString("password")

	//デバッグ用
	//fmt.Println("フォーム画面から取得したNameは:", name)
	//fmt.Println("フォーム画面から取得したEmailは:", email)
	//fmt.Println("フォーム画面から取得したPasswordは:", password)

	c.Data["json"] = map[string]interface{}{
		"status":  "成功",
		"message": "フォーム画面からデータを受け取りました。",
		"requestData": map[string]string{
			"Name":     name,
			"Email":    email,
			"Password": password,
		},
	}
	/*
		c.Data["json"] = map[string]string{
			"status":"成功",
			"message":"フォーム画面からデータを受け取りました。",
		}
	*/
	//パスワードのハッシュ化
	generatedHashedPassword, error := crypto.GeneratedPasswordHashed(password)

	if error != nil {
		fmt.Println("パスワードを暗号化中にエラーが発生しました。", error)
		c.Data["json"] = map[string]string{
			"status":  "失敗",
			"message": "パスワードのハッシュ化に失敗しました。",
		}
		c.ServeJSON()
		return
	}
	//ORMを取得する
	o := orm.NewOrm()
	//モデルTodoのインスタンスを生成
	var registerUser models.Register //var todo models.Todo
	//トランザクション開始
	tx, err := o.Begin()

	if err != nil {
		fmt.Println("トランザクション開始エラー", err)
		c.Data["json"] = map[string]string{
			"status":  "失敗",
			"message": "トランザクションの開始に失敗しました。",
		}
	}

	registerUser.Name = name
	registerUser.Email = email
	registerUser.Password = generatedHashedPassword
	registerUser.CreatedAt = time.Now()

	_, err = tx.Insert(&registerUser)
	if err != nil {
		tx.Rollback()
		fmt.Println("エラー発生", err)
		c.Data["json"] = map[string]string{
			"status":  "失敗",
			"message": "ユーザ登録に失敗しました。",
		}
		c.ServeJSON()
		return
	}
	//成功した場合コミット
	err = tx.Commit()

	if err != nil {
		fmt.Println("トランザクションのコミットに失敗しました。", err)
		c.Data["json"] = map[string]string{
			"status":  "失敗",
			"message": "トランザクションのコミットに失敗しました。",
		}
		c.ServeJSON()
		return
	}

	//登録したデータを取得する
	var registeredUser models.Register
	err = o.QueryTable("register").Filter("Email", email).One(&registeredUser)

	if err != nil {
		fmt.Println("ユーザ情報の取得に失敗しました。", err)
		c.Data["json"] = map[string]string{
			"status":  "失敗",
			"message": "ユーザー情報の取得に失敗しました。",
		}
		c.ServeJSON()
		return
	}

	fmt.Println("登録されたユーザは、", registeredUser.Name)
	fmt.Println("登録されたEmailは、", registeredUser.Email)

	//成功時のレスポンス
	c.Data["json"] = map[string]interface{}{
		"status":  "成功",
		"message": "ユーザーが正常に登録されました",
		"requestData": map[string]string{
			"Name":  registeredUser.Name,
			"Email": registeredUser.Email,
		},
	}

	//JSONデータを返却する
	c.ServeJSON()
}

func (c *LoginController) Post() {

	//フォームデータを取得する
	email := c.GetString("email")
	password := c.GetString("password")

	fmt.Println("ログイン画面から渡されたメールアドレスは、", email)
	fmt.Println("ログイン画面から渡されたパスワードは、", password)

	//ORMを定義する
	o := orm.NewOrm()

	//登録済みのデータを取得する
	var targetUser models.Register
	// `err` を宣言
	err := o.QueryTable("register").Filter("Email", email).One(&targetUser)
	fmt.Println("データベースから取得したユーザ情報は、", targetUser.Id)
	if err != nil {
		fmt.Println("ユーザ情報を取得できませんでした。", err)
		c.Data["json"] = map[string]string{
			"status":  "失敗",
			"message": "ユーザ情報をしゅとくできませんでした。",
		}
		c.ServeJSON()
		return
	}
	//fmt.Println("ここまで1", err)
	err = crypto.ComparedHashedPassword(targetUser.Password, password)
	//fmt.Println("ここまで2", err)
	if err != nil {
		fmt.Println("パスワードが一致しませんでした。", err)
		c.Data["json"] = map[string]string{
			"status":  "失敗",
			"message": "パスワードが一致しませんでした。",
		}
		c.ServeJSON()
		return
	}
	//fmt.Println("ここまで3")
	c.Data["json"] = map[string]interface{}{
		"status":  "成功",
		"message": "ログインに成功しました。",
		"user": map[string]interface{}{
			"id":    targetUser.Id,
			"name":  targetUser.Name,
			"email": targetUser.Email,
		},
	}
	c.ServeJSON()
}

func (c *TodoCreateController) Get() {
	//fmt.Println("TodoCreateControllerに来た")
	c.TplName = "mytodo/addTodo.tpl" //c.TplName = "mytodo/addTodo.tpl"
	//fmt.Println("TodoCreateControllerに来たよ")
}

// タスク登録用コントローラ
func (c *TodoAddController) Post() {
	//フォームデータから入力情報を取得する
	userId := c.GetString("userId") //20250211
	task := c.GetString("myTask")
	taskDescription := c.GetString("myTaskDescription")
	taskPriority := c.GetString("myTaskPriority")
	fmt.Println("フォーム画面から取得したユーザIDは、", userId) //20250211
	fmt.Println("フォーム画面から取得したタスク名は、", task)
	fmt.Println("フォーム画面から取得したタスク説明は、", taskDescription)
	fmt.Println("フォーム画面から取得したタスク画像は、", taskPriority)

	//ファイルをアップロード
	f, h, err := c.GetFile("myTaskImage")
	if err != nil {
		fmt.Println("ファイルエラー", err)
		c.Ctx.ResponseWriter.WriteHeader(500)
		c.Ctx.WriteString("画像アップロード失敗")
		return
	}

	defer f.Close()
	// 画像を保存するパスを指定
	uploadPath := "static/upload/"
	err = os.MkdirAll(uploadPath, os.ModePerm) // ディレクトリがない場合に作成

	if err != nil {
		fmt.Println("ファイル保存エラー", err)
		c.Ctx.ResponseWriter.WriteHeader(500)
		c.Ctx.WriteString("画像保存失敗")
		return
	}

	// ファイル名を表示
	//fmt.Println("ファイル名は", fileName)

	// 画像ファイル名を取得して保存
	fileName := h.Filename
	savePath := filepath.Join(uploadPath, fileName)

	// ファイルを保存
	outFile, err := os.Create(savePath)
	if err != nil {
		fmt.Println("ファイル保存エラー", err)
		c.Ctx.ResponseWriter.WriteHeader(500)
		c.Ctx.WriteString("画像保存に失敗しました")
		return
	}

	defer outFile.Close()

	// アップロードされたファイルの内容を保存
	_, err = f.Seek(0, 0) // ファイルの最初に戻す
	if err != nil {
		fmt.Println("ファイル読み込みエラー", err)
		c.Ctx.ResponseWriter.WriteHeader(500)
		c.Ctx.WriteString("ファイル読み込みに失敗しました")
		return
	}

	_, err = outFile.ReadFrom(f)
	if err != nil {
		fmt.Println("ファイル書き込みエラー", err)
		c.Ctx.ResponseWriter.WriteHeader(500)
		c.Ctx.WriteString("ファイル書き込みに失敗しました")
		return
	}

	// ファイル名を表示
	fmt.Println("ファイル名は", fileName)

	//----Taskテーブルへ登録する処理----

	//ORMを取得する
	o := orm.NewOrm()

	//Taskモデルのインスタンスを生成する
	var targetTask models.Task
	// Registerを初期化
	targetTask.Register = &models.Register{}

	//トランザクション開始
	tx, err := o.Begin()

	if err != nil {
		fmt.Println("トランザクション開始エラー", err)
		c.Data["json"] = map[string]string{
			"status":  "失敗",
			"message": "トランザクションの開始に失敗しました。",
		}
	}
	//ユーザIDをStringからInt64型へ変換する
	numUserId, _ := strconv.ParseInt(userId, 10, 64)
	fmt.Println("整数型に変換したIDは、", numUserId)
	targetTask.Register.Id = numUserId //ユーザID
	fmt.Println("登録したRegisterId", targetTask.Register.Id)

	targetTask.TaskName = task
	fmt.Println("登録したTaskName", targetTask.TaskName)
	targetTask.TaskDescription = taskDescription
	fmt.Println("登録したTaskName", targetTask.TaskDescription)

	targetTask.TaskPriority = taskPriority
	fmt.Println("登録したTaskName", targetTask.TaskPriority)              //taskPriority
	targetTask.TaskImage = "http://localhost:8080/upload/" + fileName //fileName

	fmt.Println("登録したRegisterId", targetTask.Register.Id)
	fmt.Println("登録したTaskName", targetTask.TaskName)
	fmt.Println("登録したTaskName", targetTask.TaskDescription)
	fmt.Println("登録したTaskName", targetTask.TaskPriority)
	fmt.Println("登録したTaskName", targetTask.TaskImage)

	_, err = tx.Insert(&targetTask)

	if err != nil {
		tx.Rollback()
		fmt.Println("エラー発生", err)
		c.Data["json"] = map[string]string{
			"status":  "失敗",
			"message": "タスク登録に失敗しました。",
		}
		c.ServeJSON()
		return
	}

	//成功した場合コミットする
	err = tx.Commit()

	if err != nil {
		fmt.Println("トランザクションコミットに失敗しました。")
		c.Data["json"] = map[string]string{
			"status":  "失敗",
			"message": "トランザクションコミットに失敗しました。",
		}
		c.ServeJSON()
		return
	}

	// 成功レスポンスを返す
	c.Ctx.ResponseWriter.WriteHeader(200)
	c.Ctx.WriteString("タスク登録が成功しました")
	//c.SaveFile("uploadname", "static/upload/"+h.Filename)
}

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?