RailsConf 2013

Sleeping with the enemy..

Sleeping with the enemy..

by George Brocklehurst

In the video titled "Sleeping with the enemy," George Brocklehurst presents a comparison between the Ruby on Rails framework and Django, highlighting the strengths and unique features of Django in the context of web development. This session was held at Rails Conf 2013 and focuses on learning from different frameworks to enhance understanding and improve applications.

The key points discussed during the session include:

  • Introduction to Django: George shares his background, detailing his experiences transitioning from Rails to Django and emphasizes that his comparisons aim to identify Django’s strengths without denigrating either framework.

  • Project Structure in Django: An overview of how a Django project is organized is provided, pointing out key components such as project settings, URL routing, and application management.

  • Models and Views: George explains how Django handles models similarly to Rails, but with different syntactic approaches. He highlights the use of classes to structure models and the absence of separate schema definitions.

  • Generic Views in Django: The presentation demonstrates Django’s generic views as a powerful feature that reduces boilerplate code by linking URL routing to the appropriate view classes, which automatically handles common functionality like showing model data.

  • Create Operations and Forms: The discussion moves to form handling in Django, illustrating the creation of forms that manage user inputs, data validation, and model interaction fluidly. He contrasts this with Rails' handling of parameters and the potential complexity involved.

  • Convention Over Configuration: George underscores how Django’s design embodies the principle of convention over configuration, allowing for less code to achieve common functionality. He discusses the template method pattern that Django employs, making it adaptable and easy to extend.

  • Learning from Each Other: The talk encourages developers to explore other frameworks like Django to glean insights and practices that can enhance Rails development. George advocates for cross-pollination among programming communities to encourage better solutions and innovations in web development.

  • Conclusion: The main takeaway is an invitation for Rails developers to experiment with Django and draw lessons that could reshape their understanding and usage of Rails. This exploration can lead to improved coding practices and application design.

This comparison is not just to favor Django but to celebrate the unique solutions different frameworks provide, ultimately suggesting that understanding diverse technologies can lead to personal and community growth in web development.

