Rails 4 + Ruby 2.0 で画面遷移無しのファイルアップロード

表題のとおりのサンプルプロジェクトを作成します。

まずはプロジェクトを新規作成します。

rails new sample_upload

scaffold でアップロード機能を作成、この辺はいつもどおり。

bundle exec rails g scaffold attachments title:string path:text

何かと不便なのでルーティングを定義しておきます。

  root 'attachments#index' 

終わったらマイグレーションを通しましょう。

bundle exec rake db:create
bundle exec rake db:migrate

次に app/models/attachment.rb を編集し、ファイルがアップロードできるようにします。

class Attachment < ActiveRecord::Base
  attr_accessor :file

  before_create :store_file
  before_destroy :destroy_file

  private
  def store_file
    self.path = '/attachments/' + self.file.original_filename
    File.open(full_path, "wb") do |f|
      f.write self.file.read
    end
  end

  def destroy_file
    begin
      File.unlink full_path
    rescue
      nil
    end
  end

  def full_path
    return Rails.root.join('public').to_s + self.path
  end
end

なお、ディレクトリトラバーサルについては考慮していません。

次に app/views/attachments/_form.html.erb を編集します。

<%= form_for(@attachment) do |f| %>
  <div class="field">
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </div>
  <div class="field">
    <%= f.label :file %><br>
    <%= f.file_field :file %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

これでファイルをアップロードできるようになりました。なお Rails 4 では内部に file_field を含むフォームについては自動的に multipart オプションをつけてくれるようになっています。

せっかくなので app/views/attachments/index.html.erb と app/views/attachmnets/show.html.erb を編集してアップロードした画像が表示されるようにします。

<%= attachment.path %>

これを

<%= image_tag attachment.path %> 

こんな風に。

ここまでで普通のアップローダーとして動作可能なはずなので、動作を確認します。問題なければ次へ。アップロードフォームを画面遷移無しにします。

まずはプラグインを下記サイトからダウンロードします。使い方についてもまとまっているので、確認してください。
http://jquery.malsup.com/form/

ダウンロードしたファイルは app/assets/javascripts 以下に配置します。

次に app/assets/javascripts を編集します。

$(document).on('page:change', function() {
  var options = {
    success: showResponse
  }
  $('#new_attachment').ajaxForm(options);
});

function showResponse(responseText, statusText, xhr, $form) {
  alert('status: ' + statusText + '\n\nresponseText: ' + responseText);
}

とりあえずレスポンスは alert で表示するだけにしました。
また Rails 4 以降では turbolinks が標準化されているので $() {} を普通に使ってもイベントが正しく発生しません。詳しくは github のドキュメントで確認してください。
https://github.com/rails/turbolinks/blob/master/README.md

app/controllers/attachments_controller.rb を編集し JSON で値を返すようにします。

  def create
    @attachment = Attachment.new(attachment_params)

    if @attachment.save
      render json: @attachment
    else
      render json: @attachment.errors, status: :unprocessable_entity
    end
  end

ここまでで今回のサンプルは作成完了です。