Skip to content

Commit 6fe9687

Browse files
committed
Simple rails app to upload and display files
1 parent f9d6d6c commit 6fe9687

File tree

12 files changed

+390
-2
lines changed

12 files changed

+390
-2
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
class UploadsController < ApplicationController
2+
def index
3+
@uploads = Upload.includes(file_attachment: :blob).order(created_at: :desc)
4+
end
5+
6+
def create
7+
@upload = Upload.new(upload_params)
8+
9+
if @upload.save
10+
@attachment = @upload.file.attachment
11+
@blob = @upload.file.blob
12+
respond_to do |format|
13+
format.turbo_stream
14+
format.html { redirect_to uploads_path, notice: "File uploaded successfully." }
15+
end
16+
else
17+
respond_to do |format|
18+
format.turbo_stream do
19+
render turbo_stream: turbo_stream.replace("upload_form", partial: "form", locals: { upload: @upload })
20+
end
21+
format.html do
22+
@uploads = Upload.includes(file_attachment: :blob).order(created_at: :desc)
23+
render :index, status: :unprocessable_entity
24+
end
25+
end
26+
end
27+
end
28+
29+
def destroy
30+
@upload = Upload.find(params[:id])
31+
@attachment = @upload.file.attachment
32+
@blob = @upload.file.blob
33+
@upload.destroy
34+
respond_to do |format|
35+
format.turbo_stream
36+
format.html { redirect_to uploads_path, notice: "File deleted successfully." }
37+
end
38+
end
39+
40+
private
41+
42+
def upload_params
43+
params.require(:upload).permit(:name, :file)
44+
end
45+
46+
end

app/models/upload.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class Upload < ApplicationRecord
2+
has_one_attached :file
3+
4+
validates :name, presence: true
5+
validates :file, presence: true
6+
end

app/views/uploads/_form.html.erb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<div id="upload_form">
2+
<%= form_with model: upload, url: uploads_path, class: "space-y-4" do |f| %>
3+
<% if upload.errors.any? %>
4+
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
5+
<ul>
6+
<% upload.errors.full_messages.each do |message| %>
7+
<li><%= message %></li>
8+
<% end %>
9+
</ul>
10+
</div>
11+
<% end %>
12+
13+
<div>
14+
<%= f.label :name, class: "block text-gray-700 text-sm font-bold mb-2" %>
15+
<%= f.text_field :name, class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" %>
16+
</div>
17+
18+
<div>
19+
<%= f.label :file, class: "block text-gray-700 text-sm font-bold mb-2" %>
20+
<%= f.file_field :file, class: "block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100" %>
21+
</div>
22+
23+
<div>
24+
<%= f.submit "Upload", class: "bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline cursor-pointer" %>
25+
</div>
26+
<% end %>
27+
</div>
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<%= turbo_stream.prepend "uploads" do %>
2+
<tr class="border-t" id="<%= dom_id(@upload) %>">
3+
<td class="px-4 py-2"><%= @upload.id %></td>
4+
<td class="px-4 py-2"><%= @upload.name %></td>
5+
<td class="px-4 py-2">
6+
<% if @upload.file.attached? %>
7+
<%= link_to @upload.file.filename, rails_blob_path(@upload.file, disposition: "attachment"), class: "text-blue-500 hover:underline" %>
8+
<% end %>
9+
</td>
10+
<td class="px-4 py-2"><%= @upload.created_at.strftime("%Y-%m-%d %H:%M") %></td>
11+
<td class="px-4 py-2">
12+
<%= button_to "Delete", upload_path(@upload), method: :delete, class: "bg-red-500 hover:bg-red-700 text-white text-sm py-1 px-2 rounded", data: { turbo_confirm: "Are you sure?" } %>
13+
</td>
14+
</tr>
15+
<% end %>
16+
17+
<%= turbo_stream.prepend "attachments" do %>
18+
<tr class="border-t" id="attachment_<%= @attachment.id %>">
19+
<td class="px-4 py-2"><%= @attachment.id %></td>
20+
<td class="px-4 py-2"><%= @attachment.name %></td>
21+
<td class="px-4 py-2"><%= @attachment.record_type %></td>
22+
<td class="px-4 py-2"><%= @attachment.record_id %></td>
23+
<td class="px-4 py-2"><%= @attachment.blob_id %></td>
24+
<td class="px-4 py-2"><%= @attachment.created_at.strftime("%Y-%m-%d %H:%M") %></td>
25+
</tr>
26+
<% end %>
27+
28+
<%= turbo_stream.prepend "blobs" do %>
29+
<tr class="border-t" id="blob_<%= @blob.id %>">
30+
<td class="px-4 py-2"><%= @blob.id %></td>
31+
<td class="px-4 py-2 text-xs font-mono"><%= truncate(@blob.key, length: 20) %></td>
32+
<td class="px-4 py-2"><%= @blob.filename %></td>
33+
<td class="px-4 py-2"><%= @blob.content_type %></td>
34+
<td class="px-4 py-2"><%= number_to_human_size(@blob.byte_size) %></td>
35+
<td class="px-4 py-2 text-xs font-mono"><%= truncate(@blob.checksum, length: 15) %></td>
36+
<td class="px-4 py-2"><%= @blob.created_at.strftime("%Y-%m-%d %H:%M") %></td>
37+
</tr>
38+
<% end %>
39+
40+
<%= turbo_stream.update "flash" do %>
41+
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4">
42+
File uploaded successfully.
43+
</div>
44+
<% end %>
45+
46+
<%= turbo_stream.replace "upload_form", partial: "form", locals: { upload: Upload.new } %>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<%= turbo_stream.remove dom_id(@upload) %>
2+
<%= turbo_stream.remove "attachment_#{@attachment.id}" %>
3+
<%= turbo_stream.remove "blob_#{@blob.id}" %>
4+
5+
<%= turbo_stream.update "flash" do %>
6+
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4">
7+
File deleted successfully.
8+
</div>
9+
<% end %>

