跳至内容 跳至搜索
命名空间
方法
A
C
D
E
F
G
H
I
J
L
N
O
P
R
S
U
W

常量

FROZEN_EMPTY_ARRAY = [].freeze
 
FROZEN_EMPTY_HASH = {}.freeze
 
VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock, :limit, :offset, :joins, :left_outer_joins, :annotate, :includes, :eager_load, :preload, :from, :readonly, :having, :optimizer_hints, :with])
 

实例公共方法

and(other)

返回一个新关系,它是当前关系和参数关系之间的逻辑交集。

两个关系必须在结构上兼容:它们必须作用于相同的模型,并且它们仅在 where(如果没有定义 group)或 having(如果存在 group)上有所不同。

Post.where(id: [1, 2]).and(Post.where(id: [2, 3]))
# SELECT `posts`.* FROM `posts` WHERE `posts`.`id` IN (1, 2) AND `posts`.`id` IN (2, 3)
# File activerecord/lib/active_record/relation/query_methods.rb, line 1135
def and(other)
  if other.is_a?(Relation)
    spawn.and!(other)
  else
    raise ArgumentError, "You have passed #{other.class.name} object to #and. Pass an ActiveRecord::Relation object instead."
  end
end

annotate(*args)

向由此关系生成的查询添加 SQL 注释。例如

User.annotate("selecting user names").select(:name)
# SELECT "users"."name" FROM "users" /* selecting user names */

User.annotate("selecting", "user", "names").select(:name)
# SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */

SQL 块注释分隔符“/*”和“*/”将自动添加。

会执行一些转义,但是不可信的用户输入不应该被使用。

# File activerecord/lib/active_record/relation/query_methods.rb, line 1530
def annotate(*args)
  check_if_method_has_arguments!(__callee__, args)
  spawn.annotate!(*args)
end

create_with(value)

设置在使用关系对象创建新记录时要使用的属性。

users = User.where(name: 'Oscar')
users.new.name # => 'Oscar'

users = users.create_with(name: 'DHH')
users.new.name # => 'DHH'

您可以将 nil 传递给 create_with 来重置属性

users = users.create_with(nil)
users.new.name # => 'Oscar'
# File activerecord/lib/active_record/relation/query_methods.rb, line 1347
def create_with(value)
  spawn.create_with!(value)
end

distinct(value = true)

指定记录是否应该是唯一的。例如

User.select(:name)
# Might return two records with the same name

User.select(:name).distinct
# Returns 1 record per distinct name

User.select(:name).distinct.distinct(false)
# You can also remove the uniqueness
# File activerecord/lib/active_record/relation/query_methods.rb, line 1411
def distinct(value = true)
  spawn.distinct!(value)
end

eager_load(*args)

指定要使用 LEFT OUTER JOIN 预加载的关联 args。执行单个查询连接所有指定的关联。例如

users = User.eager_load(:address).limit(5)
users.each do |user|
  user.address.city
end

# SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... FROM "users"
#   LEFT OUTER JOIN "addresses" ON "addresses"."id" = "users"."address_id"
#   LIMIT 5

而不是用 5 次单独的查询加载 5 个地址,所有地址都用单个连接查询加载。

可以使用哈希和数组加载多个嵌套关联,类似于 includes

User.eager_load(:address, friends: [:address, :followers])
# SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... FROM "users"
#   LEFT OUTER JOIN "addresses" ON "addresses"."id" = "users"."address_id"
#   LEFT OUTER JOIN "friends" ON "friends"."user_id" = "users"."id"
#   ...

注意:在 JOIN 中加载关联可能会导致许多包含重复数据的行,并且在规模化处理时性能较差。

# File activerecord/lib/active_record/relation/query_methods.rb, line 290
def eager_load(*args)
  check_if_method_has_arguments!(__callee__, args)
  spawn.eager_load!(*args)
end

excluding(*records)

从结果关系中排除指定的记录(或记录集合)。例如

Post.excluding(post)
# SELECT "posts".* FROM "posts" WHERE "posts"."id" != 1

Post.excluding(post_one, post_two)
# SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)

Post.excluding(Post.drafts)
# SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (3, 4, 5)

这也可以在关联上调用。如上例所示,可以指定单个记录或其集合

post = Post.find(1)
comment = Comment.find(2)
post.comments.excluding(comment)
# SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 AND "comments"."id" != 2

这是 .where.not(id: post.id).where.not(id: [post_one.id, post_two.id]) 的简写。

如果未指定记录,或者如果集合中的任何记录(如果传递了集合)不是关系正在作用的模型实例,则会引发 ArgumentError

