Rails vs. Django

Ein Kampf über acht Runden

Ruby Kafi 44.0 am 4.4.2016

Mathis Hofer, hofer@puzzle.ch

Das Programm

Konzepte

Setup und Struktur

Datenbank & Models

Controllers

Views

Advanced Topics

Deployment/Betrieb

Diverse Themen

Der Ringrichter

2009–2014
Atizo.com: Django

 

seit 2014
Puzzle/Hitobito: Rails

Die Kontrahenten

rubyonrails.org

djangoproject.com

Öffentliches Wiegen

Entstehung

Rails

Django

Herbst 2003
Lawrence Journal-World (Zeitung)

2004?
Basecamp (37signals)

Freie Software seit

Rails

Django

Juli 2005

Juli 2004

Lizenz

Rails

Django

BSD

MIT

Aktuelle Version

Rails

Django

1.9.4

(1.8 LTS bis 4/18, 1.10 8/16)

4.2.6

(5.0 beta)

Lines of Code*

Rails

Django

432k

231k

*Hauptrepo

Commits

Rails

Django

22k

(2100+ letztes Jahr)

57k

(6700+ letztes Jahr)

Github Stars und Forks

Rails

Django

18k/7k

(auf Github seit 2012)

30k/12k

(auf Github seit 2008)

Contributors

Rails

Django

1100+

(490+ letztes Jahr)

3000+

(680+ letztes Jahr)

Core Team

Core Team

Rails

Django

Luke, Russell, James, Justin, Karen, Jannis, Andrew, Carl, Ramiro, Chris, Honza, Tim, Idan, Paul, Julien, Aymeric, Claude, Anssi, Florian, Jeremy, Bryan, Simon, Donald, Marc, Shai, Baptiste, Daniele, Erik, Loïc, Michael, Collin, Tom, Curtis, Markus, Josh, Preston, Tomek, Ola

David, Jeremy, Santiago, Aaron, Xavier, Rafael, Andrew, Guillermo, Carlos, Yves, Godfrey, Matthew

Referenzprojekte

Rails

Django

Pinterest, Instagram, Mozilla, The Washington Times, Disqus, PBS, Bitbucket, OpenStack

Basecamp, GitHub, Shopify, Airbnb, Twitch, SoundCloud, Hulu, Zendesk, Square, Highrise

Runde 1

Konzepte

Philosophie

Rails

Django

Rapid development

Don't repeat yourself (DRY)

Clean, pragmatic design

Batteries included

Secure & scalable

Easier and more fun

Don't repeat yourself (DRY)

Programmer happiness

Everything you need

Progress over stability

 

Unterschied

Rails

Django

Explicit is better than implicit

Convention over Configuration

MVC

Rails

Active Record Model

Action Controller

Action View

MTV

Django

Model

View

Template

Runde 2

Setup und Struktur

Runtime

Rails

Django

Python 2.7, 3.4, 3.5

Ruby

4.x: 1.9.3, 2.0.x

5.0 beta: 2.2.2+

Envs & Deps

Rails

Django

virtualenv, pyenv

PIP

requirements.txt

RVM

RubyGems

Bundler/Gemfile

Installation

Rails

Django

$ pip install django
$ gem install rails

Projekt erstellen

Rails

Django

$ django-admin startproject blog
$ rails new blog

Projektstruktur

Rails

Django

blog/
├── blog
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── manage.py
blog/
├── Gemfile
├── Gemfile.lock
├── README.rdoc
├── Rakefile
├── app
│   ├── assets
│   │   ├── images
│   │   ├── javascripts
│   │   │   └── application.js
│   │   └── stylesheets
│   │       └── application.css
│   ├── controllers
│   │   ├── application_controller.rb
│   │   └── concerns
│   ├── helpers
│   │   └── application_helper.rb
│   ├── mailers
│   ├── models
│   │   └── concerns
│   └── views
│       └── layouts
│           └── application.html.erb
├── bin
│   ├── bundle
│   ├── rails
│   ├── rake
│   ├── setup
│   └── spring
├── config
│   ├── application.rb
│   ├── boot.rb
│   ├── database.yml
│   ├── environment.rb
│   ├── environments
│   │   ├── development.rb
│   │   ├── production.rb
│   │   └── test.rb
│   ├── initializers
│   │   ├── assets.rb
│   │   ├── backtrace_silencers.rb
│   │   ├── cookies_serializer.rb
│   │   ├── filter_parameter_logging.rb
│   │   ├── inflections.rb
│   │   ├── mime_types.rb
│   │   ├── session_store.rb
│   │   └── wrap_parameters.rb
│   ├── locales
│   │   └── en.yml
│   ├── routes.rb
│   └── secrets.yml
├── config.ru
├── db
│   └── seeds.rb
├── lib
│   ├── assets
│   └── tasks
├── log
├── public
│   ├── 404.html
│   ├── 422.html
│   ├── 500.html
│   ├── favicon.ico
├── test
│   ├── controllers
│   ├── fixtures
│   ├── helpers
│   ├── integration
│   ├── mailers
│   ├── models
│   └── test_helper.rb
├── tmp
│   └── cache
│       └── assets
└── vendor
    └── assets
        ├── javascripts
        └── stylesheets