app/views/uploads/index.html.erb

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<div class="container mx-auto px-4 py-8">
2+
<h1 class="text-3xl font-bold mb-8">File Uploads</h1>
3+
4+
<div id="flash">
5+
<% if notice %>
6+
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4">
7+
<%= notice %>
8+
</div>
9+
<% end %>
10+
</div>
11+
12+
<!-- Upload Form -->
13+
<div class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-8">
14+
<h2 class="text-xl font-semibold mb-4">Upload a New File</h2>
15+
16+
<%= render "form", upload: Upload.new %>
17+
</div>
18+
19+
<!-- Uploads Table -->
20+
<div class="mb-8">
21+
<h2 class="text-xl font-semibold mb-4">Uploaded Files</h2>
22+
<div class="overflow-x-auto">
23+
<table class="min-w-full bg-white shadow-md rounded">
24+
<thead class="bg-gray-100">
25+
<tr>
26+
<th class="px-4 py-2 text-left">ID</th>
27+
<th class="px-4 py-2 text-left">Name</th>
28+
<th class="px-4 py-2 text-left">File</th>
29+
<th class="px-4 py-2 text-left">Created At</th>
30+
<th class="px-4 py-2 text-left">Actions</th>
31+
</tr>
32+
</thead>
33+
<tbody id="uploads">
34+
<% @uploads.each do |upload| %>
35+
<tr class="border-t" id="<%= dom_id(upload) %>">
36+
<td class="px-4 py-2"><%= upload.id %></td>
37+
<td class="px-4 py-2"><%= upload.name %></td>
38+
<td class="px-4 py-2">
39+
<% if upload.file.attached? %>
40+
<%= link_to upload.file.filename, rails_blob_path(upload.file, disposition: "attachment"), class: "text-blue-500 hover:underline" %>
41+
<% end %>
42+
</td>
43+
<td class="px-4 py-2"><%= upload.created_at.strftime("%Y-%m-%d %H:%M") %></td>
44+
<td class="px-4 py-2">
45+
<%= button_to "Delete", upload_path(upload), method: :delete, class: "bg-red-500 hover:bg-red-700 text-white text-sm py-1 px-2 rounded", data: { turbo_confirm: "Are you sure?" } %>
46+
</td>
47+
</tr>
48+
<% end %>
49+
</tbody>
50+
</table>
51+
</div>
52+
</div>
53+
54+
<!-- Active Storage Attachments Table -->
55+
<div class="mb-8">
56+
<h2 class="text-xl font-semibold mb-4">Active Storage Attachments</h2>
57+
<div class="overflow-x-auto">
58+
<table class="min-w-full bg-white shadow-md rounded">
59+
<thead class="bg-gray-100">
60+
<tr>
61+
<th class="px-4 py-2 text-left">ID</th>
62+
<th class="px-4 py-2 text-left">Name</th>
63+
<th class="px-4 py-2 text-left">Record Type</th>
64+
<th class="px-4 py-2 text-left">Record ID</th>
65+
<th class="px-4 py-2 text-left">Blob ID</th>
66+
<th class="px-4 py-2 text-left">Created At</th>
67+
</tr>
68+
</thead>
69+
<tbody id="attachments">
70+
<% @uploads.each do |upload| %>
71+
<% if upload.file.attached? %>
72+
<% attachment = upload.file.attachment %>
73+
<tr class="border-t" id="attachment_<%= attachment.id %>">
74+
<td class="px-4 py-2"><%= attachment.id %></td>
75+
<td class="px-4 py-2"><%= attachment.name %></td>
76+
<td class="px-4 py-2"><%= attachment.record_type %></td>
77+
<td class="px-4 py-2"><%= attachment.record_id %></td>
78+
<td class="px-4 py-2"><%= attachment.blob_id %></td>
79+
<td class="px-4 py-2"><%= attachment.created_at.strftime("%Y-%m-%d %H:%M") %></td>
80+
</tr>
81+
<% end %>
82+
<% end %>
83+
</tbody>
84+
</table>
85+
</div>
86+
</div>
87+
88+
<!-- Active Storage Blobs Table -->
89+
<div class="mb-8">
90+
<h2 class="text-xl font-semibold mb-4">Active Storage Blobs</h2>
91+
<div class="overflow-x-auto">
92+
<table class="min-w-full bg-white shadow-md rounded">
93+
<thead class="bg-gray-100">
94+
<tr>
95+
<th class="px-4 py-2 text-left">ID</th>
96+
<th class="px-4 py-2 text-left">Key</th>
97+
<th class="px-4 py-2 text-left">Filename</th>
98+
<th class="px-4 py-2 text-left">Content Type</th>
99+
<th class="px-4 py-2 text-left">Byte Size</th>
100+
<th class="px-4 py-2 text-left">Checksum</th>
101+
<th class="px-4 py-2 text-left">Created At</th>
102+
</tr>
103+
</thead>
104+
<tbody id="blobs">
105+
<% @uploads.each do |upload| %>
106+
<% if upload.file.attached? %>
107+
<% blob = upload.file.blob %>
108+
<tr class="border-t" id="blob_<%= blob.id %>">
109+
<td class="px-4 py-2"><%= blob.id %></td>
110+
<td class="px-4 py-2 text-xs font-mono"><%= truncate(blob.key, length: 20) %></td>
111+
<td class="px-4 py-2"><%= blob.filename %></td>
112+
<td class="px-4 py-2"><%= blob.content_type %></td>
113+
<td class="px-4 py-2"><%= number_to_human_size(blob.byte_size) %></td>
114+
<td class="px-4 py-2 text-xs font-mono"><%= truncate(blob.checksum, length: 15) %></td>
115+
<td class="px-4 py-2"><%= blob.created_at.strftime("%Y-%m-%d %H:%M") %></td>
116+
</tr>
117+
<% end %>
118+
<% end %>
119+
</tbody>
120+
</table>
121+
</div>
122+
</div>
123+
</div>

