Upload de múltiplas fotos com Paperclip + Uploadify

Eu já fiz um post sobre como fazer upload de fotos usando Paperclip. Agora, vou mostrar como fazer vários uploads de uma vez só usando Paperclip + Uploadify.

Para esse exemplo, vamos considerar que um álbum possui várias fotos.

Comece adicionando a gem do Paperclip.

#Gemfile
gem 'paperclip'

Rode o bundle install para instalar a gem.

Gem adicionada, vamos criar nosso album.

rails g scaffold album name:string

Agora, vamos fazer um scaffold para as fotos.

rails g scaffold photo album_id:integer

Para facilitar, iremos apenas adicionar o album_id e, para adicionar os outros campos necessários para o paperclip, vamos usar o gerador que ele disponibiliza.

rails generate paperclip Photo image

Usaremos o rake db:migrate para atualizar o banco:

rake db:migrate

Precisamos, agora, alterar nossos models para criar os relacionamentos e adicionar o helper do paperclip.

#app/models/album.rb
class Album < ActiveRecord::Base
  has_many :photos
end
#app/models/photo.rb
class Photo < ActiveRecord::Base
  belongs_to :album
  has_attached_file :image,
                      :styles => { :medium => "300x300>",
                                   :thumb => "100x100>" }
end

Até aqui, nada diferente do que já estamos acostumados. Agora, vem a parte dos múltiplos uploads. Para começar, adicionaremos o form que irá adicionar as fotos.

#app/views/albums/show.html.erb
<%= form_for(Photo.new(:album_id => @album.id)) do |f| %>
    <%= f.hidden_field :album_id, "value" => @album.id %>
    <p><%= f.file_field :image %></p>
<% end %>

Agora, temos o nosso formulário, mas ele não faz nada por enquanto; ainda não tem nenhum botão para enviar. Para que o Uploadify funcione, precisamos, ainda, adicionar algumas coisas. Primeiro, vamos alterar o application.html.erb.

#app/views/layouts/application.html.erb
<head>
    <%= yield(:head) %>
</head>

Estamos adicionando esse yield(:head) porque só adicionaremos o javascript do uploadify no arquivo que irá utilizá-lo. Vamos a esse arquivo, então. Editaremos, novamente, o show do usuário e adicionaremos o seguinte código no inicio do arquivo.

#app/views/albums/show.html.erb
<% content_for(:head) do %>
    <%= render :partial => "upload" %>
<% end %>

Como vocês podem ver, estamos adicionando um partial upload; ele conterá o código javascript do Uploadify.

#app/views/albums/_upload.html.erb
<script type="text/javascript" charset="utf-8">
<%- session_key = Rails.application.config.session_options[:key] -%>
$(document).ready(function() {
    $('#photo_image').click(function(event){
        event.preventDefault();
    });
    $('#photo_image').uploadify({
        uploader : '/assets/uploadify.swf',
        cancelImg : '/assets/cancel.png',
        multi : true,
        auto : true,
        script : '/photos',
        scriptData : {
            '_http_accept': 'application/javascript',
            'format' : 'json',
            '_method': 'post',
            'album_id' : '<%= @album.id %>',
            '<%= session_key %>' : encodeURIComponent('<%= u cookies[session_key] %>'),
            'authenticity_token': encodeURIComponent('<%= u form_authenticity_token %>')
        },
        onComplete : function(event, queueID, fileObj, response, data) {
            $('.thumbs').fadeOut('slow').load('/albums/<%= @album.id %> .thumbs').fadeIn("slow");
        }
    });
});
</script>

O Uploadify utiliza um arquivo flash para funcionar e, para que sua aplicação funcione direito com isso, precisamos adicionar um token de autenticidade para o flash; são essas 3 linhas:

<%- session_key = Rails.application.config.session_options[:key] -%>
'<%= session_key %>' : encodeURIComponent('<%= u cookies[session_key] %>'),
'authenticity_token': encodeURIComponent('<%= u form_authenticity_token %>')

Vamos, também, adicionar um novo arquivo com o seguinte conteúdo:

#app/middleware/flash_session_cookie_middleware.rb
require 'rack/utils'
class FlashSessionCookieMiddleware
  def initialize(app, session_key = '_session_id')
    @app = app
    @session_key = session_key
  end
  def call(env)
    if env['HTTP_USER_AGENT'] =~ /^(Adobe|Shockwave) Flash/
      req = Rack::Request.new(env)
      env['HTTP_COOKIE'] = [ @session_key,
                             req.params[@session_key] ].join('=').freeze unless req.params[@session_key].nil?
      env['HTTP_ACCEPT'] = "#{req.params['_http_accept']}".freeze unless req.params['_http_accept'].nil?
    end
    @app.call(env)
  end
end

Agora, faremos com que o rails adicione isso na nossa sessão. Adicione o código abaixo no arquivo session_store.rb

#config/initializers/session_store.rb
...
Rails.application.config.middleware.insert_before(
  ActionDispatch::Session::CookieStore,
  FlashSessionCookieMiddleware,
  Rails.application.config.session_options[:key]
)

A parte do Uploadify nas views está pronta. O próximo passo é adicionar as imagens na tela. Para isso, editaremos o arquivo show, novamente.

#app/views/albums/show.html.erb
<div class="thumbs">
    <% @album.photos.each do |photo| %>
        <p><%= image_tag photo.image.url(:medium) %></p>
    <% end %>
</div>

Com os Models e views prontos, precisamos adicionar códigos no controller, também, e alterar o método create para receber os novos parâmetros enviados pelo form do Uploadify.

Primeiro, criaremos um método privado com o seguinte código:

#app/controllers/photos_controller.rb
private
def coerce(params)
  if params[:photo].nil?
    h = Hash.new
    h[:photo] = Hash.new
    h[:photo][:album_id] = params[:album_id]
    h[:photo][:image] = params[:Filedata]
    h[:photo][:image].content_type = MIME::Types.type_for(h[:photo][:image].original_filename).to_s
    h
  else
    params
  end
end

Ele vai tratar os parâmetos enviados e fazer com que fiquem em um formato que podemos usar para salvar nosso objeto. Também precisamos alterar o método create dessa forma:

#app/controllers/photos_controller.rb
def create 
  @album = Album.find(params[:album_id])
  newparams = coerce(params)
  @photo = Photo.new(newparams[:photo])
 
  respond_to do |format|
    if @photo.save
      format.html { redirect_to album_path(@album) }
      format.json { render :json => { :result => ‘success’, :upload => photo_path(@photo) } }
    else
      format.html { render :action => “new” }
    end
  end
end

Para finalizar, adicionaremos os arquivos do Uploadify na nossa aplicação. Ficará da seguinte forma:

#app/assets/javascripts
jquery.uploadify.v2.1.4.min.js
swfobject.js
#app/assets/stylesheets
uploadify.css
#app/assets/swf
uploadify.swf

Todos esses arquivos do Uploadify você encontra no zip, disponibilizado no site do plugin. Para baixar direto, clique aqui.

Acesse http://localhost:3000/albums e crie um álbum e, depois disso, teste o upload das fotos.

Você pode baixar o código fonte do exemplo no repositório blog-posts, no Github.