Building Scentfolio: A Full-Stack Fragrance Tracking Platform with Ruby on Rails
I chose a niche that I find exciting: fragrances. Throughout the years I have been using websites hanging on by their last html line of code to gather my…
The Problem#
It was time to put all I have learnt throughout my time at Le Wagon to the test. Hearing about how to build a full stack website was sounding easier than ever. However, I wanted to put myself to the test to build something outside of the construct of Le Wagon. No challenges that I had to follow broken down by steps nor being spoon fed what the code would be to decipher whether I pass a challenge.
I chose a niche that I find exciting: fragrances. Throughout the years I have been using websites hanging on by their last html line of code to gather my knowledge about fragrances. This interest came randomly but intensely. I was curious about the notes, the perfumer, and mostly excited to share my reviews on said fragrances.
However, I found the websites visually unappealing and underwhelming So for my first website, I wanted to collate and put to the test if I could create a full stack website for fragrance lovers such as myself, that was actually interesting to navigate. In turn, I would ultimately end up using what I have learned to create such.
Fragrance lovers often own multiple perfumes, sample new scents regularly, and develop preferences that evolve over time as your nose changes as we call it. Yet most people rely on memory, notes apps, or spreadsheets to track their fragrance journey. I wanted a better solution. That’s why I built Scentfolio, a web application designed to help users track, discover, and cherish their fragrance experiences in one place.
From Idea to Application#
The original vision was simple: create a digital fragrance diary web app, where users could record the scents they own and their impressions of each fragrance.
As development progressed, the project evolved into a full-stack web application featuring user accounts, fragrance collections, personal scent entries and analytics dashboards.
Technology Stack - Ruby on Rails#
In Le Wagon we used Ruby on Rails because it provides a robust framework for rapidly building database-driven applications. Rails allowed me to focus on solving user problems instead of spending excessive time on configuration.
Database Design#
The website app stores fragrance entries and user data in a structured database, enabling users to create fragrance entries, update scent notes, tack personal ratings and maintain a fragrance history over time.
Using Active Record made managing relationships and data persistence straightforward. Authentication and personalisation A fragrance journal is personal. To support this, I implemented user registration and login functionality, allowing users to securely manage their own collections and entries.
The User model uses Rails’ built-in authentication framework.
has_secure_passwordEmail addresses are normalized before storage:
normalizes :email,with: ->(e) { e.strip.downcase }This prevents duplicate accounts. Additional validations were used to ensure unique email addresses, minimum password length and proper email formatting.
Once the user is signed in, they are able to create fragrance diary, edit existing diary entries, view the analytics and maintain a unique fragrance hsitory. I ensured that the user data and fragrance data would remain separate.
The central model in the web app is the Entry model. Each entry represents a fragrance experience rather than simply a perfume bottle. This design allows users to record details such as fragrance name, brand, notes, personal rating, date worn, longevity and personal notes.
class Entry < ApplicationRecord OCCASIONS = [ "Work", "Evening Out", "Special Event", "Casual", "Date Night", "Sport", "Travel" ].freeze
validates :fragrance_name, presence: true, length: { maximum: 100 }, format: { without: /<[^>]+>/, message: "must not contain HTML" }
validates :brand, length: { maximum: 100 }, format: { without: /<[^>]+>/, message: "must not contain HTML" }, allow_blank: true
validates :notes, length: { maximum: 1000 }, format: { without: /<[^>]+>/, message: "must not contain HTML" }, allow_blank: true
validates :occasion, inclusion: { in: OCCASIONS, message: "is not a valid occasion" }, allow_blank: true
validates :strength, presence: true, inclusion: { in: 1..10 }
validates :worn_on, presence: true
scope :recent, -> { order(worn_on: :desc) } scope :this_week, -> { where(worn_on: 1.week.ago..) }
def self.favorite_occasion where.not(occasion: [ nil, "" ]) .group(:occasion) .order("count_all DESC") .count .first&.first end
def self.avg_strength average(:strength)&.round(1) endendTreating entries as experiences rather than products creates a richer dataset and allows users to build a true fragrance journey overtime. The relationship structure is intentionally simple:
User has_many: entriesEntry belongs _to userThis means the user can maintain hundreds of fragrance entries which preserving clear ownership and data integrity.
Data Validation#
One of my priorities during development was ensuring that the database only stores meaningful and clean data.
Rails validations are used extensively throughout the Entry model. I made sure that data validations would be necessary when inputting the fragrance name, strength rating must fall between 1 and 10, dates the fragrance was worn would be mandatory, text fields would have length restrictions and finally HTML input would be blocked to rpecent malicious content and maintain data integrity.
validates :strength,presence: true,inclusion: { in: 1..10 }To improve data consistency, fragrance occasions were restricted to a predefined list and to avoid duplicates. Rather than repeatedly writing database queries throughout the application, I used Active Record scopes to encapsulate common filters. This keeps controllers clean while making the code easier to maintain.
The web app followed Rails’ RESTful conventions through the Entries Controller.
class EntriesController < ApplicationController def index @entries = Entry.order(worn_on: :desc) end
def show end
def new @entry = Entry.new(worn_on: Date.today) end
def create @entry = Entry.new(entry_params) if @entry.save redirect_to root_path, notice: "Entry added successfully!" else render :new, status: :unprocessable_entity end end
def edit end
def update if @entry.update(entry_params) redirect_to entries_path, notice: "Entry updated!" else render :edit, status: :unprocessable_entity end end
def destroy @entry.destroy redirect_to entries_path, notice: "Entry deleted." endFor routes, I used clean RESTful routing
: resources :entrieswhich automatically generated routes for index, show, new, create, edit, update and delete. Then I added custom routes for the analytics dashboard, user registration and user login.
Rails.application.routes.draw do root "pages#home" get "analytics", to: "pages#analytics", as: :analytics resources :entries
get "sign-up", to: "sessions#new_signup", as: :sign_up post "sign-up", to: "users#create" get "log-in", to: "sessions#new_login", as: :log_inendKey feature was the fragrance collection management#
Users can maintain a digital inventory of their fragrances, making it easy to organise and revisit their collection. One of the most interesting aspects of the project is the analytics section. Instead of simply storing fragrance data, the application helps users understand patterns in their preferences and usage habits.
Challenges Along the Way#
Building a full-stack application introduced challenges beyond front-end development. Displaying fragrance data is easy. Turning that data into useful insights for users required additional thought around dashboard design. Because the data is stored in structured tables rather than unstructured notes, these insights can be generated efficiently through database queries.
What I Learned#
This project helped me strengthen my understanding of Ruby on rails architecture, MVC design patterns, database design, authentication systems and full-stack application development. Most importantly, it reinforced the value of building software around a genuine passion.
Final Thoughts
Scentfolio began as a simple idea and grew into a full-stack web platform that combines technology with a personal passion for fragrance.
The project continues to evolve, but it has already become one of the most rewarding applications I’ve built because it solves a real problem for a community I care about.
Building software is always more enjoyable when it’s connected to something meaningful, and for me, fragrance was the perfect place to start.