Skip to content

part2 - ユーザーの作成

まずはユーザーの作成機能について考える。 仕様は以下の通りだった。

  • Create: ユーザーを新規登録する
    • ./usersフォルダが存在しない場合は、自動的に作成する。
    • ユーザーに名前、年齢、メールアドレス(任意)を入力してもらう。
    • ツールが自動でIDを割り当てる。
    • ./users/[ID].csvにユーザー情報を保存する。

いろいろ入力のバリデーションなどを考えなければならないけれど、まずは最低限の動作を考えよう。

ユーザー情報の保存先の作成

最初に行うのは、create関数を作成することだ。なにはともあれ関数からはじまる。その関数が呼ばれたときには、./usersフォルダが作成されることにしよう。以下の通りになる。

let create = System.IO.Directory.CreateDirectory("./users") |> ignore
create

このコードをProgram.fsに追加して、dotnet runで実行してみよう。./usersフォルダが作成されていることを確認しよう。

ユーザー情報の保存

次に、ユーザーからの入力を受け付ける。名前、年齢、メールアドレスを入力してもらおう。

let create =
System.IO.Directory.CreateDirectory("./users") |> ignore
printfn "名前を入力してください(1文字以上、150字以内)。"
let name = stdin.ReadLine()
printfn "年齢を入力してください(0以上、150以下)。"
let age = stdin.ReadLine()
printfn "メールアドレスを入力してください(任意)。"
let email = stdin.ReadLine()
printfn "名前:%s" name
printfn "年齢:%s" age
printfn "メールアドレス:%s" email
create

このコードをProgram.fsに追加して、dotnet runで実行してみよう。名前、年齢、メールアドレスが入力し、入力された値が表示されることを確認しよう。まだエラーチェックなどはしていない。年齢の上限なども無視できる。-23歳なども登録可能だが、本来はエラーにしなければならない。

csvファイルへの保存

ユーザーの情報を、csv形式で1行にする。

let csvData = sprintf "%s,%s,%s" name age email

sprintfとは

sprintfは、文字列をフォーマットして新しい文字列を生成する関数だ。printfと同じようにフォーマット文字列を使うが、printfは標準出力に出力するのに対して、sprintfは文字列を返す。つまりコンソールに出すか、変数とかに入れられる値にするか、という違いか……。

それでは作成したcsvデータをファイルに保存しよう。

let path = Path.Combine("./users", "1.csv")
File.WriteAllText(path, csvData) |> ignore

このコードをProgram.fsに追加して、dotnet runで実行してみよう。./users/1.csvにユーザー情報が保存されていることを確認しよう。 Path.Combineは、node.jsでいうところのpath.joinのようなものだ。ディレクトリとファイル名を結合して、パスを作成する。たしかどのOSでも使えるはず。 File.WriteAllTextは、ファイルに文字列を書き込む関数だ。ファイルが存在しない場合は新規作成し、ファイルが存在する場合は上書きする。node.jsでいうところのfs.writeFileSyncのようなものだと考えて良いだろう。

それでは、ここまでの全体像を以下に記す。

open System.IO
let create =
Directory.CreateDirectory("./users") |> ignore
printfn "名前を入力してください(1文字以上、150字以内)。"
let name = stdin.ReadLine()
printfn "年齢を入力してください(0以上、150以下)。"
let age = stdin.ReadLine()
printfn "メールアドレスを入力してください(任意)。"
let email = stdin.ReadLine()
let csvData = sprintf "1,%s,%s,%s" name age email
let path = Path.Combine("./users", "1.csv")
File.WriteAllText(path, csvData) |> ignore
create

実行してみると、./usersフォルダが作成され、入力したユーザー情報が./users/1.csvに保存されることが確認できるだろう。

オートインクリメントIDの実装