也别名: without
# File activerecord/lib/active_record/relation/query_methods.rb, line 1575
def excluding(*records)
  relations = records.extract! { |element| element.is_a?(Relation) }
  records.flatten!(1)
  records.compact!

  unless records.all?(model) && relations.all? { |relation| relation.model == model }
    raise ArgumentError, "You must only pass a single or collection of #{model.name} objects to ##{__callee__}."
  end

  spawn.excluding!(records + relations.flat_map(&:ids))
end

extending(*modules, &block)

用于通过模块或提供的块将其他方法扩展到作用域。

返回的对象是一个关系,可以进一步扩展。

使用模块

module Pagination
  def page(number)
    # pagination code goes here
  end
end

scope = Model.all.extending(Pagination)
scope.page(params[:page])

您还可以传递一个模块列表

scope = Model.all.extending(Pagination, SomethingElse)

使用块

scope = Model.all.extending do
  def page(number)
    # pagination code goes here
  end
end
scope.page(params[:page])

您也可以使用块和模块列表

scope = Model.all.extending(Pagination) do
  def per_page(number)
    # pagination code goes here
  end
end
# File activerecord/lib/active_record/relation/query_methods.rb, line 1457
def extending(*modules, &block)
  if modules.any? || block
    spawn.extending!(*modules, &block)
  else
    self
  end
end

extract_associated(association)

从关系中提取命名的 association。命名的关联首先被预加载,然后从关系中收集各个关联记录。如下所示

account.memberships.extract_associated(:user)
# => Returns collection of User records

这是以下内容的简写:

account.memberships.preload(:user).collect(&:user)
# File activerecord/lib/active_record/relation/query_methods.rb, line 341
def extract_associated(association)
  preload(association).collect(&association)
end

from(value, subquery_name = nil)

指定记录将从哪个表获取。例如

Topic.select('title').from('posts')
# SELECT title FROM posts

可以接受其他关系对象。例如

Topic.select('title').from(Topic.approved)
# SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery

传递第二个参数(字符串或符号),为 SQL from 子句创建别名。否则使用别名“subquery”

Topic.select('a.title').from(Topic.approved, :a)
# SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a

它不会向 SQL from 子句添加多个参数。最后链接的 from 是使用的那个

Topic.select('title').from(Topic.approved).from(Topic.inactive)
# SELECT title FROM (SELECT topics.* FROM topics WHERE topics.active = 'f') subquery

对于 SQL from 子句的多个参数,您可以传递一个包含 SQL from 列表确切元素的字符串

color = "red"
Color
  .from("colors c, JSONB_ARRAY_ELEMENTS(colored_things) AS colorvalues(colorvalue)")
  .where("colorvalue->>'color' = ?", color)
  .select("c.*").to_a
# SELECT c.*
# FROM colors c, JSONB_ARRAY_ELEMENTS(colored_things) AS colorvalues(colorvalue)
# WHERE (colorvalue->>'color' = 'red')
# File activerecord/lib/active_record/relation/query_methods.rb, line 1392
def from(value, subquery_name = nil)
  spawn.from!(value, subquery_name)
end

group(*args)

允许指定分组属性

User.group(:name)
# SELECT "users".* FROM "users" GROUP BY name

返回一个基于 group 属性的去重记录数组

User.select([:id, :name])
# => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo">]

User.group(:name)
# => [#<User id: 3, name: "Foo", ...>, #<User id: 2, name: "Oscar", ...>]

User.group('name AS grouped_name, age')
# => [#<User id: 3, name: "Foo", age: 21, ...>, #<User id: 2, name: "Oscar", age: 21, ...>, #<User id: 5, name: "Foo", age: 23, ...>]

也支持传入属性数组进行分组。

User.select([:id, :first_name]).group(:id, :first_name).first(3)
# => [#<User id: 1, first_name: "Bill">, #<User id: 2, first_name: "Earl">, #<User id: 3, first_name: "Beto">]
# File activerecord/lib/active_record/relation/query_methods.rb, line 573
def group(*args)
  check_if_method_has_arguments!(__callee__, args)
  spawn.group!(*args)
end

having(opts, *rest)

允许指定 HAVING 子句。请注意,您不能在没有同时指定 GROUP 子句的情况下使用 HAVING。

Order.having('SUM(price) > 30').group('user_id')
# File activerecord/lib/active_record/relation/query_methods.rb, line 1197
def having(opts, *rest)
  opts.blank? ? self : spawn.having!(opts, *rest)
end

in_order_of(column, values, filter: true)

根据给定的 column 应用 ORDER BY 子句,并根据一组特定的 values 进行排序和过滤。

User.in_order_of(:id, [1, 5, 3])
# SELECT "users".* FROM "users"
#   WHERE "users"."id" IN (1, 5, 3)
#   ORDER BY CASE
#     WHEN "users"."id" = 1 THEN 1
#     WHEN "users"."id" = 5 THEN 2
#     WHEN "users"."id" = 3 THEN 3
#   END ASC

