Ruby | 2週間で身につく、アプリを作りながら学ぶRubyの基本 - 7日目

Ruby Ruby
スポンサーリンク

7日目のゴールとテーマ

7日目のテーマは「名簿アプリを“ちょっと賢く”する:検索と並べ替え、そして入力ミスへの対応」です。
6日目までで、Person クラス+配列+メニューで、かなりアプリらしい形になりました。

今日はそこから一歩進めて、

名前で検索できるようにする
年齢順に並べ替えて表示できるようにする
メニュー入力などで“変な入力”が来ても落ちないようにする

という、「実際に使うときに欲しくなる機能」と「エラーに強い書き方」を体験していきます。


いまの名簿アプリをざっくり振り返る

6日目までの構造を言葉で整理する

すでにあなたのアプリは、こんな構造になっています。

Person クラス
名前・年齢・趣味を持ち、introduction で自己紹介文を返す。

build_person_from_input
ユーザーから入力を受け取り、Person を1人分作って返す。

people(配列)
Person オブジェクトを複数まとめて持つ「名簿」。

メニュー+メインループ
「全員表示」「追加」「20歳未満だけ表示」「終了」を選べる。

今日はここに「検索」「並べ替え」「入力チェック」を足していきます。


名前で検索する機能を考える

まずは“やりたいこと”を日本語で書いてみる

名前検索の仕様を、いったん日本語で整理します。

ユーザーに「探したい名前(または一部)」を入力してもらう
名簿の中から、その文字列を含む人を探す
見つかった人たちの自己紹介を表示する
1人も見つからなければ、その旨を表示する

これを Ruby に翻訳していきます。

部分一致検索のイメージ

「完全一致」ではなく「部分一致」にすると、使い勝手がよくなります。

「た」で検索したら「たろう」「たかし」「やまだ」などがヒットする
「田」で検索したら「田中」「山田」がヒットする

Ruby では、文字列に対して include? を使うと「含まれているか」を判定できます。

"たろう".include?("た")   # true
"たろう".include?("ろう") # true
"たろう".include?("はな") # false
Ruby

これを Person の name に対して使えば、「名前にその文字列が含まれている人」を探せます。


名前検索の処理をメソッドとして書く

メニュー項目を1つ増やすイメージ

メニューに「4: 名前で検索」を追加するとします。

def show_menu
  puts "========================"
  puts "名簿アプリ メニュー"
  puts "1: 全員の自己紹介を表示"
  puts "2: 新しい人を追加"
  puts "3: 20歳未満の人だけ表示"
  puts "4: 名前で検索して表示"
  puts "0: 終了"
  puts "番号を入力してください:"
end
Ruby

メインループ側も、4 を受け取ったときの処理を足します。

elsif choice == 4
  handle_search_by_name(people)
Ruby

では、この handle_search_by_name を書いていきます。

handle_search_by_nameの中身

def handle_search_by_name(people)
  if people.empty?
    puts "まだ登録されている人がいません。"
    return
  end

  puts "検索したい名前、またはその一部を入力してください:"
  keyword = gets.chomp

  if keyword.strip == ""
    puts "空のキーワードでは検索できません。"
    return
  end

  matched = people.select do |person|
    person.name.include?(keyword)
  end

  if matched.empty?
    puts "「#{keyword}」を含む名前の人は見つかりませんでした。"
    return
  end

  puts "「#{keyword}」を含む名前の人が#{matched.length}人見つかりました。"

  matched.each_with_index do |person, idx|
    puts "------------------------"
    puts "#{idx + 1}人目:"
    puts person.introduction
  end
end
Ruby

ここでの重要ポイントを深掘りします。

people が空なら、検索しても意味がないので早めに return している
keyword が空文字(スペースだけなど)のときも、検索せずに戻っている
select で「名前に keyword を含む人だけ」を集めている
1人も見つからなければ、その旨を表示して return
見つかった人たちの自己紹介を順番に表示

特に person.name.include?(keyword) の部分が「部分一致検索」の核心です。


年齢順に並べ替えて表示する

並べ替えのイメージをつかむ

次は「年齢順に並べて表示したい」という欲張りを叶えます。

やりたいことはこうです。

名簿の人たちを、年齢の小さい順(若い順)に並べ替える
その順番で自己紹介を表示する

Ruby では、配列に対して sort_by を使うと「ある基準で並べ替え」ができます。

numbers = [5, 2, 9, 1]
sorted = numbers.sort   # [1, 2, 5, 9]
Ruby

Person の配列の場合は、「年齢」を基準に並べ替えたいので、

sorted_people = people.sort_by { |person| person.age }
Ruby

と書きます。
これで、「age が小さい順」に並んだ新しい配列ができます。

メニューに「年齢順表示」を追加する

メニューにもう1項目足します。

puts "5: 年齢の若い順に全員を表示"
Ruby

メインループ側も対応します。

elsif choice == 5
  handle_show_sorted_by_age(people)
Ruby

handle_show_sorted_by_ageの中身

