A Rails/Django Comparison by Ben Askins and Alan Green This work is licensed under the Creative Commons Attribution-NonCommercialShareAlike 2.5 License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/2.5/
or send a letter to Creative Commons, 543 Howard Street, 5th Floor, San Francisco, California, 94105, USA. The original version of this presentation is available at http://3columns.net/habitual/docs/Pres2.odp
Rails vs. Django
We wrote the same application twice
Reader
Book 1
1 n
n
Reading
n
m
Tag
Development environments different: ● New Mac vs Old Windows laptop ● Central Coast vs Cityrail
This was just a single trial. Really need: ● Multiple developers ● Multiple applications ● Multiple environments “Multiple” means “statistically significant”
Developer skill level: ● Not identical ● Too advanced to be called ‘beginners’ ● Insufficiently advanced to be ‘typical’
LOC and time-to-implement measurements are stupid: ● Not transferable to other developers ● Only rough indicator of complexity
Didn’t consider: ● Performance ● Deployment ● Maintainability ● Enterprise-friendliness Unrepresentative example application: ● Too few pages ● No Atom/RSS feeds ● No public data entry
Development practices: ● Didn’t do it the “best way” ● Older versions of Rails and Django ● Didn’t use this or that helpful third party feature Part-time development – start-stop effect and gaps between sessions
Some Data
Plan
$1 000 000
US$1 000 000
Time to Implement
“This’ll be easy I’ll whip it up in a weekend.”
“You don’t understand the power of the built-in admin application.”
Initial Expectations
Hours to Implement 30.00
25.00 HTML Prototype
20.00
15.00
10.00
5.00
0.00
Rails
Django
Hours to Implement 30.00
25.00 Develop Test data HTML Prototype
20.00
15.00
10.00
5.00
0.00
Rails
Django
Hours to Implement 30.00
25.00 Project set up Develop Test data HTML Prototype
20.00
15.00
10.00
5.00
0.00
Rails
Django
Hours to Implement 30.00
25.00 Models Project set up Develop Test data HTML Prototype
20.00
15.00
10.00
5.00
0.00
Rails
Django
Reader
Book 1
1 n
n
Reading
n
m
Tag
Hours to Implement 30.00
25.00 Models Project set up Develop Test data HTML Prototype
20.00
15.00
10.00
5.00
0.00
Rails
Django
Hours to Implement 30.00
25.00 Home page Models Project set up Develop Test data HTML Prototype
20.00
15.00
10.00
5.00
0.00
Rails
Django
Hours to Implement 30.00
25.00 Basic pages Home page Models Project set up Develop Test data HTML Prototype
20.00
15.00
10.00
5.00
0.00
Rails
Django
Hours to Implement 30.00
25.00 Admin pages Basic pages Home page Models Project set up Develop Test data HTML Prototype
20.00
15.00
10.00
5.00
0.00
Rails
Django
Hours to Implement 30.00
25.00 Amazon interface Admin pages Basic pages Home page Models Project set up Develop Test data HTML Prototype
20.00
15.00
10.00
5.00
0.00
Rails
Django
Hours to Implement 30.00
25.00 Data loading code Amazon interface Admin pages Basic pages Home page Models Project set up Develop Test data HTML Prototype
20.00
15.00
10.00
5.00
0.00
Rails
Django
Hours to Implement 30.00
25.00 Test, tidy Data loading code Amazon interface Admin pages Basic pages Home page Models Project set up Develop Test data HTML Prototype
20.00
15.00
10.00
5.00
0.00
Rails
Django
Hours to Implement 30.00 26:46
25.00
20.00 16:36
15.00
10.00
5.00
0.00
Rails
Django
Test, tidy Data loading code Amazon interface Admin pages Basic pages Home page Models Project set up Develop Test data HTML Prototype
Hours to Implement 30.00 26:46
Admin Pages
25.00
20.00 16:36
15.00
10.00
5.00
0.00
Rails
Django
Test, tidy Data loading code Amazon interface Admin pages Basic pages Home page Models Project set up Develop Test data HTML Prototype
Hours to Implement - without Admin 30.00
25.00
20.00
18:21 15:39
15.00
10.00
5.00
0.00
Rails
Django
Test, tidy Data loading code Amazon interface Basic pages Home page Models Project set up Develop Test data HTML Prototype
Lines of Code
Lines of Code 800 700 600
Model
500 400 300 200 100 0
Rails
Django
Lines of Code 800 700 600
View/Controller Model
500 400 300 200 100 0
Rails
Django
Lines of Code 800 700 600
YAML data loading View/Controller Model
500 400 300 200 100 0
Rails
Django
# Reader test data Ben: id: 1 username: benj72 fullname: Ben Askins bio: Eats books for breakfast Alan: id: 2 username: agreen fullname: Alan Green bio: Fond of snakes Fred: id: 3 username: fred fullname: Fred Wilkins bio: Loves a good romantic thriller
readers.yml
Lines of Code 800 700 600
YAML data loading View/Controller Model
500 400 300 200 100 0
Rails
Django
Lines of Code 800 700 600
Authentication YAML data loading View/Controller Model
500 400 300 200 100 0
Rails
Django
Lines of Code 800 700 600
Schema Migration Authentication YAML data loading View/Controller Model
500 400 300 200 100 0
Rails
Django
Lines of Code 800 700 600
HTML Helpers/ Template tags Schema Migration Authentication YAML data loading View/Controller Model
500 400 300 200 100 0
Rails
Django
Lines of Code 800 700 600
Templates HTML Helpers/ Template tags Schema Migration Authentication YAML data loading View/Controller Model
500 400 300 200 100 0
Rails
Django
●
Hand-coded admin application
●
Concise
●
Quicker
●
Slightly less code
Browser
Browser
Web Server
Web Server
Routes
urls.py
View
Template
Controller
View
Model
Model Rails
Database
Django
Database
Browser
Browser
Web Server
Web Server
Routes
urls.py
View
Template
Controller
View
Model
Model Rails
Database
Django
Database
ActionController::Routing::Routes.draw do |map| map.connect '', :controller => "home"
# Some imports here from hrproj.hr import views urlpatterns = patterns('',
# restful resources map.resources :books do |books| books.resources :readings end
(r'^$', views.index), (r'^readers/$', views.reader_list), (r'^tags/$', views.tag_list), (r'^books/$', views.book_list),
map.resources :readers do |readers| readers.resources :reader_images end
(r'^readers/(?P<username>.*)/$', views.reader_detail), (r'^tags/(?P<slug>.*)/$', views.tag_detail), (r'^books/(?P<slug>.*)/$', views.book_detail),
map.resources :tags end )
/books/hitchhikers-guide-to-the-galaxy
URL Configuration
Browser
Browser
Web Server
Web Server
Routes
urls.py
View
Template
Controller
View
Model
Model Rails
Database
Django
Database
class BooksController < ApplicationController before_filter :find_book def show @reading_paginator, @readings = paginate :readings, :conditions => ["book_id = ?", @book.id] end private def find_book @book = Book.find_by_title(params[:id]) end
def book_detail(request, slug): book = get_object_or_404(Book, slug=slug) queryset = ReadingOccasion.objects \ .filter(book=book) \ .order_by('finished') return standard_view( request, queryset, 'book_detail.html', 'readingoccasion', book=book)
end
Controller / View Function
Browser
Browser
Web Server
Web Server
Routes
urls.py
View
Template
Controller
View
Model
Model Rails
Database
Django
Database
class Reading < ActiveRecord::Base belongs_to :book belongs_to :reader end
class ReadingOccasion(models.Model): reader = models.ForeignKey(Reader) book = models.ForeignKey(Book, edit_inline=models.STACKED, num_in_admin=1) finished = models.DateField( core=True) reading_time = models.FloatField( max_digits=5, decimal_places=2, core=True, blank=True) notes = models.TextField( maxlength=2000, blank=True)
class Reading < ActiveRecord::Base belongs_to :book belongs_to :reader end
class ReadingOccasion(models.Model): reader = models.ForeignKey(Reader) book = models.ForeignKey(Book, edit_inline=models.STACKED, num_in_admin=1) finished = models.DateField( core=True) reading_time = models.FloatField( max_digits=5, decimal_places=2, core=True, blank=True) notes = models.TextField( maxlength=2000, blank=True)
Schema evolution
class CreateReadings < ActiveRecord::Migration def self.up create_table :readings do |t| t.column "book_id", :integer t.column "reader_id", :integer t.column "date_read", :datetime t.column "reading_time", :integer t.column "notes", :text end end def self.down drop_table :readings end end
Schema is Versioned
●
Drop database tables
●
Re-create tables manage.py syncdb python yaml/load_data.py
●
In production, you write migration DDL by hand
Django DB evolution
Browser
Browser
Web Server
Web Server
Routes
urls.py
View
Template
Controller
View
Model
Model Rails
Database
Django
Database
books/show.rhtml: <%= render :partial => 'readings/list', :locals => {:key_field => "Reader"} %> readings/_list.rhtml:
<%= render :partial => 'readings/reading', :collection => @readings, :locals => {:key_field => key_field} %> readings/_reading.rhtml:
<% if key_field == "Book" %> <%= link_to reading.book.title, book_url(reading.book) %> <% else %> <%= link_to reading.reader.fullname, reader_url(reading.reader) %> <% end %> | <%= reading.date_read_for_display %> | <%= reading.reading_time %> | <%= reading.notes %> |
book_detail.html:
{% for ro in readingoccasion_list %} {{ ro.reader.name }} | {{ ro.finished|date:"j M Y" }} | {{ ro.reading_time }} | {% firstof ro.notes "-" %} |
{% endfor %}
View / Template
readers/show.rhtml: <%= render :partial => 'readings/list', :locals => { :key_field => "Book" } %> reader_detail.html:
{% for ro in readingoccasion_list %} {{ ro.book.title }} | {{ ro.finished|date:"j M Y" }} | {{ ro.reading_time }} | {% firstof ro.notes "-" %} |
{% endfor %}
View / Template
And the other bits
●
Can save a lot of time
●
Good looking result
●
Does simple CRUD quite well
but... ●
Only does simple CRUD
●
Only does simple relationships
●
Security not fine-grained
●
Not intended for public-facing pages
Django Admin application
page.visual_effect :fade, dom_id(@tag) page.replace_html “feedback”, “Tag Deleted” page.visual_effect :appear, “feedback”, :queue => :end page.visual_effect :fade, “feedback”, :queue => :end
AJAX
Books on Amazon 11 10 9 8 7 6 5 4 3 2 1 0 Rails Django
Jobs on seek.com.au 120 110 100 90 80 70 60 50 40 30 20 10 0
Ruby
Ruby on Rails
Python
Django
●
Began Oct 2003
●
Began Fall 2003
●
DHH
●
Adrian and Simon
– ●
in reaction to PHP
–
Extracted from Basecamp
●
“ditched” PHP
Extracted from ljworld.com
●
Released: July 2004
●
Released: July 2005
●
1.0 shipped Dec 2005
●
1.0 not yet shipped
–
Latest is 1.1.6
–
Latest is 0.95
History
Conclusion
Already using Rails?
Already using Rails?
Already using Django?
Already using Django?
Already know Ruby?
Already know Ruby?
Already know Python?
Already know Python?
Private admin pages?
Private admin pages?
Simple AJAX?
Simple AJAX?
Non-programming web designers?
Non-programming web designers?
Evolving Schema?
Evolving Schema?
Maturity
Maturity – product, community, and market
Maturity – product, community, and market
Concise or Explict?
Concise
Explicit
Still can’t choose?
Thanks!
●
Photos –
●
Sad puppy: http://www.flickr.com/photos/sookie/108356632/
Software –
David A. Wheeler’s Sloccount ●
–
http://www.dwheeler.com/sloccount/
HTML Template by Andreas Viklund
●
All of the paper reviewers
●
Our bosses: – –
Cirrus Technologies Karen Askins
With thanks to
●
“The Builders of Basecamp” –
●
Snakes and Rubies presentation –
●
http://www.oreillynet.com/pub/a/network/2005/03/10/base
http://video.google.com/videoplay?docid=2939556954580
Django FAQ –
http://www.djangoproject.com/documentation/faq/
Lots of interest in these two frameworks Similar in some ways Different in others How to choose between them?
Why Comparing?
This is some Ruby code
This is some Python code
Bonus Material
def standard_view(request, queryset, template_name, template_object_name, **extra_context): """ Wrapper around the object_list generic view. """ return object_list(request, queryset=queryset, allow_empty=True, template_name=template_name, template_object_name=template_object_name, page=get_page(request), paginate_by=PAGE_SIZE, extra_context=extra_context)
def get_page(request): """ Determines the current page number. """ return int(request.GET.get('page', 1))
standard_view
Hours to Implement 30.00 20:40
25.00
20.00 10:30
15.00
10.00
5.00
0.00
Rails
Django
Test, tidy Data loading code Amazon interface Admin pages Basic pages Home page Models Project set up Develop Test data HTML Prototype