A Practical Ruby on Rails Project: Build a Client Document Portal

Written by

Every small agency has some version of the same problem: the client says they sent the file, the project manager remembers seeing it, and nobody can find the approved copy.

A client document portal makes a useful Ruby on Rails project because the problem is easy to recognise and the first version can be modest. Clients upload requested paperwork. Staff review it. Both sides can see what is missing and what has been accepted.

It also gives you something more interesting to build than another generic task list. You have relationships between records, file attachments, permissions, background jobs, and a few business rules that need careful thought.

Take a design studio as an example. Before work starts, the studio needs a signed brief, brand assets, and an approval form. Those files currently arrive through email and shared folders.

The opening screen should answer one question: “What are we still waiting for?”

The first version needs a project page, a list of required documents, an upload form, and a review screen. Leave live chat, automatic text extraction, and extensive reporting for later.

Clients open a request, read the instructions, and upload their file. Reviewers accept the submission or explain what needs changing. That is a workable first release.

Give requests and submissions their own records

A request describes the item you need. A submission records a particular file the client supplied. Separate models make it easier to handle the wrong version arriving.

The request can stay open while its submission history shows what arrived and what still needs correcting.

Here is a potential model layout:

class DocumentRequest < ApplicationRecord
  belongs_to :project
  has_many :submissions, dependent: :restrict_with_error

  validates :title, presence: true
end

class Submission < ApplicationRecord
  belongs_to :document_request
  has_one_attached :file
end

This model outline assumes a Project model, matching database tables and foreign keys, and an installed Active Storage setup.

The Rails association guide explains these relationships. Here, restrict_with_error prevents routine model deletion of a request that still has submissions.

Add fields for the submitter, submission time, review state, reviewer, and review time. Decide separately how archiving and deletion should work.

Make uploads useful to the person sending them

Active Storage connects uploaded files to Rails records and supports configured storage services. Your application still needs to decide which uploads it accepts.

Set file size limits and supported formats. When an upload fails, explain what the person can do about it. Show the actual size limit in the message so they do not have to guess.

Give every corrected upload its own submission record. Once a reviewer accepts a file, keep that exact version: replacing its contents would leave an approval attached to something the reviewer never saw.

If a client still requires fax, staff can send an approved export using iphone fax apps and attach the receipt to the request.

Check access when you retrieve a record

A login screen does not settle access permissions. Several clients may use the portal, but each should see only their own projects.

Retrieve projects through the accounts or memberships the current user is authorised to access. Then retrieve document requests through that permitted project. A project ID passed by the browser is not proof of permission.

Check uploads and review actions separately. A client can have permission to submit a file without permission to approve it.

Attachments need their own checks. Rails documents that the default Active Storage controllers are publicly accessible and their generated application URLs are permanent by design. For private paperwork, follow the Rails guide’s authenticated-controller approach and disable the default attachment routes that would bypass those checks.

Keep approval rules together

Approving a submission is often implemented as updating a status column on the submission model. The missing piece is the rule behind that update: who is allowed to approve this particular submission, and is it ready for review?

A pending submission might move to accepted or changes requested. Record the reviewer and time alongside that decision, on the specific submission being reviewed.

A model method may be sufficient for a small application. If approval affects several records, a Ruby service object can give the operation a clear home.

Save related database changes in a transaction. Where concurrent reviews matter, use locking or conditional updates so one reviewer cannot silently overwrite another’s decision.

Attach review comments to the exact submission they concern. “Please replace page two” becomes confusing once several different PDFs sit under the same request.

Send reminders in the background

Sending a reminder email should not hold up the reviewer’s page. Rails’ Active Job provides the background job interface for work such as queued notifications.

Have the job load the current record and check whether the reminder is still needed. The client may have already uploaded the missing document since the job was scheduled.

Plan for a job running again. Use a notification record or another deliberate deduplication mechanism to avoid sending repeated reminders; do not assume every queued action executes exactly once.

For the first version, a daily reminder schedule is a reasonable choice. Add a manual reminder action later if staff need one.

Keep the dashboard useful as records grow

Initial filters will likely include awaiting upload, awaiting review, and changes requested.

Paginate results. If each result displays its project or reviewer, eager load those associations so rendering the list does not issue a separate query for every row. The Active Record query guide covers eager loading with includes and preload.

Test actual queries before adding indexes to database tables. Use those queries to choose indexes for common lookups and filters, then measure again with a realistic number of records.

Give the project manager a list of missing paperwork and a way to act on it. Charts can wait.

Protect the files throughout their lifetime

Use HTTPS for browser transfers and review the storage service’s encryption and access settings. The ICO’s encryption and data transfer guidance explains the role of TLS and why obsolete SSL protocols should not be used.

For storage encryption, NIST’s AES specification defines the algorithm, including AES-256. Encryption still needs sensible key management and account permissions around it.

Decide what happens when a project closes. Files should follow a defined retention policy, and routine logs should not contain document contents or usable download links.

Before launch, test the whole journey with two separate client accounts: request a file, upload it, request a correction, approve the replacement, and try to access it from the other account.

That exercise gives you a concrete measure of whether the Ruby project works: the right people can find the right version, understand its status, and finish their part of the job.