From Django to Rails

A Journey from Explict to Implicit

Rebecca Meritz / @rmeritz

Overview

  • Same thing but with different vocab
  • Gain perspective by looking at the differences
  • Examining them out of the box

Structure

                              
├── urls.py
│   ├── jobs
│   │   ├── models.py
│   │   ├── views.py
│   │   ├── forms.py
│   │   └── urls.py
│   ├── applicatants
│   │   ├── models.py
│   │   ├── views.py
│   │   ├── forms.py
│   │   └── urls.py
│   ├── templates
│   │   ├── index.html
│   │   └── create_jobs.html
                              
                            
                              
├── app
│   ├── controllers
│   │   ├── application_controller.rb
│   │   ├── concerns
│   │   └── jobs_controller.rb
│   ├── helpers
│   │   └── application_helper.rb
│   ├── models
│   │   ├── concerns
│   │   └── job.rb
│   └── views
│       ├── jobs
│       │   ├── edit.html.erb
│       │   ├── _form.html.erb
│       │   ├── index.html.erb
│       │   ├── new.html.erb
│       │   └── show.html.erb
│       ├── layouts
│       │   └── application.html.erb
                              
                            

Reflections

  • Rails is simpler
  • Django has smaller conceptal units
  • Django solves the lib problem
  • Django can have giant models files

Templates / Views

                              
<form action="/jobs" method="post">

  <label for="job_name">Name</label>
  <input type="text" name="job[name]" id="job_name" />

  <label for="job_description">Description</label>
  <textarea name="job[description]" id="job_description">

  <input type="submit" value="Create Job" class="button" />
</form>
                              
                            

templates/base/form.html

                              
{% csrf_token %} {{ form.non_field_errors }}
{{ form.as_p }}

app/views/jobs/_form.html.erb

                              
<%= form_for(@job) do |f| %>
  <%= f.error_notification %>

  
<%= f.text_field :name %> <%= f.text_area :description %> <%= f.text_field :company_name %> <%= f.text_field :url %> <%= f.date_field :start_date %>
<%= f.submit %>
<% end %>

Reflections

  • Rails is easier for a designer to change
  • Django autodiscovers sensible form widgets
  • Django hides FE logic deep within the backend

URLconf / Routes

urls.py

                              
from django.conf.urls import include, patterns


urlpatterns = patterns(
    '',
    (r'^jobs/', include("skill_and_love.job.urls")),
)
                              
                            

job/urls.py

                              
from django.conf.urls import url, patterns

from .views import CreateJobView


urlpatterns = patterns(
    '',
    url(r'^create/$', CreateJobView.as_view(), name='job_create'),
)
                              
                            

config/routes.rb

                              
Rails.application.routes.draw do
  resources :jobs, only: :create
end
                              
                            

Reflections

  • Django is explicit
  • Django is unopinionated about app structure
  • Rails is RESTful

Views / Controllers

job/views.py

                              
from django.views.generic import CreateView

from .models import Job
from .forms import CreateJobForm


class JobCreateView(CreateView):
    model = Job
    form_class = CreateJobForm
                              
                            

CreateView

                              
def post(self, request, *args, **kwargs):
    form_class = self.get_form_class()
    form = self.get_form(form_class)
    if form.is_valid():
        return self.form_valid(form)
    else:
        return self.form_invalid(form)
                              
                            

app/controllers/jobs_controller.rb

                              
class JobsController < ApplicationController
  def create
    @job = Job.new(job_params)

    if @job.save
      redirect_to @job, notice: 'Job was successfully created.'
    else
      render :new
    end
  end
end
                              
                            

Reflections

  • Django's views encapsulate common patterns
  • Django's views encourage code sharing
  • Django uses the template pattern
  • The template pattern can be confusing
  • Rails controllers are very imperative

Models

Migrations

job/models.py

                              
from django.db import models


class Job(models.Model):
    name = models.CharField(max_length=100, null=False)
    published = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
                              
                            
                              
$ python manage.py makemigrations jobs
                              
                            
                              
$ bin/rails generate migration CreateJobs
                              
                            

db/migrate/20150223155659_create_jobs.rb

                              
class CreateJobs < ActiveRecord::Migration
  def change
    create_table :jobs do |t|
      t.text :name, null: false
      t.boolean :published, null: false
      t.timestamps null: false
    end
  end
end
                              
                            

db/schema.rb

                              
create_table "jobs", force: :cascade do |t|
  t.text     "name",         null: false
  t.boolean  "published",    null: false
  t.datetime "created_at",   null: false
  t.datetime "updated_at",   null: false
end
                              
                            

Reflections

  • Rails' schema is centralized
  • Django's migrations are an imperfect solution to migration conflicts

Managers / ARel

job/models.py

                              
from django.db import models

from .managers import JobsManager


class Job(models.Model):
    objects = JobsManager()

                              
                            

job/managers.py

                              
from django.db.models import Manager


class JobManager(Manager):
    def published(self):
        return self.filter(published=True)
                              
                            
                              

>>> Job.objects.get(id=2)
>>> Job.objects.all()
>>> Job.objects.published()
                              
                            

app/models/job.rb

                              
class Job < ActiveRecord::Base
  scope :published { where(published: true) }
end

                              
                            
                              
>>> Job.find(2)
>>> Job.all
>>> Job.published
                              
                            

Reflections

  • Rail's scopes clutter the model
  • Django's Managers allow the models to have a cleaner API

Philosophical Differences

Django's Upsides

  • Coding Standards
  • Culture of Documentation
  • Explicit Imports

Rail's Upsides

  • Logging
  • Packaging
  • Functional Programming Support
  • Readablity

@rmeritz

rebecca.meritz.com/pycon-sweden-2015

May 12-13, 2015