MongoDBGoドライバーを使用してMongoDBでGoを使用する方法

提供:Dev Guides
移動先:案内検索

著者は、 Free Software Foundation を選択して、 Write forDOnationsプログラムの一環として寄付を受け取りました。

序章

コミュニティが開発したソリューションに長年依存した後、MongoDBはがGoの公式ドライバーに取り組んでいることを発表しました。 2019年3月、この新しいドライバーは、v1.0.0 のリリースにより、本番環境に対応したステータスに達し、それ以降も継続的に更新されています。

他の公式のMongoDBドライバーと同様に、 GoドライバーはGoプログラミング言語に慣用的であり、GoプログラムのデータベースソリューションとしてMongoDBを使用する簡単な方法を提供します。 これはMongoDBAPIと完全に統合されており、APIのすべてのクエリ、インデックス作成、集計機能、およびその他の高度な機能を公開しています。 サードパーティのライブラリとは異なり、MongoDBエンジニアによって完全にサポートされるため、継続的な開発とメンテナンスを保証できます。

このチュートリアルでは、公式のMongoDBGoドライバーの使用を開始します。 ドライバーをインストールし、MongoDBデータベースに接続して、いくつかのCRUD操作を実行します。 その過程で、コマンドラインからタスクを管理するためのタスクマネージャープログラムを作成します。

前提条件

このチュートリアルでは、次のものが必要です。

  • MongoDBのインストール方法に従って、オペレーティングシステムにMongoDBをインストールします。 MongoDB 2.6以降は、MongoDBGoドライバーでサポートされている最小バージョンです。

Go v1.11または1.12を使用している場合は、次のようにGO111MODULE環境変数をonに設定して、Goモジュールが有効になっていることを確認します。

export GO111MODULE="on"

環境変数の実装の詳細については、環境変数とシェル変数の読み取りと設定方法に関するこのチュートリアルをお読みください。

このガイドに示されているコマンドとコードは、Gov1.14.1とMongoDBv3.6.3でテストされています。

ステップ1—MongoDBGoドライバーをインストールする

このステップでは、MongoDB用のGo Driverパッケージをインストールし、プロジェクトにインポートします。 また、MongoDBデータベースに接続して、接続のステータスを確認します。

先に進み、ファイルシステムにこのチュートリアル用の新しいディレクトリを作成します。

mkdir tasker

プロジェクトディレクトリを設定したら、次のコマンドを使用してディレクトリに変更します。

cd tasker

次に、Goプロジェクトをgo.modファイルで初期化します。 このファイルは、プロジェクト要件を定義し、依存関係を正しいバージョンにロックします。

go mod init

プロジェクトディレクトリが$GOPATHの外にある場合は、モジュールのインポートパスを次のように指定する必要があります。

go mod init github.com/<your_username>/tasker

この時点で、go.modファイルは次のようになります。

go.mod

module github.com/<your_username>/tasker

go 1.14

次のコマンドを使用して、MongoDBGoドライバーをプロジェクトの依存関係として追加します。

go get go.mongodb.org/mongo-driver

次のような出力が表示されます。

Outputgo: downloading go.mongodb.org/mongo-driver v1.3.2
go: go.mongodb.org/mongo-driver upgrade => v1.3.2

この時点で、go.modファイルは次のようになります。

go.mod

module github.com/<your_username>/tasker

go 1.14

require go.mongodb.org/mongo-driver v1.3.1 // indirect

次に、プロジェクトルートにmain.goファイルを作成し、テキストエディタで開きます。

nano main.go

ドライバの使用を開始するには、次のパッケージをmain.goファイルにインポートします。

main.go

package main

import (
    "context"
    "log"

    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
)

ここでは、MongoDBGoドライバーが提供するmongoおよびoptionsパッケージを追加します。

次に、インポートに続いて、新しいMongoDBクライアントを作成し、実行中のMongoDBサーバーに接続します。

main.go

. . .
var collection *mongo.Collection
var ctx = context.TODO()

func init() {
    clientOptions := options.Client().ApplyURI("mongodb://localhost:27017/")
    client, err := mongo.Connect(ctx, clientOptions)
    if err != nil {
        log.Fatal(err)
    }
}

