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 railsProjekt erstellen
Rails
Django
$ django-admin startproject blog$ rails new blogProjektstruktur
Rails
Django
blog/
├── blog
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── manage.pyblog/
├── 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
└── stylesheetsServer & Konsole
Rails
Django
$ ./manage.py runserver
$ ./manage.py shell$ rails server
$ rails consoleDjango Apps
Django
Python Package
Enthält Set von Features
Wiederverwendbar
Registriert in settings.py
App erstellen
Django
$ ./manage.py startapp articlesblog/
├── 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: 5000config/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
endapp/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
enddb/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 ArticleMigrationen
Rails
Django
$ ./manage.py migrate$ rake db:migrateORM (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) }
endArticle.objects.published()Article.publishedCallbacks/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
endRunde 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
endapp/controllers/articles_controller.rb:
Rails.application.routes.draw do
resources :articles
endconfig/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
endapp/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.routesconfig/application.rb
blog/settings.py
K.O.
https://slides.com/hupf/rails-vs-django/
Rails vs. Django
By Mathis Hofer
Rails vs. Django
Ein Kampf über acht Runden
- 1,351