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