mongo.Connect()は、Contextおよびoptions.ClientOptionsオブジェクトを受け入れます。これらのオブジェクトは、接続文字列およびその他のドライバー設定を設定するために使用されます。 オプションパッケージのドキュメントにアクセスして、使用可能な構成オプションを確認できます。

Context は、操作の実行を停止して戻るタイミングを示すタイムアウトまたは期限のようなものです。 特定の操作の実行速度が遅い場合に、実稼働システムのパフォーマンスが低下するのを防ぐのに役立ちます。 このコードでは、context.TODO()を渡して、現在使用するコンテキストがわからないことを示していますが、将来的に追加する予定です。

次に、MongoDBサーバーが検出され、Pingメソッドを使用して正常に接続されていることを確認しましょう。 init関数内に次のコードを追加します。

main.go

. . .
    log.Fatal(err)
  }

  err = client.Ping(ctx, nil)
  if err != nil {
    log.Fatal(err)
  }
}

データベースへの接続中にエラーが発生した場合、アクティブなデータベース接続なしでプログラムを実行し続ける意味がないため、問題を修正しようとするとプログラムがクラッシュするはずです。

次のコードを追加して、データベースを作成します。

main.go

. . .
  err = client.Ping(ctx, nil)
  if err != nil {
    log.Fatal(err)
  }

  collection = client.Database("tasker").Collection("tasks")
}

taskerデータベースとtaskコレクションを作成して、作成するタスクを保存します。 また、collectionをパッケージレベルの変数として設定して、パッケージ全体でデータベース接続を再利用できるようにします。

ファイルを保存して終了します。

この時点での完全なmain.goは次のとおりです。

main.go

package main

import (
    "context"
    "log"

    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
)

var collection *mongo.Collection
var ctx = context.TODO()

func init() {
    clientOptions := options.Client().ApplyURI("mongodb://localhost:27017/")
    client, err := mongo.Connect(ctx, clientOptions)
    if err != nil {
        log.Fatal(err)
    }

    err = client.Ping(ctx, nil)
    if err != nil {
        log.Fatal(err)
    }

    collection = client.Database("tasker").Collection("tasks")
}

Goドライバーを使用してMongoDBサーバーに接続するようにプログラムを設定しました。 次のステップでは、タスクマネージャプログラムの作成に進みます。

ステップ2—CLIプログラムの作成

このステップでは、よく知られている cli パッケージをインストールして、タスクマネージャープログラムの開発を支援します。 最新のコマンドラインツールをすばやく作成するために利用できるインターフェイスを提供します。 たとえば、このパッケージは、よりgitに似たコマンドラインエクスペリエンスのために、プログラムのサブコマンドを定義する機能を提供します。

次のコマンドを実行して、パッケージを依存関係として追加します。

go get github.com/urfave/cli/v2

次に、main.goファイルをもう一度開きます。

nano main.go

次の強調表示されたコードをmain.goファイルに追加します。

main.go

package main

import (
    "context"
    "log"
    "os"

    "github.com/urfave/cli/v2"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
)
. . .

前述のように、cliパッケージをインポートします。 また、osパッケージをインポートします。これを使用して、コマンドライン引数をプログラムに渡します。

init関数の後に次のコードを追加してCLIプログラムを作成し、コードをコンパイルします。

main.go

. . .
func main() {
    app := &cli.App{
        Name:     "tasker",
        Usage:    "A simple CLI program to manage your tasks",
        Commands: []*cli.Command{},
    }

    err := app.Run(os.Args)
    if err != nil {
        log.Fatal(err)
    }
}

このスニペットは、taskerというCLIプログラムを作成し、プログラムの実行時に出力される簡単な使用法の説明を追加します。 Commandsスライスは、プログラムのコマンドを追加する場所です。 Runコマンドは、引数スライスを適切なコマンドに解析します。

ファイルを保存して終了します。

プログラムをビルドして実行するために必要なコマンドは次のとおりです。

go run main.go

次の出力が表示されます。

OutputNAME:
   tasker - A simple CLI program to manage your tasks

USAGE:
   main [global options] command [command options] [arguments...]

COMMANDS:
   help, h  Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --help, -h     show help (default: false)

プログラムが実行され、ヘルプテキストが表示されます。これは、プログラムで何ができるか、およびその使用方法を学習するのに便利です。

次のステップでは、MongoDBでのタスクの管理に役立つサブコマンドを追加することで、プログラムの有用性を向上させます。

ステップ3—タスクの作成

このステップでは、cliパッケージを使用してCLIプログラムにサブコマンドを追加します。 このセクションの最後で、CLIプログラムで新しいaddコマンドを使用して、MongoDBデータベースに新しいタスクを追加できるようになります。

main.goファイルを開くことから始めます。

nano main.go

次に、 go.mongodb.org/mongo-driver/bson/primitivetime 、およびerrorsパッケージをインポートします。

main.go

package main

import (
    "context"
    "errors"
    "log"
    "os"
    "time"

    "github.com/urfave/cli/v2"
    "go.mongodb.org/mongo-driver/bson/primitive"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
)
. . .

次に、データベース内の単一のタスクを表す新しい構造体を作成し、main関数の直前に挿入します。

main.go

. . .
type Task struct {
    ID        primitive.ObjectID `bson:"_id"`
    CreatedAt time.Time          `bson:"created_at"`
    UpdatedAt time.Time          `bson:"updated_at"`
    Text      string             `bson:"text"`
    Completed bool               `bson:"completed"`
}
. . .

MongoDBはデフォルトで_idフィールドにObjectIDを使用するため、primitiveパッケージを使用して各タスクのIDのタイプを設定します。 MongoDBのもう1つのデフォルトの動作は、シリアル化されるときに、エクスポートされた各フィールドのキーとして小文字のフィールド名が使用されることですが、これはbson構造体タグを使用して変更できます。

次に、Taskのインスタンスを受け取り、それをデータベースに保存する関数を作成します。 main関数の後に次のスニペットを追加します。

main.go

. . .
func createTask(task *Task) error {
    _, err := collection.InsertOne(ctx, task)
  return err
}
. . .

collection.InsertOne()メソッドは、提供されたタスクをデータベースコレクションに挿入し、挿入されたドキュメントのIDを返します。 このIDは必要ないため、アンダースコア演算子に割り当てて破棄します。

次のステップは、新しいタスクを作成するための新しいコマンドをタスクマネージャープログラムに追加することです。 それをaddと呼びましょう:

main.go

. . .
func main() {
    app := &cli.App{
        Name:  "tasker",
        Usage: "A simple CLI program to manage your tasks",
        Commands: []*cli.Command{
            {
                Name:    "add",
                Aliases: []string{"a"},
                Usage:   "add a task to the list",
                Action: func(c *cli.Context) error {
                    str := c.Args().First()
                    if str == "" {
                        return errors.New("Cannot add an empty task")
                    }

                    task := &Task{
                        ID:        primitive.NewObjectID(),
                        CreatedAt: time.Now(),
                        UpdatedAt: time.Now(),
                        Text:      str,
                        Completed: false,
                    }

                    return createTask(task)
                },
            },
        },
    }

    err := app.Run(os.Args)
    if err != nil {
        log.Fatal(err)
    }
}

CLIプログラムに追加されるすべての新しいコマンドは、Commandsスライス内に配置されます。 それぞれが名前、使用法の説明、およびアクションで構成されています。 これは、コマンドの実行時に実行されるコードです。

このコードでは、addの最初の引数を収集し、それを使用して、他のプロパティに適切なデフォルトを割り当てながら、新しいTaskインスタンスのTextプロパティを設定します。 その後、新しいタスクはcreateTaskに渡され、データベースにタスクが挿入され、すべてがうまくいくとコマンドが終了した場合にnilが返されます。

ファイルを保存して終了します。

addコマンドを使用していくつかのタスクを追加して、テストしてください。 成功した場合、画面にエラーは表示されません。

go run main.go add "Learn Go"
go run main.go add "Read a book"

タスクを正常に追加できるようになったので、データベースに追加したすべてのタスクを表示する方法を実装しましょう。

