kame's engineer note

技術関連のメモをブログに記録していく

Railsでhas_manyの複数の画像をajaxを使って動的に一括登録する

概要

表題の通りのことをしたい。
ユーザーモデルと写真モデルは下記の様なhas_manyの関係とする。

ユーザー(user) ==has_many=⇛ 写真(photo)

簡単にやることをまとめると

  • field_forを使用して、複数の子モデルを同時に保存するフォームを作成する。
  • 削除用のcheckboxを実装する。
  • 追加ボタンを押したら動的にfile inputを増やす。

なお、画像登録はpaperclipを使用する。

formの作成

photo_controller.rb

def new
  @user = User.find(params[:id])
  if request.xhr?
    respond_to do |format|
      format.js
    end
  end
end

new.html.slim

#選択したファイルのプレビューを表示
javascript:
  function readUploadImage(input) {
    if (input.files && input.files[0]) {
      var reader = new FileReader();
      reader.onload = function (e) {
        $(input).next()
          .attr('src', e.target.result)
          .width(100);
      };
      reader.readAsDataURL(input.files[0]);
    }
  }

div
  = form_for(@user, url: form_path, html: {multipart: true}) do |f|
    .div
      =link_to 'ボタンを追加する', new_path(@user.id),remote: true
    .div data-role="photo_area"
      - @user.photos.each do |p|
        .div
          = image_tag p.image.url(:thumb)
          = check_box 'delete',"#{p.id}"
          = label_tag "delete[#{p.id}]", '削除'
      = render partial: 'photo_upload'
    .div
      = f.submit "登録する"


それぞれ説明をしていくと

=link_to 'ボタンを追加する', new_path(@user.id),remote: true

file inputをajaxで追加するリンクを実装する。
railslink_toremote: trueを設定するだけで、ajax通信が可能になる。


- @user.photos.each do |p|
  .div
    = image_tag p.image.url(:thumb)
    = check_box 'delete',"#{p.id}"
    = label_tag "delete[#{p.id}]", '削除'

更新時に既に写真が登録されていれば、その写真を表示し、同時に削除ボタンも実装する。


= render partial: 'add_photo_form'

_add_photo_form.html.slim

.div
  = fields_for "user[photos_attributes][#{Time.now.to_i}]" do |ff|
    = ff.file_field :image, onchange: "readUploadImage(this)"
    = image_tag ''

fields_forを使用し、小モデルのinputを実装する。
なお、親モデルuser.rb内でaccepts_nested_attributes_forで子モデルを指定することを忘れずに。

accepts_nested_attributes_for :photos, allow_destroy: true

image_tag ''new.html.slimの一番上に記述されたjavascriptでプレビュー画像を表示するためのもの。
プレビュー画像はhtml5から実装されたFileRenderを使い、fileオブジェクトが保有するバッファの中身を読み込み、 それを上記のimage_tagsrcに指定することで画像を表示させる。

f:id:ryota12609:20150410164947p:plain


ajaxでHTMLを追加する

=link_to 'ボタンを追加する', new_path(@car.id),remote: true

link_toから、ajax送信でnew actionを実行する。

photo_controller.rb

if request.xhr?
  respond_to do |format|
    format.js
  end
end

ajax送信があった場合は上記の通り、フォーマットはjsファイルでテンプレートを実行する。

new.js.slim

| $('[data-role="photo_area"]').append("#{escape_javascript(render partial: 'add_photo_form')}");

jsではappend()でviewファイルadd_photo_form.html.slimセレクタで指定した箇所に追加する。

以上で下記のように動的にinputボタンを追加できるようになる。

保存する

photo_controller.rb

  def update
    user = User.find(params[:id])
    begin
      User.transaction do
        User.destroy(params[:delete]
          .select{ |k,v| v.to_i > 0}.keys) if params[:delete].present?
        user.update user_params if params[:user].present?
      end
      redirect_to redirect_path
    rescue => e
      p e.message
    end
  end



以上で、↓のように動的に写真を複数枚選択して、一括登録ができるようになる。

f:id:ryota12609:20150410165010g:plain