Ruby Kafi 44.0 am 4.4.2016
Mathis Hofer, hofer@puzzle.ch
2009–2014
Atizo.com: Django
seit 2014
Puzzle/Hitobito: Rails
rubyonrails.org
djangoproject.com
Rails
Django
Herbst 2003
Lawrence Journal-World (Zeitung)
2004?
Basecamp (37signals)
Rails
Django
Juli 2005
Juli 2004
Rails
Django
BSD
MIT
Rails
Django
1.9.4
(1.8 LTS bis 4/18, 1.10 8/16)
4.2.6
(5.0 beta)
Rails
Django
432k
231k
*Hauptrepo
Rails
Django
22k
(2100+ letztes Jahr)
57k
(6700+ letztes Jahr)
Rails
Django
18k/7k
(auf Github seit 2012)
30k/12k
(auf Github seit 2008)
Rails
Django
1100+
(490+ letztes Jahr)
3000+
(680+ letztes Jahr)
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
Rails
Django
Pinterest, Instagram, Mozilla, The Washington Times, Disqus, PBS, Bitbucket, OpenStack
Basecamp, GitHub, Shopify, Airbnb, Twitch, SoundCloud, Hulu, Zendesk, Square, Highrise
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
Rails
Django
Explicit is better than implicit
Convention over Configuration
Rails
Active Record Model
Action Controller
Action View
Django
Model
View
Template
Rails
Django
Python 2.7, 3.4, 3.5
Ruby
4.x: 1.9.3, 2.0.x
5.0 beta: 2.2.2+
Rails
Django
virtualenv, pyenv
PIP
requirements.txt
RVM
RubyGems
Bundler/Gemfile
Rails
Django
$ pip install django$ gem install railsRails
Django
$ django-admin startproject blog$ rails new blogRails
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
└── stylesheetsRails
Django
$ ./manage.py runserver
$ ./manage.py shell$ rails server
$ rails consoleDjango
Python Package
Enthält Set von Features
Wiederverwendbar
Registriert in settings.py
Django
$ ./manage.py startapp articlesblog/
├── articles
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── ...
└── manage.py
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',
]Rails
Django
PostgreSQL, MySQL, Oracle, SQLite*
MySQL, PostgreSQL, SQLite*
*Default
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
Rails
Django
The single, definitive source of truth about your data
Active Record
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
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 ArticleRails
Django
$ ./manage.py migrate$ rake db:migrateRails
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!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}%")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')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.publishedRails
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
endDjango
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:
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:
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:
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:
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})Django
from django.forms import ModelForm
from .models import Article
class ArticleForm(ModelForm):
class Meta:
model = Article
fields = ['title', 'text']
articles/views.py:
Rails
Django
Django template language
Custom: Jinja2, Mako
Action View
Asset Pipline: ERB, HAML, Slime, ...
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:
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:
Rails
Django
Celery mit RabbitMQ oder Reddis
ActiveJob:
Delayed::Job
etc.
Rails
Django
haystack (Solr, Elasticsearch, Whoosh, Xapian)
Thinking-sphinx, Sunspot (Solr), Elasticsearch etc.
Rails
Django
django-socketio, swampdragon (Redis), Tornado etc.
Rack Hijack, Tubesock etc. (< 5)
Action Cable (>= 5)
Rails
Django
Memory, File, DB, memcached, Custom
Memory, File, memcached, Ehcache
Rails
Django
WSGI/uWSGI
Rack
nginx
Apache
native uWSGI
+ mod_(u)wsgi
(oder Passenger?)
Gunicorn
+ mod_rails
(Passenger)
Unicorn, Puma
Django
from django.contrib import admin
from .models import Article
admin.site.register(Article)articles/admin.py:
http://127.0.0.1:8000/admin/:
Django
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)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
https://slides.com/hupf/rails-vs-django/