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的抽象类,作用就是为整个应用程序模型提供一个自己的基类
1 2 3 4 5 6 7 8 9 | 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中也使用相同的方法,否则的话就会报错
1 2 | > 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] |
最好是在结尾使用:
1 | Article.where(user_id: 1 ). or (Article.where(user_id: 2 )).limit( 1 ) |
ActiveRecord::Relation#cache_key
Rails中使用缓存是很常见的行为,通常我们要缓存一组查询出来的记录,需要手动的设置缓存的key
1 2 3 4 5 | 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
1 2 3 4 5 6 | 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_all和update其中:
- update_all 通过一条SQL语句更新多条记录,不能触发callback和validate
- update 通过N条SQL语句,更新N条记录,其中N取决于其第一个ID参数的个数。
通过上面的方法定义,可以看出,如果你不知道ID的情况下,想更新一组记录并且触发它们各自的callback和validate,在Rails4中是做不到的。
那么在Rails5中修改了AR Relation#update的实现:
1 2 3 4 5 6 7 8 9 10 11 | 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 |
也就是亦可以通过下面的方法更新记录:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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_at或updated_on字段。
在Rails5中,为ActiveRecord::Base#save方法提供了一个选项,touch: boolean,默认情况下是true,如果设置成false的话,更新记录是就不会更新updated_at字段了。
1 2 3 4 5 6 7 8 9 10 11 12 | 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 方法,用于忽略数据表中不需要的字段。
1 2 3 | class User < ApplicationRecord self .ignored_columns = [ 'sign_in_count' ] end |
这样在模型中就不会有这个字段了
1 2 3 | 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 关联,默认情况下是不能为空的:
1 2 3 4 5 6 7 8 9 | 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,如果你想去除这个默认选项的话,可以通过下面的方式:
1 2 3 | class Article < ApplicationRecord belongs_to :user , optional: true , #指定可选 end |
也可以在application.rb中全局设置这个特性为可选的。
1 | Rails.application.config.active_record.belongs_to_required_by_default = false |
新的 after_{create,update,delete}_commit 回调
在Rails4中,我们可以在模型中设置事务执行后的回调方法,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | # == 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中给它们分别提供的单独的别名方法:
1 2 3 4 5 6 7 8 9 | 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的功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #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 |
运行迁移:
1 2 3 4 5 | $> rails db :migrate == 20160703025729 CreateUsers: migrating ====================================== -- create_table( :users , { :comment => "用户表" }) -> 0 .0178s == 20160703025729 CreateUsers: migrated ( 0 .0179s) ============================= |
在mysql数据库中也可以查看到注释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 | | + --------------------+--------------+-----------------+--------+-------+-----------+----------------+---------------------------------+-----------+ |
作者:falm
链接:https://www.jianshu.com/p/05719f9082fc
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话