该帖已经被评为精华帖
|
|
---|---|
作者 | 正文 |
发表时间:2007-09-21
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 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2007-09-21
哥们,好文。不过请有话直话。。看着有点肉麻。
|
|
返回顶楼 | |
发表时间:2007-09-21
好文章!
sexy migration会在rails的新版本里面取代目前的migration写法,rails 团队现在的做法很象暴雪,在wow里面,不断地把社区一些优秀的plugin集成到新版本里面,呵呵。 |
|
返回顶楼 | |
发表时间:2007-09-21
---这不仅是代码写法的不同,而且是设计思路的革新:先设定model,到attribute,最后才是数据表的细节定义。虽然我们平时也是这样设计,但是在写代码时却是反过来操作,十分别扭。但在Tom主持的Hobo中就可以保持设计与实施的思路是一致的了。
太对了,现在看到一个空荡荡的Model有时候真的很别扭. |
|
返回顶楼 | |
发表时间:2007-09-21
在我看来DHH在选择OR Mapping 库时并不是没有注意到另外一个ruby OR Mapping库:Og.
Og与ActiveRecord最大的区别就是,它是先在类内定义属性,然后自动生成数据库的表。 而为什么DHH最终选择的确是使用相反的方法的ActiveRecord呢? 我猜测,原因是考虑到如何才能更好的快速修改数据库的结构,尤其是项目已经上线的情况下。 想象一下, 如果使用在Model里定义属性的方式,当数据库的字段发生变更时, 如果直接修改了model里的属性定义, 如何来通知数据库做相应的修改呢? 当然可以重新运行一次数据库表的生成命令,让它根据新的属性定义做一些表结构修改,如果修改的比较简单尚且可以,如果修改时还要做些比如数据字段值的update工作(如从true => 1),该在哪里放置这些修改信息呢。 此时似乎还是需要引入migration文件。 我猜测本着DRY的原则,DHH因此最终选择了ActiveRecord,这样就不需要同时在两处定义对于同一属性的修改了。 |
|
返回顶楼 | |
发表时间:2007-09-21
引用 想象一下, 如果使用在Model里定义属性的方式,当数据库的字段发生变更时, 如果直接修改了model里的属性定义,如何来通知数据库做相应的修改呢?当然可以重新运行一次数据库表的生成命令,让它根据新的属性定义做一些表结构修改,如果修改的比较简单尚且可以,如果修改时还要做些比如数据字段值的 update工作(如从true => 1),该在哪里放置这些修改信息呢。 这说明一个事实:现在的migration实际上承担着两种责任 第一,是定义数据库的结构(以及model的结构) 第二,像它的名字暗示的,负责数据的迁移 |
|
返回顶楼 | |
发表时间: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 |
|
返回顶楼 | |
发表时间: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定义的清晰,也许是值得的。 |
|
返回顶楼 | |
发表时间:2007-09-23
把fields写在model里,更容易扩展出多语系应用,比如 field name在错误信息中的显示,就不用再另外配置一份field name 的映射配置。
唔~这不又是一次DRY? |
|
返回顶楼 | |
发表时间: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等等,都可以用插件抽象出来。 期待这个变化。 |
|
返回顶楼 | |