9 Technical notes for migrating from Rails to Django

Mahrad Ataeefard
7 min readNov 21, 2020

--

Years ago when I started programming with Ruby, it was famous, and there was a race between both Ruby and Python, Although, until this day the debate still stands, Python has the upper hand and becomes more popular now.
Fortunately, I had a chance to work with Both of them; however, the migration was both remarkable and challenging at the same time, but I learn a lot during this transition, and my north star was all of the notes that I gathered; as a result, I decided to share them with you guys to help someone else in my situation hopefully.

1- Ruby vs Python

Anytime someone asks me what the main difference between Python and ruby is, the uncle bob’s reaction comes to my mind, he shook his hand in the air and said: “aah, Python is Ruby”.
Even though Ruby and Python have tons of similarity as far as I can understand, there was one major thing that keeps buging me, and it was the usage of mapping. To illustrate this, I come up with this example below.

Ruby:

["Dave", "horse", "FOO"].map(&:upcase)

Python:

[x.upper() for x in ["Dave", "horse", "FOO"]]

I think mapping in ruby is way more intuitive. More of these differences could be found here.

2- Rack vs WSGI

When it comes to an application that stands between the web server and the web application, I could say I have found Rack way more comfortable to understand than WSGI, and there is an example for in both of them.

# WSGI
def app(request):
return {
"status": 200,
"headers": {"content_type": "text/plain"},
"body": "Hello World"}
# Rack
app = proc do |env|
[ 200, {'Content-Type' => 'text/plain'}, "Hello World" ]
end

3- Framework Architecture

Rails MVC
Django MVT

As you can see, Rails follows well-known MVC; on the other hand, Django is based on MVT. I don’t want to get too deep in this concept, but I strongly recommend to read a book or at least a paper about MVT before starting using Django.
At my first programming days in Django, I found these notes helpful to reminds me of the changing from MVC to MVT.

Want to read this story later? Save it in Journal.

routes.rb == urls.pyauthors_controller.rb == authors/views.pymodels/Author.rb ==  authors/models.py

4- Commands

Generally speaking, python manage.py is actually the same thing as Rails command.

Python manage.py == Rails
Rails console == Python manage.py shell

Because Python using methods instead of classes, we have to import instead of requiring.

require == from * import *#Rails
require 'render'
#Django
from django.shortcuts import render

By the way, if you see an empty __init__.py a lot don’t panic, it’s the ways for Python to define packages.

parent/
__init__.py
one/
__init__.py
two/
__init__.py
three/
__init__.py

5- Forms vs form_for

Forms are technically an answer for an embedded logic code like this:

<%= form_for(@post) do |f| %>
<%= @post.errors[:base].full_messages %>
<%= f.text_field :title %> <%= @post.errors[:title].full_messages %>
<%= f.text_area :body %> <%= @post.errors[:body].full_messages %>
<% end %>

Django handles this with Forms:

# The form, with a custom validation
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'body']
def clean(self):
cleaned_data = super().clean()
body = cleaned_data.get("body")
title = cleaned_data.get("tags")
if not title in body:
self.add_error(None, "Body must contain title")

Although this is not something that comes with Rails, I think every primary application has found a solution for separating view logic from a template in Rails, and I like TrailBlazer the most.

6- Model Manager vs Model ActiveRecord

This part was the back-breaking part for me to learn in Django and be able to adapt my self. A model in Django has at least one Manager. Let’s make our hands dirty to find out more about it and the differences it has with Rails ActiveRecord.

Here an example of creating a simple model in Rails and Django:

# Django
# app1/leads/models.py
class Lead(models.Model):
name = models.CharField(max_length=255)
credit = models.models.DecimalField()
email = models.EmailField()
phone_number = models.CharField(max_length=255, blank=True)
lead_owner = models.ForeignKey(LeadOwner)
# Rails
# models/lead.rb
class Lead < ApplicationRecord
validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }
belongs_to :lead_owner
# db/migrate/2018..._create_lead.rb
class CreateLead < ActiveRecord::Migration[5.1]
def change
create_table :lead do |t|
t.string :name
f.number_field :credit
t.string :email
t.string :phone_number, null: false
t.references :lead_owner, foreign_key: true
end
end
end

As you can see, because of the manager the model’s fields’ logic is included in the model itself; in contrary, Rails separate this logic in migrations. I like this about Django but what makes it horrible was how migration works here.

