見習い魔法使いの日常

果たして魔法が使える日は訪れるのか!?

突貫工事で英単語学習ツールを作る

はじめに...

ちょー適当な記事です。そして、筆者はWeb開発の経験がないです!!

背景

最近英語とのエンカウント率がぐっと上昇。もはや彼を避けるすべなし。

僕は彼との正面からの戦いを決意した…。

と、いうことで、英語学習の中で英単語学習のための単語保存ツールを作ることにしました。ただ、今回の趣旨としては「英単語学習ツールを作るぞ!!」ではなくて、「もうなんでもいいから英単語を保存できて見返せるやつはよ」状態なので動けばいいやの精神でいろんなことをすっ飛ばしてやります。

ほんとに現在進行形で困ってる。

概要

英語論文とかを読むとき知らない単語それはもういっぱい出てくる。その時々で調べるけれど、頭に残らず次にであった時は「あ、この単語前もみた!」で意味を覚えていない。そこで、わからなかった単語を調べて、例文とともに登録すれば覚えるための材料になるのではと考えた。

僕の思い浮かべるフローは以下の通り。

単語の保存

  1. フォームに単語と例文を入れて登録ボタンを押す。
  2. DBに書き込まれる。

登録した単語の表示

  1. URLを叩く。
  2. DBからデータを取ってくる。
  3. 登録した単語たちを表示。

これで、僕がどんな単語にであって、どんな文で出会ったのがわかる(はず)。

作成開始

技術力がないので、パクリ実装をします。

ディレクトリの構成はこんな感じ。

 .
├── db.go
├── main.go
└── templates
    ├── index.html
    └── register.gtpl

main.go

package main

import (
	"fmt"
	"log"
	"net/http"
	"path/filepath"
	"sync"
	"text/template"

	_ "github.com/lib/pq"
)

type templateHandler struct {
	once     sync.Once
	filename string
	templ    *template.Template
}

func (t *templateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	t.once.Do(func() {
		t.templ = template.Must(template.ParseFiles(filepath.Join("templates", t.filename)))
	})
	t.templ.Execute(w, r)
}

func register(w http.ResponseWriter, r *http.Request) {
	fmt.Println("method:", r.Method) //リクエストを取得するメソッド
	if r.Method == "POST" {
		r.ParseForm()
		InsertData(r.Form["word"][0], r.Form["example"][0])
	}
	http.Redirect(w, r, "/", 301)
}

type Data struct {
	Words    []string
	Examples []string
}

func show(w http.ResponseWriter, r *http.Request) {
	words, examples := SelectData()
	p := Data{
		Words:    words,
		Examples: examples,
	}
	tmpl := template.Must(template.ParseFiles(filepath.Join("templates", "index.html")))
	tmpl.Execute(w, p)
}

func main() {
	http.Handle("/", &templateHandler{filename: "register.gtpl"})
	http.HandleFunc("/register", register)
	http.HandleFunc("/show", show)
	log.Println("Webサーバを開始します")
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatal("ListenAndServe:", err)
	}
}

まず、大元は前回のブログ記事で扱った本のコードをパクりました。
nishisi.hatenablog.com

付加要素として、フォームから入力を取ってくる必要があったのでその部分の処理は以下のドキュメントを参考にさせていただきました。
https://astaxie.gitbooks.io/build-web-application-with-golang/ja/04.1.html
今回は突貫なのでこのページしか読んでいませんが、時間を作ってしっかり読んでおきたいところ。コードをみてちょこちょこいじっただけだからほとんどわかってない...。

register.html

<html>
<head>
<title></title>
</head>
<body>
<form action="/register" method="post">
    英単語:<input type="text" name="word">
    例文:<input type="text" name="example">
    <input type="submit" value="登録">
</form>
</body>
</html>

index.html

<!DOCTYPE html>
<html lang="en">
    <head>
	<meta charset="UTF-8">
	<title>登録リスト</title>
    </head>
    <body>
	<h3>登録英単語リスト</h3>
	<ul>
	    {{range $i, $v := .Words}}
	    <li>{{$v}} {{index $.Examples $i}} </li>
	    {{end}}
	</ul>
    </body>
</html>

Goのテンプレートもわからず、どうやってスライスを渡して表示すればいいのか...という悩みに下の記事が解決してくれました。ありがとうございます。
golang : template の range で index を使う - i++

最後はデータベース。初めてPostgreSQLを使って見ました。セットアップは以下の通り。

$ go get github.com/lib/pq              # goのパッケージをダウンロード
$ brew install postgresql                  # brewを使ってインストール
$ brew services start postgresql    # postgersqlのサービス開始
$ createdb eng-words                     # データベースを作成
$ psql eng-words                             # データベースにアクセス
# データベースの作成
eng-words=# CREATE TABLE wordlist (uid integer, word character varying(20) ,example character varying(200)) WITH (OIDS=FALSE);

データベースも2年ほど前授業で触ったきりだから、よく覚えていない。あとはGoでデータを出し入れできればOK。以下参考にしたドキュメント。
https://astaxie.gitbooks.io/build-web-application-with-golang/ja/05.4.html

db.go

package main

import (
	"database/sql"

	_ "github.com/lib/pq"
)

func InsertData(word string, example string) {
	db, err := sql.Open("postgres", "dbname=eng-words sslmode=disable")
	checkErr(err)

	stmt, err := db.Prepare("INSERT INTO wordlist(word,example) VALUES($1,$2) RETURNING uid")
	checkErr(err)
	_, err = stmt.Exec(word, example)
	checkErr(err)
}

func SelectData() ([]string, []string) {
	var words []string
	var examples []string
	db, err := sql.Open("postgres", "dbname=eng-words sslmode=disable")
	rows, err := db.Query("SELECT word,example FROM wordlist;")
	checkErr(err)
	for rows.Next() {
		var word string
		var example string
		err = rows.Scan(&word, &example)
		checkErr(err)
		words = append(words, word)
		examples = append(examples, example)
	}
	return words, examples
}

func checkErr(err error) {
	if err != nil {
		panic(err)
	}
}

あとは、コンパイルして実行するだけ。

突貫結果

http://localhost:8080 にアクセスして単語と例文を入力し登録ボタンを押す。
f:id:Nishisi:20180418002315p:plain

http://localhost:8080/showにアクセスするとDBに登録された内容が表示される。
f:id:Nishisi:20180418002236p:plain

まとめ

急ピッチで動くことだけを考えて作ってつもりが、ここまでくるのにエラーなどにも捕まって5時間かかりました。そして!作ったのはいいけど使う気しないーー。まあ自分でも色々ツッコミどころはあります。見た目がダサいとかログが全く出力されないとか入力を検査してないとか etc。まだまだGo自体もよくわかっていないので、今後の学習とともにアップグレードをしていけばいいかなと思います。登録すればDBにとりあえず書き込まれるので、我慢して使ってみます...。