1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Ruby】今日書いたコードの振り返り#4

Last updated at Posted at 2024-12-13

はじめに

こんにちは!アメリカの大学で語学を学びながら、独学でソフトウェアエンジニアを目指している者です。

今回は、図書館管理システムを作成する中で発生したミスと、その修正について振り返ります。問題の解決を通して、Rubyのクラス設計や状態管理について学びましょう!

今回扱った問題

図書館管理システム (レベル6)
図書館にある書籍を管理するシステムを作成する。

要件

  • 書籍の情報(タイトル、著者、ISBN、貸出状態)を管理する。
  • 書籍を貸し出す、返却する機能を提供する。
  • タイトルまたは著者名で書籍を検索する機能を追加する。
  • 同じ本を2人以上が同時に借りられないようにする。

間違えたコード

間違えた原因

  1. attr_reader を使っていた
    書籍の貸出状態を変更する処理で book.borrowed = true と書き込もうとしたが、attr_reader のためエラーが発生した。
  2. ISBNで書籍を探す処理が欠けていた
    borrowed_book メソッド内で、ISBNに一致する書籍を探す処理がなく、ループ内で毎回処理を行っていた。

間違えたコードの例

# Bookクラスに本の情報を記録する
class Book
    attr_reader :title, :author, :isbn, :borrowed

    def initialize(title,author,isbn)
        @title = title
        @author = author
        @isbn = isbn
        @borrowed = false
    end

    # 本の情報を出力するメソッド
    def book_status
        puts "本のタイトル#{@title},著者#{@author},ISBN#{@isbn},貸出状態#{@borrowed}"
    end
end

class Library
    # 配列booksに本を格納するためのメソッド
    def initialize
        @books =[]
    end

    # 書籍を追加するメソッド
    def add_books(book)
        @books << book
        puts "追加されました:#{book.title}"
    end

    # 書籍を貸出するメソッド

    def borrowed_book(borrow_isbn)
        # 貸出状況をみて書籍を貸すことができるかの処理をしている
        @books.each do |book|
            if  book.borrowed == false && book.isbn == borrow_isbn
                puts "ご利用ありがとうございます。"
                book.borrowed = true
            else
                puts "現在ほかの方がご利用されているのでお貸出できません"
            end
            # 図書館にその本がない場合の例外処理
                puts "お探しになっている書籍はこちらの図書館にございません。"  unless borrow_isbn == book.isbn
        end
    end
    
    # 書籍の返却の処理
    def return_book(return_isbn)
        @books.each do |book|
            if  book.borrowed == true && book.isbn == return_isbn
                puts "ご返却ありがとうございます。またのご利用をお待ちしております"
                book.borrowed = false
            elsif book.borrowed == false && book.isbn == return_isbn
                puts "図書館に同じISBNの書籍がありますが、貸出されている記録がありません。もう一度ISBNをご確認ください"
            else
                puts "こちらの書籍と一致するISBNが図書館に存在しません。"
            end
        end
    end

    # 検索機能実装前にミス発覚
        

end

正しいコード

修正ポイント

  1. attr_reader → attr_accessor に変更
    書籍の貸出状態を変更できるようにした。

  2. ISBNで書籍を検索する処理を追加
    先にISBNに一致する書籍を探してから、処理を行うように修正した。

修正後のコード

# Bookクラス: 書籍の情報を管理するクラス
class Book
  attr_accessor :title, :author, :isbn, :borrowed

  def initialize(title, author, isbn)
    @title = title
    @author = author
    @isbn = isbn
    @borrowed = false  # 初期状態では貸し出されていない
  end

  def to_s
    "Title: #{@title}, Author: #{@author}, ISBN: #{@isbn}, Borrowed: #{@borrowed ? 'Yes' : 'No'}"
  end
end

# Libraryクラス: 図書館を管理するクラス
class Library
  def initialize
    @books = []  # Bookインスタンスを格納する配列
  end

  # 書籍の追加
  def add_book(book)
    @books << book
    puts "追加されました: #{book.title}"
  end

  # 書籍の貸し出し
  def borrow_book(isbn)
    book = @books.find { |b| b.isbn == isbn }
    if book
      if book.borrowed
        puts "この本は既に貸し出されています: #{book.title}"
      else
        book.borrowed = true
        puts "貸し出しました: #{book.title}"
      end
    else
      puts "本が見つかりません。"
    end
  end

  # 書籍の返却
  def return_book(isbn)
    book = @books.find { |b| b.isbn == isbn }
    if book
      if book.borrowed
        book.borrowed = false
        puts "返却しました: #{book.title}"
      else
        puts "この本は貸し出されていません: #{book.title}"
      end
    else
      puts "本が見つかりません。"
    end
  end

  # タイトルで書籍を検索
  def search_by_title(title)
    results = @books.select { |b| b.title.include?(title) }
    if results.any?
      results.each { |b| puts b }
    else
      puts "該当する書籍が見つかりません。"
    end
  end

  # 著者で書籍を検索
  def search_by_author(author)
    results = @books.select { |b| b.author.include?(author) }
    if results.any?
      results.each { |b| puts b }
    else
      puts "該当する書籍が見つかりません。"
    end
  end
