Skip to content

Commit 45aff28

Browse files
Adding a sample Sinatra application based on work we did to integrate the flowjs/ng-flow library with a Sinatra application we wrote for handling file uploads.
1 parent e61a967 commit 45aff28

1 file changed

Lines changed: 141 additions & 0 deletions

File tree

samples/Ruby backend in Sinatra.md

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# Ruby backend in Sinatra
2+
3+
@rmontgomery429 has provided this sample implementation in ruby.
4+
5+
1. This is constructed here as a modular sinatra app but you app does not necessarily need to be modular.
6+
2. I've included the use of the sinatra-cross_origin gem which we required for our use case. Your use case may be different and this may not be required.
7+
3. I have not tested this specific gist of the app, but we do have a version of this tested and working in production.
8+
4. This solution does not take into account any kind of file.io race conditions or any other permissions issues.
9+
5. I provided this as a reference example not as copy/paste production ready code. Your mileage may vary. :)
10+
11+
The basic idea is that you capture chunks of files, save them as part1, part2, partN, and when you've recieved all the files you combine them into the final single file.
12+
13+
```ruby
14+
##
15+
# Gemfile
16+
gem 'sinatra', '~> 1.4.5'
17+
gem 'sinatra-cross_origin', '~> 0.3.1'
18+
19+
##
20+
# config.ru
21+
require 'sinatra'
22+
set :root, File.dirname(__FILE__)
23+
24+
require './flow_app'
25+
require './flow_controller'
26+
27+
get '/' do
28+
404
29+
end
30+
31+
run Rack::URLMap.new(
32+
"/" => Sinatra::Application,
33+
"/flow" => FlowApp.new,
34+
)
35+
36+
##
37+
# flow_app.rb
38+
class FlowApp < Sinatra::Base
39+
register Sinatra::CrossOrigin
40+
41+
get "/" do
42+
cross_origin
43+
FlowController.new(params).get
44+
end
45+
46+
post "/" do
47+
cross_origin
48+
FlowController.new(params).post!
49+
end
50+
51+
options "/" do
52+
cross_origin
53+
200
54+
end
55+
end
56+
57+
##
58+
# flow_controller.rb
59+
class FlowController
60+
attr_reader :params
61+
62+
def initialize(params)
63+
@params = params
64+
end
65+
66+
def get
67+
File.exists?(chunk_file_path) ? 200 : 404
68+
end
69+
70+
def post!
71+
save_file!
72+
combine_file! if last_chunk?
73+
200
74+
rescue
75+
500
76+
end
77+
78+
private
79+
80+
##
81+
# Move the temporary Sinatra upload to the chunk file location
82+
def save_file!
83+
# Ensure required paths exist
84+
FileUtils.mkpath chunk_file_directory
85+
# Move the temporary file upload to the temporary chunk file path
86+
FileUtils.mv params['file'][:tempfile], chunk_file_path, force: true
87+
end
88+
89+
##
90+
# Determine if this is the last chunk based on the chunk number.
91+
def last_chunk?
92+
params[:flowChunkNumber].to_i == params[:flowTotalChunks].to_i
93+
end
94+
95+
##
96+
# ./tmp/flow/abc-123/upload.txt.part1
97+
def chunk_file_path
98+
File.join(chunk_file_directory, "#{params[:flowFilename]}.part#{params[:flowChunkNumber]}")
99+
end
100+
101+
##
102+
# ./tmp/flow/abc-123
103+
def chunk_file_directory
104+
File.join "tmp", "flow", params[:flowIdentifier]
105+
end
106+
107+
##
108+
# Build final file
109+
def combine_file!
110+
# Ensure required paths exist
111+
FileUtils.mkpath final_file_directory
112+
# Open final file in append mode
113+
File.open(final_file_path, "a") do |f|
114+
file_chunks.each do |file_chunk_path|
115+
# Write each chunk to the permanent file
116+
f.write File.read(file_chunk_path)
117+
end
118+
end
119+
# Cleanup chunk file directory and all chunk files
120+
FileUtils.rm_rf chunk_file_directory
121+
end
122+
123+
##
124+
# /final/resting/place/upload.txt
125+
def final_file_path
126+
File.join final_file_directory, params[:flowFilename]
127+
end
128+
129+
##
130+
# /final/resting/place
131+
def final_file_directory
132+
File.join "", "final", "resting", "place"
133+
end
134+
135+
##
136+
# Get all file chunks sorted by cardinality of their part number
137+
def file_chunks
138+
Dir["#{chunk_file_directory}/*.part*"].sort_by {|f| f.split(".part")[1].to_i }
139+
end
140+
end
141+
```

0 commit comments

Comments
 (0)