Rails 和 Django 的深度技术对比 已翻译 100%

oschina 投递于 2015/01/05 11:46 (共 23 段, 翻译完成于 01-18)
阅读 11185
收藏 118
6
加载中

我想以一个免责声明来开始下面的内容。我使用Django开发网站已经有三年了,众所周知,我喜欢Django。我已经写了一个开源的应用程序(app),并且我已经将补丁发送到了Django.然而,我以尽可能以公正的态度写了这篇文章,这篇文章对这个框架有称赞,也有批评。

6个月以前我在大学用Ruby on Rails做了一个项目而且一直做到现在。我做地第一件事就是仔细地学习了这两个框架并对它们进行了比较,但是我记得当时我很泄气的。当我寻找这些问题(比如说:”对于这两者来说,数据库的迁移是如何操作的?“、”它们的语法有什么区别?“、”用户认证是如何做的“)的答案时,我站在一个较高的角度比较了两者,发现它们大部分是非常肤浅的。下面的评论将会回答这些问题并且比较每个web框架是如何操作模型、控制器、视图、测试的。

c
chxm1990
翻译于 2015/01/05 15:15
5

简要介绍

两个框架都是为了更快的开发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都负责一个特定的子系统.

LeoXu
LeoXu
翻译于 2015/01/11 13:24
2

模型(Model)

让我们先从看看每个框架怎样处理MVC原则开始. 模型描述了数据看起来是什么样子的,并且还包含了业务逻辑.

创建模型

Rails 通过在终端中运行一个命令来创建模型.

rails generate model Product name:string quantity_in_stock:integer 
                             category:references

该命令会自动生成一次迁移和一个空的模型文件,看起来像下面这样:

class Product < ActiveRecord::Base

end

由于我有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 这些属性的行为是如何定义的.

LeoXu
LeoXu
翻译于 2015/01/11 13:59
1

模型(Model)字段默认值和外键

Rails将默认允许字段为空。你可以在上面的例子中看到,我们创建的三个字段都被允许为空。引用字段类别也将既不创建索引,也不创建一个外键约束。这意味着引用完整性是无法保证的。Django的字段默认值是完全相反的。没有字段是被允许为空的,除非明确地设置。Django的ForeignKey将自动创建一个外键约束和索引。尽管Rails这里的制定可能是出于性能的担忧,但我会站在Django这边,我相信这个制定可以避免(意外)糟糕的设计和意想不到的情况。举例来说,在我们的项目中以前有一个学生没有意识到他创建的所有字段都被允许空(null)作为默认值。一段时间后,我们发现我们的一些表包含的数据是毫无意义的,如一个使用null作为标题的轮询。由于Rails不添加外键,在我们的例子中,我们可以删除一个持续引用其他产品的类别,这些产品将会有无效引用。一种选择是使用一个第三方应用程序,增加对自动创建外键的支持。

Luchiao
Luchiao
翻译于 2015/01/16 11:13
1

迁移(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整合进来并支持迁移。

Ev4n
Ev4n
翻译于 2015/01/15 10:02
1

执行查询

感谢对象关系映射(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)
Ev4n
Ev4n
翻译于 2015/01/15 10:27
1

控制器(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
Ev4n
Ev4n
翻译于 2015/01/15 13:33
1

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 })
Ev4n
Ev4n
翻译于 2015/01/15 14:00
2

在以上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
Ev4n
Ev4n
翻译于 2015/01/15 14:45
2

当控制器比较简单时,使用基于类的视图(class-based views)通常是最好的选择,因为代码会变得紧密,具有可读性。但是,取决于你的控制器的不标准(non-standard)程度,可能会需要重写很多函数来得到想要的功能。常遇到的情况就是程序员想向视图传递更多的变量,这时可以重写get_context_data函数来完成。你是不是想按照当前对象(模型实例)的特定的字段来渲染不同的模板?你只好重写render_to_response函数。你想不想改变获得对象的方式(默认是使用主键字段pk)?你只好重写get_object。例如,如果我们想要通过产品名称选择产品而不是id,也要把类似的产品传递给我们的视图,代码就有可能像这样:

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):
        # 先调用基类函数获取上下文
        context = super(ProductDetail, self).get_context_data(**kwargs)

        # 在相关产品(product)中添加
        context['related_products'] = self.get_object().related_products
        return context
Ev4n
Ev4n
翻译于 2015/01/15 16:39
2
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(30)

wyfspring
wyfspring
嗯 做的不错。
izuo
izuo

引用来自“冰力”的评论

Rails 的性能太差,跟 Python 没得比。

引用来自“汪岩”的评论

还真别纠结性能的问题,只有垃圾的设计,没有不满足需求的速度

引用来自“冰力”的评论

恩,国内团队还是老老实实用PHP,性能比 Ruby 强,会的人还多。
说Ruby性能差的是不是都还在用1.8早就是2.x时代了。
jstech
jstech
只是喜欢它,所以我用它,
beetle
beetle
任何一门语言或者框架,都会有版本兼容性问题的,好吧。不可能在设计之初就能考虑的那么全面,很多问题是无法预料的。所以作为程序员只有不断的学习和适应。
无所谓了
无所谓了

引用来自“上帝爱开源”的评论

flask
我也再用flask,Django限制太大了,宁愿自己多写些代码
冰力
冰力

引用来自“冰力”的评论

Rails 的性能太差,跟 Python 没得比。

引用来自“汪岩”的评论

还真别纠结性能的问题,只有垃圾的设计,没有不满足需求的速度
恩,国内团队还是老老实实用PHP,性能比 Ruby 强,会的人还多。
冰力
冰力

引用来自“冰力”的评论

Rails 的性能太差,跟 Python 没得比。

引用来自“LeeRoBeRt”的评论

呵呵 用语言和框架比
flask比rails强几倍了?懂的自然懂,语言是天生缺陷。
Klaus88
Klaus88

引用来自“上帝爱开源”的评论

flask
同意
光的交响乐
光的交响乐
end.....end....end
LeeRoBeRt
LeeRoBeRt

引用来自“冰力”的评论

Rails 的性能太差,跟 Python 没得比。
呵呵 用语言和框架比
返回顶部
顶部