ステップ4—すべてのタスクを一覧表示する

コレクション内のドキュメントの一覧表示は、collection.Find()メソッドを使用して実行できます。このメソッドでは、フィルターと、結果をデコードできる値へのポインターが必要です。 その戻り値はCursorであり、一度に1つずつ繰り返してデコードできるドキュメントのストリームを提供します。 カーソルが使い果たされると、カーソルは閉じられます。

main.goファイルを開きます。

nano main.go

必ずbsonパッケージをインポートしてください。

main.go

package main

import (
    "context"
    "errors"
    "log"
    "os"
    "time"

    "github.com/urfave/cli/v2"
    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/bson/primitive"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
)
. . .

次に、createTaskの直後に次の関数を作成します。

main.go

. . .
func getAll() ([]*Task, error) {
  // passing bson.D{{}} matches all documents in the collection
    filter := bson.D{{}}
    return filterTasks(filter)
}

func filterTasks(filter interface{}) ([]*Task, error) {
    // A slice of tasks for storing the decoded documents
    var tasks []*Task

    cur, err := collection.Find(ctx, filter)
    if err != nil {
        return tasks, err
    }

    for cur.Next(ctx) {
        var t Task
        err := cur.Decode(&t)
        if err != nil {
            return tasks, err
        }

        tasks = append(tasks, &t)
    }

    if err := cur.Err(); err != nil {
        return tasks, err
    }

  // once exhausted, close the cursor
    cur.Close(ctx)

    if len(tasks) == 0 {
        return tasks, mongo.ErrNoDocuments
    }

    return tasks, nil
}

BSON(バイナリエンコードされたJSON)は、MongoDBデータベースでドキュメントを表現する方法であり、bsonパッケージは、GoでBSONオブジェクトを操作するのに役立ちます。 getAll()関数で使用されるbson.Dタイプは、BSONドキュメントを表し、プロパティの順序が重要な場合に使用されます。 bson.D{{}}をフィルターとしてfilterTasks()に渡すことにより、コレクション内のすべてのドキュメントを照合することを示します。

filterTasks()関数では、collection.Find()メソッドによって返されるカーソルを反復処理し、各ドキュメントをTaskのインスタンスにデコードします。 次に、各Taskは、関数の開始時に作成されたタスクのスライスに追加されます。 カーソルがなくなると、カーソルが閉じられ、tasksスライスが返されます。

すべてのタスクを一覧表示するコマンドを作成する前に、tasksのスライスを取得して標準出力に出力するヘルパー関数を作成しましょう。 colorパッケージを使用して出力を色付けします。

このパッケージを使用する前に、次のコマンドを使用してインストールしてください。

go get gopkg.in/gookit/color.v1

次の出力が表示されます。

Outputgo: downloading gopkg.in/gookit/color.v1 v1.1.6
go: gopkg.in/gookit/color.v1 upgrade => v1.1.6

そして、それをfmtパッケージと一緒にmain.goファイルにインポートします。

main.go

package main

import (
    "context"
    "errors"
  "fmt"
    "log"
    "os"
    "time"

    "github.com/urfave/cli/v2"
    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/bson/primitive"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
    "gopkg.in/gookit/color.v1"
)
. . .

次に、main関数に続いて、新しいprintTasks関数を作成します。

main.go

. . .
func printTasks(tasks []*Task) {
    for i, v := range tasks {
        if v.Completed {
            color.Green.Printf("%d: %s\n", i+1, v.Text)
        } else {
            color.Yellow.Printf("%d: %s\n", i+1, v.Text)
        }
    }
}
. . .

このprintTasks関数は、tasksのスライスを取得し、それぞれを繰り返して、完了したタスクを示す緑色と未完了のタスクを示す黄色を使用して標準出力に出力します。

先に進み、次の強調表示された行を追加して、新しいallコマンドをCommandsスライスに作成します。 このコマンドは、追加されたすべてのタスクを標準出力に出力します。

main.go