column 可以指向枚举列;生成的实际查询可能因数据库适配器和列定义而异。

class Conversation < ActiveRecord::Base
  enum :status, [ :active, :archived ]
end

Conversation.in_order_of(:status, [:archived, :active])
# SELECT "conversations".* FROM "conversations"
#   WHERE "conversations"."status" IN (1, 0)
#   ORDER BY CASE
#     WHEN "conversations"."status" = 1 THEN 1
#     WHEN "conversations"."status" = 0 THEN 2
#   END ASC

values 也可以包含 nil

Conversation.in_order_of(:status, [nil, :archived, :active])
# SELECT "conversations".* FROM "conversations"
#   WHERE ("conversations"."status" IN (1, 0) OR "conversations"."status" IS NULL)
#   ORDER BY CASE
#     WHEN "conversations"."status" IS NULL THEN 1
#     WHEN "conversations"."status" = 1 THEN 2
#     WHEN "conversations"."status" = 0 THEN 3
#   END ASC

filter 可以设置为 false 以包含所有结果,而不是仅包含 values 中指定的结果。

Conversation.in_order_of(:status, [:archived, :active], filter: false)
# SELECT "conversations".* FROM "conversations"
#   ORDER BY CASE
#     WHEN "conversations"."status" = 1 THEN 1
#     WHEN "conversations"."status" = 0 THEN 2
#     ELSE 3
#   END ASC
# File activerecord/lib/active_record/relation/query_methods.rb, line 717
def in_order_of(column, values, filter: true)
  model.disallow_raw_sql!([column], permit: model.adapter_class.column_name_with_order_matcher)
  return spawn.none! if values.empty?

  references = column_references([column])
  self.references_values |= references unless references.empty?

  values = values.map { |value| model.type_caster.type_cast_for_database(column, value) }
  arel_column = column.is_a?(Arel::Nodes::SqlLiteral) ? column : order_column(column.to_s)

  scope = spawn.order!(build_case_for_value_position(arel_column, values, filter: filter))

  if filter
    where_clause =
      if values.include?(nil)
        arel_column.in(values.compact).or(arel_column.eq(nil))
      else
        arel_column.in(values)
      end

    scope = scope.where!(where_clause)
  end

  scope
end

includes(*args)

指定要预加载的关联 args 以防止 N+1 查询。除非条件需要 JOIN,否则将为每个关联执行单独的查询。

例如

users = User.includes(:address).limit(5)
users.each do |user|
  user.address.city
end

# SELECT "users".* FROM "users" LIMIT 5
# SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)

而不是用 5 次单独的查询加载 5 个地址,所有地址都用单个查询加载。

在单独的查询中加载关联通常比简单的 JOIN 能带来更好的性能,因为 JOIN 可能会导致许多包含重复数据的行,并且在规模化处理时性能较差。

您也可以指定多个关联。每个关联都将导致一个额外的查询

User.includes(:address, :friends).to_a
# SELECT "users".* FROM "users"
# SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
# SELECT "friends".* FROM "friends" WHERE "friends"."user_id" IN (1,2,3,4,5)

可以使用哈希加载嵌套关联

User.includes(:address, friends: [:address, :followers])

条件

如果您想为包含的模型添加字符串条件,您必须明确引用它们。例如

User.includes(:posts).where('posts.name = ?', 'example').to_a

将引发错误,但这样做是可行的

User.includes(:posts).where('posts.name = ?', 'example').references(:posts).to_a
# SELECT "users"."id" AS t0_r0, ... FROM "users"
#   LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
#   WHERE "posts"."name" = ?  [["name", "example"]]

由于 LEFT OUTER JOIN 已经包含了 posts,因此不再执行 posts 的第二次查询。

请注意,includes 使用关联名称,而 references 需要实际的表名。

如果您通过 Hash 传递条件,则无需显式调用 references,因为 where 会为您引用表。例如,这样做是正确的

User.includes(:posts).where(posts: { name: 'example' })

注意:条件会影响关联的双方。例如,上面的代码将只返回名称为“example”的用户,并且仅包含名称为“example”的帖子,即使匹配的用户还有其他额外的帖子。

# File activerecord/lib/active_record/relation/query_methods.rb, line 250
def includes(*args)
  check_if_method_has_arguments!(__callee__, args)
  spawn.includes!(*args)
end

invert_where()

允许您反转整个 where 子句,而不是手动应用条件。

class User
  scope :active, -> { where(accepted: true, locked: false) }
end

User.where(accepted: true)
# WHERE `accepted` = 1

User.where(accepted: true).invert_where
# WHERE `accepted` != 1

User.active
# WHERE `accepted` = 1 AND `locked` = 0

User.active.invert_where
# WHERE NOT (`accepted` = 1 AND `locked` = 0)

