I created a web chat app in a week: my fastest Rails project yet

I gave myself a goal: one week, one Rails project. And that I did. This one is called Evenfall. It is a real time chat application where people can make rooms and type back and forth. Is it reinventing the wheel? Yep. Is it going to make a splash? Nope. Did I learn a lot? Yep. As you’ll see, the biggest challenges did not come from the networking part of the chat, but rather making it not annoying to use.
This project started on July 23rd 2025 and ended July 30th.
Some context
Evenfall is part of a series of projects called the 1212 learning process. For this I am learning Ruby on Rails in an attempt to get past my nasty habit of overthinking everything and instead just get stuff done. You all know that over-thinker. You might even be one of them.
Anyway, to give a high level overview, here are my projects I completed as part of the process:
β
Complete a project within 1 month (Bloggington) (~60 hours of focused work)
β
Complete another project within 2 weeks (Phasmid) (~50 hours of focused work)
β
Complete another within 1 week (This project, Evenfall) (~33 hours of focused work)
π§ Complete a final project within 2 days
For those who want to see the repository as it was after just the 7 days, refer to the legacy branch of the repository.
The end result
This is a quick 1 minute demo on creating a chat room and doing some simple back and forth chatting. I use two separate browsers on two separate accounts to simulate the interaction.
How it went
Day 0 – Strong start
I did the usual day 0 things. Got the Github repository up. Ran the convenience scripts to get things like authentication, ActionText, and the models in order. The database schema was simple this time, and I did my fair share of wrangling models during Phasmid, so it was easy to get a working database skeleton working in short order.
Day 1 – Plot twist
I got real time messaging working. This is huge news given how early it is in the project. I do want to elaborate on this a bit, as it is kind of the core gimmick of the whole project, and yet said gimmick was complete in 2 days.
How was this done so quickly?
This was done using ActionCable. This comes with Rails and makes it incredibly easy to incorporate real time updates pretty much whenever your business logic wants to. I will admit, I did use ActionCable when writing Phasmid so that contributed to my headstart.
Real-time messaging works in 3 major stages
- A message model instance is created
- A broadcast is triggered, sending the message’s HTML contents to a subscription
- The room that has the subscription receives it, and displays it in real time.
Here is the entire Message model in my project (edited slightly for formatting):
message.rb
class Message < ApplicationRecord
belongs_to :user
belongs_to :room
has_rich_text :content
after_create_commit -> {
broadcast_append_to "room_messages_sub_#{self.room.id}",
partial: "messages/message",
locals: {
room: room,
message: self
},
target: "messages"
}
end
The important part is after_create_commit
, which essentially does the following:
- After a
Message
instance is created (AKA anybody sends a message), send a broadcast to all subscriptions associated with this message’s Room. For the purposes of this project, only one subscription exists which I will mention shortly. partial
refers to an HTML “chunk” which will be appended to atarget
called"messages"
. Thetarget
refers to theid
of adiv
that is located in the chat room.locals
passes along the information to be used when appending the message data to thetarget div
.
A moment ago, I mentioned a subscription that will receive the broadcast. This is where the subscription exists:
_messages.html.erb
<%= turbo_stream_from "room_messages_sub_#{room.id}" %>
<div id="messages" data-controller="messages" data-messages-target="messages">
<%= render "layouts/flash" %>
<p>--- This is the beginning of the chat ---</p>
<% messages.each do |message| %>
<%= render "messages/message", message: message, room: room %>
<% end %>
</div>
The important part is the beginning, where we define the turbo_stream_from
with the same name "room_messages_sub_#{room.id}"
that is referred to in the broadcast_append_to
line in message.rb
. The second important part is the <div id="messages" ...>
portion as this defines where the broadcast needs to append to (i.e. the target
).
And for reference, this is the partial being broadcast, the HTML “chunk” as it were:
_message.html.erb
<div id=<%= combined_dom_id(room, message) %> class="room_message">
<span><b><%= message.user.username %>:</b></span>
<%= message.content %>
<br>
</div>
I want to note that ActionCable is not perfect. If you are not actively looking at your tab, it’s easy to miss messages when you come back to it. While this can be fixed by refreshing, that’s obviously not very cash money of the application.
There are ways to improve this, such as setting something up to queue messages for you. Or perhaps you can use AnyCable, which is apparently a drop-in replacement of ActionCable with more stability/features. That said, I accepted the limitations of ActionCable in the meantime for the sake of moving my project along and not getting bogged down in trying to get an external library to work.
Day 2 – Making stuff look nice
This day was pretty run of the mill. A bit of HTML/CSS formatting to make things look neater.
Day 3 – Mobile, navbar, and early chat interaction logic
More stuff like day 3, as well as making the site look better on mobile. I made a navbar and edited chat rooms so that they will auto-submit when pressing enter. Chat room interactions start out simple, but the next day will show me that to get them precise is a different story.
Day 4 – Chat rooms are hard
So I did the thing where I underestimated the complexity of handling the intricacies of a chat room. Here is the layout of the chat room:

