Generate a QR code for a Product and store it in ActiveStorage. Service Objects
Flow:
- Generate a QR code based on Product URL
- Generate a Tempfile PNG file for the QR code
- Store the PNG file as an ActiveStorage::Blob
- Attach the ActiveStorage::Blob to the Product
- Display the attached QR code
Tools:
- gem rqrcode
- ActiveStorage
- Url Helpers
- Storage & Temp files
- Service Objects
STEP 1. Create Products, Install ActiveStorage, Gem Rqrcode #
console
rails g scaffold Product name
bin/rails active_storage:install
bin/rails db:migrate
bundle add rqrcode
STEP 2. Product has one attached QR, generate QR when Product is created #
app/models/product.rb
# store qr code in ActiveStorage
has_one_attached :qr_code
after_create :generate_qr
def generate_qr
GenerateQrService.new(self).call
end
STEP 3. ServiceObject - perfect place to store the logic of generating and attaching a QR #
console
mkdir app/services
echo > app/services/generate_qr.rb
app/services/generate_qr_service.rb
class GenerateQrService
attr_reader :product
def initialize(product)
@product = product
end
# url_for helper
include Rails.application.routes.url_helpers
# ensure rqrcode works here
require "rqrcode"
def call
# https://superails.com/products/5?abc=d+e+f
qr_url = url_for(controller: 'products',
action: 'show',
id: product.id,
host: 'superails.com',
only_path: false,
abc: 'd e f'
)
# generate QR code
qr_code = RQRCode::QRCode.new(qr_url)
# QR code to image
qr_png = qr_code.as_png(
bit_depth: 1,
border_modules: 4,
color_mode: ChunkyPNG::COLOR_GRAYSCALE,
color: "black",
file: nil,
fill: "white",
module_px_size: 6,
resize_exactly_to: false,
resize_gte_to: false,
size: 120
)
# name the image
image_name = SecureRandom.hex
# attach the generated image object
product.qr_code.attach(
io: StringIO.new(qr_png.to_s),
filename: "#{image_name}.png",
content_type: "image/png"
)
end
end
STEP 4. Display the QR code #
app/views/products/_product.html.erb
<%= image_tag(product.qr_code) if product.qr_code.attached? %>
OTHER THOUGHTS & NOTES #
Previously, before using StringIO
, I would save the file locally, upload it to storage, and later attach it:
# save the image in TMP
image = IO.binwrite("tmp/storage/#{image_name}.png", qr_png.to_s)
# save TMP file to ActiveStorage
# blob = ActiveStorage::Blob.create_after_upload!(
blob = ActiveStorage::Blob.create_and_upload!(
io: File.open("tmp/storage/#{image_name}.png"),
filename: image_name,
content_type: 'png'
)
# attach ActiveStorage::Blob to the product
product.qr_code.attach(blob)
What if we write not to tmp/storage/..
, but to Tempfile?
file = Tempfile.new(['hello', '.jpg'])
file.path # => something like: "/tmp/foo2843-8392-92849382--0.jpg"
product.qr_code.attach(io: File.open("/path/to/face.jpg"), filename: "face.jpg", content_type: "image/jpg")
blob = ActiveStorage::Blob.create_after_upload!(
io: File.open("tmp/storage/04755c23b32185f09bb1a20aabcc823c.png"),
filename: '04755c23b32185f09bb1a20aabcc823c',
content_type: 'png'
)
ActiveStorage::Blob.first
Did you like this article? Did it save you some time?