Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save callmesangio/304a29439ce4fb1d70d472288385e6b3 to your computer and use it in GitHub Desktop.

Select an option

Save callmesangio/304a29439ce4fb1d70d472288385e6b3 to your computer and use it in GitHub Desktop.
Sure, here are some ideas that will hopefully expand on my post:
1. validation: in Django, the responsibility of validating model data
is shared between forms and models. Plus, validation logic is not
triggered by default on `Model.save()`, you need to explicitly invoke
`Model.full_clean()` before saving, otherwise you may incur in errors
from the database itself. I think this is not a good pattern to go with.
This works great as long as the only source of incoming data is forms,
but in modern web apps data can come in from a very broad variety of
sources, which makes this approach unpractical.
In my opinion, the ultimate (sometimes not exclusive, of course) place
you want your whole validation logic to run at is just the model itself.
The model should be the single, centralized, source of truth about the
shape of the data it holds, the last resort of validation before hitting
database constraints, and the last chance of getting meaningful error
messages whenever validation fails. And I think that having all of this
on `save` is the most sensible approach.
ActiveRecord models behave this way, and, as a bonus, you can have the
same validation primitives on your own classes via ActiveModel.
2. loose coupling between models and database schema: in Django, models
and migrations are tightly coupled. You change something in the model,
and you get a database migration for free. Of course you need to describe
each and every column in the model, because Django needs to know how to manage
tables on your behalf. And that most of the time is a great feature to
have! But I think that opting out of it is something that the framework
should allow more gracefully (sure, you have `managed=False` on
models' `Meta`, that's a nice escape hatch, but IMHO not a very powerful
one). Rails's models and migrations are loosely coupled, you can have
each one independently of the other. ActiveRecord models don't require
upfront annotation of table columns. Admittedly, coming from Django,
this can be confusing at first, but it's something I got used to pretty
quickly. I find this particularly valuable and convenient when the use
case is building a web frontend against an existing database, where the
web app has no (or partial) ownership of the schema.
Also about migrations: Django applies migrations all the way through
when testing (except when `--keepdb` is used) or when recreating the database
from scratch, while Rails relies on a snapshot of the current schema
(`schema.rb`), which speeds things up during tests or when the need of
starting from a clean slate comes up.
3. background jobs/scheduled tasks: currently Django does not support
background jobs out of the box, and that's very unfortunate. In my
experience you end up needing them most of the time, and pretty soon
into the development lifecycle of an app. So not having them in core
Django usually means installing Celery or something similar, just to
satisfy a dependency that's basically hardly avoidable. Maybe this
will change with Django 6.0, as it appears tasks will be added to core,
but I think it's undoubtedly long overdue. Rails supports tasks out of
the box via ActiveJob, which, on top of that, provides a uniform interface
for working with pluggable queue backends.
4. frontend support: Django is basically frontend agnostic. Just drop
your assets (wherever they come from) into a directory and have them
picked up by `collectstatic` and injected into your templates.
With some work, you can plug just about any frontend library
into a Django project, if you want so. And that's a very nice property to have!
But I would *love* to get an opinionated frontend stack ready to use upon a
default Django installation. Having a fullstack, out of the box solution
would be empowering at least. Rails pursues this goal with the Hotwire suite
of tools, and that helps in reaching a good level of productivity in a
reasonable amount of time.
5. language integration: Ruby being
Ruby, it allows libraries to tap into the language and augment it at
will. ActiveSupport does that with many core classes, providing multiple
big and small quality of life improvements that add up into a globally better
DX. Being able to write `2.days.ago` instead of juggling with `timedelta`
objects is bliss. That's not an issue with Django of course, it's an
opportunity offered by the underlying platform that Rails got to take
advantage of. I just like it a lot.
6. autoloading/Zeitwerk: again thanks to Ruby, in Rails you get autoloading
of constants out of the box, that is, you usually don't have to `require`
things before using them. It just happens automagically when you reference them.
That means less boilerplate, and that moving files around while refactoring
is a simple operation, since most of the time you don't have to fix your `require`s
all over the place. Coming from Python, this was at best confusing to me when
picking Rails up ("where the heck does this class come from!?"), but again,
you end up getting used to it, and I find it speeds the dev flow up.
The fact that Zeitwerk relies on module/class nesting being mirrored by the
filesystem layout of files and directories is definitely helpful.
7. object storage: `django-storages` works great for object storage integration,
but ActiveStorage comes with Rails and thus is maintained by the same team,
no need to have an external dependency and/or to wait for that dependency to
catch up with new framework releases.
8. websockets: again, `django-channels` is awesome, but I think ActionCable
is more convenient, same reasons of #7.
9. inbound email: ActionMailbox provides support for inbound email processing.
I honestly don't know if something along the lines of this actually exists for
Django, but I think I would really appreciate having that already implemented
for me should the need arise.
10. encrypted credentials: Django does not have an opinion, nor does provide
solutions, about secrets management, delegating the user to provide their own
solution. Rails solves the problem of the storage of secrets
in a very effective, elegant, and transparent way. Handling credentials is
basically a universal requirement, so having it solved from the start is great.
11. deployment: Rails goes the extra mile by providing an out of the box solution
for deploying applications, Kamal. I got to use it recently for the first time
and the experience I had was super smooth. I'm not saying it should be a web
framework responsibility to provide a deployment tool, but hey, it's there, it
works, everything is already configured for me to use it, why not.
12. Ruby: this may actually be a byproduct of the "new toy" effect, but I really
am having fun writing Ruby. It's a pleasure to work with it. Python has been a long
time companion to me, so maybe I just needed something new, but I'm actually
having a great time, so I'll put it here among the reasons I find the whole RoR
stack more pleasant. Time will tell, I guess.
On top of this, I find the Ruby ecosystem much more oriented
to web development than Python's: there are more libraries, better solutions to
common problems, more tools and in general a wider spectrum of opportunities.
The community is at least on par with Python's with regards to kindness
and inclusiveness.
That's about everything that comes to my mind right now. There may be other things,
we'll see if I notice something new along the way of using RoR.
I for sure have a nitpick about Rails, namely JSON validation. I'd like a more
effective solution for validating complex JSON structures. It seems to me that
Strong Parameters are not the right tool if not for basic use cases, so probably
I will reach for an external validation library.
Hope this helps!

Comments are disabled for this gist.