Here are the problems I had while making the chat room usable:
Scrollable elements in HTML start at the top and not at the bottom
This means that to show the latest messages, I have to auto-scroll the scrollbar to the bottom when the user enters the room. This was the easiest of the issues to solve.
When a user sends or receives a message, the scrollbar does not adjust
If the chat box has a new message, the position of the scroll bar stays the same. The message is “hidden” until the user scrolls down. This is of course really bad user experience.
Now, this could be solved by auto-scrolling to the bottom when any message is entered into chat. This creates a new problem though. If you’re looking through the chat history, you will get rudely interrupted every time someone in the chat posts a message. I could have invested time in a fancy thing that notifies you of new messages at the bottom, but the whole 1-week deadline said “no sir you’re keeping it simple.”
As a result, my simple solution was this: if your scroll bar is low enough in the container when any message comes in, then it will auto scroll down. This “low enough” value was definitely based on vibes. I settled at 200 pixels. This was eventually done on day 7.
Pressing return on the message editor does not send messages
This is an understandable problem. The Trix editor has no business auto-submitting forms when pressing return for a multitude of reasons. However, my app certainly has that kind of business. Fast paced messaging is the name of the game.
But of course, we still want people to be able to create newlines. As a result, I needed to account for shift + enter.
It all needed to work on mobile
I made it a point to try and make this site mobile compatible. So all of the problems above needed to be solved for mobile too. Most solutions transferred over, but pressing return was the only way for someone to realistically create a newline on mobile, so I had to code a workaround for that on day 7.
Time to learn Stimulus
I realized that regular Javascript inside of the HTML was not going to cut it, especially when you have stuff constantly changing around in a chat room. Using Stimulus controllers made it far easier to keep track of the state of the page, and act upon those changes.
I don’t like how my controllers came out, and as a result I will not be going into too much detail about them in this post. The good news is I think the next time I write a stimulus controller, it will be far more organized. And perhaps more worthy of writing about.
Day 5 – Centering some divs
I spent the session centering the forms so they look good on desktop and mobile. You heard that right gamers, I centered some divs.
Day 6 – UX improvements
I made the login/register forms a bit more informative if the application has any issues with form submissions. like mismatching passwords and cases where username/email are already taken.
Oh I also added the favicon.ico

Day 7 – Cleanup
A lot of this was a continuation of Day 4: Chat rooms are hard. I got around to fix the remaining issues I ran into that day. The rest of the time was spent cleaning up formatting, applying max character restrictions on forms/models, and stopping the chat box from taking any drag-and-drop files other than images.
The numbers and conclusions
Project | Time Window | Hours Worked | % of Total Time |
Bloggington | 1 month (~730 hrs) | ~60 hrs | 8.2% |
Phasmid | 2 weeks (~336 hrs) | ~50 hrs | 14.8% |
Evenfall | 1 week (~168 hrs) | ~33 hrs | 19.6% |
Project 4 | 2 days (~48 hours) | TBD | TBD |
The time spent on Evenfall was past my initial expectation of about 25 hours, which instead landed at about 33 hours.
Interestingly, even though I spent a considerable amount of time during the week, I didn’t burn myself out. There was plenty of time for me to do other things most evenings like gaming or going out. Having a remote job also helps.
Given my practice on the first two projects, it is reasonable to say I was able to do just a little more in less time. Plus, I’ve found it’s more enjoyable to code using Rails now that I’m getting the hang of things. Great news overall.
The two day project will be coming soon. It will need to be on a weekend where I don’t have much plans, so it may be some time. But it will happen, and it will be the toughest challenge yet. My projects sequentially halved my available time. This final one is the outlier, cutting my time by more than half. I described it as such in my initial post about the 1212 process:
“Project 4: The Crucible (2 days): Your ability to very quickly get something out the door will be forcibly squeezed out of you. The shortest path to success is the only path.”
Time for The Crucible. Stay tuned.