Rails5中AR的新特性

Rails5正式版, 終于經過眾多測試版后,與我們見面了,本文就來介紹一下,在Rails5中有哪些關于ActiveRecord相關的新特性,以便能夠更好的使用Rails5進行開發。

ApplicationRecord


在Rails4中所有的模型都繼承自ActiveRecord::Base,不過在Rails5中新引進了一個叫ApplicationRecord的類,存放在: app/models/application_record.rb中,所有Rails5應用都會有這個類, 它的內容非常簡單:

class ApplicationRecord < ActiveRecord::Base  
  self.abstract_class = true
end

就是一個繼承ActiveRecord::Base的抽象類,作用就是為整個應用程序模型提供一個自己的基類

module MyModule
end
# Rails4.x中擴展模型的方式
ActiveRecord::Base.send :include, MyModule
# Rails5
class ApplicationRecord < ActiveRecord::Base
  include MyModule
  self.abstract_class = true
end

OR語法支持


Rails5中提供了對兩個AR Relation對象的OR方法:

 > Article.where(user_id: 1).or(Article.where(user_id: 2))
=> Article Load (2.5ms)  SELECT `articles`.* FROM `articles` WHERE (`articles`.`user_id` = 1 OR `articles`.`user_id` = 2)

需要注意的是如果你在第一個Relation中是用了:limit distinct offset 這三個方法的話,那么就必須在后面的Relation中也使用相同的方法,否則的話就會報錯

> Article.where(user_id: 1).limit(1).or(Article.where(user_id: 2))
#=>ArgumentError: Relation passed to #or must be structurally compatible. Incompatible values: [:limit]

最好是在結尾使用:

Article.where(user_id: 1).or(Article.where(user_id: 2)).limit(1)

ActiveRecord::Relation#cache_key


Rails中使用緩存是很常見的行為,通常我們要緩存一組查詢出來的記錄,需要手動的設置緩存的key

no_nick_name_users = User.where(nick_name: nil)
cache_key = [User.name, 'no_nick_name_users', no_nick_name_users.maximum(:updated_at).to_i]
Rails.cache.fetch(cache_key) do
  no_nick_name_users.to_a
end

Rails5中提供了ActiveRecord::Relation#cache_key

no_nick_name_users = User.where(nick_name: nil)
Rails.cache.fetch(no_nick_name_users.cache_key) do
  no_nick_name_users.to_a
end
puts no_nick_name_users.cache_key
#=> "users/query-dae9b6f1d9babd4a9ec4c532614c29eb-1-20160703095605000000"

上面最后一行,Rails5提供的cache_key和我們自己設置的很相似,分別有5個組成部分分別是:

  • users : 表名
  • query : 常值
  • dae9b6f1d9babd4a9ec4c532614c29eb : 緩存SQL的MD5碼
  • 1 : 結果集數量
  • 20160703095605000000 : 結果集最大的updated_at的時間戳

AR Relation調用update會觸發callbacks和validates


在Rails4中的AR Relation 提供了兩個更新記錄的方法update_allupdate其中:

  • update_all 通過一條SQL語句更新多條記錄,不能觸發callback和validate
  • update 通過N條SQL語句,更新N條記錄,其中N取決于其第一個ID參數的個數。

通過上面的方法定義,可以看出,如果你不知道ID的情況下,想更新一組記錄并且觸發它們各自的callback和validate,在Rails4中是做不到的。
那么在Rails5中修改了AR Relation#update的實現:

def update(id = :all, attributes)
   if id.is_a?(Array)
     id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) }
   elsif id == :all
     to_a.each { |record| record.update(attributes) }
   else
     object = find(id)
     object.update(attributes)
    object
  end
end

也就是亦可以通過下面的方法更新記錄:

2.3.0 :007 > User.where(nick_name: 'Falm').update(nick_name: 'falm')
  User Load (0.3ms)  SELECT `users`.* FROM `users` WHERE `users`.`nick_name` = 'Falm'
   (0.1ms)  BEGIN
  SQL (0.3ms)  UPDATE `users` SET `nick_name` = 'falm', `updated_at` = '2016-07-03 05:00:18' WHERE `users`.`id` = 1
   (2.0ms)  COMMIT
   (0.1ms)  BEGIN
  SQL (0.2ms)  UPDATE `users` SET `nick_name` = 'falm', `updated_at` = '2016-07-03 05:00:18' WHERE `users`.`id` = 2
   (0.3ms)  COMMIT
   (0.1ms)  BEGIN
  SQL (0.2ms)  UPDATE `users` SET `nick_name` = 'falm', `updated_at` = '2016-07-03 05:00:18' WHERE `users`.`id` = 3
   (0.2ms)  COMMIT
   (0.1ms)  BEGIN
  SQL (0.2ms)  UPDATE `users` SET `nick_name` = 'falm', `updated_at` = '2016-07-03 05:00:18' WHERE `users`.`id` = 4
   (0.2ms)  COMMIT

更新操作被按ID分解成多個update語句,并且其中每一個都會執行callback和validates, 要注意的是如果你要更新的記錄不必要觸發callback或validates,那么因為性能原因最好使用update_all方法。

更新記錄時,不更新updated_at/updated_on


Rails4.x中,更新記錄是,AR都會連帶更新,記錄上的updated_atupdated_on字段。
在Rails5中,為ActiveRecord::Base#save方法提供了一個選項,touch: boolean,默認情況下是true,如果設置成false的話,更新記錄是就不會更新updated_at字段了。

