I’d like to start with a disclaimer. I have been developing websites using Django for 3 years now and it’s no secret that I like Django. I wrote an open-source app for it and I have started sending patches to Django. I have however written this article to be as unbiased as possible and there’s plenty of compliments (and criticism) to both frameworks.
Six months ago I joined a project at my University using Ruby on Rails and have been working with it since then. The first thing I did was to look for for reviews and comparisons between the two frameworks and I remember being frustrated. Most of them were a little shallow and compared them on a higher level while I was looking for answers to questions like ‘how are database migrations handled by both?’, ‘what are the differences on the template syntax?’, ‘how is user authentication done?’. The following review will answer these questions and compare how the model, controller, view and testing is handled by each web framework.
A SHORT INTRODUCTION
Both frameworks were born out of the need of developing web applications faster and organizing the code better. They follow the MVC principle, which means the modelling of the domain (model), the presentation of the application data (view) and the user’s interaction (controller) are all separated from each other. As a side note, Django actually considers the framework to be the controller, so Django addresses itself as a model-template-view framework. Django’s template can be understood as the view and the view as the controller of the typical MVC scheme. I’ll be using the standard MVC nomenclature on this post.
Ruby on Rails
Ruby on Rails (RoR) is a web framework written in Ruby and is frequently credited with making Ruby “famous”. Rails puts strong emphasis on convention-over-configuration and testing. Rails CoC means almost no config files, a predefined directory structure and following naming conventions. There’s plenty of magic everywhere: automatic imports, automatically passing controller instance variables to the view, a bunch of things such as template names are inferred automatically and much more. This means a developer only needs to specify unconventional aspects of the application, resulting in cleaner and shorter code.
Django is a web framework written in Python and was named after the guitarrist Django Reinhardt. Django’s motivation lies in the “intensive deadlines of a newsroom and the stringent requirements of the experienced Web developers who wrote it”. Django follows explicit is better than implicit (a core Python principle), resulting in code that is very readable even for people that are not familiar with the framework. A project in Django is organized around apps. Each app has its own models, controllers, views and tests and feels like a small project. Django projects are basically a collection of apps, with each app being responsible for a particular subsystem.
Let’s start by looking how each framework handles the MVC principle. The model describes how the data looks like and contains the business logic.
Rails creates models by running a command in terminal.
The command above will automatically generate a migration and an empty model file, which looks like this:
Coming from a Django background, one thing that annoyed me was the fact that I couldn’t know which fields a model has just by looking into its model file. What I learned was that Rails uses the model files basically only for business logic and stores how all models looks like in a file called
schemas.rb. This file is automatically updated every time a migration is ran. If we take a look at this file we can see how our
Product model looks like.
Notice how there are two extra fields in the model.
updated_at are fields that are added to every model in Rails and will be set automatically.
In Django models are defined in a file called
models.py. The same
Product model would look like this
Notice that we had to explicitly add
updated_at in Django. We also had to tell Django how these fields behave through the parameters
Model field defaults and foreign keys
Rails will per default allow fields to be null. You can see on the example above that the three fields we created allowed null. The reference field to a
Category will also neither create an index nor a foreign key constraint. This means referential integrity is not guaranteed. Django’s default is the exact opposite. No field is allowed to be null unless explicitly set so. Django’s
ForeignKey will also create a foreign key constraint and an index automatically. Although Rails decision here may be motivated by performance concerns, I’d side with Django here as I believe this decision avoids (accidental) bad design and unexpected situations. For example, a previous student in our project wasn’t aware that all fields he had created allowed
null per default. After a while we noticed that some of our tables contained data that made no sense, such as a poll with
null as the title. Since Rails doesn’t add FKs, following our example we could have for example deleted a category that is still referenced by other products and these products would then have invalid references. An option is to use a third-party app that adds support for automatic creation of foreign keys.
Migrations allow the database schema to be changed after it has already been created (actually, in Rails everything is a migration — even creating). I have to give props to Rails for supporting this out-of-the-box for a long time. This is done using Rails’ generator
This would add a new field called
part_number to the
Django only supports migrations by using an third-party library called South, however, I find South’s approach to be both somewhat cleaner and more practical. The equivalent migration above could be done by directly editing the
Product model definition and adding a new field
and then calling
South will automatically recognize that a new field was added to the
Product model and create a migration file. It can then be synced by calling
Django is finally going to support migrations in its newest version (1.7) by integrating South into it.
Thanks to object-relation mapping, you will not have to write a single SQL line in either framework. Thanks to Ruby’s expressiveness you can actually write range queries quite nicely.
This would find all
Clients created yesterday. Python doesn’t support syntax like
1.day, which is extremely readable and succinct, neither the
.. range operator. However, sometimes in Rails I feel like I’m writing prepared statements again. To select all rows where a particular field is greater than a value, you have to write
Django’s way of doing this is not much better, but in my opinion, more elegant. The equivalent line in Django is looks like this:
Controllers have the task of making sense of a request and returning an appropriate response. Web applications typically support adding, editing, deleting and showing details of a resource and RoR’s conventions really shine here by making the development of controllers short and sweet. Controllers are divided into methods, each representing one action (
show for fetching the details of a resource,
new for showing the form to create a resource,
create for receiving the
POST data from
new and really creating the resource, etc.) The controllers’ instance variables (prefixed with
@) are automatically passed to the view and Rails knows from the method name which template file to use as a view.
Django has two different ways of implementing controllers. You can use a method to represent each action, very similar to how Rails’ does it, or you can create a class for each controller action. Django however doesn’t have separate
create methods, the creation of a resource happens in the same controller where an empty resource is created. There is also no convention on how to name your views. View variables need to be passed explicitly from the controller and the template file to be used needs to be set as well.
The amount of boilerplate in this example in Django is obvious when compared to RoR. Django seems to have noticed this and developed a second way of implementing controllers by harnessing inheritance and mixins. This second method is called class-based views (remember, Django calls the controller, view) and was introduced in Django 1.5 for promoting code reuse. Many commons actions such as displaying, listing, creating and updating a resource already have a class from which you can inherit from, greatly simplifying the code. Repetitive tasks such as specifying the view filename to be used, fetching the object and passing the object to the view are done automatically. The same example above would only be 4 lines using this pattern.
When the controllers are simple, using class-based views is usually the best choice, as the code generally ends up very compact and readable. However, depending on how non-standard your controllers are, many functions may need to be overridden to achieve the desired functionality. A common case is when the programmer wants to pass in additional variables to the view, this is done by overriding the function
get_context_data. Do you want to render a different template depending on a particular field of the current object (model instance)? You’ll have to override
render_to_response. Do you want to change how the object will be fetched (default is using the primary key field
pk)? You’ll have to override
get_object. For example, if we wanted to select a particular product by its name instead of id and to also pass to our view which products are similar to it, the code would look like this:
Rails views uses the Embedded Ruby template system which allows you to write arbitrary Ruby code inside your templates. This means it is extremely powerful and fast, but with great power comes great responsibility. You have to be very careful to not mix your presentation layer with any other kind of logic. I have another example involving fellow students again. A new student had joined our RoR Project and was working on a new feature. It was time for code review. We started at the controller and the first thing that struck me was how empty his controller looked like. I immediately looked at his views and saw massive ruby blocks intermixed with HTML. Yes, Rails it not to blame for a programmers lack of experience, but my point is that frameworks can protect the developer from some bad practices. Django for instance has a very frugaltemplate language. You can do
ifs and iterate through data using
for loops, but there’s no way to select objects that were not passed in from the controller as it does not execute arbitrary Python expressions. This is a design decision that I strongly believe that pushes developers into the right direction. This would have forced the new student in our project to find the correct way of organizing his code.
Forms in web applications are the interface through which users give input. Forms in Rails consist of helper methods that are used directly in the views.
The input fields
message, etc. can than be read at a controller through the ruby hash (a dictionary-like structure)
params, for example
params[:message]. Django on the other hand abstracted the concept of forms. Forms are normal classes that encapsulate fields and can contain validation rules. They look a lot like models.
Django knows that the
CharField counterpart in html are text input boxes and that
BooleanFields are checkboxes. If you wish, you can change which input element will be used through the widget field. Forms in Django are instantiated in the controller.
Django will throw in validation for free. By default all fields are required, except when defined otherwise (such as
cc_myself). Using the snippet above, if the form fails the validation, the same form will be automatically displayed with an error message and the input given is shown again. The code below displays a form in a view.
URLS AND ROUTING
Routing is the task of matching a particular URL to a controller. Rails makes building REST web services a breeze and routes are expressed in terms of HTTP verbs.
In the example above a
GET request to
/products/any_id will be automatically routed to the controller
products and the action
show. Since it’s such a common task to have all actions (create, show, index, etc) in a controller and thanks to convention-over-configuration, RoR created a way of quickly declaring all common routes, called
resources. If you followed Rails’ conventions when naming your controller methods this is very handy.
Django does not use the HTTP verb to route. Instead it is more verbose and uses regular expressions to match URLs to controllers.
Since regular expressions are used, simple validation is automatically built in. Requesting
/products/test/will not match any routing rule and a 404 will be raised, as
test is not a valid integer. The difference in philosophy can be seen here again. Django does have any convention when naming controller actions, so Django does not have any cool helpers like Rails’
resource and every route has to be explicitly defined. This results in each controller requiring several routing rules.
Testing is just a breeze in Rails and there’s a much stronger emphasis on it in Rails than in Django.
Both frameworks support fixtures (a fancy word for sample data) in a very similar way. I’d however give Rails a bonus point for making it more practical writing them as it infers from the file name for which model you’re writing them. Rails uses YAML-formatted fixtures, which is a human-readable data serialization format.
All fixtures are automatically loaded and can be accessed directly as local variables on test cases
Django also supports YAML fixtures but developers tend to use JSON-formatted ones.