In a past post, we have learned to attach base64 file on ActiveStorage. Now we will learn to attach a file URL to ActiveStorage.
In my current job, we maintain REST APIs consumed by 3rd party companies with their own developers.
There is an endpoint that needs a valid ID to be accessed, what those 3rd party companies pass to us is a
string of the file URL where it can be downloaded.
Now we would need to make it an attachment to Rails ActiveStorage.
Another use case that I have encountered is when we migrated a CSV of products from a Shopify store to our Rails app, when you export Shopify products as CSV it adds a
Image Src column that contains that product’s image URL.
When migrating we need to attach that image URL in our Rails app ActiveStorage.
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 named
cd test-app- navigating into our new Rails app.
bundle exec rails g scaffold post- scaffolded a
bundle exec rails active_storage:install- installing
bundle exec rails db:migrate- running all pending database migrations.
Step 2: Add attachment to our Post model
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
Step 3: Create a sample Post record
Open our Rails console.
1 $ bundle exec rails c
Create a sample
Step 4: Attach an image URL to our Post model
We will use this website’s favicon for our sample image URL to be attached.
Open our Rails console again.
1 $ bundle exec rails c
On our Rails console, we should require
1 require 'open-uri'
We’ll now assign the content of the file URL to our
1 file = URI.open('https://www.davidangulo.xyz/assets/img/favicons/favicon-16x16.png')
Then, we’ll extract the
1 mime_type = file.content_type
There are cases that this is not served by the server, or the server does not serve that real mime type and spoof us. There are gems that can help us sniff the mime type of those files such as ruby-filemagic and fastimage.
Then we’ll use
strict_encode64 encode it in base64.
1 encoded_file = Base64.strict_encode64(file.read)
strict_encode64 so that it doesn’t add
\n to our base64
Then we’ll decode the
1 decoded_file = Base64.decode64(encoded_file)
Generate filename with extension. It’s up to you on how you will generate your filename, for simplicity we’ll just use
1 2 extension = Rack::Mime::MIME_TYPES.invert[mime_type] filename = [SecureRandom.uuid, extension].join
Rack::Mime::MIME_TYPES to get the extension based on the file’s
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_file), 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.
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 16 17 18 require 'open-uri' module Attachable def attach_file!(file_url, association) file = URI.open(file_url) mime_type = file.content_type encoded_file = Base64.strict_encode64(file.read) decoded_file = Base64.decode64(encoded_file) extension = Rack::Mime::MIME_TYPES.invert[mime_type] filename = [SecureRandom.uuid, extension].join self.send(association).attach( io: StringIO.new(decoded_file), filename: filename, content_type: mime_type ) end end
We just created an
Attachable module that we can include in our models.
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 file_url = 'https://www.davidangulo.xyz/assets/img/favicons/favicon-16x16.png' post = Post.last # or any Post record post.attach_file!(file_url, :image)
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_file), filename: filename, content_type: mime_type )
That’s it. We now learned to attach a file URL to ActiveStorage in Rails.