2.3.0 :027 > user = User.first
  User Load (0.3ms)  SELECT  `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
 => #<User id: 1, phone: "13303300333", email: nil, sign_in_count: 0, name: "Jason", nick_name: "falm", encrypted_password: nil, created_at: "2016-07-03 05:00:14", updated_at: "2016-07-03 05:00:18">
2.3.0 :028 > user.phone = '15088833388'
 => "15088833388"
2.3.0 :029 > user.save(touch: false)
   (0.2ms)  BEGIN
  SQL (0.3ms)  UPDATE `users` SET `phone` = '15088833388' WHERE `users`.`id` = 1
   (0.4ms)  COMMIT
 => true
2.3.0 :030 > user.updated_at
 => Sun, 03 Jul 2016 05:00:18 UTC +00:00

忽略字段


Rails5中新增了 ActiveRecord::Base.ignored_columns 方法,用于忽略數據表中不需要的字段。

class User < ApplicationRecord
  self.ignored_columns = ['sign_in_count']
end

這樣在模型中就不會有這個字段了

2.3.0 :033 > User.first
  User Load (0.2ms)  SELECT  `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
 => #<User id: 1, phone: "15088833388", email: nil, name: "Jason", nick_name: "falm", encrypted_password: nil, created_at: "2016-07-03 05:00:14", updated_at: "2016-07-03 05:00:18">

Belongs_to關聯,默認必填


在Rails5中AR中的belongs_to 關聯,默認情況下是不能為空的:

class User < ApplicationRecord
end
class Article < ApplicationRecord  
  belongs_to :user
end
> Article.create(title: 'without user').errors.full_messages.to_sentence
   (0.2ms)  BEGIN
   (0.1ms)  ROLLBACK
 => "User must exist"

Article屬于User,但是如果沒有在創建時指定user的話,就無法通過AR的validates,如果你想去除這個默認選項的話,可以通過下面的方式:

class Article < ApplicationRecord  
  belongs_to :user, optional: true, #指定可選
end

也可以在application.rb中全局設置這個特性為可選的。

Rails.application.config.active_record.belongs_to_required_by_default = false

新的 after_{create,update,delete}_commit 回調


在Rails4中,我們可以在模型中設置事務執行后的回調方法,

# == Schema Information
#
# Table name: users
#
#  id                 :integer          not null, primary key
#  phone              :string(255)      not null
#  email              :string(255)
#  sign_in_count      :integer          default(0)
#  name               :string(255)      not null
#  nick_name          :string(255)
#  encrypted_password :string(255)
#  created_at         :datetime         not null
#  updated_at         :datetime         not null
#
class User < ApplicationRecord
  after_commit :send_message, on: :create
  after_commit :send_message, on: :update
  after_commit :send_message, on: :destroy
  private
  def send_message
    do_someting
  end
end

以上就是分別在 創建,更新,和刪除事務執行后,被調用的回調方法
那么在Rails5中給它們分別提供的單獨的別名方法:

class User < ApplicationRecord
  after_create_commit :send_message
  after_update_commit :send_message
  after_destroy_commit :send_message
  private
  def send_message
    do_someting
  end
end

支持在migration中添加comments


在大多數的項目中,快速變化的業務模型是很常見的事情,隨之而來的就是數據模型的頻繁變化,在這種場景下,我們通常會使用 migration_comments + annotate_models 這兩個gem 去為模型添加注釋信息,以便能夠更好的解釋模型的由來和用途。現在Rails5原生支持了 migration_comments的功能:

#encoding: utf-8
class CreateUsers < ActiveRecord::Migration[5.0]
  def change
    create_table :users, comment: '用戶表' do |t|
      t.string :phone, null: false, comment: '手機號'
      t.string :email, comment: '郵箱'
      t.integer :sign_in_count, default: 0, comment: '登錄次數'
      t.string :name, null: false, comment: '用戶名'
      t.string :nick_name, comment: '昵稱'
      t.string :encrypted_password, comment: '加密密碼'
      t.timestamps
    end
  end
end

運行遷移:

$> rails db:migrate
== 20160703025729 CreateUsers: migrating ======================================
-- create_table(:users, {:comment=>"用戶表"})
   -> 0.0178s
== 20160703025729 CreateUsers: migrated (0.0179s) =============================

在mysql數據庫中也可以查看到注釋:

mysql> show full columns from users;
+--------------------+--------------+-----------------+--------+-------+-----------+----------------+---------------------------------+-----------+
| Field              | Type         | Collation       | Null   | Key   |   Default | Extra          | Privileges                      | Comment   |
|--------------------+--------------+-----------------+--------+-------+-----------+----------------+---------------------------------+-----------|
| id                 | int(11)      | <null>          | NO     | PRI   |    <null> | auto_increment | select,insert,update,references |           |
| phone              | varchar(255) | utf8_general_ci | NO     |       |    <null> |                | select,insert,update,references | 手機號    |
| email              | varchar(255) | utf8_general_ci | YES    |       |    <null> |                | select,insert,update,references | 郵箱      |
| sign_in_count      | int(11)      | <null>          | YES    |       |         0 |                | select,insert,update,references | 登錄次數  |
| name               | varchar(255) | utf8_general_ci | NO     |       |    <null> |                | select,insert,update,references | 用戶名    |
| nick_name          | varchar(255) | utf8_general_ci | YES    |       |    <null> |                | select,insert,update,references | 昵稱      |
| encrypted_password | varchar(255) | utf8_general_ci | YES    |       |    <null> |                | select,insert,update,references | 加密密碼  |
| created_at         | datetime     | <null>          | NO     |       |    <null> |                | select,insert,update,references |           |
| updated_at         | datetime     | <null>          | NO     |       |    <null> |                | select,insert,update,references |           |
+--------------------+--------------+-----------------+--------+-------+-----------+----------------+---------------------------------+-----------+
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容