. . .
func main() {
    app := &cli.App{
        Name:  "tasker",
        Usage: "A simple CLI program to manage your tasks",
        Commands: []*cli.Command{
            {
                Name:    "add",
                Aliases: []string{"a"},
                Usage:   "add a task to the list",
                Action: func(c *cli.Context) error {
                    str := c.Args().First()
                    if str == "" {
                        return errors.New("Cannot add an empty task")
                    }

                    task := &Task{
                        ID:        primitive.NewObjectID(),
                        CreatedAt: time.Now(),
                        UpdatedAt: time.Now(),
                        Text:      str,
                        Completed: false,
                    }

                    return createTask(task)
                },
            },
            {
                Name:    "all",
                Aliases: []string{"l"},
                Usage:   "list all tasks",
                Action: func(c *cli.Context) error {
                    tasks, err := getAll()
                    if err != nil {
                        if err == mongo.ErrNoDocuments {
                            fmt.Print("Nothing to see here.\nRun `add 'task'` to add a task")
                            return nil
                        }

                        return err
                    }

                    printTasks(tasks)
                    return nil
                },
            },
        },
    }

    err := app.Run(os.Args)
    if err != nil {
        log.Fatal(err)
    }
}

. . .

allコマンドは、データベースに存在するすべてのタスクを取得し、それらを標準出力に出力します。 タスクが存在しない場合は、代わりに新しいタスクを追加するためのプロンプトが出力されます。

ファイルを保存して終了します。

allコマンドを使用して、プログラムをビルドして実行します。

go run main.go all

これまでに追加したすべてのタスクが一覧表示されます。

Output1: Learn Go
2: Read a book

データベース内のすべてのタスクを表示できるようになったので、次のステップでタスクを完了としてマークする機能を追加しましょう。

ステップ5—タスクの完了

このステップでは、doneという新しいサブコマンドを作成します。これにより、データベース内の既存のタスクに完了のマークを付けることができます。 タスクを完了としてマークするには、collection.FindOneAndUpdate()メソッドを使用できます。 コレクション内のドキュメントを検索し、そのプロパティの一部またはすべてを更新できます。 この方法では、ドキュメントを見つけるためのフィルターと、操作を説明するための更新ドキュメントが必要です。 これらは両方ともbson.Dタイプを使用して構築されています。

main.goファイルを開くことから始めます。

nano main.go

filterTasks関数の後に次のスニペットを挿入します。

main.go

. . .
func completeTask(text string) error {
    filter := bson.D{primitive.E{Key: "text", Value: text}}

    update := bson.D{primitive.E{Key: "$set", Value: bson.D{
        primitive.E{Key: "completed", Value: true},
    }}}

    t := &Task{}
    return collection.FindOneAndUpdate(ctx, filter, update).Decode(t)
}
. . .

この関数は、textプロパティがtextパラメーターと等しい最初のドキュメントと一致します。 updateドキュメントは、completedプロパティがtrueに設定されることを指定しています。 FindOneAndUpdate()の操作でエラーが発生した場合は、completeTask()から返されます。 それ以外の場合は、nilが返されます。

次に、タスクを完了としてマークする新しいdoneコマンドをCLIプログラムに追加しましょう。

main.go

. . .
func main() {
    app := &cli.App{
        Name:  "tasker",
        Usage: "A simple CLI program to manage your tasks",
        Commands: []*cli.Command{
            {
                Name:    "add",
                Aliases: []string{"a"},
                Usage:   "add a task to the list",
                Action: func(c *cli.Context) error {
                    str := c.Args().First()
                    if str == "" {
                        return errors.New("Cannot add an empty task")
                    }

                    task := &Task{
                        ID:        primitive.NewObjectID(),
                        CreatedAt: time.Now(),
                        UpdatedAt: time.Now(),
                        Text:      str,
                        Completed: false,
                    }

                    return createTask(task)
                },
            },
            {
                Name:    "all",
                Aliases: []string{"l"},
                Usage:   "list all tasks",
                Action: func(c *cli.Context) error {
                    tasks, err := getAll()
                    if err != nil {
                        if err == mongo.ErrNoDocuments {
                            fmt.Print("Nothing to see here.\nRun `add 'task'` to add a task")
                            return nil
                        }

                        return err
                    }

                    printTasks(tasks)
                    return nil
                },
            },
            {
                Name:    "done",
                Aliases: []string{"d"},
                Usage:   "complete a task on the list",
                Action: func(c *cli.Context) error {
                    text := c.Args().First()
                    return completeTask(text)
                },
            },
        },
    }

    err := app.Run(os.Args)
    if err != nil {
        log.Fatal(err)
    }
}

