Last active
September 26, 2021 08:14
-
-
Save usutani/cac74984ac386c45fcaa455770c8efdf to your computer and use it in GitHub Desktop.
Hotwire: Rails: バックグラウンドで処理したファイルをダウンロードする時のUI
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<%= tag.li id: convert.id do %> | |
<%= tag.span convert.status_summary %> | |
<%= tag.span do %> | |
<% out_file_path = rails_blob_path(convert.out_file) if convert.out_file.attached? %> | |
<%= link_to_if(convert.succeeded?, 'Download', out_file_path) { "N/A" } %> | |
<% end %> | |
<%= tag.span convert.message %> | |
<% end %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<%= form_with(model: convert) do |form| %> | |
<% if convert.errors.any? %> | |
<div id="error_explanation"> | |
<h2><%= pluralize(convert.errors.count, "error") %> prohibited this convert from being saved:</h2> | |
<ul> | |
<% convert.errors.each do |error| %> | |
<li><%= error.full_message %></li> | |
<% end %> | |
</ul> | |
</div> | |
<% end %> | |
<div class="field"> | |
<%= form.label :in_file %> | |
<%= form.file_field :in_file %> | |
</div> | |
<div class="actions"> | |
<%= form.submit %> | |
</div> | |
<% end %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Convert < ApplicationRecord | |
RELOAD_INTERVAL_MARGIN = 2.seconds | |
enum status: { | |
waiting: 0, | |
validating: 1, | |
converting: 2, | |
succeeded: 3, | |
failed: 4 | |
} | |
has_one_attached :in_file | |
has_one_attached :out_file | |
validates :in_file, presence: true | |
scope :running, -> { where(status: [:waiting, :validating, :converting]) } | |
scope :need_to_notify, -> { where('updated_at >= ?', Time.zone.now - RELOAD_INTERVAL_MARGIN) } | |
def self.need_to_reload? | |
running.exists? | |
end | |
def status_summary | |
"#{id}:#{status}" | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ConvertsController < ApplicationController | |
before_action :set_convert, only: [:show, :edit, :update, :destroy] | |
# GET /converts | |
def index | |
@need_to_reload = Convert.need_to_reload? | |
@convert = Convert.new | |
@converts = Convert.all | |
end | |
# GET /converts/1 | |
def show | |
end | |
# GET /converts/new | |
def new | |
@convert = Convert.new | |
end | |
# GET /converts/1/edit | |
def edit | |
end | |
# POST /converts | |
def create | |
@convert = Convert.new(convert_params) | |
if @convert.save | |
FileConvertJob.perform_later(@convert) | |
@new_convert = Convert.new | |
@notice = 'Convert was successfully created.' | |
else | |
@need_to_reload = Convert.need_to_reload? | |
@converts = Convert.all | |
render :index, status: :unprocessable_entity | |
end | |
end | |
# PATCH/PUT /converts/1 | |
def update | |
if @convert.update(convert_params) | |
redirect_to @convert, notice: 'Convert was successfully updated.' | |
else | |
render :edit | |
end | |
end | |
# DELETE /converts/1 | |
def destroy | |
@convert.destroy | |
redirect_to converts_url, notice: 'Convert was successfully destroyed.' | |
end | |
private | |
# Use callbacks to share common setup or constraints between actions. | |
def set_convert | |
@convert = Convert.find(params[:id]) | |
end | |
# Only allow a list of trusted parameters through. | |
def convert_params | |
params.require(:convert).permit(:in_file) | |
rescue ActionController::ParameterMissing | |
nil | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<%= turbo_stream.update 'notice', @notice %> | |
<%= turbo_stream.update 'reload', 'true' %> | |
<%= turbo_stream.replace 'new_convert' do %> | |
<div id="new_convert"> | |
<%= render 'form', convert: @new_convert %> | |
</div> | |
<% end %> | |
<%= turbo_stream.append 'converts' do %> | |
<%= render 'converts/convert', convert: @convert %> | |
<% end %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class FileConvertJob < ApplicationJob | |
queue_as :default | |
def perform(convert) | |
unless convert.waiting? | |
logger.info "Convert #{@convert.id} is already performed." | |
return | |
end | |
unless pseudo_validate_input_file(convert) | |
message = 'Failed to validate the input file.' | |
logger.info message | |
convert.update(status: :failed, message: message) | |
return | |
end | |
pseudo_convert_file(convert) | |
end | |
def pseudo_validate_input_file(convert) | |
convert.update(status: :validating) | |
logger.info convert.status | |
sleep 5 | |
true | |
end | |
def pseudo_convert_file(convert) | |
convert.update(status: :converting) | |
logger.info convert.status | |
attach_out_file(convert) | |
sleep 5 | |
message = "The process was successful. #{Time.zone.now.iso8601}" | |
convert.update(status: :succeeded, message: message) | |
logger.info convert.status | |
end | |
def attach_out_file(convert) | |
convert.in_file.open(tmpdir: Dir.tmpdir) do |file| | |
convert.out_file.attach(io: File.open(file.path), filename: 'out_file.csv') | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<p id="notice"><%= notice %></p> | |
<%= tag.div @need_to_reload.to_s, id: 'reload', hidden: false %> | |
<div hidden | |
data-controller="reload" | |
data-reload-url-value="/converts/status" | |
data-reload-interval-value="1000" | |
data-action="turbo:submit-end@document->reload#startReloading"></div> | |
<h1>New Convert</h1> | |
<div id="new_convert"> | |
<%= render 'form', convert: @convert %> | |
</div> | |
<h1>Converts</h1> | |
<ul> | |
<div id="converts"> | |
<%= render @converts %> | |
</div> | |
</ul> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<% @converts.each do |convert| %> | |
<%= turbo_stream.replace convert.id do %> | |
<%= render 'converts/convert', convert: convert %> | |
<% end %> | |
<% end %> | |
<%= turbo_stream.update "reload", @need_to_reload.to_s %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Controller } from "@hotwired/stimulus" | |
export default class extends Controller { | |
static values = { | |
url: String, | |
interval: Number | |
} | |
initialize() { | |
this.intervalId = 0 | |
} | |
connect() { | |
this.startReloading() | |
} | |
disconnect() { | |
this.stopReloading() | |
} | |
startReloading() { | |
if (this.intervalId !== 0) { | |
return | |
} | |
this.intervalId = setInterval(() => { | |
if (this.canReload()) { | |
this.updateElements() | |
} else { | |
this.stopReloading() | |
} | |
}, this.intervalValue); | |
} | |
updateElements() { | |
fetch(this.urlValue, { headers: { 'Accept': 'text/vnd.turbo-stream.html' } }) | |
.then(response => response.text()) | |
.then(message => Turbo.renderStreamMessage(message)) | |
.catch (() => this.stopReloading()) | |
} | |
canReload() { | |
const reload = document.getElementById('reload').textContent | |
return (reload === 'true') | |
} | |
stopReloading() { | |
if (this.intervalId !== 0) { | |
clearInterval(this.intervalId) | |
this.intervalId = 0 | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Rails.application.routes.draw do | |
namespace :converts do | |
resources :status, only: :index | |
end | |
resources :converts | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Converts::StatusController < ApplicationController | |
def index | |
@need_to_reload = Convert.need_to_reload? | |
@converts = Convert.running.or(Convert.need_to_notify) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment