论坛首页 编程语言技术论坛

migrations不止可以更性感,还可以更感性,甚至更个性

浏览 17099 次
该帖已经被评为精华帖
作者 正文
   发表时间:2007-09-21  
凡是有过RoR经验的同志必定会写过如下类似数据表定义:
class UpdateYourFamily < ActiveRecord::Migration
  create_table :updates do |t|
    t.column :user_id,  :integer
    t.column :group_id, :integer
    t.column :body,     :text
    t.column :type,     :string

    t.column :created_at, :datetime
    t.column :updated_at, :datetime 
  end

  def self.down
    drop_table :updates
  end
end


这里面的代码有问题吗?
没问题吗?
有问题吗?
真的没问题吗?
不用瞪大眼找了,没有问题!
只是,不觉得有点...啰!嗦!吗?
在create_table里定义的不就是字段(column)相关的东西吗?难道还会在这里点菜什么的?“伙计,来份宫爆鸡丁,加辣!”。
说到点菜,在麦当劳,你会这样点菜吗:
“我要 一份薯条”
“我要 一个汉堡”
“我要 一杯可乐”
...
就不能一次把话说直吗:“我要 一份薯条、一个汉堡、一杯可乐...”

以上跟Rails的migrations有什么关系?
OK,有了足够的铺垫,现在来感受一下以下代码:

class UpdateYourFamily < ActiveRecord::Migration
  create_table :updates do
    foreign_key :user
    foreign_key :group

    text   :body
    string :type

    timestamps!
  end

  def self.down
    drop_table :updates
  end
end


wow,清!爽!
实现者Chris同学称之为sexy,我猜可能less is sexy,大概是女生穿得比较清凉就被觉得性感...吗?

但与Chris在同一屋檐(blog)下的PJ同学看到后却不服输,声称“性感的没有我聪明,聪明的没有我感性”。

何出此言呢?

当我们将在db/migrate下的一堆01xx、02xx...加入到svn时,总会主动忽略某物,不错,就是schema.rb。看到一个表的定义支零破碎的分散在各个migration中时,总有种凌乱、迷失的感觉,幸好有schema.rb,能还原出每个表定义的真面目,这又正是不得不把它从svn中排除出去的原因。

PJ同学正式看中了这个具有特殊身份地位的文件,赋予了她新的能力:

第一次让她打扮成这样:
ActiveRecord::Schema.define(:version => 1) do
  create_table :posts do |t|
    t.string  :title
    t.text    :body
  end
end


emm~sexy!

然后再让她补下妆:
ActiveRecord::Schema.define(:version => 1) do
  create_table :posts do |t|
    t.string  :title
    t.text    :body
    t.integer :published
  end

  create_table :comments do |t|
    t.string  :name, :url
    t.text    :body
    t.integer :post_id
  end
end


still perty.

然后让她喊一句:希瑞~ 请赐予我力量吧!
翻译成Rails语就是:rake db:auto:migrate
她很聪明的执行了以下操作:
-- add_column("posts", :published, :integer)
   -> 0.0096s
-- create_table(:comments)
   -> 0.0072s

再来一次 rake db:auto:migrate 呢?别担心,什么都没有发生,她知道该怎么处理。

oh~gorgeous!

看吧,以后schema.rb不再是只能用来收拾残局的了。

背后的原理当然就是比较前后定义,无则删之,新则添之。

当然,家有小女初长成,不足的地方还是有的,她现在还不会rename_column这招,不过已经有人建议用 :as =>ld_name 这种连读得懂本文的人都会明白的句式来改进。

aha,还有个小问题,如果要招揽这位既sexy又smart的migrator,必须是在edge Rails和启用 active_resource。

哈哈,这时,sexy migration的始祖、Hobo的主持Tom同学显身说法了,他认为:
毫无疑问
我的migration
是全天下
最DRY的

没错,当初Chris就是瞥到Tom家的Hobo出浴时惊世骇俗的migration,由此据模画样的而产生了独立于Hobo外的sexy migration,而PJ又在Chris的基础上制造出了更smart的migrator,他们就是如此清晰的三角链关系。

Tom见自家的Hobo散发出惊人魅力后并没有自满停步,而是不断思考、套索migration存在的真谛。他想啊,想啊想啊,最后他想通了一个方面:字段定义。