00:00:16.320 I'll make a start. My name's George, and I work for Thoughtbot. We're a web and mobile consultancy. Most of the web stuff we do is with Rails, but I haven't always worked for Thoughtbot, and I haven't always worked with Rails. I spent more than a year writing Django for a couple of different startups. I found moving from Rails to Django and then back to Rails again to be really fascinating. There were lots of interesting comparisons and differences that came up in that process. That's why I wanted to talk to you all about Django today.
00:00:42.840 Before we start properly, I want to clarify something. I've titled this "Sleeping with the Enemy." Django people aren't really the enemy; they're actually lovely if you get to know them. This talk will be a comparison, focusing on places where I think Django has made better decisions. That's only because this is RailsConf, and that's where we can learn the most. If this were DjangoCon, then I would be discussing where Rails has made better decisions. There's no favoritism here; it's just a comparison and a way we can learn.
00:01:06.000 But if I'm not trying to pick a winner or say which framework is best, then why are we doing this? Well, Rails and Django solve similar problems, but in different ways and different languages. This is probably literally true for this audience. I say tomato, and most of you say tomato. It's interesting seeing those differences in language; the same idea can be expressed in different ways. Similarly, when we look at Rails and Django, we see the same problems. We see all the boring, repetitive, or tricky bits of web development being abstracted for us, but we see it done in different ways.
00:01:36.320 We're going to look at Django, and we'll see unfamiliar solutions to very familiar problems, which hopefully can help us understand those problems better and improve Rails and our own apps. I mentioned in my abstract that I was going to do some live coding, but everyone said, "What? That's a terrible idea!" So I've chickened out on that.
00:01:59.760 All the code you're going to see is from a GitHub repository, which I'll link to at the end. This is a working application. The real reason for not doing the live coding is that there's quite a bit of configuration to do when you first set up a Django app. Watching me type database configuration for 10 minutes really isn't what anyone's here for, so we'll just look at the interesting bits of the code. You can verify that it all works by looking at GitHub later.
00:02:33.440 Now, let's start with the anatomy of a Django project. The project that I have on GitHub is called 'Demo,' and it's a simple blog. First of all, we have a directory called 'demo,' which contains project-level settings. This is where all that database config that I had to type lives. There's a URLs file which is like the routes file in a Rails app, and there's a WSGI file which is like the config.ru file in a Rails app. Then, we have some apps. 'Apps' is where the meat of a Django project lives; this is where the models and the views are organized, but they're grouped together into different apps. This project is just a blog, so it only has one app, called 'blog.' Inside that app, there's a blog post model and any other models we might need.
00:03:05.040 We also have manage.py, which is the command line interface for a Django app. In Rails, you type 'rails server'; in Django, you type 'python manage.py runserver.' It's the same idea. Any rake tasks we would have in Rails or custom extensions would all be done through this. Then we have a requirements file which is the Python equivalent of your gem file; it's just a list of dependencies and their versions. Finally, we have some templates, which are just the chunks of HTML that Django is going to insert things into and use to render our pages.
00:03:41.440 We'll start from the database and work our way up, so let's begin with the models. Here is a blog post. We're declaring a class called 'BlogPost.' The parentheses here in Python mean inheritance, so it's inheriting from models.Model. This is the Django equivalent of inheriting from ActiveRecord in a Rails app. This class is a model tied to a database table. We can save instances to that database table and retrieve them later.
00:04:12.560 Now, something that might be unfamiliar if you've used ActiveRecord, but if you've used DataMapper, you might recognize this pattern: we define the fields that the model has directly in the model class. The schema isn't defined separately; it's defined within this object. The assignments here aren't local variables; these are attributes on the class. For instance, I could access blogPost.title to get the title object. The title field is an instance of models.CharField.
00:04:45.120 In Ruby, we’d typically use CharField.new to create an instance of a character field, but in Python, we just use parentheses to call it as if it’s a function. We pass in a maximum length and define other fields such as body, published, and publish date. However, this is just setting the scene. A website is much more than models; what we really need to do is display blog posts.
00:05:03.680 To make this interesting, we need another concept in Django called a view. Now, a view in Django is nothing like a view in Rails. I'll try to keep these two things separate as I speak. If I talk about Django views, they're much more like Rails actions. Therefore, in Rails, you would have a show action that displays a single blog post. In Django, you would have a detail view that shows the detailed view of a blog post.
00:05:36.000 Views use quite a simple mechanism. Django instantiates a request object and passes it to the view, which is just some kind of callable. It could be a lambda, a function, or a callable object, which is quite a nice feature of Python. The view returns a response that Django gives back to the user. However, such code can get very boring and repetitive. We've all seen show actions in Rails controllers that look the same: something like 'blogPost = BlogPost.find(params[:id])'. It's a very repetitive task.
00:06:15.760 So how does Django abstract away this repetition? Rails has conventional structures, including RESTful resources, which help us avoid writing the same code repeatedly. In contrast, Django provides a lot of help through a mechanism called generic views. A view handles the request and returns a response. Our blog post detail view, which we're declaring here, inherits from DetailView, a generic view that handles everything you might want to do with showing a single instance of a model.
00:06:44.800 We configure it by telling it which model to use. We set an attribute on it to denote the model class, and that's it. This will now work. Yes, there's a line in the URLs file that connects a URL to this view, but when you send a GET request to that URL, you get back a page showing the blog post. Notice all the code that isn’t here. There's no code to pull the ID out of the URL; there's no code to load the blog post from the database; there's no code to assign it to a variable to pass down to the template layer. All of that is handled by the DetailView superclass, abstracting everything into the framework.
00:07:26.000 The first time I saw this, I was a bit concerned; I thought that was too much convention. Convention is nice, but this seemed to take it too far. They’d taken so many details away. All I did was say which model to use, and I assumed this setup would be brittle and hard to change. So let's change something and see what breaks. In our BlogPost model, we have a 'published' field that determines whether the post is published.
00:08:03.440 We might want our detail view to display only published blog posts. The code we need to implement this isn’t as extensive as I expected. I originally thought I'd have to re-implement a lot of this stuff. Instead, we just assign another attribute on our class called 'queryset.' In Django, a queryset is the equivalent of an ActiveRecord relation; it describes a query that you can add filtering to later or ask for results. In this case, the queryset we're using filters blog posts where published equals true.
00:08:42.559 The DetailView will now take this queryset and use it as the basis for its query. It will look inside the queryset for something with the ID we request. While that works, we are doing this at class definition time, meaning it's a bit brittle because this is a static queryset. It can't change based on request properties.
00:09:20.160 Let’s add previews to our blog. If you add '?preview=1' to your URL, you can see a blog post even if it is unpublished. Here's the code for that: we drop our queryset attribute, and instead, we have the 'get_queryset' method. The parameter 'self' is just how Python implements classes; you must include a self parameter in every method. Some people hate it, while others don’t mind it, but it must be included.
00:09:54.560 We pull the current request from 'self' and look at its GET dictionary; that's a hash of all the query parameters. We check if there's something called 'preview'. If it exists, we return all blog posts as a queryset; otherwise, we do the same filtering we had earlier. Now, we’ve transitioned from a typical conventional detail view that shows any blog post based on the ID provided in the URL to one that exhibits dynamic behavior based on the request.
00:10:30.320 However, we are still not loading a blog post here. We’re still not extracting the ID from the URL, and we're not passing this information down to the template. We wanted to alter the queryset, and that is all we had to change. In fact, there are many of these methods that we can override. We've overridden 'get_queryset,' but there's much more we can accomplish behind the scenes.
00:11:05.800 When the user makes a GET request, Django calls our view. Our view has checked the type of request and determined it was a GET request, leading to the call of the ‘get’ method. The 'get' method defines what should happen with this request, but internally that's also calling 'get_template_names' to figure out the name of the template that will be returned. This will give back a template name attribute if defined, or return a sensible default based on conventions.
00:11:35.920 The 'get_slug_field' method is responsible for looking up blog posts by slug instead of by ID. Furthermore, it has 'get_object,' which loads the entire model. This allows us to stop using Django's ORM entirely and just use a plain old Python object or pull something from another source, which can be achieved by overriding this method.
00:12:01.680 The 'get_context_object_name' method is a mouthful, but context consists of the data passed from the view to the template. This method determines the name our blog post should be given within the template. Again, we can either set a static name by assigning an attribute on the class or override this method for a dynamic name, or just return a sensible default.
00:12:30.320 Finally, the 'get_context_data' method collects everything that gets passed to the template. There are several methods being used by 'get' that we can override to alter the algorithm. If you're a fan of 'Gang of Four' design patterns or have read Sandy Metz's book, you may recognize this as the template method pattern, which you can tick off on your bingo card.
00:12:59.760 The template method pattern serves a purpose that acts as a seam between framework code and application code. It allows the framework to define a standard algorithm, which is initiated with a single method call. You invoke the template method, which then calls many smaller operations, while subclasses in application code can easily modify one of those operations. This results in flexible and clean code. The only code you write in a Django view pertains to aspects outside the conventions.
00:13:35.760 This stands in contrast to a Rails action. If you use the scaffold, the generated code contains boilerplate and sections that define the conventions. I have been pleasantly surprised and impressed by how Django has taken the idea of convention over configuration to a new level by providing not just conventional structure but also conventional functionality.
00:14:03.920 However, this functionality is still simple to change with minimal code and remains flexible when moving outside the established patterns of convention. So, we can display blog posts now, but that doesn’t make a complete blog; we still have an empty database and need to create blog posts.
00:14:39.680 This example showcases a common pattern in web applications, where you have an HTML form. The user submits it, which results in a POST request. Some operations occur in the framework and application, ultimately leading to SQL execution where user data is funneled into the database. This process, while commonplace, carries inherent risks such as CSRF vulnerabilities, SQL injection attacks, and type conversion issues since HTML forms primarily handle strings while databases manage various data types.
00:15:15.440 There are potential drawbacks like the strong parameters problem. A user could add an extra field to the form in an attempt to exploit the system. Given the frequency of this occurrence, frameworks offer robust support to address such issues. So, how does Django handle this?
00:15:43.680 First, we create another view. Recall that views roughly correlate to Rails actions; in this case, we are using a create view, which is similar to both the new and create actions in a Rails RESTful controller. If you send a GET request, it shows a form; if you send a POST request, it processes that form. If successful, it redirects you somewhere, while failures yield descriptive error messages.
00:16:12.560 Thus, this action encapsulates both new and create functionalities. Additionally, there's a remarkable level of convention in action here, allowing us to function with minimal code.
00:16:36.800 Behind the scenes, however, there exists an object intermediary between the view and the model. This intermediary is called a Form, and it's possibly my favorite feature of Django. I have great enthusiasm for it! A form comprises a collection of fields, and these fields are aware of the data types they manage and related conversions.
00:17:00.960 For instance, for a date field, when provided with a date input, it converts it into a string that the form can utilize. Conversely, if given a string from the form, it converts it back into a date for use in the model. The widget is responsible for the user interface and manages HTML forms and HTTP concerns.
00:17:27.440 When we send a GET request to our view, the template instructs the form to render itself. The form iterates over its fields and tells each field, 'Render yourself'. Each widget then renders the appropriate HTML. When submitting the form, the data flows back in the opposite direction, working through the widgets and fields before reaching the form.
00:18:13.920 Next, let’s use a custom form in code for clarity. Here's our create view; we need to import the BlogPost form where it's defined. This view should utilize the specified form class to manage our blog post.
00:18:44.640 Defining the form class looks like this; while the top part of the code merely consists of imports, we create a BlogPost form that inherits from forms.ModelForm. Animal_model form has three noteworthy features. Firstly, it can automatically derive its list of fields from the model definition.
00:19:07.440 Secondly, if you provide a model, it can populate its fields with values from that model. Lastly, it has a save method, allowing you to say, 'Save yourself,' which instructs it to write values back to the model instance it’s associated with before the model saves itself to the database.
00:19:39.440 In this form class, we have a Meta class, which we can view as a collection of configuration options. Although there's more complexity involved, the essentials of this example focus on telling it which model to use. We could cease here, allowing the form to derive all its fields from the model.
00:20:10.880 However, we might want to exclude specific fields. This is where Django addresses similar issues as strong parameters and attr_accessible. This filtering mechanism enables us to specify a form that represents a blog post but excludes the published field, ensuring the user can only create drafts, not publish posts.
00:20:39.280 I believe this is a more practical approach to filtering. It doesn't reside within the controller or the model, but instead exists within a third entity. However, as the form object's responsibility encompasses both the creation of the user interface and the data processing, it mitigates the risk of errors.
00:21:11.680 If you unintentionally leave a field within the form, it's visible in the UI, making it easy to catch before a user might sneakily add data. Its purpose ensures that the process of constructing a form naturally leads to the inclusion or exclusion of the appropriate fields.
00:21:44.160 If the form lacks a field, it knows nothing of the model's structure; it merely recognizes its assigned fields. So if the model has a field that the form does not recognize, the form simply ignores it without generating errors.
00:22:12.960 Consequently, our form comprises a selection of fields. The final layer consists of widgets. While we could adhere to the default widgets that accompany the fields, the default date widget isn't particularly effective.
00:22:45.840 It's essentially a text field where users must enter ISO 8601 formatted dates, which can be challenging for users less familiar with such standards. Let's enhance the user experience by employing a select date widget.
00:23:15.840 We specify a dictionary, similar to a hash, indicating that the publish date should utilize an instance of the select date widget. At this stage, we are ready. Our view is now utilizing this custom form. Once we send a GET request, we expect to see a rendering of the blog post creation form.
00:23:44.560 However, it requires some improvements such as CSS, a submit button, and a title. There's still some work ahead, but this is a functioning interface.
00:24:17.680 Upon submission of this form, the process it undergoes is as follows: the HTML form generates an HTTP-encoded version of the fields, comprising data. I'm only displaying the date-related details here to streamline the example, but all other fields would also be included. This data traverses various paths—that may include the internet and web server infrastructure—before reaching Django and the routing processes.
00:25:04.640 Eventually, the data appears in a more useful format, being transformed from strings into a standard dictionary. However, these values currently pertain to the HTML form fields, not the fields in our model, with all types defaulting as strings.
00:25:48.320 Upon reaching our view, it passes the unmodified data to the form and states 'Hey, I got this from the browser; see what you can do with it.' The form will iterate over its fields, and field by field, it will provide the entire dictionary to the widgets associated with those fields.
00:26:37.440 The form isn’t concerned about which values correspond to which widgets; instead, it just states: here's all this data—extract the correct values. As a result, our select date widget will take the appropriate values, aggregate them, and convert them into a nicely formatted ISO 8601 string—the exact format that we want to simplify for the users.
00:27:26.720 After this, each field sends its values back to the form, which compiles them into a dictionary called 'cleaned_data' due to the validity of this data; it’s formatted accurately. This data is valid; it’s now the right types as all necessary conversions have occurred, which prepares it for subsequent usage.
00:28:10.560 Only then does it reach the model layer. Because we're using a model form, this data is allocated to the model, saved, and then forwarded to the database. I find this fascinating. To clarify the advantages, let's compare this to the analogous flow in Rails.
00:28:50.560 In Rails, we'd implement a 'new' action and a 'create' action within our blog post controller. The 'new' action generates the form view, and upon submission, the 'create' action would handle the posted parameters. These parameters embody the essential details we wish to extract; we process them and incorporate them into the corresponding blog post model.
00:29:22.879 This workflow seems quite similar to the Python example. However, it introduces complexities: Rails uses ActiveRecord attribute assignment, which directly assigns a hash to the model fields based on the incoming parameters.
00:29:56.040 If the model receives a hash containing potentially conflicting parameter keys—parenthetical keys indicating that these belong to the same group—it still needs to convert those values accurately before resulting in the targeted data types.
00:30:26.000 The resultant data still requires individual attention from components at both ends of the stack, leading them to communicate effectively by using the strong parameters approach. Unfortunately, this necessitates intricate coordination where modifications made require revisions across multiple classes.
00:30:49.760 Conversely, the Django paradigm emphasizes a clear understanding of individual components. Here, we ensure single responsibility by using distinct roles to manage layers of the application effectively. This modularity allows flexibility and clarity in understanding data flow, enabling you to modify or replace specific parts of the stack without overhauling the entire system.
00:31:20.760 I recount this to illustrate that Django embodies commendable conventions and effectively employs the single responsibility principle in contexts where Rails might not. Both frameworks possess their unique strengths, but Django's strengths inspire ideas that can enhance our work in Rails.
00:31:52.560 This collaboration acknowledges the great work happening within web development, and ongoing discourse between communities serves to enrich our collective understanding. For instance, the date select reliance on the database schema highlighted a limitation in utilizing Django conventions within Active Model.
00:32:34.000 I have initiated a pull request aiming to improve this aspect, inspired by my understanding of Django while working with Rails. It's not about self-promotion; it's about recognizing that various web frameworks and communities offer fruitful opportunities to refine each other's approaches.
00:33:15.440 Therefore, the next time you encounter a DjangoCon talk or consider a side project, explore different frameworks or languages—try something new. At worst, you might reaffirm your commitment to Rails; at best, you'll gain insights that can be brought back to our community—knowledge that could lead to improvement in Rails.
00:33:49.360 I hope this conversation has presented a worthwhile overview of Django and fostered new avenues for knowledge sharing and collaboration. If you have questions, feel free to reach out via email or ask them now; we've got about ten minutes left.