🍑

【Rails7】ヘルパーメソッドを使ってビューに画像を表示させる

2024/08/19に公開

はじめに

こんにちは。業務未経験でRuby on Railsを学習しているmockeyです。
現在作成中のアプリで旅行先に応じた異なる画像を表示するべく、サービスクラスを作ってビューに呼び出したところ、エラーが発生しました。
その後、解決策としてヘルパーメソッドを使用しました。
サービスクラスとヘルパーメソッドの違いを理解できていなかったのでその違いに触れながら実装した内容について備忘録として記載します。
遭遇したエラーや間違っていたコードも掲載いたします。

想定される読者

  • 画像を異なる条件下で表示させたい
  • サービスクラスとヘルパーメソッドの定義がイマイチ掴めていない

実装のゴール

作成した旅行先に合わせた画像が表示されること。
下記の例はヘルパー内において、"ロンドン"で設定してある画像が表示されています。
Image from Gyazo

まず考えたこと(間違いの実装)

  • destinationカラムに入る値ごとに異なる画像を表示したいが指定方法が明確でない。(パスが変わるため)
  • 都市に対応する画像URLを設定するロジックを、独立したサービスクラスに分けるためのサービスクラスを作成すればいいと考えた。
  • 以前外部APIを使用した際にサービスクラスで定義したのでその要領で実装すると考えた。

上記を踏まえて画像をapp/assets/images配下に置き、
TripImageService クラスを以下のように作成しました。

class TripImageService
  def self.set_image_url(trip)
    case trip.destination
    when "ロサンゼルス"
      trip.image_url = asset_path("los_angeles.png")
    when "ニューヨーク"
      trip.image_url = asset_path("new_york.png")
    when "ハワイ"
      trip.image_url = asset_path("hawaii.png")
    when "パリ"
      trip.image_url = asset_path("paris.png")
    ~~~~~~~~~~~~~~~~~省略~~~~~~~~~~~~~~~~~~~~~~~~
    end
  end
end

その後コントローラーで呼び出して旅を作成するアクションで画像を設定するように設定しました。

app/controllers/trips_controller.rb
def create
    @trip = Trip.new(trip_params) # フォームから送信されたパラメータを元に旅行を作成
    TripImageService.set_image_url(@trip) # TripImageService クラスの set_image_url メソッドを使って画像URLを設定
    @trip.user = current_user # 旅行を作成したユーザーを設定
    if @trip.save # 旅行の保存に成功した場合
      add_default_todos(@trip)  # default_todos_for メソッドを使って旅行先に応じたデフォルトのToDoを追加
      redirect_to @trip, notice: "旅行を作成しました"
    else
      flash.now[:alert] = "旅行の作成に失敗しました"
      render :new, status: :unprocessable_entity
    end
  end

発生したエラー

先ほどサービスクラスで定義したasset_pathをビューに呼び出したところNoMethodErrorが発生しました。
Image from Gyazo

【なぜサービスクラスでエラーが発生したのか】
参考記事やGPTなどのAIを使用して調査した結果、以下の理由であることが分かりました。

- サービスクラスにasset_pathメソッドを使用しようとして、NoMethodErrorが発生した
- asset_pathは通常、ビューやヘルパーからのみ呼び出せるメソッドであり、サービスクラスから直接呼び出すことができない

【解決できそうな手段】

  • ビューに関する処理をヘルパーメソッドで実施する。

サービスクラスとヘルパーメソッドの違い

一部参考記事からも引用させていただきます。

サービスクラス

サービスクラスとは、アプリケーション内でビジネスロジックをまとめる役割を持つクラスのことを指します。
これにより、コードの再利用性が高まり、メンテナンスが容易になります。

使用例: 複数のモデルやコントローラーをまたぐ処理や、外部APIとの連携、データベースの処理
ファイルの場所:app/servicesディレクトリに置かれ、主にコントローラーやモデルなどから呼び出される

ヘルパーメソッド

参考記事によると、

Railsのヘルパーはビューに特化したモジュールです。ビューの中で繰り返し使われるコードをモジュールとして定義し、整理することで、ビューの複雑さを減らし、保守性を高めることができます。

使用例: フォームの生成や日付のフォーマットの設定、リンクやボタンのスタイルを統一するためのコードを整理する、ビューで使用する画像のパスを決定する
ファイルの場所: app/helpersディレクトリに置かれ、全てのビューから呼び出せる

※もしコントローラーからヘルパーを呼び出したい場合は、こちらの記事が参考になりました。

どうやって画像を呼び出すか?

上記で違いを確認してみると、今回の実装ではヘルパーメソッドを使うことが適切であると判断しました。

  • image_url_for_tripメソッドをヘルパーメソッドとして定義し、ビューから直接呼び出せるようにした。
app/helpers/trip_helper.rb
  module TripHelper
  def image_url_for_trip(trip)
    case trip.destination
    when "ロサンゼルス"
      asset_path("los_angeles.png")
    when "ニューヨーク"
      asset_path("new_york.png")
    when "ハワイ"
      asset_path("hawaii.png")
    when "パリ"
      asset_path("paris.png")
    when "ロンドン"
      asset_path("london.png")
    when "マドリード"
      asset_path("madrid.png")
   ~~~~~~~~~~~~~~~~~省略~~~~~~~~~~~~~~~~~~~~~~
    end
  end
end

そしてビューから呼び出します。

app/views/trips/show.html.erb

<div class="flex flex-col justify-center items-center min-h-screen">
  <h1 class="text-3xl font-bold mb-6">旅の詳細</h1>
  <div class="card bg-blue-50 w-96 shadow-xl rounded-lg p-4">
    <div class="relative">
      <%= image_tag image_url_for_trip(@trip), class: "w-full h-60 object-cover rounded-lg border border-white shadow-lg" %>#追加したコード
    </div>
    <div class="bg-white p-4 rounded-b-lg mt-4 text-center">
      <div class"text-center">
        <p class="text-lg font-bold mb-4"><%= @trip.destination %></p>
        <p class="text-gray-700 mb-2">出発日: <%= @trip.departure_date %></p>
        <p class="text-gray-700 mb-4">帰国日: <%= @trip.return_date %></p>
      </div>

        <!-- 編集・削除ボタン -->
        <div class="card-actions flex justify-end space-x-4">
          <%= link_to edit_trip_path(@trip) do %>
            <i class="fas fa-edit fa-lg"></i>
          <% end %>
          <%= link_to trip_path(@trip), data: { turbo_method: "delete", turbo_confirm: "本当に削除しますか?" } do %>
            <i class="fas fa-trash fa-lg"></i>
          <% end %>
        </div>

            <!-- モーダル -->
            <button class="btn btn bg-purple-300 hover:bg-purple-500 btn btn primary px-6 py-3 hover:shadow-sm hover:translate-y-0.5 transform transition rounded shadow-lg mt-6" onclick="document.getElementById('my_modal_4').showModal()">TO DO</button>
    <dialog id="my_modal_4" class="modal">
        <div class="modal-box">
            <form method="dialog">
              <button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
            </form>
         <h3 class="text-lg font-bold mb-4"><%= current_user.name %>のTO DO</h3>
         <div class="border-t border-dashed border-4 border-blue-800 mb-10"></div>
        <turbo-frame id="todos">
          <ul id="todo-list">
           <% @trip.todos.each do |todo| %>
               <%= render partial: "trips/todo", locals: { todo: todo } %>
           <% end %>
          </ul>
        </turbo-frame>

        <turbo-frame id="todo-form" >
          <%= render partial: "trips/todo_form", locals: { todo: @new_todo } %>
        </turbo-frame>
        </div>
    </dialog>
  </div>
</div>

これにより、ビューに関連する処理を適切な場所に配置することで、エラーも解消されて画像が表示されました。

おわりに

今回の実装で、どのような状況でサービスクラスを使うべきか、またはヘルパーメソッドを使うべきか、その判断基準について学びました。

今回もご覧いただきありがとうございました。

参考

Discussion