不管是Chris、PJ还是DHH,思想都停留在把表字段的定义放在migrations当中,但其实字段都是与表关联,而表都是为了ActiveRecord model服务,字段定义的最终归宿都是回到model里成为其attributes,那干脆直接在model里定义字段,在migrate时再抽出相关的定义,这才是符合DRY的终极真理啊:
1.先在model里定义好相关
class User < ActiveRecord::Base
  fields do
    name :string, :null => false
    email :string
    about :text, :default => "No description available"
  end
end

2.然后生成migration:
ruby script/generate hobo_migration create_initial_tables
3.希瑞她娘~ 请赐予我力量吧!
rake db:migrate

这不仅是代码写法的不同,而且是设计思路的革新:先设定model,到attribute,最后才是数据表的细节定义。虽然我们平时也是这样设计,但是在写代码时却是反过来操作,十分别扭。但在Tom主持的Hobo中就可以保持设计与实施的思路是一致的了。

另外就是当我们编辑model时就能一眼看到它有哪些属性,就不用翻到schema.rb里找来找去了,这又是DRY的一种体现。
(注:关于这点有个类似功能的插件:annotate_models plugin,实现的是把attribute的定义以注释的形式,每次执行migrate时更新到model的代码里)

那migration呢?
跟PJ的类似,比较前后定义,无则删之,新则添之。而对于rename_column,在执行第二步时generate会询问一个新增的属性是新添的还是改名来的而进行不同的定义。

对于这么

当然,要尝试这么极致DRY的个性migration,你需要的是Hobo的tom_sandbox分支(如果顺利的话,月底释出)

最后:
毫无疑问/我写的POST/是全天下/最话痨的/THE END
   发表时间:2007-09-21  
哥们,好文。不过请有话直话。。看着有点肉麻。
0 请登录后投票
   发表时间:2007-09-21  
好文章!
sexy migration会在rails的新版本里面取代目前的migration写法,rails 团队现在的做法很象暴雪,在wow里面,不断地把社区一些优秀的plugin集成到新版本里面,呵呵。
0 请登录后投票
   发表时间:2007-09-21  
---这不仅是代码写法的不同,而且是设计思路的革新:先设定model,到attribute,最后才是数据表的细节定义。虽然我们平时也是这样设计,但是在写代码时却是反过来操作,十分别扭。但在Tom主持的Hobo中就可以保持设计与实施的思路是一致的了。

太对了,现在看到一个空荡荡的Model有时候真的很别扭.
0 请登录后投票
   发表时间:2007-09-21  
在我看来DHH在选择OR Mapping 库时并不是没有注意到另外一个ruby OR Mapping库:Og.

Og与ActiveRecord最大的区别就是,它是先在类内定义属性,然后自动生成数据库的表。

而为什么DHH最终选择的确是使用相反的方法的ActiveRecord呢? 我猜测,原因是考虑到如何才能更好的快速修改数据库的结构,尤其是项目已经上线的情况下。

想象一下, 如果使用在Model里定义属性的方式,当数据库的字段发生变更时, 如果直接修改了model里的属性定义, 如何来通知数据库做相应的修改呢? 当然可以重新运行一次数据库表的生成命令,让它根据新的属性定义做一些表结构修改,如果修改的比较简单尚且可以,如果修改时还要做些比如数据字段值的update工作(如从true => 1),该在哪里放置这些修改信息呢。

此时似乎还是需要引入migration文件。

我猜测本着DRY的原则,DHH因此最终选择了ActiveRecord,这样就不需要同时在两处定义对于同一属性的修改了。

0 请登录后投票
   发表时间:2007-09-21  
引用
想象一下, 如果使用在Model里定义属性的方式,当数据库的字段发生变更时, 如果直接修改了model里的属性定义,如何来通知数据库做相应的修改呢?当然可以重新运行一次数据库表的生成命令,让它根据新的属性定义做一些表结构修改,如果修改的比较简单尚且可以,如果修改时还要做些比如数据字段值的 update工作(如从true => 1),该在哪里放置这些修改信息呢。

这说明一个事实:现在的migration实际上承担着两种责任
第一,是定义数据库的结构(以及model的结构)
第二,像它的名字暗示的,负责数据的迁移
0 请登录后投票
   发表时间:2007-09-21  
lvflying 写道
在我看来DHH在选择OR Mapping 库时并不是没有注意到另外一个ruby OR Mapping库:Og.

