part2 - ユーザーの作成
まずはユーザーの作成機能について考える。 仕様は以下の通りだった。
- Create: ユーザーを新規登録する
./usersフォルダが存在しない場合は、自動的に作成する。- ユーザーに名前、年齢、メールアドレス(任意)を入力してもらう。
- ツールが自動でIDを割り当てる。
./users/[ID].csvにユーザー情報を保存する。
いろいろ入力のバリデーションなどを考えなければならないけれど、まずは最低限の動作を考えよう。
ユーザー情報の保存先の作成
最初に行うのは、create関数を作成することだ。なにはともあれ関数からはじまる。その関数が呼ばれたときには、./usersフォルダが作成されることにしよう。以下の通りになる。
let create = System.IO.Directory.CreateDirectory("./users") |> ignorecreateこのコードを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 emailsprintfとは
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が保存されていることを確認しよう。
いままでのコードと少し変更したのは、counterDirectoryとcounterFileを定義し、readCounterとincrementCounter関数でそれを使っていることだ。これにより、もしファイルパスを変更したいときには変更できるようになった。
まとめ
これで、一応ユーザーの作成関数については完成したことにしよう。いろいろバリデーションは必要なのはわかっているが、ひとまず後回しにする。次回は……コマンドについて実装しよう。