. . .

doneコマンドに渡された引数を使用して、textプロパティが一致する最初のドキュメントを検索します。 見つかった場合、ドキュメントのcompletedプロパティはtrueに設定されます。

ファイルを保存して終了します。

次に、doneコマンドを使用してプログラムを実行します。

go run main.go done "Learn Go"

allコマンドを再度使用すると、完了としてマークされたタスクが緑色で印刷されていることがわかります。

go run main.go all

まだ実行されていないタスクのみを表示したい場合があります。 次にその機能を追加します。

手順6—保留中のタスクのみを表示する

このステップでは、MongoDBドライバーを使用してデータベースから保留中のタスクを取得するコードを組み込みます。 保留中のタスクは、completedプロパティがfalseに設定されているタスクです。

まだ完了していないタスクを取得する新しい関数を追加しましょう。 main.goファイルを開きます。

nano main.go

次に、completeTask関数の後に次のスニペットを追加します。

main.go

. . .
func getPending() ([]*Task, error) {
    filter := bson.D{
        primitive.E{Key: "completed", Value: false},
    }

    return filterTasks(filter)
}
. . .

MongoDBドライバーのbsonおよびprimitiveパッケージを使用してフィルターを作成します。これは、completedプロパティがfalseに設定されているドキュメントと一致します。 その後、保留中のタスクのスライスが呼び出し元に返されます。

保留中のタスクを一覧表示する新しいコマンドを作成する代わりに、コマンドなしでプログラムを実行するときのデフォルトのアクションにしましょう。 これを行うには、次のようにActionプロパティをプログラムに追加します。

main.go