请注意,这会在调用 invert_where 之前反转所有条件。

class User
  scope :active, -> { where(accepted: true, locked: false) }
  scope :inactive, -> { active.invert_where } # Do not attempt it
end

# It also inverts `where(role: 'admin')` unexpectedly.
User.where(role: 'admin').inactive
# WHERE NOT (`role` = 'admin' AND `accepted` = 1 AND `locked` = 0)
# File activerecord/lib/active_record/relation/query_methods.rb, line 1101
def invert_where
  spawn.invert_where!
end

joins(*args)

args 执行 JOIN。给定的符号应与关联的名称匹配。

User.joins(:posts)
# SELECT "users".*
# FROM "users"
# INNER JOIN "posts" ON "posts"."user_id" = "users"."id"

多个 JOIN

User.joins(:posts, :account)
# SELECT "users".*
# FROM "users"
# INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
# INNER JOIN "accounts" ON "accounts"."id" = "users"."account_id"

嵌套 JOIN

User.joins(posts: [:comments])
# SELECT "users".*
# FROM "users"
# INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
# INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"

您可以使用字符串来自定义 JOIN

User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id")
# SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
# File activerecord/lib/active_record/relation/query_methods.rb, line 868
def joins(*args)
  check_if_method_has_arguments!(__callee__, args)
  spawn.joins!(*args)
end

left_joins(*args)

left_outer_joins(*args)

args 执行 LEFT OUTER JOIN

User.left_outer_joins(:posts)
# SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
也别名: left_joins
# File activerecord/lib/active_record/relation/query_methods.rb, line 883
def left_outer_joins(*args)
  check_if_method_has_arguments!(__callee__, args)
  spawn.left_outer_joins!(*args)
end

limit(value)

指定检索记录的数量限制。

User.limit(10) # generated SQL has 'LIMIT 10'

User.limit(10).limit(20) # generated SQL has 'LIMIT 20'
# File activerecord/lib/active_record/relation/query_methods.rb, line 1211
def limit(value)
  spawn.limit!(value)
end

lock(locks = true)

指定锁定设置(默认为 true)。有关锁定的更多信息,请参阅 ActiveRecord::Locking

# File activerecord/lib/active_record/relation/query_methods.rb, line 1239
def lock(locks = true)
  spawn.lock!(locks)
end

none()

返回一个可链式的、零记录的关系。

返回的关系实现了 Null Object 模式。它是一个具有已定义空行为的对象,并且始终返回一个空记录数组,而无需查询数据库。

链在此关系之后的任何后续条件将继续生成一个空关系,并且不会触发任何数据库查询。

用于方法或作用域可能返回零记录但结果需要可链式的情况。

例如

@posts = current_user.visible_posts.where(name: params[:name])
# the visible_posts method is expected to return a chainable Relation

def visible_posts
  case role
  when 'Country Manager'
    Post.where(country: country)
  when 'Reviewer'
    Post.published
  when 'Bad User'
    Post.none # It can't be chained if [] is returned.
  end
end
# File activerecord/lib/active_record/relation/query_methods.rb, line 1282
def none
  spawn.none!
end

offset(value)

指定在返回行之前要跳过的行数。

User.offset(10) # generated SQL has "OFFSET 10"

应与 order 一起使用。

User.offset(10).order("name ASC")
# File activerecord/lib/active_record/relation/query_methods.rb, line 1228
def offset(value)
  spawn.offset!(value)
end

optimizer_hints(*args)

在 SELECT 语句中使用指定的优化器提示。

示例(针对 MySQL)

Topic.optimizer_hints("MAX_EXECUTION_TIME(50000)", "NO_INDEX_MERGE(topics)")
# SELECT /*+ MAX_EXECUTION_TIME(50000) NO_INDEX_MERGE(topics) */ `topics`.* FROM `topics`

示例(针对 PostgreSQL 和 pg_hint_plan)

Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)")
# SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics"
# File activerecord/lib/active_record/relation/query_methods.rb, line 1486
def optimizer_hints(*args)
  check_if_method_has_arguments!(__callee__, args)
  spawn.optimizer_hints!(*args)
end

or(other)

返回一个新关系,它是当前关系和参数关系之间的逻辑并集。

两个关系必须在结构上兼容:它们必须作用于相同的模型,并且它们仅在 where(如果没有定义 group)或 having(如果存在 group)上有所不同。

Post.where("id = 1").or(Post.where("author_id = 3"))
# SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
# File activerecord/lib/active_record/relation/query_methods.rb, line 1167
def or(other)
  if other.is_a?(Relation)
    if @none
      other.spawn
    else
      spawn.or!(other)
    end
  else
    raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
  end
end

order(*args)

对查询应用 ORDER BY 子句。

order 接受以下几种格式的参数。

符号

