Buy my course: Security for Rails Developers.
Hi there,
I am working on a newsletter tool(Pombo) and this week, I want to share how I solved a problem I came across last week while working on it.
The problem: the newsletter tool needs to support importing past issues from other tools. These imported issues might contain references to images hosted at the other provider, and during the import, I need to move those images into Pombo because once the account on the old service is deleted, the images won’t be accessible anymore.
In Pombo, I use Action Text to store the body of a newsletter issue, and I use Active Storage for the images. My initial plan was to parse the HTML output of the Action Text field with Nokogiri, extract the image tags, create an Active Storage blob from them, and replace the image tag’s src
in the markup to the blob’s URL.
Then I decided to look at Action Text’s source code to see how it works under the hood, and it turned out it will be way easier to do what I need than I expected.
An Action Text field has a “body” attribute, which is an instance of ActionText::Content
which has a fragment
method, which returns an instance of ActionText::Fragment
and that’s a wrapper around the Nokogiri parsed markup.
Now putting this all together, to find all image tags on a content
attribute backed by Action Text, we can just do the following:
content.body.fragment.find_all('img')
Next part is to download the image and create an ActiveStorage::Blob
:
require 'open-uri'
content.body.fragment.find_all('img').each do |img|
url = img.attribute('src').to_s # extract the src of the image
io = URI.open(url) # download the file
filename = File.basename(URI(url).path) # extract the filename from the URL
# create the blob
blob = ActiveStorage::Blob.create_and_upload! io: io, filename: filename
end
Next thing to figure out is how to replace the old image with the new one.
Digging a bit more into Action Text, I figured out I need to create an ActionText::Attachment
from the blob and replace the nokogiri node with that:
attachment = ActionText::Attachment.from_attachable(
blob,
url: Rails.application.routes.url_helpers.rails_blob_url(blob)
)
img.replace attachment.node
And finally, we need to save the changes on the content
. Here is the full snippet:
require "open-uri"
content.body.fragment.find_all('img').each do |img|
url = img.attribute('src').to_s
io = URI.open(url)
filename = File.basename(URI(url).path)
blob = ActiveStorage::Blob.create_and_upload! io: io, filename: filename
attachment = ActionText::Attachment.from_attachable(
blob,
url: Rails.application.routes.url_helpers.rails_blob_url(blob)
)
img.replace attachment.node
end
content.save!
Based on what I learned from figuring out this solution, it will be pretty easy to add an outgoing link validator to make sure a newsletter doesn’t contain broken links.
I hope you enjoyed this, until next time.
Did you enjoy reading this? Sign up to the Rails Tricks newsletter for more content like this!
Or follow me on Twitter
I run an indie startup providing vulnerability scanning for your Ruby on Rails app.
It is free to use at the moment, and I am grateful for any feedback about it.If you would like to give it a spin, you can do it here: Vulnerability Scanning for your Ruby on Rails app!