A simple Rails app demonstrating how to use Active Storage to upload and manage employee information, PAN card, and certificate attachments using nested forms, file system storage, and Bootstrap-styled views.
- Ruby:
3.2.3
- Rails:
8.0.2
If you get stuck, check out my tutorial for the full working version.
rails new active_storage_demo
cd active_storage_demo
code .
In your Gemfile:
gem "toastr-rails"
gem "jquery-rails"
gem "rails-ujs"
Then install them:
bundle install
🔌 Setup Active Storage
rails active_storage:install
Creates:
active_storage_blobs
active_storage_attachments
active_storage_variant_records
🧱 Models
rails g model employeeinfo firstname:string lastname:string rails g model employeecertificate employeeinfo:references
app/models/employeecertificate.rb
class Employeecertificate < ApplicationRecord
belongs_to :employeeinfo
has_one_attached :certi
end
app/models/employeeinfo.rb
class Employeeinfo < ApplicationRecord
has_many :employeecertificates, dependent: :destroy
accepts_nested_attributes_for :employeecertificates, allow_destroy: true
has_one_attached :pan_card
end
rails db:migrate
📁 Active Storage Config
config/storage.yml
test:
service: Disk
root: <%= Rails.root.join("tmp/storage") %>
local:
service: Disk
root: <%= Rails.root.join("storage") %>
Uncomment and configure Amazon/GCS/Azure if needed for cloud deployments.
🎮 Controller
Generate controller:
rails g controller employeeinfodetails
app/controllers/employeeinfodetails_controller.rb
class EmployeeinfodetailsController < ApplicationController
def index
@employees = Employeeinfo.all
end
def new
@employee = Employeeinfo.new
end
def create
@employee = Employeeinfo.new(employee_params)
if @employee.save
folder_name = "#{@employee.id}_#{@employee.lastname}"
folder_path = Rails.public_path.join('employee_files', folder_name)
FileUtils.mkdir_p(folder_path)
upload_file(params[:cv], folder_path, "#{folder_name}_cv")
upload_file(params[:photo], folder_path, "#{folder_name}_my_pic")
certificate_path = folder_path.join('certificates')
FileUtils.mkdir_p(certificate_path)
params[:employee][:employeecertificates_attributes].each_with_index do |certificate, index|
upload_file(certificate[:certi], certificate_path, "certificate_#{index + 1}")
end
@employee.update(certificate_params)
redirect_to employeeinfodetails_path
end
end
def show
@employee = Employeeinfo.find_by(id: params[:id])
if @employee
@folder_name = "#{@employee.id}_#{@employee.lastname}"
folder_path = Rails.public_path.join('employee_files', @folder_name)
file_list = Dir["#{folder_path}/*"]
@cv_file_path = extract_file_path(file_list, 'cv')
@my_pic_file_path = extract_file_path(file_list, 'my_pic')
certi_path = folder_path.join('certificates')
certi_files = Dir["#{certi_path}/*"]
@certificates = certi_files.map { |c| c[/"employee_files.*$/] }
end
end
def download_file
send_file("#{Rails.root}/public/#{params[:download_path]}")
end
private
def employee_params
params.require(:employeeinfo).permit(:firstname, :lastname, :pan_card)
end
def certificate_params
params.require(:employee).permit(employeecertificates_attributes: [:certi])
end
def upload_file(uploaded_file, folder_path, new_fname)
ext = File.extname(uploaded_file.original_filename)
new_file = "#{new_fname}#{ext}"
File.open(File.join(folder_path, new_file), 'wb') { |f| f.write(uploaded_file.read) }
end
def extract_file_path(files, keyword)
file = files.find { |f| f.include?(keyword) }
"employee_files/#{@folder_name}/#{File.basename(file)}" if file
end
end
🗺 Routes
config/routes.rb
resources :employeeinfodetails, only: [:index, :show, :create, :new]
root "employeeinfodetails#index"
get "employees/download_file" => "employeeinfodetails#download_file"
🎨 Front-End Setup
app/views/layouts/application.html.erb
<!-- Bootstrap -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- Toastr CSS/JS -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js"></script>
💡 Alternative:
Use importmap (Rails 7+) via config/importmap.rb
and app/javascript/application.js
.
💻 JavaScript
app/javascript/application.js
import "@hotwired/turbo-rails"
import "controllers"
$(document).on('turbo:load', function() {
addCertificate();
submitEmployeeForm();
});
function addCertificate() {
$('.add-certicate').on('click', function(e) {
if ($('form .certi-box').length < 10) {
// Add more certificate fields...
}
});
}
📂 Folder Structure Example
public/employee_files/
└── 1_Smith/
├── 1_Smith_cv.pdf
├── 1_Smith_my_pic.jpg
└── certificates/
├── certificate_1.pdf
└── certificate_2.pdf
✅ Done!
You now have a working Rails app with Active Storage, file uploads, nested attributes, and Bootstrap UI!
Happy coding! 🎉