符号表示您要排序结果的列名。

User.order(:name)
# SELECT "users".* FROM "users" ORDER BY "users"."name" ASC

默认情况下,排序是升序的。如果您想要降序,可以将列名符号映射到 :desc

User.order(email: :desc)
# SELECT "users".* FROM "users" ORDER BY "users"."email" DESC

可以以这种方式传递多个列,它们将按指定的顺序应用。

User.order(:name, email: :desc)
# SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC

字符串

字符串直接传递给数据库,允许您指定简单的 SQL 表达式。

这可能成为 SQL 注入的来源,因此只允许由纯列名和简单 function(column_name) 表达式(带有可选的 ASC/DESC 修饰符)组成的字符串。

User.order('name')
# SELECT "users".* FROM "users" ORDER BY name

User.order('name DESC')
# SELECT "users".* FROM "users" ORDER BY name DESC

User.order('name DESC, email')
# SELECT "users".* FROM "users" ORDER BY name DESC, email

Arel

如果您需要传递经过验证对数据库安全的复杂表达式,您可以使用 Arel

User.order(Arel.sql('end_date - start_date'))
# SELECT "users".* FROM "users" ORDER BY end_date - start_date

自定义查询语法,例如 PostgreSQL 的 JSON 列,可以通过这种方式支持。

User.order(Arel.sql("payload->>'kind'"))
# SELECT "users".* FROM "users" ORDER BY payload->>'kind'
# File activerecord/lib/active_record/relation/query_methods.rb, line 656
def order(*args)
  check_if_method_has_arguments!(__callee__, args) do
    sanitize_order_arguments(args)
  end
  spawn.order!(*args)
end

preload(*args)

指定要使用单独查询预加载的关联 args。将为每个关联执行单独的查询。

users = User.preload(:address).limit(5)
users.each do |user|
  user.address.city
end

# SELECT "users".* FROM "users" LIMIT 5
# SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)

而不是用 5 次单独的查询加载 5 个地址,所有地址都用单独的查询加载。

可以使用哈希和数组加载多个嵌套关联,类似于 includes

User.preload(:address, friends: [:address, :followers])
# SELECT "users".* FROM "users"
# SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
# SELECT "friends".* FROM "friends" WHERE "friends"."user_id" IN (1,2,3,4,5)
# SELECT ...
# File activerecord/lib/active_record/relation/query_methods.rb, line 322
def preload(*args)
  check_if_method_has_arguments!(__callee__, args)
  spawn.preload!(*args)
end

readonly(value = true)

将关系标记为只读。尝试更新记录将导致错误。

users = User.readonly
users.first.save
# => ActiveRecord::ReadOnlyRecord: User is marked as readonly

要使只读关系可写,请传递 false

users.readonly(false)
users.first.save
# => true
# File activerecord/lib/active_record/relation/query_methods.rb, line 1310
def readonly(value = true)
  spawn.readonly!(value)
end

references(*table_names)

用于指示给定的 table_names 被 SQL 字符串引用,因此应该在任何查询中被 +JOIN+,而不是单独加载。此方法仅与 includes 结合使用。有关详细信息,请参阅 includes

User.includes(:posts).where("posts.name = 'foo'")
# Doesn't JOIN the posts table, resulting in an error.

User.includes(:posts).where("posts.name = 'foo'").references(:posts)
# Query now knows the string references posts, so adds a JOIN
# File activerecord/lib/active_record/relation/query_methods.rb, line 355
def references(*table_names)
  check_if_method_has_arguments!(__callee__, table_names)
  spawn.references!(*table_names)
end

regroup(*args)

允许您更改先前设置的分组语句。

Post.group(:title, :body)
# SELECT `posts`.`*` FROM `posts` GROUP BY `posts`.`title`, `posts`.`body`

Post.group(:title, :body).regroup(:title)
# SELECT `posts`.`*` FROM `posts` GROUP BY `posts`.`title`

这是 unscope(:group).group(fields) 的简写。请注意,我们正在取消对整个分组语句的作用域。

# File activerecord/lib/active_record/relation/query_methods.rb, line 593
def regroup(*args)
  check_if_method_has_arguments!(__callee__, args)
  spawn.regroup!(*args)
end

reorder(*args)

用指定的顺序替换关系上已定义的任何现有顺序。

User.order('email DESC').reorder('id ASC') # generated SQL has 'ORDER BY id ASC'

对同一关系后续调用 order 将被追加。例如

User.order('email DESC').reorder('id ASC').order('name ASC')

生成一个带有 ORDER BY id ASC, name ASC 的查询。

# File activerecord/lib/active_record/relation/query_methods.rb, line 752
def reorder(*args)
  check_if_method_has_arguments!(__callee__, args) do
    sanitize_order_arguments(args)
  end
  spawn.reorder!(*args)