. . .
func main() {
    app := &cli.App{
        Name:  "tasker",
        Usage: "A simple CLI program to manage your tasks",
        Action: func(c *cli.Context) error {
            tasks, err := getPending()
            if err != nil {
                if err == mongo.ErrNoDocuments {
                    fmt.Print("Nothing to see here.\nRun `add 'task'` to add a task")
                    return nil
                }

                return err
            }

            printTasks(tasks)
            return nil
        },
        Commands: []*cli.Command{
            {
                Name:    "add",
                Aliases: []string{"a"},
                Usage:   "add a task to the list",
                Action: func(c *cli.Context) error {
                    str := c.Args().First()
                    if str == "" {
                        return errors.New("Cannot add an empty task")
                    }

                    task := &Task{
                        ID:        primitive.NewObjectID(),
                        CreatedAt: time.Now(),
                        UpdatedAt: time.Now(),
                        Text:      str,
                        Completed: false,
                    }

                    return createTask(task)
                },
            },
. . .

Actionプロパティは、プログラムがサブコマンドなしで実行されたときにデフォルトのアクションを実行します。 これは、保留中のタスクを一覧表示するためのロジックが配置される場所です。 getPending()関数が呼び出され、結果のタスクがprintTasks()を使用して標準出力に出力されます。 保留中のタスクがない場合は、代わりにプロンプトが表示され、addコマンドを使用して新しいタスクを追加するようにユーザーに促します。

ファイルを保存して終了します。

コマンドを追加せずに今すぐプログラムを実行すると、データベース内のすべての保留中のタスクが一覧表示されます。

go run main.go

次の出力が表示されます。

Output1: Read a book

未完了のタスクを一覧表示できるようになったので、完了したタスクのみを表示できる別のコマンドを追加しましょう。

ステップ7—完了したタスクの表示

このステップでは、データベースから完了したタスクをフェッチして画面に表示する新しいfinishedサブコマンドを追加します。 これには、completedプロパティがtrueに設定されているタスクのフィルタリングと返却が含まれます。

main.goファイルを開きます。

nano main.go

次に、ファイルの最後に次のコードを追加します。

main.go

. . .
func getFinished() ([]*Task, error) {
    filter := bson.D{
        primitive.E{Key: "completed", Value: true},
    }

    return filterTasks(filter)
}
. . .

getPending()関数と同様に、完了したタスクのスライスを返すgetFinished()関数を追加しました。 この場合、フィルターのcompletedプロパティはtrueに設定されているため、この条件に一致するドキュメントのみが返されます。

次に、完了したすべてのタスクを出力するfinishedコマンドを作成します。

main.go

. . .
func main() {
    app := &cli.App{
        Name:  "tasker",
        Usage: "A simple CLI program to manage your tasks",
        Action: func(c *cli.Context) error {
            tasks, err := getPending()
            if err != nil {
                if err == mongo.ErrNoDocuments {
                    fmt.Print("Nothing to see here.\nRun `add 'task'` to add a task")
                    return nil
                }

                return err
            }

            printTasks(tasks)
            return nil
        },
        Commands: []*cli.Command{
            {
                Name:    "add",
                Aliases: []string{"a"},
                Usage:   "add a task to the list",
                Action: func(c *cli.Context) error {
                    str := c.Args().First()
                    if str == "" {
                        return errors.New("Cannot add an empty task")
                    }

                    task := &Task{
                        ID:        primitive.NewObjectID(),
                        CreatedAt: time.Now(),
                        UpdatedAt: time.Now(),
                        Text:      str,
                        Completed: false,
                    }

                    return createTask(task)
                },
            },
            {
                Name:    "all",
                Aliases: []string{"l"},
                Usage:   "list all tasks",
                Action: func(c *cli.Context) error {
                    tasks, err := getAll()
                    if err != nil {
                        if err == mongo.ErrNoDocuments {
                            fmt.Print("Nothing to see here.\nRun `add 'task'` to add a task")
                            return nil
                        }

                        return err
                    }

                    printTasks(tasks)
                    return nil
                },
            },
            {
                Name:    "done",
                Aliases: []string{"d"},
                Usage:   "complete a task on the list",
                Action: func(c *cli.Context) error {
                    text := c.Args().First()
                    return completeTask(text)
                },
            },
            {
                Name:    "finished",
                Aliases: []string{"f"},
                Usage:   "list completed tasks",
                Action: func(c *cli.Context) error {
                    tasks, err := getFinished()
                    if err != nil {
                        if err == mongo.ErrNoDocuments {
                            fmt.Print("Nothing to see here.\nRun `done 'task'` to complete a task")
                            return nil
                        }

                        return err
                    }

                    printTasks(tasks)
                    return nil
                },
            },
        }
    }

    err := app.Run(os.Args)
    if err != nil {
        log.Fatal(err)
    }
}
. . .

finishedコマンドは、ここで作成したgetFinished()関数を使用して、completedプロパティがtrueに設定されているタスクを取得します。 次に、それをprintTasks関数に渡して、標準出力に出力します。

ファイルを保存して終了します。

次のコマンドを実行します。

go run main.go finished

次の出力が表示されます。

Output1: Learn Go

最後のステップでは、データベースからタスクを削除するオプションをユーザーに提供します。

ステップ8—タスクを削除する

このステップでは、新しいdeleteサブコマンドを追加して、ユーザーがデータベースからタスクを削除できるようにします。 1つのタスクを削除するには、MongoDBドライバーからcollection.DeleteOne()メソッドを使用します。 また、削除するドキュメントを照合するためにフィルターに依存しています。

main.goファイルをもう一度開きます。

nano main.go

このdeleteTask関数を追加して、getFinished関数の直後にデータベースからタスクを削除します。

main.go

. . .
func deleteTask(text string) error {
    filter := bson.D{primitive.E{Key: "text", Value: text}}

    res, err := collection.DeleteOne(ctx, filter)
    if err != nil {
        return err
    }

    if res.DeletedCount == 0 {
        return errors.New("No tasks were deleted")
    }

    return nil
}
. . .

このdeleteTaskメソッドは、削除するタスク項目を表す文字列引数を取ります。 textプロパティが文字列引数に設定されているタスクアイテムに一致するようにフィルターが作成されます。 コレクション内のアイテムに一致するDeleteOne()メソッドにフィルターを渡し、それを削除します。

DeleteOneメソッドの結果のDeletedCountプロパティをチェックして、ドキュメントが削除されたかどうかを確認できます。 フィルタが削除するドキュメントと一致しない場合、DeletedCountはゼロになり、その場合はエラーを返すことができます。

次に、強調表示されているように、新しいrmコマンドを追加します。

main.go

. . .
func main() {
    app := &cli.App{
        Name:  "tasker",
        Usage: "A simple CLI program to manage your tasks",
        Action: func(c *cli.Context) error {
            tasks, err := getPending()
            if err != nil {
                if err == mongo.ErrNoDocuments {
                    fmt.Print("Nothing to see here.\nRun `add 'task'` to add a task")
                    return nil
                }

                return err
            }

            printTasks(tasks)
            return nil
        },
        Commands: []*cli.Command{
            {
                Name:    "add",
                Aliases: []string{"a"},
                Usage:   "add a task to the list",
                Action: func(c *cli.Context) error {
                    str := c.Args().First()
                    if str == "" {
                        return errors.New("Cannot add an empty task")
                    }

                    task := &Task{
                        ID:        primitive.NewObjectID(),
                        CreatedAt: time.Now(),
                        UpdatedAt: time.Now(),
                        Text:      str,
                        Completed: false,
                    }

                    return createTask(task)
                },
            },
            {
                Name:    "all",
                Aliases: []string{"l"},
                Usage:   "list all tasks",
                Action: func(c *cli.Context) error {
                    tasks, err := getAll()
                    if err != nil {
                        if err == mongo.ErrNoDocuments {
                            fmt.Print("Nothing to see here.\nRun `add 'task'` to add a task")
                            return nil
                        }

                        return err
                    }

                    printTasks(tasks)
                    return nil
                },
            },
            {
                Name:    "done",
                Aliases: []string{"d"},
                Usage:   "complete a task on the list",
                Action: func(c *cli.Context) error {
                    text := c.Args().First()
                    return completeTask(text)
                },
            },
            {
                Name:    "finished",
                Aliases: []string{"f"},
                Usage:   "list completed tasks",
                Action: func(c *cli.Context) error {
                    tasks, err := getFinished()
                    if err != nil {
                        if err == mongo.ErrNoDocuments {
                            fmt.Print("Nothing to see here.\nRun `done 'task'` to complete a task")
                            return nil
                        }

                        return err
                    }

                    printTasks(tasks)
                    return nil
                },
            },
            {
                Name:  "rm",
                Usage: "deletes a task on the list",
                Action: func(c *cli.Context) error {
                    text := c.Args().First()
                    err := deleteTask(text)
                    if err != nil {
                        return err
                    }

                    return nil
                },
            },
        }
    }

    err := app.Run(os.Args)
    if err != nil {
        log.Fatal(err)
    }
}
. . .

以前に追加された他のすべてのサブコマンドと同様に、rmコマンドは最初の引数を使用してデータベース内のタスクを照合し、それを削除します。

ファイルを保存して終了します。

サブコマンドを渡さずにプログラムを実行することにより、保留中のタスクを一覧表示できます。

go run main.go
Output1: Read a book

"Read a book"タスクでrmサブコマンドを実行すると、データベースから削除されます。

go run main.go rm "Read a book"

保留中のすべてのタスクを再度一覧表示すると、"Read a book"タスクが表示されなくなり、代わりに新しいタスクを追加するためのプロンプトが表示されます。

go run main.go
OutputNothing to see here
Run `add 'task'` to add a task

このステップでは、データベースからタスクを削除する関数を追加しました。

結論

タスクマネージャーのコマンドラインプログラムを正常に作成し、その過程でMongoDBGoドライバーを使用するための基本を学びました。

GoDoc でMongoDBGoドライバーの完全なドキュメントを確認して、ドライバーの使用が提供する機能の詳細を確認してください。 集約またはトランザクションの使用について説明しているドキュメントは、特に興味深いものになる可能性があります。

このチュートリアルの最終的なコードは、このGitHubリポジトリで表示できます。