3
6

More than 3 years have passed since last update.

[Rails]画像の複数投稿とプレビュー機能 ~ fields_for ~

Last updated at Posted at 2020-04-08

はじめに

以前に最大1枚の画像プレビュー機能について書きましたが、
この記事では、その応用として画像の複数投稿を実装するまでを書いていきます。
[Rails]画像選択時にプレビュー表示
albumモデル(親)
photoモデル(子)
今回はアルバム名をalbumsテーブルのnameカラムに、
写真をphotosテーブルのimageカラムに保存していきます。

  • Ruby 2.5.1
  • Ruby on Rails 5.0.1.7
  • Carrierwave Gem
  • 画像投稿は最大5枚
  • 今回はalbumモデルを渡した1つのフォームの中でfields_forを用いてphtosテーブルへの保存も行う

なお、Carrierwaveの設定は今回の記事では割愛させていただきます。

アソシエーションを組む

今回はfields_forを使用するため以下の記述となります。
fields_forについては、また別の記事に書こうと思います。

album.rb
has_many :photos
accepts_nested_attributes_for :photos, allow_destroy: true
photo.rb
belongs_to :album

allow_destroy: true

accepts_nested_attributes_forメソッドのオプションとして、引数に書くことができる記述です。このオプションをつけることで、親のレコードが削除された場合に、関連付いている子のレコードも一緒に削除してくれます。

コントローラーの編集

new, createアクション

フォームで使用するインスタンスを作成します。
今回は保存の結果次第でフラッシュメッセージを出してみます。

albums_controller.rb
def new
  @album = Album.new
  @album.photos.new # Albumクラスのインスタンスに関連づけられたPhotoクラスのインスタンスが作成されます。
end

def create
    @album = Album.new(album_params)
    unless @album.valid?
      flash[:alert] = "入力情報を確認してください"
      redirect_to new_album_path
    end
    @album.save
    flash[:notice] = "保存が完了しました"
    redirect_to root_path
  end

ストロングパラメータ

album_controller.rb
def album_params
  params.require(:album).permit(:name, photos_attributes: [:image])
end

photos_attributes: [:image]

fields_forを利用して作成されたフォームから来る値は、○○s_attributes: [:××]という形でparamsに入ります。○○は関連付く側のモデルの名前、××にはフォームに対応するカラムの名前が入ります。

フォームの編集

次に、hamlを用いてフォームを作成していきます。
なお、フォーム本体は隠してます。
.label-boxをクリックする度に随時、.label-boxが対応するファイルを切り替えていきます。
そのため投稿枚数に関わらず、ユーザーは同じボタンのみを押せばいいことになります。

views/albums/new.html.haml
= form_for @album do |f|
      .names
        .names__inner
          .teams
            %h3.team
              アルバム名
          = f.text_field :name, placeholder: "アルバム名"
      .image
        .image__inner
          .teams
            %h3.team
              写真
          .image__inner__container
            .prev-content
            / ここにプレビュー画像を表示していきます
            .label-content
              / ユーザーはここのみをクリックします
              %label{for: "album_photos_attributes_0_image", class: "label-box", id: "label-box--0"}
                %pre.label-box__text-visible クリックしてファイルをアップロード
                %p 最大5枚までアップロードできます
            .hidden-content
              = f.fields_for :photos do |i|
                = i.file_field :image, class:"hidden-field"
                %input{class:"hidden-field", type: "file", name: "album[photos_attributes][1][image]", id: "album_photos_attributes_1_image" }
                %input{class:"hidden-field", type: "file", name: "album[photos_attributes][2][image]", id: "album_photos_attributes_2_image" }
                %input{class:"hidden-field", type: "file", name: "album[photos_attributes][3][image]", id: "album_photos_attributes_3_image" }
                %input{class:"hidden-field", type: "file", name: "album[photos_attributes][4][image]", id: "album_photos_attributes_4_image" }

最大5枚までの投稿ができるため、最初から5つのフォームを用意して隠しておきます。

%input{class:"hidden-field", type: "file", name: "album[photos_attributes][0][image]", id: "album_photos_attributes_0_image" }

fields_forで生成されるフォームの1つ目は、このようにidやclassに0という添え字が当てられます。2つ目のフォーム以降は、この添え字の部分を1,2...といった具合に増やしていけば大丈夫です。
idやclassの数字に応じて.label-boxを対応させるようにJSファイルを編集していきます。

jsファイルの編集

album.js
$(document).on('turbolinks:load', function () {
  $(function () {

    function buildHTML(count) {
      var html = `<div class="preview-box" id="preview-box__${count}">
                    <div class="upper-box">
                      <img src="" alt="preview">
                    </div>
                    <div class="lower-box">
                      <div class="delete-box" id="delete_btn_${count}">
                        <span>削除</span>
                      </div>
                    </div>
                  </div>`
      return html;
    }

     // ファイルに画像が選択された瞬間に発火します
    $(document).on('change', '.hidden-field', function () {
      var id = $(this).attr('id').replace(/[^0-9]/g, '');
      $('.label-box').attr({ id: `label-box--${id}`, for: `album_photos_attributes_${id}_image` });
      var file = this.files[0];
      var reader = new FileReader();
      $('.hidden-btn').show();
      reader.readAsDataURL(file);
      reader.onload = function () {
        var image = this.result;
        if ($(`#preview-box__${id}`).length == 0) {
          var count = $('.preview-box').length;
          var html = buildHTML(id);
          var prevContent = $('.label-content').prev();
          $(prevContent).append(html);
        }
        $(`#preview-box__${id} img`).attr('src', `${image}`);
        var count = $('.preview-box').length;
        // 画像が5枚選択された場合、投稿ボタン(.label-content)を隠します
        if (count == 5) {
          $('.label-content').hide();
        }

        if (count < 5) {
          $('.label-box').attr({ id: `label-box--${count}`, for: `album_photos_attributes_${count}_image` });
        }
      }
    });

    $(document).on('click', '.delete-box', function () {
      var count = $('.preview-box').length;
      var id = $(this).attr('id').replace(/[^0-9]/g, '');
      $(`#preview-box__${id}`).remove();
    });
  });
});

おわり

今回は新規投稿でしたが、次回はそのデータの編集パターンも書ければと思います。
最後まで見ていただき、ありがとうございました。

3
6
0

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
3
6