end

reselect(*args)

允许您更改先前设置的 select 语句。

Post.select(:title, :body)
# SELECT `posts`.`title`, `posts`.`body` FROM `posts`

Post.select(:title, :body).reselect(:created_at)
# SELECT `posts`.`created_at` FROM `posts`

这是 unscope(:select).select(fields) 的简写。请注意,我们正在取消对整个 select 语句的作用域。

# File activerecord/lib/active_record/relation/query_methods.rb, line 541
def reselect(*args)
  check_if_method_has_arguments!(__callee__, args)
  args = process_select_args(args)
  spawn.reselect!(*args)
end

reverse_order()

反转关系上现有的 order 子句。

User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
# File activerecord/lib/active_record/relation/query_methods.rb, line 1499
def reverse_order
  spawn.reverse_order!
end

rewhere(conditions)

允许您更改给定属性的先前设置的 where 条件,而不是追加到该条件。

Post.where(trashed: true).where(trashed: false)
# WHERE `trashed` = 1 AND `trashed` = 0

Post.where(trashed: true).rewhere(trashed: false)
# WHERE `trashed` = 0

Post.where(active: true).where(trashed: true).rewhere(trashed: false)
# WHERE `active` = 1 AND `trashed` = 0

这是 unscope(where: conditions.keys).where(conditions) 的简写。请注意,与 reorder 不同,我们只取消了命名条件的范围,而不是整个 where 语句。

# File activerecord/lib/active_record/relation/query_methods.rb, line 1061
def rewhere(conditions)
  return unscope(:where) if conditions.nil?

  scope = spawn
  where_clause = scope.build_where_clause(conditions)

  scope.unscope!(where: where_clause.extract_attributes)
  scope.where_clause += where_clause
  scope
end

select(*fields)

有两种独特的工作方式。

第一种:采用块,因此可以像 Array#select 一样使用。

Model.all.select { |m| m.field == value }

这将从数据库中为作用域构建一个对象数组,将其转换为数组并使用 Array#select 进行迭代。

第二种:修改查询的 SELECT 语句,以便只检索某些字段

Model.select(:field)
# => [#<Model id: nil, field: "value">]

尽管在上面的例子中,此方法看起来返回一个数组,但它实际上返回一个关系对象,并且可以附加其他查询方法,例如 ActiveRecord::QueryMethods 中的其他方法。

该方法的参数也可以是字段数组。

Model.select(:field, :other_field, :and_one_more)
# => [#<Model id: nil, field: "value", other_field: "value", and_one_more: "value">]

参数也可以是字段和别名的哈希。

Model.select(models: { field: :alias, other_field: :other_alias })
# => [#<Model id: nil, alias: "value", other_alias: "value">]

Model.select(models: [:field, :other_field])
# => [#<Model id: nil, field: "value", other_field: "value">]

您还可以使用一个或多个字符串,这些字符串将原样用作 SELECT 字段。

Model.select('field AS field_one', 'other_field AS field_two')
# => [#<Model id: nil, field_one: "value", field_two: "value">]

如果指定了别名,则结果对象可以访问它

Model.select('field AS field_one').first.field_one
# => "value"

访问没有通过 select 检索除 id 之外的字段的对象属性将引发 ActiveModel::MissingAttributeError

Model.select(:field).first.other_field
# => ActiveModel::MissingAttributeError: missing attribute 'other_field' for Model
# File activerecord/lib/active_record/relation/query_methods.rb, line 413
def select(*fields)
  if block_given?
    if fields.any?
      raise ArgumentError, "`select' with block doesn't take arguments."
    end

    return super()
  end

  check_if_method_has_arguments!(__callee__, fields, "Call `select' with at least one field.")

  fields = process_select_args(fields)
  spawn._select!(*fields)
end

strict_loading(value = true)

将返回的关系设置为 strict_loading 模式。如果记录尝试延迟加载关联,这将引发错误。

user = User.strict_loading.first
user.comments.to_a
# => ActiveRecord::StrictLoadingViolationError
# File activerecord/lib/active_record/relation/query_methods.rb, line 1325
def strict_loading(value = true)
  spawn.strict_loading!(value)
end

structurally_compatible?(other)

检查给定关系是否与当前关系在结构上兼容,以确定是否可以不引发错误地使用 andor 方法。结构兼容的定义是:它们必须作用于相同的模型,并且它们仅在 where(如果没有定义 group)或 having(如果存在 group)上有所不同。

Post.where("id = 1").structurally_compatible?(Post.where("author_id = 3"))
# => true

Post.joins(:comments).structurally_compatible?(Post.where("id = 1"))
# => false
# File activerecord/lib/active_record/relation/query_methods.rb, line 1121
def structurally_compatible?(other)
  structurally_incompatible_values_for(other).empty?
end

uniq!(name)

对多个值进行去重。

# File activerecord/lib/active_record/relation/query_methods.rb, line 1542
def uniq!(name)
  if values = @values[name]
    values.uniq! if values.is_a?(Array) && !values.empty?
  end
  self
end

unscope(*args)

删除已定义在关系链上的不需要的关系。这在传递关系链并希望修改关系而不必重建整个链时很有用。

User.order('email DESC').unscope(:order) == User.all

方法参数是符号,对应于应取消作用域的方法的名称。有效参数在 VALID_UNSCOPING_VALUES 中给出。该方法也可以用多个参数调用。例如

User.order('email DESC').select('id').where(name: "John")
    .unscope(:order, :select, :where) == User.all

您还可以将哈希作为参数传递,以取消特定 :where 值的范围。这是通过传递一个具有单个键值对的哈希来完成的。键应该是 :where,值应该是要取消作用域的 where 值。例如

User.where(name: "John", active: true).unscope(where: :name)
    == User.where(active: true)

此方法类似于 except,但与 except 不同,它在合并时会持久存在

User.order('email').merge(User.except(:order))
    == User.order('email')

User.order('email').merge(User.unscope(:order))
    == User.all

这意味着它可以在关联定义中使用

has_many :comments, -> { unscope(where: :trashed) }
# File activerecord/lib/active_record/relation/query_methods.rb, line 806
def unscope(*args)
  check_if_method_has_arguments!(__callee__, args)
  spawn.unscope!(*args)
end

where(*args)

返回一个新关系,该关系是根据参数中的条件过滤当前关系的结果。

where 接受以下几种格式的条件。在下面的示例中,生成的 SQL 作为说明;实际生成的查询可能因数据库适配器而异。

字符串

单个字符串,没有其他参数,作为 SQL 片段传递给查询构造函数,并在查询的 where 子句中使用。

Client.where("orders_count = '2'")
# SELECT * from clients where orders_count = '2';

请注意,从用户输入构建自己的字符串可能会使您的应用程序容易受到注入攻击(如果未正确处理)。作为替代方案,建议使用以下方法之一。

数组

如果传递数组,则数组的第一个元素被视为模板,其余元素被插入到模板中以生成条件。Active Record 负责构建查询以避免注入攻击,并在需要时将 Ruby 类型转换为数据库类型。元素按其出现的顺序插入到字符串中。

User.where(["name = ? and email = ?", "Joe", "joe@example.com"])
# SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';

或者,您可以使用模板中的命名占位符,并将哈希作为数组的第二个元素传递。模板中的名称将被哈希中的相应值替换。

User.where(["name = :name and email = :email", { name: "Joe", email: "joe@example.com" }])
# SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';

这可以使复杂查询中的代码更具可读性。

最后,您可以使用类似 sprintf 的 % 匹配符。这与前面的方法略有不同;您负责确保模板中的值被正确引用。值将传递给连接器进行引用,但调用者负责确保它们在生成的 SQL 中被包含在引号内。引用后,将使用与 Ruby 核心方法 Kernel::sprintf 相同的匹配符插入值。

User.where(["name = '%s' and email = '%s'", "Joe", "joe@example.com"])
# SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';

如果用多个参数调用 where,这些参数将被视为传递到单个数组中的元素。

User.where("name = :name and email = :email", { name: "Joe", email: "joe@example.com" })
# SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';

使用字符串指定条件时,您可以使用数据库提供的任何运算符。虽然这提供了最大的灵活性,但您也可能无意中引入对底层数据库的依赖。如果您的代码旨在通用,请使用多种数据库后端进行测试。

哈希

where 也接受哈希条件,其中键是字段,值是要搜索的值。

字段可以是符号或字符串。值可以是单个值、数组或范围。

User.where(name: "Joe", email: "joe@example.com")
# SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'

User.where(name: ["Alice", "Bob"])
# SELECT * FROM users WHERE name IN ('Alice', 'Bob')

User.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight)
# SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000')

对于 belongs_to 关系,如果值为 ActiveRecord 对象,则可以使用关联键来指定模型。

author = Author.find(1)

# The following queries will be equivalent:
Post.where(author: author)
Post.where(author_id: author)

这对于多态 belongs_to 关系也适用

treasure = Treasure.create(name: 'gold coins')
treasure.price_estimates << PriceEstimate.create(price: 125)

# The following queries will be equivalent:
PriceEstimate.where(estimate_of: treasure)
PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)

哈希条件也可以以类似元组的语法指定。哈希键可以是列的数组,值为元组的数组。

Article.where([:author_id, :id] => [[15, 1], [15, 2]])
# SELECT * FROM articles WHERE author_id = 15 AND id = 1 OR author_id = 15 AND id = 2

JOIN

如果关系是 JOIN 的结果,您可以在 JOIN 的任何表上创建条件。对于字符串和数组条件,请在条件中使用表名。

User.joins(:posts).where("posts.created_at < ?", Time.now)

对于哈希条件,您可以使用表名作为键,也可以使用子哈希。

User.joins(:posts).where("posts.published" => true)
User.joins(:posts).where(posts: { published: true })

无参数

如果未传递参数,where 将返回 WhereChain 的新实例,该实例可以与 WhereChain#notWhereChain#missingWhereChain#associated 链接。

WhereChain#not 链接

User.where.not(name: "Jon")
# SELECT * FROM users WHERE name != 'Jon'

WhereChain#associated 链接

Post.where.associated(:author)
# SELECT "posts".* FROM "posts"
# INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
# WHERE "authors"."id" IS NOT NULL

WhereChain#missing 链接

Post.where.missing(:author)
# SELECT "posts".* FROM "posts"
# LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
# WHERE "authors"."id" IS NULL

空条件

如果条件是任何空的(blank-ish)对象,那么 where 将什么都不做并返回当前关系。

# File activerecord/lib/active_record/relation/query_methods.rb, line 1033
def where(*args)
  if args.empty?
    WhereChain.new(spawn)
  elsif args.length == 1 && args.first.blank?
    self
  else
    spawn.where!(*args)
  end
end

with(*args)

添加一个公共表表达式 (CTE),您可以在另一个 SELECT 语句中引用它。

注意:CTE 仅在 MySQL 8.0 及更高版本中支持。您将无法在 MySQL 5.7 中使用 CTE。

Post.with(posts_with_tags: Post.where("tags_count > ?", 0))
# => ActiveRecord::Relation
# WITH posts_with_tags AS (
#   SELECT * FROM posts WHERE (tags_count > 0)
# )
# SELECT * FROM posts

您也可以传递一个子查询数组以 +UNION ALL+ 连接。

Post.with(posts_with_tags_or_comments: [Post.where("tags_count > ?", 0), Post.where("comments_count > ?", 0)])
# => ActiveRecord::Relation
# WITH posts_with_tags_or_comments AS (
#  (SELECT * FROM posts WHERE (tags_count > 0))
#  UNION ALL
#  (SELECT * FROM posts WHERE (comments_count > 0))
# )
# SELECT * FROM posts

定义公共表表达式后,您可以使用自定义的 FROM 值或 JOIN 来引用它。

Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).from("posts_with_tags AS posts")
# => ActiveRecord::Relation
# WITH posts_with_tags AS (
#  SELECT * FROM posts WHERE (tags_count > 0)
# )
# SELECT * FROM posts_with_tags AS posts

Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).joins("JOIN posts_with_tags ON posts_with_tags.id = posts.id")
# => ActiveRecord::Relation
# WITH posts_with_tags AS (
#   SELECT * FROM posts WHERE (tags_count > 0)
# )
# SELECT * FROM posts JOIN posts_with_tags ON posts_with_tags.id = posts.id

建议将查询作为 ActiveRecord::Relation 传递。如果无法做到这一点,并且您已验证其对数据库是安全的,则可以使用 Arel 以 SQL 字面量形式传递。

Post.with(popular_posts: Arel.sql("... complex sql to calculate posts popularity ..."))

应格外小心以避免 SQL 注入漏洞。此方法不应用于包含未经验证输入的不安全值。

要添加多个 CTE,只需传递多个键值对

Post.with(
  posts_with_comments: Post.where("comments_count > ?", 0),
  posts_with_tags: Post.where("tags_count > ?", 0)
)

或链接多个 .with 调用

Post
  .with(posts_with_comments: Post.where("comments_count > ?", 0))
  .with(posts_with_tags: Post.where("tags_count > ?", 0))
# File activerecord/lib/active_record/relation/query_methods.rb, line 493
def with(*args)
  raise ArgumentError, "ActiveRecord::Relation#with does not accept a block" if block_given?
  check_if_method_has_arguments!(__callee__, args)
  spawn.with!(*args)
end

with_recursive(*args)

添加一个递归公共表表达式 (CTE),您可以在另一个 SELECT 语句中引用它。

Post.with_recursive(post_and_replies: [Post.where(id: 42), Post.joins('JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id')])
# => ActiveRecord::Relation
# WITH RECURSIVE post_and_replies AS (
#   (SELECT * FROM posts WHERE id = 42)
#   UNION ALL
#   (SELECT * FROM posts JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id)
# )
# SELECT * FROM posts

有关更多信息,请参阅 ‘#with`。

# File activerecord/lib/active_record/relation/query_methods.rb, line 518
def with_recursive(*args)
  check_if_method_has_arguments!(__callee__, args)
  spawn.with_recursive!(*args)
end

without(*records)

别名: excluding