config/routes.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
# get "manifest" => "rails/pwa#manifest", as: :pwa_manifest
1010
# get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker
1111

12-
# Defines the root path route ("/")
13-
# root "posts#index"
12+
resources :uploads, only: [:index, :create, :destroy]
13+
14+
root "uploads#index"
1415
end
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# This migration comes from active_storage (originally 20170806125915)
2+
class CreateActiveStorageTables < ActiveRecord::Migration[7.0]
3+
def change
4+
# Use Active Record's configured type for primary and foreign keys
5+
primary_key_type, foreign_key_type = primary_and_foreign_key_types
6+
7+
create_table :active_storage_blobs, id: primary_key_type do |t|
8+
t.string :key, null: false
9+
t.string :filename, null: false
10+
t.string :content_type
11+
t.text :metadata
12+
t.string :service_name, null: false
13+
t.bigint :byte_size, null: false
14+
t.string :checksum
15+
16+
if connection.supports_datetime_with_precision?
17+
t.datetime :created_at, precision: 6, null: false
18+
else
19+
t.datetime :created_at, null: false
20+
end
21+
22+
t.index [ :key ], unique: true
23+
end
24+
25+
create_table :active_storage_attachments, id: primary_key_type do |t|
26+
t.string :name, null: false
27+
t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type
28+
t.references :blob, null: false, type: foreign_key_type
29+
30+
if connection.supports_datetime_with_precision?
31+
t.datetime :created_at, precision: 6, null: false
32+
else
33+
t.datetime :created_at, null: false
34+
end
35+
36+
t.index [ :record_type, :record_id, :name, :blob_id ], name: :index_active_storage_attachments_uniqueness, unique: true
37+
t.foreign_key :active_storage_blobs, column: :blob_id
38+
end
39+
40+
create_table :active_storage_variant_records, id: primary_key_type do |t|
41+
t.belongs_to :blob, null: false, index: false, type: foreign_key_type
42+
t.string :variation_digest, null: false
43+
44+
t.index [ :blob_id, :variation_digest ], name: :index_active_storage_variant_records_uniqueness, unique: true
45+
t.foreign_key :active_storage_blobs, column: :blob_id
46+
end
47+
end
48+
49+
private
50+
def primary_and_foreign_key_types
51+
config = Rails.configuration.generators
52+
setting = config.options[config.orm][:primary_key_type]
53+
primary_key_type = setting || :primary_key
54+
foreign_key_type = setting || :bigint
55+
[ primary_key_type, foreign_key_type ]
56+
end
57+
end
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class CreateUploads < ActiveRecord::Migration[8.1]
2+
def change
3+
create_table :uploads do |t|
4+
t.string :name
5+
6+
t.timestamps
7+
end
8+
end
9+
end

db/schema.rb

Lines changed: 50 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)