Server & Konsole

Rails

Django

$ ./manage.py runserver

$ ./manage.py shell
$ rails server

$ rails console

Django Apps

Django

Python Package

Enthält Set von Features

Wiederverwendbar

Registriert in settings.py

App erstellen

Django

$ ./manage.py startapp articles
blog/
├── articles
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── ...
└── manage.py

Apps Registrierung

Django

# Application definition

INSTALLED_APPS = [
    'articles.apps.ArticlesConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

Runde 3

Datenbank & Models

Datenbank-adapter

Rails

Django

 PostgreSQL, MySQL, Oracle, SQLite*

MySQL, PostgreSQL, SQLite*

*Default

Datenbank-konfiguration

Rails

Django

DATABASES = {
    'default': {
        'ENGINE': \
          'django.db.backends.sqlite3',
        'NAME': \
          os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}
default: &default
  adapter: sqlite3
  pool: 5
  timeout: 5000

config/database.yml

blog/settings.py

Models

Rails

Django

The single, definitive source of truth about your data

Active Record

Model erstellen

Rails

class Article < ActiveRecord::Base
end

app/models/article.rb:

class CreateArticles < ActiveRecord::Migration
  def change
    create_table :articles do |t|
      t.string :title
      t.text :text

      t.timestamps null: false
    end
  end
end

db/migrate/..._create_articles.rb

Model erstellen

Django

from django.db import models

class Article(models.Model):
    title = models.CharField(max_length=200)
    text = models.TextField()
    created_date = models.DateTimeField(auto_now=True)

articles/models.py:

$ ./manage.py makemigrations articles
Migrations for 'articles':
  0001_initial.py:
    - Create model Article

Migrationen

Rails

Django

$ ./manage.py migrate
$ rake db:migrate

ORM (1)

Rails

Django

from articles.models import Article

a = Article(title='Lorem', text='Ipsum')
a.full_clean() # validation!
a.save()

a2 = Article.objects.create(title='Lorem',
                            text='Ipsum')

a.text += ' dolor sit amet'
a.save()

a.delete()


a = Article.new(title: 'Lorem', text: 'Ipsum')
a.save!


a2 = Article.create!(title: 'Lorem',
                     text: 'Ipsum')

a.text += ' dolor sit amet'
a.save!

a.destroy!

ORM (2)

Rails

Django

from articles.models import Article
from datetime import datetime

Article.objects.all()

Article.objects.get(id=1)

articles = Article.objects.filter(title='Lorem')

articles = articles.exclude(
  created_date__gt=datetime.now())

prefix = 'Ipsu'
articles.filter(text__startswith=prefix)



Article.all

Article.find(1)

articles = Article.where(title: 'Lorem')

articles = articles.where.not('created_at < ?',
                              DateTime.now)

prefix = 'Ipsu'
articles.where('text LIKE ?', "#{prefix}%")

Associations

Django

from django.db import models

class Author(models.Model):
    # ...

class Article(models.Model):
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    # ...
author = Author(name='Bob')
author.save()

article = Article(title='Lorem', text='Ipsum', author=author)
article.save()

article.author.name
author.article_set.count()
author.article_set.first().title

article = author.article_set.create(title='Lorem', text='Ipsum')

articles = Article.objects.all().select_related('author')

Scopes/Model Manager

Rails

Django

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

class Article(models.Model):
    title = ...

    objects = ArticleManager()
class Article < ActiveRecord::Base
  scope :published,
        -> { where(published: true) }
end
Article.objects.published()
Article.published

Callbacks/Signals

Rails

Django

from django.db.models.signals import pre_save
from django.dispatch import receiver
from .models import Article

@receiver(pre_save, sender=Article)
def do_something_before_save(sender, **kwargs):
    # ...
class Article < ActiveRecord::Base
  before_create :do_something_before_save

  private

  def do_something_before_save
    # ...
  end
end

Runde 4

Controllers

View

Django

from django.http import HttpResponse
from .models import Article

def index(request):
    latest_articles = Article.objects.order_by('-created_date')[:5]
    output = ', '.join([a.title for a in latest_articles])
    return HttpResponse(output)

articles/views.py:

from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^$', views.index, name='index'),
    url(r'^(?P<article_id>[0-9]+)/$', views.detail, name='detail')
]

articles/urls.py:

Action Controller

Rails

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end
 
  def show
    @article = Article.find(params[:id])
  end
end

app/controllers/articles_controller.rb:

Rails.application.routes.draw do
  resources :articles
end

config/routes.rb:

Class-based view

Django

from django.http import HttpResponse
from django.views.generic import View

class ArticleView(View):
    def get(self, request):
        # ...
        return HttpResponse('result')

    def post(self, request):
        # ...
        return HttpResponse('result')

articles/views.py:

from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^articles/$', views.ArticleView.as_view())
]

