Skip to content

Instantly share code, notes, and snippets.

@robzolkos
Last active September 18, 2023 17:52
Show Gist options
  • Save robzolkos/ce126bc8d79cdce514736b5727ac66eb to your computer and use it in GitHub Desktop.
Save robzolkos/ce126bc8d79cdce514736b5727ac66eb to your computer and use it in GitHub Desktop.
Jump zip download refactor from service object
# app/models/jump/downloaded_photo.rb
class Jump::DownloadedPhoto
include ActiveModel::API
attr_accessor :file_name, :file_path
end
# app/models/jump/downloaded_photos_zipper.rb
require 'zip'
class Jump::DownloadedPhotosZipper
def initialize(jump)
@jump = jump
end
def zip
Zip::File.open("#{folder_name}.zip", Zip::File::CREATE) do |zf|
temporary_downloaded_photos_paths.each do |downloaded_photo|
zf.add(downloaded_photo.file_name, downloaded_photo.file_path)
end
end
end
end
# app/models/jump.rb
class Jump < ApplicationRecord
include ZippablePhotos
has_many :photos
end
# app/models/photo.rb
class Photo < ApplicationRecord
def filename_for_zip
"#{stable_name}.jpg"
end
end
# app/models/jump/photos_to_temporary_folder_downloader.rb
class Jump::PhotosToTemporaryFolderDownloader
def initialize(jump)
@jump = jump
@jump.temporary_downloaded_photos = []
end
def download
@jump.photos {|photo| download_photo(photo)}
end
private
def download_photo(photo)
full_download_path = File.join(folder_name, photo.filename_for_zip)
File.open(full_download_path, 'wb') do |file|
photo.image.download { |chunk| file.write(chunk) }
end
@jump.temporary_downloaded_photos << Jump::DownloadedPhoto.new(file_name: photo.filename_for_zip, file_path: full_download_path)
rescue => e
Rails.logger.error("Error downloading photo #{photo.id}: #{e.message}")
end
end
end
# app/models/jump/zippable_photos.rb
module Jump::ZippablePhotos
include ActiveSupport::Concern
JOB_TIMEOUT = 10.minutes
included do
has_attached :photos_zip
attr_accessor :temporary_downloaded_photos
def zip_photos_later
return if ineligible_for_zipping?
update(zip_processing_started_at: Time.current)
ZipPhotosJob.perform_later self
end
def zip_photos
create_temporary_folder
ActsAsTenant.without_tenant do
Jump::PhotosToTemporaryFolderDownloader.new(self).download
Jump::DownloadedPhotosZipper.new(self).zip
attach_zipped_file
broadcast_download_link
end
ensure
delete_temporary_folder
end
private
def ineligible_for_zipping?
photos.blank? || zip_in_progress? || zip_already_attached?
end
def zip_in_progress?
zip_processing_started_at && zip_processing_started_at > JOB_TIMEOUT.ago
end
def zip_already_attached?
photos_zip.attached?
end
def sanitized_booking_name
"#{booking.name.gsub(/[^0-9A-Za-z.\-]/, '_')}"
end
def final_zip_file_name
"#{sanitized_booking_name}_tandem_photos.zip"
end
def temporary_zip_file_name
"#{uuid}_#{SecureRandom.hex(8)}"
end
def folder_name
@folder_name ||= "tmp/photos/archive_#{temporary_zip_file_name}"
end
def attach_zipped_file
@jump.skip_broadcast = true
@jump.photos_zip.attach(io: File.open("#{folder_name}.zip"), filename: final_zip_file_name, content_type: "application/zip")
@jump.skip_broadcast = false
end
def create_temporary_folder
FileUtils.mkdir_p(folder_name) unless Dir.exist?(folder_name)
end
def delete_temporary_folder
FileUtils.rm_rf([folder_name, "#{folder_name}.zip"])
end
end
end
@ziraqyoung
Copy link

For curiosity,

  • why not put app/models/jump/zippable_photos.rb to app/models/concerns/jump/zippable_photos.rb and make it a module? And why is it a class?

@robzolkos
Copy link
Author

For curiosity,

  • why not put app/models/jump/zippable_photos.rb to app/models/concerns/jump/zippable_photos.rb and make it a module? And why is it a class?

You're right @ziraqyoung - typo. I updated to module. As for why its not in app/models/concerns - I like to only put stuff in app/models/concerns that is shared by more than one model. If this module is only included in the jump.rb model than I like to keep it close to that namespaced appropriately close.

@ziraqyoung
Copy link

Oh Nice!

Also in Jump::DownloadedPhotosZipper#zip, I guess you meant to pass folder_name as an argument to the initializer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment