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 |           |
+--------------------+--------------+-----------------+--------+-------+-----------+----------------+---------------------------------+-----------+
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容