Base64 has been my go-to encoding when uploading files from my external front-end apps such as React Native to my Rails APIs.
It’s a simple way to represent a binary data as string, where string is probably the common data passed through REST APIs.
We have learned to upload using ActiveStorage before where our file is a Blob, but now it’s in base64.
Let’s initialize a blank Rails app with a model that has an image attachment.
Step 1: Initialize our Rails app
You may skip this step if you already have a Rails app running.
1
2
3
4
5
$ rails new test-app
$ cd test-app
$ bundle exec rails g scaffold post
$ bundle exec rails active_storage:install
$ bundle exec rails db:migrate
What those commands above do?
rails new test-app
- initializes a blank Rails app namedtest-app
.cd test-app
- navigating into our new Rails app.bundle exec rails g scaffold post
- scaffolded aPost
model CRUD.bundle exec rails active_storage:install
- installingActiveStorage
.bundle exec rails db:migrate
- running all pending database migrations.
Step 2: Add attachment to our Post model
In your app/models/post.rb
add the following association.
1
2
3
class Post < ApplicationRecord
has_one_attached :image
end
This creates an association between our Post
model and ActiveStorage
.
Step 3: Create a sample Post record
Open our Rails console.
1
$ bundle exec rails c
Create a sample Post
record.
1
Post.create
Step 4: Attach an image to our Post model
What we will attach to our record created in the previous step is a 1 black pixel image.
1
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAA1JREFUGFdjYGBg+A8AAQQBAHAgZQsAAAAASUVORK5CYII=
Any file should work but we only chose that for demonstration purposes and also to keep our base64 string short.
Open our Rails console again.
1
$ bundle exec rails c
We’ll assign our sample image to a variable.
1
base64_file = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAA1JREFUGFdjYGBg+A8AAQQBAHAgZQsAAAAASUVORK5CYII="
Then, we’ll extract the mime_type
.
1
mime_type = base64_file.split(',').first.split(';').first.split(':').last
Then, get the base64
data.
1
base64_data = base64_file.split(',').last
Then, decode the base64_data
.
1
decoded_base64 = Base64.decode64(base64_data)
Generate filename with extension. It’s up to you on how you will generate your filename, for simplicity we’ll just use RANDOM-UUID
+ extension
.
1
2
extension = Rack::Mime::MIME_TYPES.invert[mime_type]
filename = [SecureRandom.uuid, extension].join
We used Rack::Mime::MIME_TYPES
to get the extension based on the file’s mime_type
and SecureRandom.uuid
to generate a random UUID.
You may also read: Advantages and Disadvantages of UUID
Now we can attach the sample image to our record.
1
2
3
4
5
Post.last.image.attach(
io: StringIO.new(decoded_base64),
filename: filename,
content_type: mime_type
)
Step 5: DRY code.
Instead of copy-pasting the codes above every time, we can DRY our code a bit.
In your app/models/concerns/attachable.rb
, paste the following. (create if it does not exist)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module Attachable
def attach_file!(base64_file, association)
mime_type = base64_file.split(',').first.split(';').first.split(':').last
base64_data = base64_file.split(',').last
decoded_base64 = Base64.decode64(base64_data)
extension = Rack::Mime::MIME_TYPES.invert[mime_type]
filename = [SecureRandom.uuid, extension].join
self.send(association).attach(
io: StringIO.new(decoded_base64),
filename: filename,
content_type: mime_type
)
end
end
We just created an Attachable
module that we can include in our models.
In your app/models/post.rb
, we can now do:
1
2
3
4
5
class Post < ApplicationRecord
include Attachable
has_one_attached :image
end
Then we can attach file to our Post
record like the following code:
1
2
3
base64_file = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAA1JREFUGFdjYGBg+A8AAQQBAHAgZQsAAAAASUVORK5CYII="
post = Post.last # or any Post record
post.attach_file!(base64_file, :image)
Bonus
We could also set a custom directory where our files will be saved.
1
2
3
4
5
6
self.send(association).attach(
+ key: ['custom-directory', filename].join('/'),
io: StringIO.new(decoded_base64),
filename: filename,
content_type: mime_type
)
That’s it. We now learned to attach a base64 file to ActiveStorage in Rails.