- 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) 链接
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 块注释定界符“/*”和“*/”将自动添加。
会执行一些转义,但是不应该使用不可信的用户输入。
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'
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
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"
# ...
注意:在连接中加载关联会导致包含冗余数据的许多行,并且在扩展时性能很差。
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
。
# File activerecord/lib/active_record/relation/query_methods.rb, line 1574 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
extract_associated(association) 链接
从关系中提取命名的 association
。首先预加载命名的关联,然后从关系中收集各个关联记录。就像这样
account.memberships.extract_associated(:user)
# => Returns collection of User records
这是简写
account.memberships.preload(:user).collect(&:user)
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')
group(*args) 链接
允许指定分组属性
User.group(:name)
# SELECT "users".* FROM "users" GROUP BY name
返回一个数组,其中包含基于 group
属性的 distinct 记录
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">]
having(opts, *rest) 链接
允许指定 HAVING 子句。请注意,您不能在没有指定 GROUP 子句的情况下使用 HAVING。
Order.having('SUM(price) > 30').group('user_id')
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 个查询。对于每个关联,都会执行一个单独的查询,除非条件需要连接。
例如
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 个地址不同,所有地址都通过一个查询加载。
与简单的连接相比,在单独的查询中加载关联通常会导致性能提升,因为连接会导致包含冗余数据的许多行,并且在扩展时性能很差。
您还可以指定多个关联。每个关联都会导致一个额外的查询
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
已经包含了帖子,因此不再执行帖子的第二个查询。
请注意,includes
使用关联名称,而 references
需要实际的表名。
如果您通过 Hash
传递条件,则不需要明确地调用 references
,因为 where
会为您引用表。例如,以下代码将正常工作
User.includes(:posts).where(posts: { name: 'example' })
注意:条件会影响关联的双方。例如,上面的代码将只返回拥有名为“example”的帖子的用户,并且将只包含名为“example”的帖子,即使匹配的用户还有其他帖子。
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)
joins(*args) 链接
对args
执行 JOIN 操作。给定的符号应该与关联的名称匹配。
User.joins(:posts)
# SELECT "users".*
# FROM "users"
# INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
多个联接
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"
嵌套联接
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"
您可以使用字符串来自定义联接
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
来源: 显示 | 在 GitHub 上
# 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_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"
来源: 显示 | 在 GitHub 上
# 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'
来源: 显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation/query_methods.rb, line 1211 def limit(value) spawn.limit!(value) end
lock(locks = true) 链接
指定锁定设置(默认值为true
)。有关锁定的更多信息,请参见 ActiveRecord::Locking
。
来源: 显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation/query_methods.rb, line 1238 def lock(locks = true) spawn.lock!(locks) end
none() 链接
返回一个包含零条记录的可链式关系。
返回的关系实现了空 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
来源: 显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation/query_methods.rb, line 1281 def none spawn.none! end
offset(value) 链接
指定在返回行之前跳过的行数。
User.offset(10) # generated SQL has "OFFSET 10"
应与 order 一起使用。
User.offset(10).order("name ASC")
来源: 显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation/query_methods.rb, line 1227 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`
示例(对于带有 pg_hint_plan 的 PostgreSQL)
Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)")
# SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics"
来源: 显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation/query_methods.rb, line 1485 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))
来源: 显示 | 在 GitHub 上
# 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'
来源: 显示 | 在 GitHub 上
# 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 ...
来源: 显示 | 在 GitHub 上
# 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
来源: 显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation/query_methods.rb, line 1309 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
来源: 显示 | 在 GitHub 上
# 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) 链接
允许您更改先前设置的 group 语句。
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)
的简写。请注意,我们正在对整个 group 语句进行取消作用域。
来源: 显示 | 在 GitHub 上
# 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
的查询。
来源: 显示 | 在 GitHub 上
# 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 语句进行取消作用域。
来源: 显示 | 在 GitHub 上
# 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'
来源: 显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation/query_methods.rb, line 1498 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 语句)进行取消作用域。
来源: 显示 | 在 GitHub 上
# 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
来源: 显示 | 在 GitHub 上
# 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
来源: 显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation/query_methods.rb, line 1324 def strict_loading(value = true) spawn.strict_loading!(value) end
structurally_compatible?(other) 链接
检查给定的关系是否与当前关系在结构上兼容,以确定是否可以使用 and
和 or
方法而不引发错误。结构兼容定义为:它们必须作用于同一个模型,并且它们只能在 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
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation/query_methods.rb, line 1121 def structurally_compatible?(other) structurally_incompatible_values_for(other).empty? end
uniq!(name) 链接
对多个值进行去重。
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation/query_methods.rb, line 1541 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
还可以将哈希作为参数传递给 unscope 以取消作用域特定 :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) }
来源:显示 | 在 GitHub 上
# 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", "[email protected]"])
# SELECT * FROM users WHERE name = 'Joe' AND email = '[email protected]';
或者,您可以在模板中使用命名占位符,并将哈希作为数组的第二个元素传递。模板中的名称将被哈希中相应的 value 替换。
User.where(["name = :name and email = :email", { name: "Joe", email: "[email protected]" }])
# SELECT * FROM users WHERE name = 'Joe' AND email = '[email protected]';
这可以使复杂的查询代码更易读。
最后,您可以在模板中使用 sprintf 样式的 % 转义符。这与之前的方法略有不同;您有责任确保模板中的值被正确引用。值将传递给连接器进行引用,但调用者有责任确保它们在生成的 SQL 中包含在引号中。引用后,值使用与 Ruby 核心方法 Kernel::sprintf
相同的转义符插入。
User.where(["name = '%s' and email = '%s'", "Joe", "[email protected]"])
# SELECT * FROM users WHERE name = 'Joe' AND email = '[email protected]';
如果 where
使用多个参数调用,则这些参数将被视为传递为单个数组的元素。
User.where("name = :name and email = :email", { name: "Joe", email: "[email protected]" })
# SELECT * FROM users WHERE name = 'Joe' AND email = '[email protected]';
在使用字符串指定条件时,您可以使用数据库中提供的任何运算符。虽然这提供了最大的灵活性,但您也可能无意中引入对底层数据库的依赖关系。如果您的代码 предназначен для общего использования, протестируйте его с несколькими бэкендами баз данных.
哈希
where
还会接受一个哈希条件,其中键是字段,值是要搜索的值。
字段可以是符号或字符串。值可以是单个值、数组或范围。
User.where(name: "Joe", email: "[email protected]")
# SELECT * FROM users WHERE name = 'Joe' AND email = '[email protected]'
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)
Hash
条件也可以使用类似元组的语法指定。 Hash
键可以是列的数组,值是元组的数组。
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
联接
如果关系是联接的结果,则可以创建一个使用联接中任何表的条件。对于字符串和数组条件,请在条件中使用表名。
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#not
、WhereChain#missing
或 WhereChain#associated
连接。
与 WhereChain#not
连接
User.where.not(name: "Jon")
# SELECT * FROM users WHERE name != 'Jon'
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
空条件
如果条件是任何空对象,则 where
是一个无操作,并返回当前关系。
来源:显示 | 在 GitHub 上
# 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))
来源:显示 | 在 GitHub 上
# 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 posts_and_replies ON posts.in_reply_to_id = posts_and_replies.id)
# )
# SELECT * FROM posts
有关更多信息,请参见 ‘#with`。
来源:显示 | 在 GitHub 上
# 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
实例保护方法
arel_columns(columns) 链接
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation/query_methods.rb, line 1662 def arel_columns(columns) columns.flat_map do |field| case field when Symbol, String arel_column(field) when Proc field.call when Hash arel_columns_from_hash(field) else field end end end
async!() 链接
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation/query_methods.rb, line 1656 def async! @async = true self end