Mas repito: leia a documentação e faça provas de conceito, vou reduzir o exemplo da documentação num passo a passo para o cenário de Direct Upload para S3. Assumindo que você tem um model User e quer colocar uma imagem de perfil nela. Comece editando a Gemfile:
1 2 3 |
gem "mini_magick" gem "refile", require: ["refile/rails", "refile/image_processing"] gem "aws-sdk" |
Agora vamos criar a configuração do Refile para o AWS-S3, crie o arquivo config/initializer/refile.rb:
1 2 3 4 5 6 7 8 9 |
require "refile/backend/s3" aws = { access_key_id: ENV['S3_ACCESS_KEY'], secret_access_key: ENV['S3_SECRET_ACCESS_KEY'], bucket: ENV['S3_BUCKET'], } Refile.cache = Refile::Backend::S3.new(prefix: "cache", **aws) Refile.store = Refile::Backend::S3.new(prefix: "store", **aws) |
Como podem ver estou assumindo que você usa corretamente o dotenv-rails para colocar as configurações de S3. Assumindo também que você saber configurar um bucket, incluindo habilitar a configuração de Cross-Origin Resource Sharing (CORS) onde você vai colocar algo parecido com isso:
1 2 3 4 5 6 7 8 9 10 |
<CORSConfiguration> <CORSRule> <AllowedOrigin>*</AllowedOrigin> <AllowedMethod>GET</AllowedMethod> <AllowedMethod>POST</AllowedMethod> <MaxAgeSeconds>3000</MaxAgeSeconds> <AllowedHeader>Authorization</AllowedHeader> <AllowedHeader>Content-Type</AllowedHeader> </CORSRule> </CORSConfiguration> |
Agora vamos criar o campo na tabela de usuários:
1 2 |
rails generate migration add_profile_image_to_users profile_image_id:string rake db:migrate |
Adicione o Refile no seu model config/model/user.rb:
1 2 3 |
class User < ActiveRecord::Base attachment :profile_image end |
E edite a view do formulário de edição que, se você criou via scaffold, provavelmente vai ser algo como app/views/users/_form.html.erb:
1 2 3 4 5 6 7 8 9 |
<%= form_for(@user) do |f| %> ... <div class="field"> <%= f.attachment_field :profile_image, direct: true, presigned: true %> </div> <div class="actions"> <%= f.submit %> </div> <% end %> |
Note a opção de direct que indica que será um Direct Upload, ou seja, seu browser vai fazer o upload diretamente para o S3 e trazer a chave de identificação pra montar a URL depois. No caso o Refile primeiro joga numa chave "cache/" e quando você faz o POST do formulário ele manda o S3 copiar para a pasta "store/" que é o que seu model vai usar. Dessa forma, se você escolher o arquivo no browser, fizer o upload mas desistir de dar POST, não vai poluir a pasta final.
Como o controller vai receber campos novos, precisamos permitir que esses parâmetros cheguem no model, então vamos editar o controller responsável que, nesse exemplo, é o app/controllers/users_controller.rb:
1 2 3 4 5 6 7 8 9 10 |
class UsersController < ApplicationController ... private ... # Never trust parameters from the scary internet, only allow the white list through. def user_params params.require(:user).permit(:name, :email, :profile_image, :profile_image_cache_id) end end |
No trecho acima deixei de exemplo possíveis campos como "name" e "email" mas, claro, configure de acordo com os campos que seu formulário realmente envia, o importante são os parâmetros "profile_*". Com isso a aplicação já recebe tudo que precisa, mas como o Direct Upload acontece no browser significa que precisamos de algum Javascript para controlar a chamada Ajax e os eventos associados então vamos adicionar a dependência do Refile editando o arquivo app/assets/javascripts/application.js:
1 2 3 4 5 6 7 |
... //= require jquery //= require jquery_ujs //= require turbolinks //= require refile //= require_tree . ... |
Esse é um exemplo então, novamente, edite conforme o que você tem na sua aplicação. A documentação explica como lidar com os eventos como "upload:start" ou "upload:progress" para que você tenha a opção de mostrar coisas como uma barra de progresso ou outra notificação ao usuário indicando se ele está fazendo o upload e quando terminar. Para este exemplo vamos fazer algo simples: apenas desabilitar o botão de submit e reabilitar quando o upload terminar. Para isso vamos editar o arquivo app/assets/javascripts/users.js.coffee:
1 2 3 4 5 |
$(document).on "upload:start", "form", (e) -> $(this).find("input[type=submit]").attr "disabled", true $(document).on "upload:complete", "form", (e) -> $(this).find("input[type=submit]").removeAttr "disabled" unless $(this).find("input.uploading").length |
Pronto! Reinicie seu servidor e de agora em diante o upload vai acontecer diretamente do browser para o S3 e quando fizer o POST ele vai guardar a chave de identificação no campo "profile_image_id" e quando for puxar a imagem ele vai passar por um filtro Rack que vai mostrar algo parecido com isso no seu console:
1 2 3 4 |
Started GET "/attachments/store/fill/300/300/365d81a10ba21f2544177580f12110509f57e086bcd49f5336079ca17ea8/profile_image" for 192.168.47.2 at 2014-12-18 15:53:14 +0000 Refile: GET /store/fill/300/300/365d81a10ba21f2544177580f12110509f57e086bcd49f5336079ca17ea8/profile_image Refile: serving "365d81a10ba21f2544177580f12110509f57e086bcd49f5336079ca17ea8" from store backend which is of type Refile::Backend::S3 [AWS S3 200 3.345285 0 retries] get_object(:bucket_name=>"testing-bucket-akitaonrails",:key=>"store/365d81a10ba21f2544177580f12110509f57e086bcd49f5336079ca17ea8") |
Significa que você pode dar override nisso e colocar coisas como autenticação para acessar certos buckets e assim por diante. Com o tempo você pode querer limpar a pasta de cache e isso você pode configurar diretamente no S3 como ensina a documentação.
Finalmente, o correto é sempre configurar o Amazon Cloudfront na frente do storage que estiver usando (no nosso caso, o S3) e este blog post explica como é simples fazer isso. O conceito é basicamente trocar o domínio e passar a URL para o Cloudfront que, se não tiver a imagem em cache vai pedir pra sua aplicação e a partir daí vai usar do seu próprio cache, deixando a sua aplicação e seu storage mais leves. E para que sua aplicação gere tags de imagens apontando pro CDN, apenas configure o Refile assim:
1 |
Refile.host = "//your-dist-url.cloudfront.net" |
Pronto! A solução definitiva para gerenciar seus assets da FORMA CORRETA! Servir assets diretamente da sua aplicação está errado. Fazer sua aplicação fazer o upload para storages está errado. Não usar storages separados da sua aplicação está errado!