end

動作の確認

library = Library.new

# 書籍の追加
library.add_book(Book.new("The Great Gatsby", "F. Scott Fitzgerald", "9780743273565"))
library.add_book(Book.new("1984", "George Orwell", "9780451524935"))
library.add_book(Book.new("To Kill a Mockingbird", "Harper Lee", "9780060935467"))
library.add_book(Book.new("Brave New World", "Aldous Huxley", "9780060850524"))

# 書籍の一覧表示
puts "\n--- 図書館の書籍一覧 ---"
library.list_books

# 書籍の貸し出し
puts "\n--- 書籍の貸し出し ---"
library.borrow_book("9780451524935")
library.borrow_book("9780451524935")  # 2回目の貸し出しはエラー

# 書籍の返却
puts "\n--- 書籍の返却 ---"
library.return_book("9780451524935")
library.return_book("9780451524935")  # 2回目の返却はエラー

# タイトルで検索
puts "\n--- タイトルで検索 ('Brave') ---"
library.search_by_title("Brave")

# 著者で検索
puts "\n--- 著者で検索 ('George Orwell') ---"
library.search_by_author("George Orwell")

コードの解説と自分がわからなかったところの解説

attr_accessorinitialize メソッドについて

attr_accessor の役割:

attr_accessor :title, :author, :isbn, :borrowed

attr_accessor は、titleauthorisbnborrowed という4つのインスタンス変数に対して、読み書き可能なゲッターメソッドとセッターメソッドを自動生成します。
例えば、book.title でタイトルを取得し、book.title = "新しいタイトル" でタイトルを設定することができます。

initialize メソッドの役割:

def initialize(title, author, isbn)
  @title = title
  @author = author
  @isbn = isbn
  @borrowed = false  # 初期状態では貸し出されていない
end

initialize メソッドは、Book クラスのインスタンスが生成されたときに呼ばれる特別なメソッドです。
@title@author@isbn@borrowed に初期値を設定します。
@borrowedfalse にすることで、新しく作られた本は「貸し出されていない」状態になります。

なぜ両方定義するのか?

attr_accessor は メソッド(ゲッターとセッター) を生成しますが、インスタンス変数自体の初期値を設定するわけではありません。
初期値を設定するためには initialize メソッド内で明示的に代入する必要があります。

Array#find メソッドについて

find メソッドは、配列の中からブロックの条件に合う最初の要素を返します。

book = @books.find { |b| b.isbn == "9780451524935" }

このコードは、@books 配列の中で isbn"9780451524935" に一致する最初の Book インスタンスを返します。
条件に合う要素が見つからない場合は nil を返します。
(追記)
実際に値を変更しているのは@borrowedのみなので意図しないエラーをおこさないためにも以下のように書くべきです

attr_accessor :borrowed #読み取りと書き取りができる
attr_reader :title, :author, :isbn #読みとりのみ

Array#select メソッドについて

select メソッドは、配列の中からブロックの条件に合うすべての要素を配列として返します。

results = @books.select { |b| b.title.include?("Great") }

このコードは、@books 配列の中でタイトルに "Great" を含むすべての Book インスタンスを新しい配列として返します。
条件に合う要素がない場合は空配列 [] を返します。

results.any? メソッドについて

any? メソッドは、配列やコレクションに1つ以上の要素が存在する場合に true を返します。
逆に、すべての要素が偽である場合に false を返します

results = @books.select { |b| b.title.include?("Great") }
if results.any?
  results.each { |b| puts b }
else
  puts "該当する書籍が見つかりません。"
end

results.any?true の場合、検索結果が1つ以上あるため、results の内容を出力します。
results が空の場合、else ブロックの "該当する書籍が見つかりません。" が実行されます。

(追記) any?empty?について

今回のコードで特段問題になるわけではないのですが、any?は配列の中に真の要素があればtrueを返すのが目的です。
今回は配列resultsの中に要素が入っているか否かが知りたいので、empty?を使用するべきです

results = @books.select { |b| b.title.include?("Great") }
if !results.empty?
  results.each { |b| puts b }
else
  puts "該当する書籍が見つかりません。"
end

まとめ

  1. アクセサメソッドの選択
    attr_reader は読み取り専用、書き込みが必要なら attr_accessor を使う。
  2. 処理の順序
    処理は分割し、最初に行った方がいいものを考えてからコードを書く
  3. attr_accessorはインスタンス変数を読み書き可能なメソッドとして自動生成する
  4. initializeはインスタンス変数の初期値を代入するためのメソッド
  5. findは配列の中からブロックの条件にあう最初の要素を返し、selectは配列の中からブロックの条件に合うすべての要素を配列として返します。
1
0
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?