ユーカー情報を保存する際に、IDを自動で割り当てるようにしよう。さて、どのように実装するべきだろうか。いろいろ考えられる。たとえば、./usersフォルダ内のファイル数を数えて、その数に1を足すとか。もしくは、./usersフォルダ内のファイル名を取得し、最大のIDのを算出し、それに+1したデータを扱うなど。ただ、ここでもう一度仕様と向き合ってみよう。仕様書には、以下のような文章があったはずだ。

一度使われたIDは再利用されない。たとえば、あるユーザーを削除しても、そのユーザーのIDがあとで別のユーザーに割りあてられることはない。

そうなると、たとえばID1のユーザーが作成されたあと、削除され、また新しくユーザーが作成されたときにはどうなるだろうか? さきほどの実装ではID1が再利用されてしまう。

今回は、./counterファイルにIDのカウンターを保存し、その値をインクリメントしてIDとして使用することにしよう。まずは、カウンターの値を読み込む関数を作成しよう。

let readCounter =
let path = Path.Combine(".", "counter")
if File.Exists(path) then
let counter = File.ReadAllText(path)
counter |> int
else
Directory.CreateDirectory(path) |> ignore
File.WriteAllText(path, "1") |> ignore
1

この関数は、./counterファイルが存在する場合はその値を読み込み、存在しない場合は1を書き込んで1を返す。File.Existsは、ファイルが存在するかどうかを判定する関数だ。File.ReadAllTextは、ファイルの内容を読み込む関数だ。しつこいようだが、node.jsでいえばfs.existsに相当する。また、この関数はint型の値を返すため、取得した数字についてはint関数で無理やりパースしている。もしデータが破損していたり、おかしな文字列が入っていたときにはどうするのか。それはまた次に考えよう。

次に、カウンターの値をインクリメントして書き込む関数を作成しよう。

let incrementCounter =
let path = Path.Combine(".", "counter")
let counter = readCounter + 1
File.WriteAllText(path, counter.ToString()) |> ignore
counter

この関数は、readCounter関数で取得した値に1を足して、./counterファイルに書き込む。そして、その値を返す。counter.ToString()は、int型の値を文字列に変換する関数だ。

それでは、これらの関数を使って、ユーザー情報を保存する際にIDを自動で割り当てるようにしよう。

open System.IO
let counterDirectory = Path.Combine(".", "counter")
let counterFile = Path.Combine(counterDirectory, "counter.txt")
let readCounter =
if File.Exists(counterFile) then
let counter = File.ReadAllText(counterFile)
counter |> int
else
Directory.CreateDirectory(counterDirectory) |> ignore
File.WriteAllText(counterFile, "1") |> ignore
1
let incrementCounter =
let counter = readCounter + 1
File.WriteAllText(counterFile, counter.ToString()) |> ignore
counter
let create =
Directory.CreateDirectory("./users") |> ignore
printfn "名前を入力してください(1文字以上、150字以内)。"
let name = stdin.ReadLine()
printfn "年齢を入力してください(0以上、150以下)。"
let age = stdin.ReadLine()
printfn "メールアドレスを入力してください(任意)。"
let email = stdin.ReadLine()
let id = incrementCounter
printfn "id: %d" id
let csvData = sprintf "%d,%s,%s,%s" id name age email
let path = Path.Combine("./users", "1.csv")
File.WriteAllText(path, csvData) |> ignore
create

このコードをProgram.fsに追加して、dotnet runで実行してみよう。./users/1.csvにユーザー情報が保存されていることを確認しよう。また、./counter/counter.txtにIDが保存されていることを確認しよう。

いままでのコードと少し変更したのは、counterDirectorycounterFileを定義し、readCounterincrementCounter関数でそれを使っていることだ。これにより、もしファイルパスを変更したいときには変更できるようになった。

まとめ

これで、一応ユーザーの作成関数については完成したことにしよう。いろいろバリデーションは必要なのはわかっているが、ひとまず後回しにする。次回は……コマンドについて実装しよう。