Image Upload Carrierwave Tutorial
Image Upload Carrierwave Tutorial ๊ด๋ จ
Jets, a Ruby Serverless Framework, makes use of API Gateway Binary Support to make uploading images and files from AWS Lambda a straightforward task. Weโll cover how to upload images or any file with Jets and carrierwaveuploader/carrierwave
in this tutorial guide. Note, please use Jets v1.3.1 and above for this tutorial guide as the jets new generator was updated for it. Hereโs the Live Demo of this tutorial. The full source code is on Github: tongueroo/jets-example-upload
.
Brand New App
Create a brand new Jets app with scaffolding.
gem install jets
jets new upload
cd upload
jets generate scaffold Post title:string photo:string
vim .env.development # edit with local db settings
jets db:create db:migrate
jets server
The app has a Post model with title and photo fields.
Update form
The starter scaffold provided us with basic CRUD abilities. However, the form does not handle uploading files yet. Weโll modify it so it does. Do the following to add a file input field:
- Update the form with
multipart: true
- IMPORTANT: Get rid of the:
<input type="hidden" name="_method" value="put" />
- For multipart form data, we need to route the request and POST to the resource.
- Replace
<%= text_field_tag "post[photo]", post.photo %>
with<%= file_field_tag "post[photo]" %>
The form html should look something like this:
<%= form_tag(action, multipart: true) do %>
<div class="field">
<%= label_tag :title %>
<%= text_field_tag "post[title]", post.title %>
</div>
<div class="field">
<%= label_tag :photo %>
<%= file_field_tag "post[photo]" %>
</div>
...
Hereโs the full source code of the form.
Upload the form again and youโll notice that a text field value gets save to something like this now: #<ActionDispatch::Http::UploadedFile:0x00007fbe42cd1948>
By changing the form to send multipart data, the controller now receives an ActionDispatch::Http::UploadedFile
object. The #<ActionDispatch::Http::UploadedFile:0x00007fbe42cd1948>
is a String representation of the file handle. Weโre getting closer. This is the first step to getting image uploads working.
Enter Carrierwave
Now weโll add the carrierwave
and fog-aws
gems to save the actual uploaded data instead of just a text String representation of the upload. Add the following to your Gemfile:
gem "carrierwave", git: "https://github.com/tongueroo/carrierwave.git"
gem "fog-aws" # to use s3 as the storage backend
Note
weโre using a fork of Carriewave which adds Jets support via Turbines.
bundle install
jets generate uploader Photo
#
# create app/uploaders/photo_uploader.rb
Enable the uploader by overriding the Post model photo setter and getter methods with mount_uploader
.
app/models/
post.rb
:
require 'carrierwave/orm/activerecord'
class Post < ApplicationRecord
mount_uploader :photo, PhotoUploader # overrides photo
end
Change CarrierWave to S3 Storage
By default, the generated CarrierWave uploader persists the upload to the filesystem. In the Serverless world, this doesnโt make sense. Instead, weโll configure the uploader to save to s3.
Open up photo_uploader.rb
and make sure that storage: fog
is used instead of the default storage: file
.
class PhotoUploader < CarrierWave::Uploader::Base
storage :fog # we want to use the fog backend for s3 support
# ...
end
Add an config/initializers/
carrierwave.rb
:
CarrierWave.configure do |config|
config.fog_provider = 'fog/aws'
config.fog_credentials = {
provider: 'AWS',
aws_access_key_id: ENV['CARRIERWAVE_AWS_ACCESS_KEY_ID'],
aws_secret_access_key: ENV['CARRIERWAVE_AWS_SECRET_ACCESS_KEY'],
# use_iam_profile: true, # optional, defaults to false
region: ENV['CARRIERWAVE_AWS_REGION'],
}
config.fog_directory = ENV['CARRIERWAVE_S3_BUCKET']
config.fog_public = true # optional, defaults to true
config.fog_attributes = { cache_control: "public, max-age=#{1.hour.to_i}" } # optional, defaults to {}
end
CarrierWave uses itโs own homegrown AWS client internally instead of the aws-sdk and currently does not support the use of AWS_PROFILE
. So weโll need to create an IAM user and generate AWS access key for it. For the instructions to create AWS access keys refer to these docs: How do I create an AWS access key?
Next, create the s3 bucket:
aws s3 mb s3://demo-uploads-$(date +%s)
#
# make_bucket: demo-uploads-1544733003
Weโre using date +%s
to just add a timestamp to the bucket name. Note the S3 bucket name in my case is demo-uploads-1544733003
, yours will be different.
Add these values to the .env.development
file so they get picked up by config/initializers/
carrierwave.rb
. You can use a different .env.production file for different settings like an s3 bucket. Weโre working only with .env.development
for the sake of this tutorial.
It should look something like this:
.env.development
:
CARRIERWAVE_AWS_ACCESS_KEY_ID=AKIAI7KI5LIKEXAMPLE
CARRIERWAVE_AWS_SECRET_ACCESS_KEY=XOGuscz2lNyGFo1ChFntigzBp3IMMGdEXAMPLE
CARRIERWAVE_AWS_REGION=us-west-2
CARRIERWAVE_S3_BUCKET=demo-uploads-1544733003 # remember yours will be different
Test CarrierWave
Now weโre ready to start the server and test real uploading with CarrierWave:
jets server
Go through the http://localhost:8888/posts form again and upload an image.
![https://blog.boltops.com/img/posts/2018/12/upload-posts-show-text.png](This time you should see that the photo field has an s3 url. Something like: https://demo-uploads-1544733003.s3.amazonaws.com/uploads/post/photo/1/jets.png
)
Letโs change the show page so that the photo shows up, change the photo ERB line to <%= image_tag(@post.photo) %>
. The code should look something like this:
app/views/posts/
show.html.erb
:
<p>
<strong>Photo:</strong>
<%= image_tag(@post.photo) %>
</p>
We have successfully uploaded an image to s3 locally! ๐
Deploy to AWS Lambda
Before deploying to AWS Lambda, adjust your .env.development.remote
to point to an RDS database. Setting up an RDS database is covered in Step 1: Create an RDS DB Instance. It is also briefly mentioned in this video: Jets Tutorial Deploy to AWS Lambda Part 2: AWS Lambda Ruby.
Also, remember to migrate your remote database:
JETS_ENV_REMOTE=1 jets db:create db:migrate
Now weโre ready to deploy:
jets deploy
#
# ...
# https://mf3z5ou0b1.execute-api.us-west-2.amazonaws.com/dev/
Go to the url provided at the end of the deploy.
More info
- For an Jets Introduction: Introducing Jets: A Ruby Serverless Framework.
- Also more info at: Jets documentation site.