Skip to content

Instantly share code, notes, and snippets.

@hinagiku
Forked from nightire/Changes in Rails 4_1.md
Created April 21, 2013 07:13
Show Gist options
  • Select an option

  • Save hinagiku/5428756 to your computer and use it in GitHub Desktop.

Select an option

Save hinagiku/5428756 to your computer and use it in GitHub Desktop.

Revisions

  1. @nightire nightire revised this gist Mar 27, 2013. 2 changed files with 0 additions and 4 deletions.
    2 changes: 0 additions & 2 deletions Changes in Rails 4_2.md
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,3 @@
    # 拥抱 Rails 4_2

    ## ActiveRecord

    ### Finders
    2 changes: 0 additions & 2 deletions Changes in Rails4_3.md
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,3 @@
    # 拥抱 Rails 4_3

    ## ActiveRecord

    ### Scopes
  2. @nightire nightire revised this gist Mar 27, 2013. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion Changes in Rails 4_1.md
    Original file line number Diff line number Diff line change
    @@ -11,7 +11,7 @@ _注:(r3 代表 Rails 3,r4 代表 Rails 4)_
    match '/books/:id/purchase', to: 'books@purchase'
    ```

    用户可以很轻松地使用 <de>XSS Attack</del> CSRF Attack,比如使用这样一个链接:
    用户可以很轻松地使用 <del>XSS Attack</del> CSRF Attack,比如使用这样一个链接:

    > CodeSchool 的 Rail 4 教程里写的是 XSS Attack,经查证和问询,证明这是 CodeSchool 的失误,应该会很快改正过来,再次先做一个修正,并向受到误导的朋友致歉
  3. @nightire nightire revised this gist Mar 27, 2013. 1 changed file with 4 additions and 2 deletions.
    6 changes: 4 additions & 2 deletions Changes in Rails 4_1.md
    Original file line number Diff line number Diff line change
    @@ -11,13 +11,15 @@ _注:(r3 代表 Rails 3,r4 代表 Rails 4)_
    match '/books/:id/purchase', to: 'books@purchase'
    ```

    用户可以很轻松地使用 XSS Attack,比如使用这样一个链接:
    用户可以很轻松地使用 <de>XSS Attack</del> CSRF Attack,比如使用这样一个链接:

    > CodeSchool 的 Rail 4 教程里写的是 XSS Attack,经查证和问询,证明这是 CodeSchool 的失误,应该会很快改正过来,再次先做一个修正,并向受到误导的朋友致歉
    ```html
    <a href="http://yoursite.com/books/4/purchase">Get It Free!</a>
    ```

    你绝对不想看到这种情况,所以你要限制客户端可以访问此资源的方式。例如:
    这会使用 GET 去请求这个资源,你绝对不想看到这种情况(你希望的是 POST),所以你要限制客户端可以访问此资源的方式。例如:

    ```ruby
    match '/books/:id?purchase', to: 'books@purchase', via: :post # :all 代表匹配所有的 HTTP methods
  4. @nightire nightire revised this gist Mar 26, 2013. 1 changed file with 51 additions and 0 deletions.
    51 changes: 51 additions & 0 deletions Changes in Rails4_3.md
    Original file line number Diff line number Diff line change
    @@ -251,6 +251,28 @@ Person.pluck(:id, :name)

    现在将会返回包含两个字段的记录了,一个小小的但是很有用的改进。

    ---

    ### Relation#unscope

    ```ruby
    Post.comments.except(:order)
    ```

    像上面这一句代码,你以为会排除 order 的排序,但却不尽然。因为如果 Comment 的 `default_scope` 是带有 order 的话,except 并无法改变 Post.comments 的查询结果。幸好 Rails 4 中多了一个新方法:

    ```ruby
    Post.comments.unscope(:order) == Post.comments.order
    ```

    这样会确保你想要的结果,而不必担心 `default_scope` 所造成的影响。另外,`unscope` 方法是支持多个参数的。

    ---

    ### Partial inserts

    当向数据库插入新的记录的时候,Rails 会对比缺省值,然后只把发生变化的字段放进 `INSERT` 语句里,剩下的部分由数据库自动填充。这一变化会使得增加记录效率更高,移除数据库字段也会更加安全。

    ## ActiveModel

    ### ActiveModel::Model
    @@ -307,6 +329,35 @@ Easy and clear!

    ## Others

    ### Migration Helper

    #### #create_join_table

    Migration 文件里新添加了一个 Helper method, 专门用于为 HABTM 关系创建关联表:

    ```ruby
    create_join_table :categories, :products, :id => false do |f|
    f.integer :categories_id, :null => false
    f.integer :products_id, :null => false
    end
    ```

    现在主键会自己初始化为 nil,除非你用别的值覆盖它。

    #### self.disable_ddl_transaction!

    如果你选用的数据库支持 DDL Transaction,那么所有的数据库迁移会被包裹在一个事务中完成;然而某些 SQL 命令无法在事物内部成功执行,这会造成迁移的失败。在 Rails 4 中,你可以把这些造成失败的命令抽取出来放在一个单独的 migration 里,然后使用这个方法来禁止事务处理:

    ```ruby
    class ChangeSth < ActiveRecord::Migration
    self.disable_ddl_transaction!
    def change
    # some SQLs those can not execute in a transaction
    end
    end
    ```
    ----

    ### Schema Cache Dump

    在产品环境中,Rails 应用在初始化的时候会把所有 model 的数据库模式(schema)载入至一个 schema cache(模式缓存)中。对那些拥有庞大数量的 models 的应用程序而言,Rails 4 提供了 schema cache dump(模式缓存转储)的新功能,用来加速应用程序的启动。你可以使用这个 rake task:
  5. @nightire nightire revised this gist Mar 26, 2013. No changes.
  6. @nightire nightire revised this gist Mar 26, 2013. 1 changed file with 6 additions and 0 deletions.
    6 changes: 6 additions & 0 deletions Changes in Rails4_3.md
    Original file line number Diff line number Diff line change
    @@ -299,6 +299,12 @@ end

    Easy and clear!

    ## Association in Rails 4

    相比 Rails 3,Rails 4 里的 Association 返回的不再是数组而是一个集合代理(CollectionProxy),这一变化是好是坏应该说莫衷一是,具体产生的影响由于演示起来篇幅过长,所以请移步[这篇博客](http://blog.olivierlacan.com/posts/associations-in-rails-4/)

    总结起来就是输出到客户端的关系数据会有所变化,会影响到 JSON API,不过在适应了规则之后,前端工程师处理这些小变化应该是没什么问题的。

    ## Others

    ### Schema Cache Dump
  7. @nightire nightire revised this gist Mar 26, 2013. 1 changed file with 13 additions and 1 deletion.
    14 changes: 13 additions & 1 deletion Changes in Rails4_3.md
    Original file line number Diff line number Diff line change
    @@ -239,6 +239,18 @@ Post.includes(:comments).where('comments.name' => 'foo' })
    Post.includes(:comments).order('comments.name')
    ```

    ---

    ### Relation#pluck

    `pluck` 方法现在可以接受多个参数了(每个参数代表数据库表中的一个字段):

    ```ruby
    Person.pluck(:id, :name)
    ```

    现在将会返回包含两个字段的记录了,一个小小的但是很有用的改进。

    ## ActiveModel

    ### ActiveModel::Model
    @@ -309,4 +321,4 @@ config.active_record.use_schema_cache_dump = false

    ```bash
    $ RAILS_ENV=production bundle exec rake db:schema:cache:clear
    ```
    ```
  8. @nightire nightire revised this gist Mar 26, 2013. 1 changed file with 31 additions and 3 deletions.
    34 changes: 31 additions & 3 deletions Changes in Rails4_3.md
    Original file line number Diff line number Diff line change
    @@ -169,7 +169,7 @@ Post.none.recent # 不会报错,这是对的查询

    ---

    # 排序
    ### Relation#order

    `#order` 方法现在产生了一些新的变化,主要是针对生成的 SQL 语句,以下简明列举:

    @@ -207,7 +207,7 @@ User.order(:name, created_at: :desc)

    ---

    # Relation#references
    ### Relation#references

    说到字符串形式的查询条件,在 Rails 4 中对于这样的代码:

    @@ -226,12 +226,16 @@ Post.includes(:comments).where("comments.name = 'foo'").references(:comments)
    ```

    然而对于 hash 形式的条件传递,就不需要特意声明了:

    ```ruby
    Post.includes(:comments).where(comments: { name: 'foo' })
    # or
    Post.includes(:comments).where('comments.name' => 'foo' })
    ```

    像下面这样没有条件的查询,尽管是字符串也无需声明 references

    # 像下面这样没有条件的查询,尽管是字符串也无需声明 references
    ```ruby
    Post.includes(:comments).order('comments.name')
    ```

    @@ -282,3 +286,27 @@ end
    ```

    Easy and clear!

    ## Others

    ### Schema Cache Dump

    在产品环境中,Rails 应用在初始化的时候会把所有 model 的数据库模式(schema)载入至一个 schema cache(模式缓存)中。对那些拥有庞大数量的 models 的应用程序而言,Rails 4 提供了 schema cache dump(模式缓存转储)的新功能,用来加速应用程序的启动。你可以使用这个 rake task:

    ```bash
    $ RAILS_ENV=production bundle exec rake db:schema:cache:dump
    ```

    这会生成一个 `db/schema_cache.dump` 文件,Rails 用它来加载 `SchemaCache` 实例的内部状态。

    你可以选择关闭这个功能,编辑 `config/production.rb` 文件,添加这一行:

    ```ruby
    config.active_record.use_schema_cache_dump = false
    ```

    如果你要清除 schema cache,执行:

    ```bash
    $ RAILS_ENV=production bundle exec rake db:schema:cache:clear
    ```
  9. @nightire nightire revised this gist Mar 25, 2013. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion Changes in Rails4_3.md
    Original file line number Diff line number Diff line change
    @@ -229,7 +229,7 @@ Post.includes(:comments).where("comments.name = 'foo'").references(:comments)
    ```ruby
    Post.includes(:comments).where(comments: { name: 'foo' })
    # or
    Post.includes(:comments).where('comments.name': 'foo' })
    Post.includes(:comments).where('comments.name' => 'foo' })

    # 像下面这样没有条件的查询,尽管是字符串也无需声明 references
    Post.includes(:comments).order('comments.name')
  10. @nightire nightire revised this gist Mar 25, 2013. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion Changes in Rails4_3.md
    Original file line number Diff line number Diff line change
    @@ -134,7 +134,7 @@ else
    end
    ```

    但是这太丑了,不是么?你必须要检查查询数组里有没有东西,然后在明智没有的情况下再返回代表“没有”的空数组……多愚蠢啊~为什么 Rails 不能帮我们检查是否“没有”呢?在 Rails 4 里这变成了可能:
    但是这太丑了,不是么?你必须要检查查询数组里有没有东西,然后在明知没有的情况下再返回代表“没有”的空数组……多愚蠢啊~为什么 Rails 不能帮我们检查是否“没有”呢?在 Rails 4 里这变成了可能:

    ```ruby
    Class User < ActiveRecord::Base
  11. @nightire nightire revised this gist Mar 25, 2013. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions Changes in Rails4_3.md
    Original file line number Diff line number Diff line change
    @@ -76,6 +76,7 @@ if author
    else
    Book.where('author IS NOT NULL')
    end
    ```

    现在,同样的需求在 Rails 4 里可以这样写:

  12. @nightire nightire revised this gist Mar 25, 2013. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions Changes in Rails4_3.md
    Original file line number Diff line number Diff line change
    @@ -18,6 +18,7 @@ default_scope where(state: 'available')
    这样定义 scope 就称之为 eager-evaluated,因为在进行查询之前并不知道具体要调用该查询的对象是谁。在 Rails 4 中,以上代码会抛出警告:

    > DEPRECATION WARNING: Using #scope without passing a callable object is deprecated
    >
    > DEPRECATION WARNING: Calling #default_scope without a block is deprecated
    按照提示所说,你需要在定义 scope 的时候传递一个 proc 对象,所以修正的方法也很简单:
  13. @nightire nightire revised this gist Mar 25, 2013. 1 changed file with 282 additions and 0 deletions.
    282 changes: 282 additions & 0 deletions Changes in Rails4_3.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,282 @@
    # 拥抱 Rails 4_3

    ## ActiveRecord

    ### Scopes

    顺着上一节的话题,我们继续讲讲 Scopes。在 Rails 4 当中,eager-evaluated scopes 不再推荐使用了,因为通常没搞清对象(obejct)的热心(eager)帮助往往会帮倒忙!

    > 附注:eager 这个词在这里不好翻译,其原意是有“热情”、“渴望”的含义,在这里代表“在预先不知道查询请求的对象时就先做查询”。这一点固然有积极的意义,但有时候也会带来意料不到的结果。请看下文:
    举个例子:

    ```ruby
    scope :sold, where(state: 'sold')
    default_scope where(state: 'available')
    ```

    这样定义 scope 就称之为 eager-evaluated,因为在进行查询之前并不知道具体要调用该查询的对象是谁。在 Rails 4 中,以上代码会抛出警告:

    > DEPRECATION WARNING: Using #scope without passing a callable object is deprecated
    > DEPRECATION WARNING: Calling #default_scope without a block is deprecated
    按照提示所说,你需要在定义 scope 的时候传递一个 proc 对象,所以修正的方法也很简单:

    ```ruby
    scope :sold, -> { where(state: 'sold') }
    default_scope -> { where(state: 'available') }
    ```

    为什么呢?看一个实际的例子就明白了:

    ```ruby
    scope :recent, where(published_at: 2.weeks.ago)
    ```

    这段代码的问题就在于 `2.weeks.ago` 的求值只会在这个 class 载入时发生一次,以后再调用的时候你还是会得到一模一样的值。

    ```ruby
    scope :recent, -> { where(published_at: 2.weeks.ago) }
    scope :recent_red, recent.where(color: 'red')
    ```

    转变成 proc 对象后,当你再次调用它就会重新求值(上例第二行,recent_red 调用 recent,recent 会重新求值),于是此问题就解决了。

    当然,你应该在所有的 scopes 里应用这一原则,因此上例最终应写成:

    ```ruby
    scope :recent, -> { where(published_at: 2.weeks.ago) }
    scope :recent_red, -> { recent.where(color: 'red') }
    ```

    这个变化可能不是那么新鲜,毕竟多数开发者在 Rails 3 的时候就是这么处理的,Rails 4 只是对未处理过的 scopes 报出警告而已,算是一个小小的变化。

    ---

    ### Relation#not

    `#not` 是一个新方法,而且非常好用。我们先来看一段代码:

    ```ruby
    Book.where('author != ?', author)
    ```

    你知道这个查询有什么问题么?大部分情况下它工作良好,但如果 `author = nil` 的话,Rails 会产生如下 SQL 语句:

    ```sql
    SELECT "posts".* FROM "posts" WHERE (author != NULL)
    ```

    它能用,但是最后括号里的部分不符合 SQL 的语法规则,这会让许多“代码洁癖”患者感到寝食难安的!(玩笑)所以他们通常会写出如下无可奈何的临时解决方案:

    ```ruby
    if author
    Book.where('author != ?', author)
    else
    Book.where('author IS NOT NULL')
    end

    现在,同样的需求在 Rails 4 里可以这样写:

    ```ruby
    Book.where.not(author: author)
    ```

    该查询生成的 SQL 语句非常标准:

    ```sql
    SELECT "posts".* FROM "posts" WHERE (author IS NOT NULL)
    ```

    ---

    ### Relation#none

    `#none` 也是和 `#not` 一样棒的新方法,考察一下这段代码:

    ```ruby
    Class User < ActiveRecord::Base
    def visible_posts # 查询可见的帖子...
    case role # ...基于用户的角色
    when 'Country Manager'
    Post.where(country: country)
    when 'Reviewer'
    Post.published
    when 'Bad User'
    ???
    end
    end
    end
    ```

    那么,对于 Bad User 我们要求不返回任何帖子,你要怎么做?比较直觉性的做法就是返回一个空数组 `[]`,但是对于下面的代码来说:

    ```ruby
    @posts = current_user.visible_posts
    @posts.recent
    ```

    会报错:

    > NoMethodError: undefined method `recent' for []:Array
    本着“头疼医头,脚疼医脚”的精神……你可以这么搞:
    ```ruby
    @posts = current_user.visible_posts

    if @posts.any?
    @posts.recent
    else
    []
    end
    ```

    但是这太丑了,不是么?你必须要检查查询数组里有没有东西,然后在明智没有的情况下再返回代表“没有”的空数组……多愚蠢啊~为什么 Rails 不能帮我们检查是否“没有”呢?在 Rails 4 里这变成了可能:

    ```ruby
    Class User < ActiveRecord::Base
    def visible_posts
    case role
    when 'Country Manager'
    Post.where(country: country)
    when 'Reviewer'
    Post.published
    when 'Bad User'
    Post.none # 空即是空,无便是无……
    end
    end
    end
    ```

    上例中的 `Post.none` 并不只是返回空数组,而是返回一个不去碰数据库的 `ActiveRecord::Relation`,你可以获得如下的查询:

    ```ruby
    @posts = current_user.visible_posts
    @posts.recent # 根据前文的条件,这个方法会产生三个可能的查询:

    # 1
    Post.where(country: country).recent

    # 2
    Post.published.recent

    # 3
    Post.none.recent # 不会报错,这是对的查询
    ```

    ---

    # 排序

    `#order` 方法现在产生了一些新的变化,主要是针对生成的 SQL 语句,以下简明列举:

    ```ruby
    class User < ActiveRecord::Base
    default_scope -> { order(:name) }
    end

    User.order("created_at DESC")
    ```

    以上代码在 3 和 4 里产生了有所区别的 SQL:

    ```sql
    /*in r3*/
    SELECT * FROM users ORDER BY name asc, created_at desc

    /*in r4*/
    SELECT * FROM users ORDER BY created_at desc, name asc
    ```

    另外,现在可以用 symbol 来代表排序的查询条件了:

    ```ruby
    # in r3
    User.order('created_at DESC')
    User.order(:name, 'created_at DESC')

    # in r4
    User.order(created_at: :desc)
    User.order(:name, created_at: :desc)
    ```

    这么做的好处还是为了增强一致性,并且利于使用 hash 传入查询条件。

    ---

    # Relation#references

    说到字符串形式的查询条件,在 Rails 4 中对于这样的代码:

    ```ruby
    Post.includes(:comments).where("comments.name = 'foo'")
    ```

    会抛出警告:

    > DEPRECATION WARNING: It looks like you are eager loading table(s) (one of: posts, comments) that are referenced in a string SQL snippet. (...)
    所以你必须对字符串形式的查询显式声明其引用的表是哪一个,就像这样:

    ```ruby
    Post.includes(:comments).where("comments.name = 'foo'").references(:comments)
    ```

    然而对于 hash 形式的条件传递,就不需要特意声明了:
    ```ruby
    Post.includes(:comments).where(comments: { name: 'foo' })
    # or
    Post.includes(:comments).where('comments.name': 'foo' })

    # 像下面这样没有条件的查询,尽管是字符串也无需声明 references
    Post.includes(:comments).order('comments.name')
    ```

    ## ActiveModel

    ### ActiveModel::Model

    Rails 3 中增加了 `ActiveModel` 使得我们可以创建和 `ActiveRecord` 一样的模型,拥有几乎全部功能却不需要和数据库关联,就像这样:

    ```ruby
    class SupportTicket
    include ActiveModel::Conversion
    include ActiveModel::Validations
    extend ActiveModel::Naming

    attr_accessor :title, :description

    validates_presence_of :title
    validates_presence_of :description
    end
    ```

    于是,你可以为其生成关系表单,做条件验证等等,非常方便。在 Rails 4 中,对 `ActiveModel` 做了小小的改进,现在你可以直接 include 它的“精简版”:

    ```ruby
    class SupportTicket
    include ActiveModel::Model

    attr_accessor :title, :description

    validates_presence_of :title
    validates_presence_of :description
    end
    ```

    `ActiveModel::Model` 是一个“混编模组”:

    ```ruby
    # activemodel/lib/active_model/model.rb
    def self.included(base)
    base.class_eval do
    extend ActiveModel::Naming
    extend ActiveModel::Translation
    include ActiveModel::Validations
    include ActiveModel::Conversion
    end
    end
    ```

    Easy and clear!
  14. @nightire nightire revised this gist Mar 23, 2013. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion Changes in Rails 4_2.md
    Original file line number Diff line number Diff line change
    @@ -135,7 +135,7 @@ SELECT "books".* FROM "books" WHERE "books"."author" = 'admin'
    ```ruby
    @book.update(post_params) # 会触发验证

    @book.update_columns(post_params) # 构建 SQL 语句,直接执行与数据库层,不会触发验证
    @book.update_columns(post_params) # 构建 SQL 语句,直接执行于数据库层,不会触发验证
    ```

    就这俩,不会搞错了吧?以前的方式也还能用,但是不排除会被废弃。既然 Rails 4 提供了更好用的方法,那就不要再犹豫了。
  15. @nightire nightire revised this gist Mar 23, 2013. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion Changes in Rails 4_2.md
    Original file line number Diff line number Diff line change
    @@ -112,7 +112,7 @@ SELECT "books".* FROM "books" WHERE "books"."title" = 'Rails 4' AND "books"."aut
    ```


    注意,这里的 `after_create` 回调原本是在创建一条记录后立刻返回**所有作者是 Albert Yu 的记录**,但最终的结果却是**所有标题是 Rails 4 并且 作者是 Albert Yu的记录**。这是因为触发该回调函数的方法调用已经有了 `title: 'Rails 4'` 的作用域,于是产生了作用域叠加。
    注意,这里的 `after_create` 回调原本是在创建一条记录后立刻返回__所有作者是 Albert Yu 的记录__,但最终的结果却是__所有标题是 Rails 4 并且作者是 Albert Yu 的记录__。这是因为触发该回调函数的方法调用已经有了 `title: 'Rails 4'` 的作用域,于是产生了作用域叠加。

    Rails 4 里推荐这样来做:

  16. @nightire nightire revised this gist Mar 23, 2013. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion Changes in Rails 4_1.md
    Original file line number Diff line number Diff line change
    @@ -8,7 +8,7 @@ _注:(r3 代表 Rails 3,r4 代表 Rails 4)_

    ```ruby
    # routes.rb
    match '/books/:id?purchase', to: 'books@purchase'
    match '/books/:id/purchase', to: 'books@purchase'
    ```

    用户可以很轻松地使用 XSS Attack,比如使用这样一个链接:
  17. @nightire nightire revised this gist Mar 23, 2013. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions Changes in Rails 4_2.md
    Original file line number Diff line number Diff line change
    @@ -133,9 +133,9 @@ SELECT "books".* FROM "books" WHERE "books"."author" = 'admin'
    是不是经常被 `#update_attributes``#update_attribute` 还有 `#update_column` 搞晕?好消息来了——Rails 4 重新整理了属性更新的方法,现在的方式简单明了:

    ```ruby
    @book.update(post_params) // 会触发验证
    @book.update(post_params) # 会触发验证

    @book.update_columns(post_params) // 构建 SQL 语句,直接执行与数据库层,不会触发验证
    @book.update_columns(post_params) # 构建 SQL 语句,直接执行与数据库层,不会触发验证
    ```

    就这俩,不会搞错了吧?以前的方式也还能用,但是不排除会被废弃。既然 Rails 4 提供了更好用的方法,那就不要再犹豫了。
  18. @nightire nightire revised this gist Mar 23, 2013. 1 changed file with 8 additions and 8 deletions.
    16 changes: 8 additions & 8 deletions Changes in Rails 4_2.md
    Original file line number Diff line number Diff line change
    @@ -23,21 +23,21 @@ Book.where(author: 'Albert Yu')
    没人不爱它!而且还没完,同样的变化还有:

    ```ruby
    Book.find_all_by_title('Rails 4') // r3 way
    Book.find_last_by_author('Albert Yu') // r3 way
    Book.find_all_by_title('Rails 4') # r3 way
    Book.find_last_by_author('Albert Yu') # r3 way

    Book.where(title: 'Rails 4') // r4 way
    Book.where(author: 'Albert Yu').last // r4 way
    Book.where(title: 'Rails 4') # r4 way
    Book.where(author: 'Albert Yu').last # r4 way
    ```

    动态的 `find_by` 也不例外:

    ```ruby
    Book.find_by_title('Rails 4') // 接收单个参数的用法在 r3 & r4 都可以
    Book.find_by(title: 'Rails4') // 不过 r4 更偏爱这样写
    Book.find_by_title('Rails 4') # 接收单个参数的用法在 r3 & r4 都可以
    Book.find_by(title: 'Rails4') # 不过 r4 更偏爱这样写

    Book.find_by_title('Rails 4', conditions: { author: 'Albert Yu' }) // 这就不好了,得改
    Book.find_by(title: 'Rails4', author: 'Albert Yu') // Wow! 太棒了!
    Book.find_by_title('Rails 4', conditions: { author: 'Albert Yu' }) # 这就不好了,得改
    Book.find_by(title: 'Rails4', author: 'Albert Yu') # Wow! 太棒了!
    ```

    统一使用 `find_by` 不仅有更好的一致性,而且更便于接收 hash 参数:
  19. @nightire nightire revised this gist Mar 23, 2013. 1 changed file with 173 additions and 0 deletions.
    173 changes: 173 additions & 0 deletions Changes in Rails 4_2.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,173 @@
    # 拥抱 Rails 4_2

    ## ActiveRecord

    ### Finders

    ```ruby
    Book.find(:all, conditions: { author: 'Albert Yu' })
    ```

    这种方法已经用了很久了吧?在 Rails 4 中,你会看到如下警告:

    > DEPRECATION WARNING: Calling #find(:all) is deprecated. Please call #all directly instead. You have also used finder options. These are also deprecated. Please build a scope instead of using finder options.
    实际上,老式的 finders 已经被抽取成了 `activerecord-deprecated_finders` gem,你要还想用就得自己安装它。

    在 Rails 4 中,推荐这样用:

    ```ruby
    Book.where(author: 'Albert Yu')
    ```

    没人不爱它!而且还没完,同样的变化还有:

    ```ruby
    Book.find_all_by_title('Rails 4') // r3 way
    Book.find_last_by_author('Albert Yu') // r3 way

    Book.where(title: 'Rails 4') // r4 way
    Book.where(author: 'Albert Yu').last // r4 way
    ```

    动态的 `find_by` 也不例外:

    ```ruby
    Book.find_by_title('Rails 4') // 接收单个参数的用法在 r3 & r4 都可以
    Book.find_by(title: 'Rails4') // 不过 r4 更偏爱这样写

    Book.find_by_title('Rails 4', conditions: { author: 'Albert Yu' }) // 这就不好了,得改
    Book.find_by(title: 'Rails4', author: 'Albert Yu') // Wow! 太棒了!
    ```

    统一使用 `find_by` 不仅有更好的一致性,而且更便于接收 hash 参数:

    ```ruby
    book_param = { title: 'Rails 4', author: 'Albert Yu' }
    Book.find_by(book_param)
    ```

    `find_by` 方法的内部实现其实很简单:

    ```ruby
    # activerecord/lib/active_record/relation/finder_methods.rb
    def find_by(*args)
    where(*args).take
    end
    ```

    这意味着这样用也没有问题:

    ```ruby
    Book.find_by("published_on < ?", 3.days.ago)
    ```

    ### find\_or_*

    这两种方法不再推荐使用了:

    ```ruby
    Book.find_or_initialize_by_title('Rails 4')
    Book.find_or_create_by_title('Rails 4')
    ```

    会抛出如下警告:

    > DEPRECATION WARNING: This dynamic method is deprecated. Please use e.g. Post.find_or_initialize_by(name: 'foo') instead.
    > DEPRECATION WARNING: This dynamic method is deprecated. Please use e.g. Post.find_or_create_by(name: 'foo') instead.
    让我们从善如流:

    ```ruby
    Book.find_or_initialize_by(title: 'Rails 4')
    Book.find_or_create_by(title: 'Rails 4')
    ```

    还有一种容易让人迷惑的用法

    ```ruby
    Book.where(title: 'Rails 4').first_or_create
    # 若找不到…
    Book.where(title: 'Rails 4').create
    ```

    这方法在 Rails 3 和 Rails 4 里都可以用,它先是查询是否有符合条件的记录,若没有就以该条件创建一个。听起来还不错,然而当存在这样的代码时,其表现就不是你想的那样了:

    ```ruby
    class Book < ActiveRecord::Base
    after_create :foo

    def foo
    books = books.where(author: 'Albert Yu')
    ...
    end
    end
    ```

    产生的 SQL 是:

    ```sql
    SELECT "books".* FROM "books" WHERE "books"."title" = 'Rails 4' AND "books"."author" = 'Albert Yu'
    ```


    注意,这里的 `after_create` 回调原本是在创建一条记录后立刻返回**所有作者是 Albert Yu 的记录**,但最终的结果却是**所有标题是 Rails 4 并且 作者是 Albert Yu的记录**。这是因为触发该回调函数的方法调用已经有了 `title: 'Rails 4'` 的作用域,于是产生了作用域叠加。

    Rails 4 里推荐这样来做:

    ```ruby
    Book.find_or_create_by(title: 'Rails 4')
    # 若找不到…
    Book.create(title: 'Rails 4')
    ```

    这样就不会产生叠加副作用,真正的 SQL 语句如下:

    ```sql
    SELECT "books".* FROM "books" WHERE "books"."author" = 'admin'
    ```

    ### #update & #update_column

    是不是经常被 `#update_attributes``#update_attribute` 还有 `#update_column` 搞晕?好消息来了——Rails 4 重新整理了属性更新的方法,现在的方式简单明了:

    ```ruby
    @book.update(post_params) // 会触发验证

    @book.update_columns(post_params) // 构建 SQL 语句,直接执行与数据库层,不会触发验证
    ```

    就这俩,不会搞错了吧?以前的方式也还能用,但是不排除会被废弃。既然 Rails 4 提供了更好用的方法,那就不要再犹豫了。

    ### Model.all

    也不是所有的变化都那么显而易见的令人愉悦,一部分人大概会对接下来的变化感到不适应。以前普遍认为不要直接使用 `Model.all`,因为这会产生很严重的性能问题,开发者更倾向于先对 Model 进行 scope:

    ```ruby
    def index
    @books = Book.scoped
    if params[:recent]
    @books = @books.recent
    end
    end
    ```

    然而,Rails 4 会抛出如下警告:

    > DEPRECATION WARNING: Model.scoped is deprecated. Please use Model.all instead.
    WTF?`Model.all` 又回来了?

    没错。不过你不用担心,Rails 4 里的 `Model.all` 不会立即执行对数据库的查询,而仅仅是返回一个 `ActiveRecord::Relation`,你可以继续进行链式调用:

    ```ruby
    def index
    @books = Book.all # 我不会碰数据库的哦,直到你告诉我下一个条件…
    if params[:recent]
    @books = @books.recent # 这时候我才会行动
    end
    end
    ```

    当然,这并不是说不能用 scoped model 了,只不过是多了一层防范措施,以减少初学者不小心造成的性能问题。
  20. @nightire nightire renamed this gist Mar 23, 2013. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  21. @nightire nightire revised this gist Mar 23, 2013. 1 changed file with 9 additions and 9 deletions.
    18 changes: 9 additions & 9 deletions routes and thread_safe.md
    Original file line number Diff line number Diff line change
    @@ -6,7 +6,7 @@ Rails 3 提供了 `match` 方法供我们自定义 routes,然而我们要小

    _注:(r3 代表 Rails 3,r4 代表 Rails 4)_

    ```ruby:r3
    ```ruby
    # routes.rb
    match '/books/:id?purchase', to: 'books@purchase'
    ```
    @@ -19,7 +19,7 @@ match '/books/:id?purchase', to: 'books@purchase'

    你绝对不想看到这种情况,所以你要限制客户端可以访问此资源的方式。例如:

    ```ruby:r4
    ```ruby
    match '/books/:id?purchase', to: 'books@purchase', via: :post # :all 代表匹配所有的 HTTP methods

    # 或者
    @@ -49,7 +49,7 @@ post '/books/:id?purchase', to: 'books@purchase'

    同时还增加了一个 `#patch` 方法,可以在合适的时候使用:

    ```ruby:r4
    ```ruby
    test "update book with PATCH verb" do
    patch :update, id: @book, book: { title: @book.title }
    assert_redirected_to book_url(@book)
    @@ -62,7 +62,7 @@ end

    Concerns(关注点)是一种组织代码结构的方式,用来帮助开发者将复杂的逻辑和重复代码梳理清楚,我们在 Rails 4 中多次看到对于 Concerns 的设计和实现。先看一段老代码:

    ```ruby:r3
    ```ruby
    resources :messages do
    resources :comments
    resources :categories
    @@ -84,7 +84,7 @@ end

    像这样的代码存在许多的重复,Rails 4 允许我们重构它:

    ```ruby:r4
    ```ruby
    concern :sociable do
    resources :comments
    resources :categories
    @@ -98,7 +98,7 @@ resources :articles, concerns: :sociable

    可以通过传递参数来实现对个例的特化:

    ```ruby:r4
    ```ruby
    concern :sociable do |options|
    resources :comments, options
    resources :categories, options
    @@ -114,7 +114,7 @@ end

    甚至我们可以抽取出来变成单独的类:

    ```ruby:r4
    ```ruby
    # app/concerns/sociable.rb
    class Sociable
    def self.call(mapper, options)
    @@ -154,7 +154,7 @@ Rails 3 无法永远摆脱这恼人的提示,因为它要同时兼容 1.8 和

    线程安全的处理在 Rails 3 中已有,不过默认是关闭的:

    ```ruby:r3
    ```ruby
    # config/environments/production.rb
    MyApp::Application.configure do
    # Enable threaded mode
    @@ -164,7 +164,7 @@ end

    这个方法在 Rails 4 中不推荐使用,新的线程安全机制在默认情况下就已经开启:

    ```ruby:r4
    ```ruby
    # config/environments/production.rb
    MyApp::Application.configure do
    config.cache_classes = true # 阻止类在请求中重新载入,并保证 Rack::Lock 不包含在中间件堆栈中
  22. @nightire nightire created this gist Mar 23, 2013.
    173 changes: 173 additions & 0 deletions routes and thread_safe.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,173 @@
    ## Routes

    ### 小心地使用 Match(Rails 3 已实现)

    Rails 3 提供了 `match` 方法供我们自定义 routes,然而我们要小心使用它以避免“跨站脚本攻击”(XSS Attack)。比如像这样的 routes:

    _注:(r3 代表 Rails 3,r4 代表 Rails 4)_

    ```ruby:r3
    # routes.rb
    match '/books/:id?purchase', to: 'books@purchase'
    ```

    用户可以很轻松地使用 XSS Attack,比如使用这样一个链接:

    ```html
    <a href="http://yoursite.com/books/4/purchase">Get It Free!</a>
    ```

    你绝对不想看到这种情况,所以你要限制客户端可以访问此资源的方式。例如:

    ```ruby:r4
    match '/books/:id?purchase', to: 'books@purchase', via: :post # :all 代表匹配所有的 HTTP methods
    # 或者
    post '/books/:id?purchase', to: 'books@purchase'
    ```

    否则你就会收到如下错误提示:

    > You should not use the `match` method in your router without specifying an HTTP method. (RuntimeError)
    ---

    ### 新的 HTTP Verb:patch

    过去我们使用 `put` 来完成对资源的更新请求,然而 `put` 本身是对整个资源(数据集合)进行更新,若要实现部分资源的更新(单个数据,或是几个产生变化的数据实体),`put` 就有点过重了,此时 `patch` 会更加合适。

    `patch` 并不是什么新东西,此前就一直存在于 HTTP 1.1 协议规范之中,只不过这一次 Rails 4 把它正式的引入进来。在 Rails 4 中,`put``patch` 都指向 `controller#update`,在更新部分资源时(比如 @book)会使用 `patch`,生成类似下例中的页面元素:

    ```html
    <form action="/books/20" method="post">
    <div style="margin:0;padding:0;display:inline">
    <input name="utf8" type="hidden" value="&#x2713;" />
    <input name="_method" type="hidden" value="patch" /> <!-- 关键就是这一行了 -->
    </div>
    </form>
    ```

    同时还增加了一个 `#patch` 方法,可以在合适的时候使用:

    ```ruby:r4
    test "update book with PATCH verb" do
    patch :update, id: @book, book: { title: @book.title }
    assert_redirected_to book_url(@book)
    end
    ```

    ---

    ### Concerns for Routing

    Concerns(关注点)是一种组织代码结构的方式,用来帮助开发者将复杂的逻辑和重复代码梳理清楚,我们在 Rails 4 中多次看到对于 Concerns 的设计和实现。先看一段老代码:

    ```ruby:r3
    resources :messages do
    resources :comments
    resources :categories
    resources :tags
    end
    resources :posts do
    resources :comments
    resources :categories
    resources :tags
    end
    resources :articles do
    resources :comments
    resources :categories
    resources :tags
    end
    ```

    像这样的代码存在许多的重复,Rails 4 允许我们重构它:

    ```ruby:r4
    concern :sociable do
    resources :comments
    resources :categories
    resources :tags
    end
    resources :messages, concerns: :sociable
    resources :posts, concerns: :sociable
    resources :articles, concerns: :sociable
    ```

    可以通过传递参数来实现对个例的特化:

    ```ruby:r4
    concern :sociable do |options|
    resources :comments, options
    resources :categories, options
    resources :tags, options
    end
    resources :messages, concerns: :sociable
    resources :posts, concerns: :sociable
    resources :articles do
    concerns :sociable, only: :create
    end
    ```

    甚至我们可以抽取出来变成单独的类:

    ```ruby:r4
    # app/concerns/sociable.rb
    class Sociable
    def self.call(mapper, options)
    mapper.resources :comments, options
    mapper.resources :categories, options
    mapper.resources :tags, options
    end
    end
    # config/routes.rb
    concern :sociable, Sociable
    resources :messages, concerns: :sociable
    resources :posts, concerns: :sociable
    resources :articles do
    concerns :sociable, only: :create
    end
    ```

    ## 抛弃 Ruby 1.8.x

    我们都听说 Rails 4 需要 Ruby 的版本不能小于 1.9.3,不过这一点所引起的变化通常都十分微妙,不容易让人注意到。

    ### 聒噪的 nil

    1.8.x 时代,`nil.id` 是合法的(一切都是对象!),但是不合理,经常惹人厌。于是 1.9.2 之后,逐渐使用 `object_id` 来代替,使用旧的 `id` 方法会抛出运行时错误:

    > RuntimeError: Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id
    Rails 3 无法永远摆脱这恼人的提示,因为它要同时兼容 1.8 和 1.9,于是一旦碰上可能会出现的 `nil.id` 就会看到上面那个错误

    在 Rails 4 的世界里,手起刀落,喀嚓~~~ 从此 nil 不再聒噪,世界终于清净了……

    > NoMethodError: undefined method `id' for nil:NilClass
    ## 线程安全

    线程安全的处理在 Rails 3 中已有,不过默认是关闭的:

    ```ruby:r3
    # config/environments/production.rb
    MyApp::Application.configure do
    # Enable threaded mode
    # config.threadsafe!
    end
    ```

    这个方法在 Rails 4 中不推荐使用,新的线程安全机制在默认情况下就已经开启:

    ```ruby:r4
    # config/environments/production.rb
    MyApp::Application.configure do
    config.cache_classes = true # 阻止类在请求中重新载入,并保证 Rack::Lock 不包含在中间件堆栈中
    config.eager_load = true # 在新线程创建前加载全部代码
    end
    ```