articles/urls.py:

Generic views

Django

from django.views.generic import ListView, DetailView, CreateView
from .models import Articles

class ArticleList(ListView):
    model = Article

class ArticleDetail(DetailView):
    model = Article

class ArticleCreate(CreateView):
    model = Article
    fields = ['title', 'text']

# ...

articles/views.py:

Forms

Django

from django import forms

class ArticleForm(forms.Form):
    title = forms.CharField()
    text = forms.CharField(widget=forms.Textarea)

articles/views.py:

def create(request):
    if request.method == 'POST':
        form = ArticleForm(request.POST)
        if form.is_valid():
            # ...
            return HttpResponseRedirect('/thanks/')
    else:
        form = ArticleForm()

    return render(request, 'article_create.html', {'form': form})

Model Forms

Django

from django.forms import ModelForm
from .models import Article

class ArticleForm(ModelForm):
    class Meta:
        model = Article
        fields = ['title', 'text']

articles/views.py:

Runde 5

Views

View/Template

Rails

Django

Django template language

Custom: Jinja2, Mako

Action View

Asset Pipline: ERB, HAML, Slime, ...

Action View

Rails

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end
end

app/controllers/articles_controller.rb

<h1>Articles</h1>
<ul>
  <% @articles.each do |article| %>
    <li>
      <a href="{% url 'detail' article.id %}">
        <%= link_to article.title, article_path(article) %>
      </a>
    </li>
  <% end %>
</ul>

app/views/articles/index.html.erb:

Rendering

Django

from django.shortcuts import render
from .models import Question

def index(request):
    latest_articles = Article.objects.order_by('-creation_date')[:5]
    context = {'articles': latest_articles}
    return render(request, 'index.html', context)

articles/views.py:

<h1>Articles</h1>
<ul>
{% for article in articles %}
    <li>
      <a href="{% url 'detail' article.id %}">
        {{ article.title }}
      </a>
    </li>
{% endfor %}
</ul>

articles/templates/index.html:

Runde 6

Advanced Topics

Asynchrone Jobs

Rails

Django

Celery mit RabbitMQ oder Reddis

ActiveJob:
Delayed::Job
etc.

Search

Rails

Django

haystack (Solr, Elasticsearch, Whoosh, Xapian)

Thinking-sphinx, Sunspot (Solr), Elasticsearch etc.

WebSockets

Rails

Django

django-socketio, swampdragon (Redis), Tornado etc.

Rack Hijack, Tubesock etc. (< 5)

Action Cable (>= 5)

Caching

Rails

Django

Memory, File, DB, memcached, Custom

Memory, File, memcached, Ehcache

Runde 7

Deployment/Betrieb

Webserver Interface

Rails

Django

WSGI/uWSGI

Rack

nginx

Apache

native uWSGI

+ mod_(u)wsgi
(oder Passenger?)

Gunicorn

 

+ mod_rails
(Passenger)

Unicorn, Puma

Runde 8

Diverse Themen

Admin Interface

Django

from django.contrib import admin
from .models import Article

admin.site.register(Article)

articles/admin.py:

http://127.0.0.1:8000/admin/:

Admin Interface

Django

REST

Django

Django REST framework

# Serializers define the API representation.
class UserSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = User
        fields = ('url', 'username', 'email', 'is_staff')

# ViewSets define the view behavior.
class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

# Routers provide an easy way of automatically determining the URL conf.
router = routers.DefaultRouter()
router.register(r'users', UserViewSet)

Middlewares

Rails

Django

MIDDLEWARE_CLASSES = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
use Rack::Sendfile
use ActionDispatch::Static
use Rack::Lock
use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x000000029a0838>
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use ActionDispatch::RemoteIp
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::Migration::CheckPending
use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use ActionDispatch::ParamsParser
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
run Rails.application.routes

config/application.rb

blog/settings.py

K.O.

https://slides.com/hupf/rails-vs-django/

Made with Slides.com