def handle_show_sorted_by_age(people)
  if people.empty?
    puts "まだ登録されている人がいません。"
    return
  end

  sorted = people.sort_by { |person| person.age }

  puts "年齢の若い順に自己紹介を表示します。"

  sorted.each_with_index do |person, idx|
    puts "------------------------"
    puts "#{idx + 1}人目(#{person.age}歳):"
    puts person.introduction
  end
end
Ruby

ここでの重要ポイントは、

people.sort_by { |person| person.age } で「年齢を基準に並べ替え」している
元の people はそのまま、新しい配列 sorted に結果が入る
表示するときに「何歳か」も一緒に出してあげると分かりやすい

ということです。

sort_by は、Ruby で「オブジェクトの配列を並べ替える」ときの定番です。
「何を基準に並べたいか」を { |person| person.age } の部分で指定します。


入力ミスに強いメニューにする

いまの read_menu_number の弱点

6日目では、メニューの番号をこう読んでいました。

def read_menu_number
  input = gets.chomp
  input.to_i
end
Ruby

to_i は、数字以外が混ざっていても 0 にしてしまいます。

“abc” → 0
“1abc” → 1

つまり、ユーザーが「abc」と打っても、
「0が入力された」とみなされて終了してしまう可能性があります。

これだと、ちょっと不親切です。

ちゃんと「数字かどうか」をチェックする

ここで、少しだけ“丁寧な”読み方に変えます。

def read_menu_number
  loop do
    input = gets
    return 0 if input.nil?  # Ctrl+D などで入力が終わった場合の保険

    text = input.chomp

    if text.match?(/\A[0-9]+\z/)
      return text.to_i
    else
      puts "数字で入力してください。もう一度どうぞ:"
    end
  end
end
Ruby

ここでの重要ポイントを深掘りします。

gets が nil を返すことがある(入力ストリームが終わったとき)ので、その場合は 0 を返して終了扱いにしている
text.match?(/\A[0-9]+\z/) は「0〜9の数字だけで構成されているか」をチェックしている
数字だけなら to_i して返す
そうでなければ「数字で入力してください」とメッセージを出して、loop の先頭に戻る

これで、ユーザーが「abc」や「1a」などと入力しても、
アプリが勝手に終了したり、変なメニューに飛んだりしなくなります。

正直、ここは少し“プログラミングっぽい”書き方になりますが、
「入力をちゃんとチェックする」という感覚は、どの言語でも超重要です。


名前入力にも“空文字チェック”を入れてみる

「名前が空の人」を作らないようにする

今の build_person_from_input は、名前が空でもそのまま Person を作ってしまいます。
それを少しだけ丁寧にします。

def read_non_empty_line(message)
  loop do
    puts message
    input = gets
    return "" if input.nil?

    text = input.chomp

    if text.strip == ""
      puts "空では登録できません。何か入力してください。"
    else
      return text
    end
  end
end
Ruby

このメソッドは、

メッセージを表示する
入力を受け取る
空文字やスペースだけなら、もう一度聞き直す
何か文字が入っていれば、それを返す

という「空でない1行」を読むための部品です。

これを使って、build_person_from_input を書き換えます。

def build_person_from_input
  name = read_non_empty_line("名前を入力してください:")

  puts "年齢を入力してください:"
  age_text = gets.chomp
  age = age_text.to_i

  hobby = read_non_empty_line("好きなこと(趣味)を入力してください:")

  Person.new(name, age, hobby)
end
Ruby

これで、「名前が空の人」「趣味が空の人」は作られなくなります。
こういう小さな“優しさ”が、アプリの使い心地を大きく変えます。


7日目まででできることを俯瞰してみる

あなたの名簿アプリはここまで来ている

7日目の時点で、あなたの Ruby アプリはすでにこんなことができます。

Person クラスで「人」を表現している
Person の配列を「名簿」として扱っている
メニューから操作を選べる
新しい人を追加できる
全員分の自己紹介を表示できる
20歳未満だけを絞り込んで表示できる
名前で検索して表示できる(部分一致)
年齢の若い順に並べ替えて表示できる
メニュー番号の入力ミスにある程度耐えられる
名前や趣味が空のまま登録されるのを防いでいる

これはもう、「学習用のサンプル」を超えて、
小さな“実用アプリ”の入り口に立っています。


7日目のまとめ

今日のキーワードを整理します。

include?
文字列に対して「この文字列を含んでいるか?」を調べるメソッド。
person.name.include?(keyword) で部分一致検索ができる。

select
配列から「条件に合う要素だけ」を集めて新しい配列を作る。
検索結果やフィルタリングに使える。

sort_by
配列を「ある基準」に従って並べ替える。
people.sort_by { |p| p.age } で年齢順に並べ替え。

入力チェック
メニュー番号が数字かどうかを正規表現でチェックする。
空文字を弾く read_non_empty_line のような“入力専用メソッド”を作ると、
アプリ全体の入力が安定する。

「ちょっと賢いアプリ」にする視点
検索・並べ替え・入力ミスへの対応は、
どんなアプリでもほぼ必ず出てくる要素。
ここを自分の手で書けたのはかなり大きいです。

タイトルとURLをコピーしました