Rails 和 Django 的深度技術對比
我想以一個免責聲明來開始下面的內容。我使用Django開發網站已經有三年了,眾所周知,我喜歡Django。我已經寫了一個開源的應用程序(app),并且我已經將補丁發送到了Django.然而,我以盡可能以公正的態度寫了這篇文章,這篇文章對這個框架有稱贊,也有批評。
6個月以前我在大學用Ruby on Rails做了一個項目而且一直做到現在。我做地第一件事就是仔細地學習了這兩個框架并對它們進行了比較,但是我記得當時我很泄氣的。當我尋找這些問題(比如說:”對于這兩者來說,數據庫的遷移是如何操作的?“、”它們的語法有什么區別?“、”用戶認證是如何做的“)的答案時,我站在一個較高的角度比較了兩者,發現它們大部分是非常膚淺的。下面的評論將會回答這些問題并且比較每個web框架是如何操作模型、控制器、視圖、測試的。
簡要介紹
兩個框架都是為了更快的開發web應用程序和更好的組織代碼這兩個需求應運而生的. 它們都遵循 MVC 原則, 這意味著域(模型層)的建模,應用程序的展現(視圖層)以及用戶交互(控制層)三者之間都是相互分開的. 附帶說明一下, Django 實際上只考慮了讓框架做控制層的工作,因此Django 自己聲稱它是一個模型-模板-視圖(model-template-view)框架. Django 的模板可以被理解為視圖,而視圖則可以看做是MVC典型場景中的控制層. 本文中我將都是用標準的MVC術語.
Ruby on Rails
Ruby on Rails (RoR) 是一個用 Ruby 寫就的web開發框架,并且Ruby“famous”也經常被認為是歸功于它的. Rails 著重強調了約定大于配置和測試這兩個方面. Rails 的約定大于配置(CoC)意味著幾乎沒有配置文件, 只有實現約定好的目錄結構和命名規則. 它的每一處都藏著很多小魔法: 自動引入, 自動向視圖層傳遞控制器實體,一大堆諸如模板名稱這樣的東西都是框架能自動推斷出來的. 這也就意味著開發者只需要去指定應用程序中沒有約定的部分, 結果就是干凈簡短的代碼了.
Django
Django 是一個用 Python 寫成的web開發框架,并以吉他手 Django Reinhardt 命名. Django 出現的動機在于 "產品部密集的最后期限和開發了它的有經驗的Web開發者他們的嚴格要求". Django 遵循的規則是 明確的說明要比深晦的隱喻要好 (這是一條核心的 Python 原則), 結果就是即使對框架不熟悉的人,代碼都是非常具有可讀性的. 項目中的Django是圍繞app組織的. 每一個app都有其自己的模型,控制器,視圖以及測試設置,從而像一個小項目一樣. Django 項目基本上就是一個小app的集合, 每一個app都負責一個特定的子系統.
模型(Model)
讓我們先從看看每個框架怎樣處理MVC原則開始. 模型描述了數據看起來是什么樣子的,并且還包含了業務邏輯.
創建模型
Rails 通過在終端中運行一個命令來創建模型.
rails generate model Product name:string quantity_in_stock:integer category:references
該命令會自動生成一次遷移和一個空的模型文件,看起來像下面這樣:
class Product < ActiveRecord::Baseend</pre>
由于我有Django的技術背景, 令我很生氣的一個事實就是我不能只通過模型文件就了解到一個模型有哪些字段. 我了解到Rails基本上只是將模型文件用于業務邏輯,而把模型長什么樣存到了一個叫做 schemas.rb 的文件中. 這個文件會在每次有遷移運行時被自動更新. 如果我們看看該文件,我們可以看到我們的 Product 模型長什么樣子.
create_table "products", :force => true do |t| t.string "name", t.integer "quantity_in_stock", t.integer "category_id", t.datetime "created_at", :null => false t.datetime "updated_at", :null => false end在這個模型中你可以看到兩個額外的屬性. created_at 和 updated_at 是兩個會被自動添加到Rails中的每個模型中的屬性.
在 Django 中,模型被定義到了一個叫做models.py的文件中. 同樣的 Product 模型看起來也許會像下面這樣
class Product(models.Model): name = models.CharField() quantity_in_stock = models.IntegerField() category = models.ForeignKey('Category') created_at = models.DateTimeField(auto_now_add=True) # set when it's created updated_at = models.DateTimeField(auto_now=True) # set every time it's updated注意,我們必須在Django中明確的(也就是自己手動的)添加 created_at 和 updated_at 屬性. 我們也要通過auto_now_add 和 auto_now 兩個參數告訴 Django 這些屬性的行為是如何定義的.
模型(Model)字段默認值和外鍵
Rails將默認允許字段為空。你可以在上面的例子中看到,我們創建的三個字段都被允許為空。引用字段類別也將既不創建索引,也不創建一個外鍵約束。這意味著引用完整性是無法保證的。Django的字段默認值是完全相反的。沒有字段是被允許為空的,除非明確地設置。Django的ForeignKey將自動創建一個外鍵約束和索引。盡管Rails這里的制定可能是出于性能的擔憂,但我會站在Django這邊,我相信這個制定可以避免(意外)糟糕的設計和意想不到的情況。舉例來說,在我們的項目中以前有一個學生沒有意識到他創建的所有字段都被允許空(null)作為默認值。一段時間后,我們發現我們的一些表包含的數據是毫無意義的,如一個使用null作為標題的輪詢。由于Rails不添加外鍵,在我們的例子中,我們可以刪除一個持續引用其他產品的類別,這些產品將會有無效引用。一種選擇是使用一個第三方應用程序,增加對自動創建外鍵的支持。
遷移(Migrations)
遷移允許數據庫的模式(schema)在創建之后可以再次更改(實際上,在Rails中所有的內容都使用遷移,即使是創建)。我不得不敬佩Rails長期以來支持這個特性。通過使用Rails的生成器(generator)即可完成工作。
$ rails generate migration AddPartNumberToProducts part_number:string
這會向Product模型(model)添加一個名為part_number的新字段(field)。
然而Django只能通過名為South的第三方庫來支持遷移。我感覺South的方式更加簡潔和實用。上面對應的遷移工作可以直接編輯Product模型的定義并添加新的字段
class Product(models.Model): ... # 舊字段 part_number = models.CharField()
然后調用
$ python manage.py schemamigration products --auto
South會自動識別到一個新增字段添加到Product模型并創建遷移文件。隨后會調用下面命令完成同步(synced)
$ python manage.py migrate products
Django最終在它的最新版本(1.7) 將South整合進來并支持遷移。
執行查詢
感謝對象關系映射(object-relation mapping),你不需要在任何框架中寫一行SQL語句。感謝Ruby表達式,你能夠很優雅的寫出范圍搜索查詢(range query)。.
Client.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight)
這會查詢到昨天創建的Clients。Python不支持像1.day這種極其可讀和簡潔的語法,也不支持..范圍操作符。但是,有時在寫Rails時我感覺像是又在寫預聲明(prepared statement)一樣。比如為了選擇所有的某一字段大于某個值的行,你不得不像下面這樣
Model.where('field >= ?', value)
Django完成的方式不是太好,但以我的觀點,卻更加簡介。在Django對應的代碼如下:
Model.objects.filter(field__gt=value)控制器(Controller)
控制器的工作就是利用請求返回準確的應答。網絡應用程序典型工作是支持添加,編輯,刪除和顯示具體的資源,而RoR的便捷表現在使開發控制器的工作簡單而貼心。控制器被拆分為若干個方法(method),每個方法代表指定的動作(action)(show代表請求某個資源,new代表顯示創建資源的表單,create代表從new接收POST的數據并真正的創建資源)。控制器的實例變量(以@為前綴)會自動被傳遞給視圖(view),Rails從方法名稱就會識別應該把哪個模板(template)作為視圖。
class ProductsController < ApplicationController # 自動渲染views/products/show.html.erb def show # params是包含請求變量的ruby hash # 實例變量會自動被傳遞給視圖 @product = Product.find(params[:id]) end# 返回空的product,渲染views/products/new.html.erb def new @product = Product.new end
# 接收用戶提交的POST數據。多數來至于在'new'視圖中表單 def create @product = Product.new(params[:product]) if @product.save redirect_to @product else # 重寫渲染create.html.erb的默認行為 render "new" end end end</pre>
Django使用兩種不同的方式實現控制器。你可以使用一個方法來實現每個動作,與Rails做法非常相似,或者你可以為每個控制器動作創建一個類。 Django沒有區分new和create方法,資源的創建和空資源的創建發生在同一個控制器中。也沒有便捷的方法命名你的視圖。視圖變量需要從控制器顯式的傳遞,而使用的模板文件也需要顯式的設置。
# django通常稱 'show' 方法為'detail'product_id 參數由route傳遞過來
def detail(request, product_id): p = Product.objects.get(pk=product_id) # pk 表示主鍵
# 使用傳遞的第三個參數作為內容渲染detail.html return render(request, 'products/detail.html', {'product': p})
def create(request): # 檢查表單是否提交 if request.method == 'POST': # 類似于RoR的 'create' 動作 form = ProductForm(request.POST) # 綁定于POST數據的表單 if form.is_valid(): # 所有的驗證通過 new_product = form.save() return HttpResponseRedirect(new_product.get_absolute_url()) else: # 類似于RoR的 'new' 動作 form = ProductForm() # 空的表單
return render(request, 'products/create.html', { 'form': form })</pre>
在以上Django的例子中代碼數量與RoR相比很明顯。Django似乎也注意到這個問題,于是利用繼承和mixin開發出了第二種實現控制器的方法。第二種方法稱為基于類的視圖(class-based views) (注意, Django稱這個控制器為view),并且在Django 1.5中引入以提高代碼重用。很多常用的動作都存在可被用來繼承的類,比如對資源的顯示,列表,創建和更新等,這大大簡化了代碼開發。重復的工作比如指定將被使用的視圖文件名稱,獲取對象并向view傳遞該對象等工作也會被自動完成。上面相同的例子使用這種方式只有四行代碼。
# 假設route傳遞了名為 'pk' 的參數,包含對象的 id 并使用該id獲得對象。自動渲染視圖 /products/product_detail.html
并將product作為上下文(context)變量傳遞給該視圖
class ProductDetail(DetailView): model = Product
為給定的模型生成表單。如果得到POST數據
自動驗證表單并創建資源。
自動渲染視圖 /products/product_create.html
并將表單作為上下文變量傳遞給視圖
class ProductCreate(CreateView): model = Product</pre>
當控制器比較簡單時,使用基于類的視圖(class-based views)通常是最好的選擇,因為代碼會變得緊密,具有可讀性。但是,取決于你的控制器的不標準(non-standard)程度,可能會需要重寫很多函數來得到想要的功能。常遇到的情況就是程序員想向視圖傳遞更多的變量,這時可以重寫get_context_data函數來完成。你是不是想按照當前對象(模型實例)的特定的字段來渲染不同的模板?你只好重寫render_to_response函數。你想不想改變獲得對象的方式(默認是使用主鍵字段pk)?你只好重寫get_object。例如,如果我們想要通過產品名稱選擇產品而不是id,也要把類似的產品傳遞給我們的視圖,代碼就有可能像這樣:
class ProductDetail(DetailView): model = Productdef get_object(self, queryset=None): return get_object_or_404(Product, key=self.kwargs.get('name'))
def get_context_data(self, kwargs): # 先調用基類函數獲取上下文 context = super(ProductDetail, self).get_context_data(kwargs)
# 在相關產品(product)中添加 context['related_products'] = self.get_object().related_products return context</pre>
視圖
Rails 視圖使用 內置的Ruby 模板系統,它可以讓你在你的模板里面編寫任意的Ruby代碼. 這就意味著它非常強大和快速, 而非常強大的同時就意味著非常大的責任. 你不得不非常小心的不去把表現層同任何其它類型的邏輯混在一起. 這里我需要再次提到涉及一位學生的例子. 一位新同學加入了我們的RoR項目,并且在學習一項新特性. 代碼審查的時間到了. 我們首先從控制器開始,第一件令我吃驚的事情是他寫的控制器里面代碼非常少. 我轉而很快去看看他寫的視圖,看到了大塊混著HTML的ruby代碼. 誠然,Rails并不會嫌棄缺乏經驗的程序員,但我的觀點是框架可以幫助開發者避免一些壞的實踐. 例如 Django 就有一個非常簡潔的 模板語言. 你可以進行if判斷以及通過for循環進行迭代,但是沒有方法選擇沒有從控制器傳入的對象,因為它并不會執行任意的Python表達式. 這是一個我認為可以敦促開發者方向正確的設計決定. 這能讓我們項目中的新手找到組織他們代碼的正確方式.
資源: CSS, Javascript 以及 圖片
Rails 有一個很不錯的內置 資源管道. Rails 的資源管道具有對JavaScript和CSS文件進行串聯、最小化和壓縮的能力. 不僅僅如此,它也還支持諸如 Coffeescript, Sass 和 ERB 等其它語言. Django 對資源的支持同Rails相比就顯得相形見絀了,它把要麻煩都丟給了開發者去處理. Django 唯一提供的就是所謂的 靜態文件, 這基本上就只是從每個應用程序那里將所有的靜態文件集合到一個位置. 有一個叫做 django_compressor 的第三方app提供了一種類似于Rails的資源管道的解決方案.
表單(Forms)
網絡應用中的表單是用戶輸入(input)的界面。在Rails中的表單包含在視圖中直接使用的幫助方法(method)。
<%= form_tag("/contact", method: "post") do %> <%= label_tag(:subject, "Subject:") %> <%= text_field_tag(:subject) %> <%= label_tag(:message, "Message:") %> <%= text_field_tag(:message) %> <%= label_tag(:subject, "Sender:") %> <%= text_field_tag(:sender) %> <%= label_tag(:subject, "CC myself:") %> <%= check_box_tag(:sender) %> <%= submit_tag("Search") %> <% end %>
像subject,message這樣的輸入字段可以在控制器中通過ruby哈希 (類似字典的結構)params來讀取,比如params[:subject]和params[:message]。Django通過另一種方式抽象了表單概念。表單封裝了字段并包含驗證規則。它們看起來像是模型。
class ContactForm(forms.Form): subject = forms.CharField(max_length=100) message = forms.CharField() sender = forms.EmailField() cc_myself = forms.BooleanField(required=False)
Django會將CharField解析為對應HTML元素的文本輸入框,將BooleanField解析為單選框。你可以按照自己的意愿使用 widget 字段更換為其他輸入元素。Django的表單會在控制器中實例化。
def contact(request): if request.method == 'POST': form = ContactForm(request.POST) if form.is_valid(): return HttpResponseRedirect('/thanks/') # POST之后重定向 else: form = ContactForm() # An unbound formreturn render(request, 'contact.html', { 'form': form })</pre>
Django會自動添加驗證信息。默認情況下所有的字段都是必須的,除非特意定義(比如cc_myself)。使用上面的代碼片段,如果表單驗證失敗,帶有錯誤信息的表單會自動重新顯示,已經輸入的內容也會顯示。下面的代碼在視圖中顯示顯示了一個表單。
<form action="/contact/" method="post"> {{ form.as_p }} <!-- 生成類似于rails的表單 --> <input type="submit" value="Submit" /> </form>URL 和 Route
Route 是將特定的URL匹配到指定控制器的工作。Rails建立REST網絡服務非常輕松,而route以HTTP的行為(verbs)來表達。
get '/products/:id', to: 'products#show'以上的例子顯示,向/products/any_id發起的GET請求會自動route到products控制器和show動作(action)。感謝慣例優先原則(convention-over-configuration),對于建立包含所有動作(create,show,index等等)的控制器的這種常見任務,RoR建立了一種快速聲明所有常用route的方法,叫做resources。如果你依照Rails的慣例(convention)命名了控制器的方法時這會很方便。
# automatically maps GET /products/:id to products#showGET /products to products#index
POST /products to products#create
DELETE /products/:id to products#destroy
etc.
resources :products</pre>
Django不是通過HTTP的行為來決定route。而是使用更復雜的使用正則表達式來匹配URL和對應的控制器。
urlpatterns = patterns('', # 在products控制器中匹配具體方法 url(r'^products/(?P\d+)/$', products.views.DetailView.as_view(), name='detail'), # 匹配index方法就獲得了主頁 url(r'^products/$', products.views.IndexView.as_view(), name='index'), url(r'^products/create/$', products.views.CreateView.as_view(), name='create'), url(r'^products/(?P\d+)/delete/$', products.views.DeleteView.as_view(), name='delete'), )由于使用了正則表達式,框架會自動使用單純的驗證。請求/products/test/會因匹配不到任何route而返回404,因為test不是正確的數字。不同的哲學思想在這里又一次出現。Django在命名控制器動作方面確實方便一些,以至于Django就沒有像Rails的resource那樣方便的助手,而且每個route必須顯式的定義。這將導致每個控制器需要若干個route規則。
測試
在Rails中測試很輕松,與Django比較起來更需要著重強調。
Fixture
兩個框架以相似的方式都支持fixture(示例數據)。我卻給Rails更高的評價,因為它更實用,能從文件的名稱得知你在使用哪個模板。Rails使用YAML格式的fixture,這是人類可讀的數據序列化格式。
# users.yml (Rails當前知道我們在使用user的fixtures) john: name: John Smith birthday: 1989-04-17 profession: Blacksmithbob: name: Bob Costa birthday: 1973-08-10 profession: Surfer</pre>
所有的fixture會自動加載而且在測試中可以作為本地變量來訪問。
users(:john).name # John Smith
Django也支持YAML格式的fixture但是開發人員更傾向于使用JSON格式。
[ { "model": "auth.user", "fields": { "name": "John Smith", "birthday": "1989-04-17", "profession": "Blacksmith", } }, { "model": "auth.user", "fields": { "name": "Bob Costa", "birthday": "1973-08-10", "profession": "Surfer", } } ]這沒什么吸引力,注意它有多啰嗦,因為你必須顯式的定義它屬于哪個模板,然后在fields下面列出每個字段。
測試模板
在單元測試模板時兩種框架的方式基本一致。使用一組不同類型的斷言來進行確定,比如assert_equal,assert_not_equal,assert_nil,assert_raises等等。
class AnimalTest < ActiveSupport::TestCase test "Animals that can speak are correctly identified" do assert_equal animals(:lion).speak(), 'The lion says "roar"' assert_equal animals(:cat).speak(), 'The cat says "meow"' end end類似功能的代碼在Django非常相似。
class AnimalTestCase(TestCase): def test_animals_can_speak(self): """Animals that can speak are correctly identified""" # no way of directly accessing the fixtures, so we have to # manually select the objects lion = Animal.objects.get(name="lion") cat = Animal.objects.get(name="cat") self.assertEqual(lion.speak(), 'The lion says "roar"') self.assertEqual(cat.speak(), 'The cat says "meow"')
測試控制器(controller)
Rails又因為它魅力而更勝一籌。Rails 使用類名稱來決定哪個控制器正在被測試,而測試某個特定動作(action)就像調用http_verb :action_name一樣簡單。我們看一下例子。
class UsersControllerTest < ActionController::TestCase test "should get index" do get :index # 向index 動作發起GET請求 assert_response :success # 請求返回200 # assigns是包含所有實例變量的hash assert_not_nil assigns(:users) end end上面的代碼很容易理解正在發生什么。第一行測試模擬了向User控制器的index動作發起一個請求。第二行隨后檢查請求是否成功(返回代碼200-299)。assigns是一個hash,包含了傳遞到視圖(view)的實例變量。所以第三行檢查是否存在名為users的實例變量并且值不是nil。
也有一些類似于assert_difference這樣方便的斷言幫助方法。
# assert_difference檢查被測試的數字在開始和結束之間是否更改 assert_difference('Post.count') do # 創建post post :create, post: {title: 'Some title'} end在Django中測試控制器可以通過使用一個叫Client類來完成,它扮演著虛擬瀏覽器(dummy web browser)的角色。下面是Django中對應的代碼。
class UsersTest(unittest.TestCase): def setUp(self): self.client = Client()def test_index(self): """ should get index """ response = self.client.get(reverse('users:index')) self.assertEqual(response.status_code, 200) self.assertIsNotNone(response.context['users'])</pre>
首先我們必須在測試設置時初始化Client。test_index的第一行模擬了向Users控制器的index動作申請了一個GET請求。reverse查找對應index動作的URL。注意代碼是如此冗余并且沒有類似于assert_response :success的幫助方法。response.context包含我們傳遞給視圖的變量。
很顯然Rails的magic是相當有幫助的。Rails/Ruby同時也擁有很多第三方app,比如factory_girl,RSpec,Mocha,Cucumber,這使得編寫測試是一種樂趣。
工具和其他特征
依賴性管理(Dependency management)
兩種框架都有出色的依賴性管理工具。Rails使用 Bundler 來讀取Gemfile文件并跟蹤文件中對應ruby應用程序運行所依賴的gem。
gem 'nokogiri' gem 'rails', '3.0.0.beta3' gem 'rack', '>=1.0' gem 'thin', '~>1.1'簡單的在Gemfile文件中添加一個新行即可完成增加依賴( Dependency)。通過簡單調用如下命令即可安裝所有需要的gem:
bundle installDjango強烈推薦使用 virtualenv 來隔離Python環境。 pip 則用來管理python包。單獨的python包的安裝可以通過以下命令完成:
pip install django-debug-toolbar而項目依賴文件可以通過以下集中起來:
pip freeze > requirements.txt管理命令
基本上每個項目完成時都有相同的管理工作要做,比如預編譯文件(precompiling assets),清理記錄(log)等等。Rails使用Rake來管理這些任務。Rake非常靈活而且將開發任務變得簡單,特別是依賴于其他任務的。
desc "吃掉食物。在吃之前需要烹飪(Cooks)和設置表格(table)。" task eat: [:cook, :set_the_table] do # 在吃掉美味的食物之前, :cook和:set_the_table需要做完 # 吃的哪部分代碼可以寫在這里 endRake的任務可以有前提條件(prerequisite)。上面稱為eat的任務,在執行之前必須運行任務cook和任務set_the_table。Rake也支持命名空間(namespace),能將相同的任務結合成組(group)來完成。執行任務只需簡單的調用任務的名稱:
rake eatDjango管理命令就沒那么靈活而且不支持前提條件和命名空間。雖然任務最終也會完成,但不是很出色。
class Command(BaseCommand): help = '吃掉食物'def handle(self, *args, **options): call_command('cook') # 這里是如何在代碼中調用管理命令 set_the_table() # 但子任務需要是常規的python函數 # 這里是吃的那些代碼</pre>
如果我們將上面內容保存到eat.py中,我們可以如下調用它:
python manage.py eat國際化和本地化
在Rails中國際化有些偏弱。在文件夾config/locales,翻譯字符串在文件中作為ruby哈希來定義。
# config/locales/en.yml en: # the language identifier greet_username: "Hello, %{user}!" # translation_key: "value"config/locales/pt.yml
pt: greet_username: "Olá, %{user}!"</pre>
通過函數t進行翻譯。函數第一個變量是決定哪個字符串需要使用的key(比如greet_username)。Rails會自動選擇正確的語言。
t('greet_username', user: "Bill") # Hi, Bill or Olá, Bill
我發現處理本地語言文件中key名字和手動注冊這些內容很繁瑣。Django將其打包進非常便捷的gettext。翻譯也是通過一個幫助函數完成(ugettext),但是這次的key是不需要翻譯的字符串本身。
ugettext('Hi, %(user)s.') % {'user': 'Bill'} # Hi, Bill 或者 Olá, Bill
Django會檢查所有的源代碼并調用以下命令自動收集將要翻譯的字符串:
django-admin.py makemessages -a
上面的命令執行后會為每一種你想要的翻譯的語言生成一個文件。文件內容可能像這樣:
# locale/pt_BR/LC_MESSAGES/django.po msgid "Hi, %(user)s." # key msgstr "Olá, %(user)s" # 值 (翻譯)
注意到我已經在msgstr填充了翻譯內容(那些本來是空的)。一旦翻譯完成,必須對它們進行編譯。
django-admin.py compilemessages
這種本地化項目的方法實際上更實用,因為不需要考慮key的名字,在需要的時候也不需要現查找。
【譯注】即無需自定義key,django會將整句話作為key值代入
用戶授權
不得不說當我知道RoR(Ruby on Rails)沒有打包任何形式的用戶授權時多少有點震驚。我想不出任何不需要授權和用戶管理的項目。在這方面最受歡迎的gem是devise,毫無疑問也是Rails上最受歡迎的,在Github上有Rails一半的得分。
盡管Django從最開始就將授權框架打包進來,但直到一年之前這種授權方式的靈活性才有所改善,就是當版本1.5發布并帶來可配置的用戶模型(user model)。之前,你會被強制要求使用Django的方式定義用戶,而不能任意更改字段或者添加字段(field)。如今這不再是問題,你可以用自己定義的用戶模型代替原有模型
第三方庫
這里沒什么好說的。這篇文章里已經提到了很多二者可使用的第三方庫,而且都擁有太多的app。Django Packages是個非常好的網站,可以用來搜索Django的App。不過還未發現Rails有類似的網站。
社區
雖然我沒有更具體的數據來證明,但我相當確定Rails的社區更大一些。在Github上RoR擁有Django兩倍的得分。在Stackoverflow上標記為Rails的問題也有兩倍之多。而且似乎RoR比Django有更多的工作(在Stackoverflow職業中241對58)。Rails很龐大而且有非常多迷人的資源來供學習,比如Rails Casts和Rails for Zombies。Django擁有Getting Started with Django但是沒有可比性。我知道Django使用The Django Book,但是已經落后若干年了。不要以為我說錯了,盡管有很多Django團體而且如果你遇到問題,你很容易通過google找到答案,但Django就是沒有Rails龐大。
結論
Ruby on Rails和Django在網絡開發方面都是非常出色的框架。在開發模塊化的簡潔的代碼,在減少開發時間。我已經離不開ORM框架,模板引擎和會話管理系統。那么問題是我如何選擇呢?
選擇任何一個都不會錯。我的建議通常就是兩者都使用并選出你最合適的。最終的決定會取決于你傾向于哪種語言或者哪種原則:慣例優先原則(convention-over-configuration,CoC)還是顯式優先隱式原則(explicit is better than implicit)。使用CoC可以自動加載(import),控制器實例會自動傳遞給視圖以及便捷的編寫測試。使用顯式優先隱式,會明確知道代碼在做什么,即使對那些不熟悉框架的人。
從我個人經驗來看我更喜歡Django。我喜歡Python的明確(explicitness),喜歡Django的表單以及此框架更有防御性(有限的模板語言,在model字段中null默認不可用)。但我也知道更多人離開了Rails的魔法和它優秀的測試環境是沒法活的。