Other than lacking the ability of completely and simply rollback in Django, like what I have used to in Rails, other simple things are way more complicated to develop for models in Django, for instance, if we want to run a custom migration in Django we should do as follow:

#Django
from django.db import migrations
def forwards(apps, schema_editor):
if schema_editor.connection.alias != 'default':
return
#Your migration code goes here even SQL <<
class Migration(migrations.Migration):dependencies = [
# Dependencies to other migrations
]
operations = [
migrations.RunPython(forwards),
]

And in Rails, it is easy as three lines of codes:

#Rails
class ExampleMigration < ActiveRecord::Migration
def change
#Your migration code goes here even SQL <<
end

Doing simple tasks like filtering or aggregating are also way easier in ruby

#DjangoLead.objects.last()
Lead.objects.filter(name__startswith="Foo")
Model.objects.all().aggregate(Sum("num_field"))
# RailsLead.last
Lead.where("name LIKE 'Foo%'")
Lead.sum("num_field")

7- Scopes vs Custom Model Manager

We all are familiar with scopes in Rails, how easy and intuitive they are and how useful they could be when being chained together, even though we don’t have chain ability in Django, at least as far as I researched, Rails’ implementation is clean and tiny against what should be done in Django to achieve the almost same thing. This is how they being implemented in each of them:

#Railsscope :with_counts, -> () {
find_by_sql("SOME QUERY")
}
#Djangoclass PollManager(models.Manager):
def with_counts(self):
from django.db import connection
with connection.cursor() as cursor:
cursor.execute("""SOME QUERY""")
result_list = []
for row in cursor.fetchall():
p = self.model(id=row[0], question=row[1], poll_date=row[2])
p.num_responses = row[3]
result_list.append(p)
return result_list
class OpinionPoll(models.Model):
...
objects = PollManager()

8- Interfaces vs abstract class!

Oh, suitable old interfaces, or even Mixin, do you like them? are you finding them useful especially if you have come from languages like Java and C#, here how ruby could handle this in Ruby.

For example, imagine you had this incredibly useful Module:
module Greetings

module Greetings  
def hello
puts "Hello!"
end
def bonjour
puts "Bonjour!"
end
def hola
puts "Hola!"
end
end

To add these methods as instance methods on a class, you would simply do this:

class User  
include Greetings
end

Now you have access to the methods on any instance of that Class:

philip = User.new  
philip.hola
=> Hola!

You can rais an implementation error to make it similar to interfaces in Java or C#, now let’s take a look at the implementation of the same thing but this time with Django:

class Greetings:
def hello(self):
prints("Hello!")
def bonjour(self):
print("Bonjour!")
def hola(self):
print("Hola!")
class User(Greetings):
pass
philip = User()
philip.hola
=> Hola!

As you maybe noticed a class would be multiply inherited, even though we are talking about dynamic languages and these kinds of inheriting could probably be ignored, but I think it is more obscure and can mislead developers.
I have to mention that I am not talking about ABC(abstract class methods), those are different from this concept even in dynamically typed languages like Python and Ruby, in this case, what we want is to share method messages between entities.

9- Tests, VirtualEnv, IDE, And other things

I think with something like Django noes and Factory Boy, and The testing environment was way more similar to what I have experienced in Rails.

I am not going to talk about python environment, but I highly recommend if you are coming from Rails just go for Pipenv.

In case of IDE, I was using vim for Rails but here I feel comfortable with VScode, and one of my friend who also migrated from Rails, Happily using Pycharm right now. (Hey Milad if you read this I talking about you), to be honest I can not recommend anything special here.

Also, i Found Dot.Env also acceptable here; however, settings.py or utils.py or even helpers.py could be useful if you need to separate logic from models or views.

10 — Conclusion

In this essay, I tried to shed light on what was similar and what was different in my experience of moving from Rails to Django, I hope these notes could help people, and by the way, Because I am still learning Django if you find any error in the above examples or even if you know a better solution for them, please tell me, So I could change it.

And my final thoughts about Django is, However, is powerful, well documented and has a vast community but I have to say that I don’t like it because of the cases I have mentioned above, To be honest, I hope someday, in python world, Flask could get Django’s

More from Journal

There are many Black creators doing incredible work in Tech. This collection of resources shines a light on some of us:

--

--