Og与ActiveRecord最大的区别就是,它是先在类内定义属性,然后自动生成数据库的表。

而为什么DHH最终选择的确是使用相反的方法的ActiveRecord呢? 我猜测,原因是考虑到如何才能更好的快速修改数据库的结构,尤其是项目已经上线的情况下。

想象一下, 如果使用在Model里定义属性的方式,当数据库的字段发生变更时, 如果直接修改了model里的属性定义, 如何来通知数据库做相应的修改呢? 当然可以重新运行一次数据库表的生成命令,让它根据新的属性定义做一些表结构修改,如果修改的比较简单尚且可以,如果修改时还要做些比如数据字段值的update工作(如从true => 1),该在哪里放置这些修改信息呢。

此时似乎还是需要引入migration文件。

我猜测本着DRY的原则,DHH因此最终选择了ActiveRecord,这样就不需要同时在两处定义对于同一属性的修改了。



Tom的做法是可以实现的。注意,他实际是实现了一个migrations的scaffold generator,会根据你的model变化生成新的migration文件,如果有属性初始化赋值等的操作,就可以在生成的migration文件里修改,最后再执行rake db:mirage
0 请登录后投票
   发表时间:2007-09-21  
rainchen 写道
lvflying 写道
在我看来DHH在选择OR Mapping 库时并不是没有注意到另外一个ruby OR Mapping库:Og.

Og与ActiveRecord最大的区别就是,它是先在类内定义属性,然后自动生成数据库的表。

而为什么DHH最终选择的确是使用相反的方法的ActiveRecord呢? 我猜测,原因是考虑到如何才能更好的快速修改数据库的结构,尤其是项目已经上线的情况下。

想象一下, 如果使用在Model里定义属性的方式,当数据库的字段发生变更时, 如果直接修改了model里的属性定义, 如何来通知数据库做相应的修改呢? 当然可以重新运行一次数据库表的生成命令,让它根据新的属性定义做一些表结构修改,如果修改的比较简单尚且可以,如果修改时还要做些比如数据字段值的update工作(如从true => 1),该在哪里放置这些修改信息呢。

此时似乎还是需要引入migration文件。

我猜测本着DRY的原则,DHH因此最终选择了ActiveRecord,这样就不需要同时在两处定义对于同一属性的修改了。



Tom的做法是可以实现的。注意,他实际是实现了一个migrations的scaffold generator,会根据你的model变化生成新的migration文件,如果有属性初始化赋值等的操作,就可以在生成的migration文件里修改,最后再执行rake db:mirage
你说的没错,此时就有两处需要修改了。

我明白Tom这样做的考虑, 正如gigix所说目前的migration完成了两个工作, 可能这本身并不符合单一职责原则, 使用Tom的做法,虽然需要同时修改两处,但是目的是不同的,一点重复的工作,换来Model定义的清晰,也许是值得的。
0 请登录后投票
   发表时间:2007-09-23  
把fields写在model里,更容易扩展出多语系应用,比如 field name在错误信息中的显示,就不用再另外配置一份field name 的映射配置。
唔~这不又是一次DRY?
0 请登录后投票
   发表时间:2007-09-29  
rainchen 写道
把fields写在model里,更容易扩展出多语系应用,比如 field name在错误信息中的显示,就不用再另外配置一份field name 的映射配置。
唔~这不又是一次DRY?


我还看到这样做的一个好处,更有利于Aspect Programming。

比如,Model假删除:现在Rails里的Model.destroy就是从数据库里彻底删除,我想把系统里的几个模型做成假的删除。就是把is_active字段设为false,find也找不到它。

    要做到find找不到假删除的记录,只要hack一下ActiveRecord::Base就行了,但是is_active字段还是要一个个数据表里去加的。

    如果把数据库字段也定义在Model里,就可以在一个插件里一揽子解决这个问题。

    做一个插件,先添加一个is_active字段,default设为true,然后hack一下find,还要hack一下destroy方法。起个名字,比如acts_as_fake_delete。然后在需要的Model里call一下就都搞定了。

DRY的好处之外,是代码有意义。

    实际项目的数据表中有不少功能性字段,比如Rails考虑到的created_at, updated_at,is_locked,再比如Rails没考虑的is_active,created_by, updated_by等等,都可以用插件抽象出来。

期待这个变化。

0 请登录后投票
论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics