深入模型查询无标题文章-摘抄

概要:
本课时讲解模型在数据查询时,如何避免 N+1问题,使用 scope 包装查询条件,编写模型 Rspec 测试。
知识点:
N+1
Scope
实用的查询

正文
4.2.1 两个 Gem
ActiveRecord 这个 gem 中,包含了两个重要的 gem,打开它的 源代码,可以看到这两个 gem:activemodelarel
activemodel
为一个类增加了许多特性,比如属性校验,回调等,这在后面章节会介绍。
arel
是 Ruby 编写的 sql 工具,使用它,可以通过简单的 Ruby 语法,编写复杂 sql 查询,我们上面使用的例子,语法就来自 arel。arel 还可以面向多种关系型数据库。
ActiveRecord 在使用 arel 的时候,提供了一个方法:sanitize_sql。
在我们以上的讲解中,会经常传递这样的参数["name = ? and price=?", "foobar", 4]
,它会由sanitize_sql
方法进行处理,这是一个 protected 方法,我们使用 send 来调用它:
Product.send(:sanitize_sql, ["name = ? and price=?", "Shoes", 4])=> "name = 'Shoes' and price=4"

这是一种安全的手段,保护我们的 sql 不会被插入恶意代码。我们不必去直接使用这个方法,除非特殊情况,我们只需要按照它的格式要求来书写就可以了。
4.2.2 N+1
N+1 是查询中经常遇到的一个问题。在下一节里,我们经常使用关联关系的查询,比如,列出十个用户的同时,显示它地址中的电话:
users = User.limit(10)users.each do |user| puts user.address.phoneend

这样就会造成,在 each 中又去查询数据,得到电话。这种情况会经常出现在我的列表中,所以在列表中会经常遇到 N+1 的问题。
为了避免这个问题,Rails 提供了预加载的功能,在查询的时候,使用includes
来解决。上面的例子修改一下:
users = User.includes(:address).limit(10)users.each do |user| puts user.address.phoneend

我们查看一下终端的输出:
SELECT * FROM users LIMIT 10SELECT addresses.* FROM addresses WHERE (addresses.user_id IN (1,2,3,4,5,6,7,8,9,10))

这里只有两个 sql 查询,提高了查询效率。
4.2.3 查询中使用 Scope
当我们使用 where 查询的时候,会遇到多个条件组合查询。通常我们可以把它们都写到一个 where 的条件里,比如:
Product.where(name: "T-Shirt", hot: true, top: true)

我增加了两个条件,hot: true
和top: true
,但是,这种条件组合只能在这里使用,在其他地方,我们还要再写一遍,这不符合 Rails 的哲学:“不要重复自己”。
Rails 提供了 scope,让我们复用查询条件:
class Product < ActiveRecord::Base scope :hot, -> { where(hot: true) } scope :top, -> { where(top: true) }end

使用的时候,我们可以将多个 scope 组合在一起:
Product.top.hot.where(name: "T-Shirt")

default_scope
可以为所有查询加上它定义的查询条件,比如:
class Product < ActiveRecord::Base default_scope { where("deleted_at IS NULL") }end

default_scope
要慎用,慎用,慎用(重要的话说三遍),在我们程序变的复杂的时候,性能往往会消耗在数据库查询上,维护已有查询时,很容易忽视 default_scope 的作用。如果使用了 default_scope,而在其他地方不得不去掉它,可以使用 unscoped,然后再附上其他查询:
Product.unscoped.load.top.hot

如果一个地方使用了某个 scope,而要在另一个地方把它的条件改变,可以使用 merge:
class Product < ActiveRecord::Base scope :active, -> { where state: 'active' } scope :inactive, -> { where state: 'inactive' }end

看一下它的执行结果:
Product.active.merge(User.inactive)# SELECT "products".* FROM "products" WHERE "products"."state" = 'inactive'

4.2.4 实用的查询
4.2.4.1 sql 查询集合
我们使用where查询,得到的是 ActiveRecord::Relation 实例,它的源代码在这里。阅读这里的代码,会让你学习到更多优雅的查询方法。在查询时,我们还可以使用 sql 直接查询,如果你更熟悉 sql 语法,可以这样来查询:
Client.find_by_sql("SELECT * FROM clients INNER JOIN orders ON clients.id = orders.client_id ORDER BY clients.created_at desc")# => [ #<Client id: 1, first_name: "Lucas" >, #<Client id: 2, first_name: "Jan" >, # ...]

这个例子来自这里
它返回的是实例的集合,这在我们 Rails 内使用很方便,但是提供 json 格式的 api时,需要转换一下,不过我们可以用 select_all 查询,得到包含 hash 的 array:
Client.connection.select_all("SELECT first_name, created_at FROM clients WHERE id = '1'")# => [ {"first_name"=>"Rafael", "created_at"=>"2012-11-10 23:23:45.281189"}, {"first_name"=>"Eileen", "created_at"=>"2013-12-09 11:22:35.221282"}]

4.2.4.2 pluck
pluck 可以直接在 Relation 实例的基础上,使用 sql 的 select 方法,得到字段值的集合(Array),而不用把返回结果包装成 ActiveRecord 实例,再得到属性值。在查询属性集合时,pluck
的性能更高。
Client.where(active: true).pluck(:id) SELECT id FROM clients WHERE active = 1 => [1, 2, 3]Client.distinct.pluck(:role) SELECT DISTINCT role FROM clients => ['admin', 'member', 'guest']Client.pluck(:id, :name) SELECT clients.id, clients.name FROM clients => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]

ActiveRecord 有一个类似的方法,select,比较下两者的区别:
Product.select(:id, :name) Product Load (8.5ms) SELECT "products"."id", "products"."name" FROM "products" => #<ActiveRecord::Relation [#<Product id: 1, name: "f">]> Product.pluck(:id, :name) (0.3ms) SELECT "products"."id", "products"."name" FROM "products" => [[1, "f"]]

前者显示返回 AR 实例,然后取其属性值,后者直接读取数据库记录,返回数组。
pluck 只能用在查询的最后,因为它直接返回了结果,而不是 ActiveRecord::Relation。
4.2.4.3 ids
ids 返回主键集合:
Person.ids=> SELECT id FROM people

不要被 ids 字面迷惑,它返回的是主键的集合,我们可以在 model 里设定其他字段为主键。
class Person < ActiveRecord::Base self.primary_key = "person_id"endPerson.ids=> SELECT person_id FROM people

4.2.4.4 查询记录数量
这里有四个方法,方便我们判断一个模型中的记录数量。
Client.exists?(1)Client.exists?(id: [1,2,3])Client.exists?(name: ['John', 'Sergei'])

exists?
判断记录是否存在,和它类似的方法有两个:
Client.exists? [1]Client.any? [2]Client.many? [3]

[1] 是否有记录[2] 是否至少有一条记录[3] 是否有多于一条的记录
any? 和 many? 与 exists? 不同的是,他们可以使用在 Relation 实例上,比如:
Article.where(published: true).any?Article.where(published: true).many?

还可以接收 block:
person.pets.any? do |pet| pet.group == 'cats'end=> falseperson.pets.many? do |pet| pet.group == 'dogs'end=> true

4.2.4.5 查询记录数量
下面五个方法,完全可以按照字面意义理解,并且适用于 Relation 上:
Client.countClient.average("orders_count")Client.minimum("age")Client.maximum("age")Client.sum("orders_count")

以上的例子来自 这里,闲暇的时候应该多读读这个文档,翻看源码。

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

推荐阅读更多精彩内容