Spec-Zone.ru › Ruby on Rails 5.0

модуль ActiveRecord::QueryMethods

Константы

FROZEN_EMPTY_ARRAY
FROZEN_EMPTY_HASH
VALID_UNSCOPING_VALUES

Общедоступные методы экземпляра

bound_attributes() Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 101
def bound_attributes
  if limit_value && !string_containing_comma?(limit_value)
    limit_bind = Attribute.with_cast_value(
      "LIMIT".freeze,
      connection.sanitize_limit(limit_value),
      Type::Value.new,
    )
  end
  if offset_value
    offset_bind = Attribute.with_cast_value(
      "OFFSET".freeze,
      offset_value.to_i,
      Type::Value.new,
    )
  end
  connection.combine_bind_parameters(
    from_clause: from_clause.binds,
    join_clause: arel.bind_values,
    where_clause: where_clause.binds,
    having_clause: having_clause.binds,
    limit: limit_bind,
    offset: offset_bind,
  )
end
create_with(value) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 818
def create_with(value)
  spawn.create_with!(value)
end

Устанавливает атрибуты, которые будут использоваться при создании новых записей из объекта отношения.

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) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 865
def distinct(value = true)
  spawn.distinct!(value)
end

Указывает, должны ли записи быть уникальными или нет. Например:

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
Также алиасирован как: uniq
eager_load(*args) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 185
def eager_load(*args)
  check_if_method_has_arguments!(:eager_load, args)
  spawn.eager_load!(*args)
end

Принудительно загружает связанные данные выполняя LEFT OUTER JOIN на args:

User.eager_load(:posts)
# SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ...
# FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
# "users"."id"
extending(*modules, &block) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 915
def extending(*modules, &block)
  if modules.any? || block
    spawn.extending!(*modules, &block)
  else
    self
  end
end

Используется для расширения области с помощью дополнительных методов, либо через модуль, либо через предоставленный блок.

Возвращаемый объект — это отношение, которое можно дополнительно расширить.

Использование модуля

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
from(value, subquery_name = nil) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 846
def from(value, subquery_name = nil)
  spawn.from!(value, subquery_name)
end

Указывает таблицу, из которой будут извлечены записи. Например:

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

Topic.select('a.title').from(Topic.approved, :a)
# SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
group(*args) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 306
def group(*args)
  check_if_method_has_arguments!(:group, args)
  spawn.group!(*args)
end

Позволяет указать атрибут группировки:

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">]
having(opts, *rest) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 688
def having(opts, *rest)
  opts.blank? ? self : spawn.having!(opts, *rest)
end

Позволяет указать условие HAVING. Обратите внимание, что вы не можете использовать HAVING без указания условия GROUP.

Order.having('SUM(price) > 30').group('user_id')
includes(*args) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 166
def includes(*args)
  check_if_method_has_arguments!(:includes, args)
  spawn.includes!(*args)
end

Указывает отношения, которые следует включить в результирующий набор. Например:

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

позволяет получить доступ к атрибуту address модели User без выполнения дополнительного запроса. Это часто приводит к повышению производительности по сравнению с простым объединением.

Также можно указать несколько отношений, например так:

users = User.includes(:address, :friends)

Загрузка вложенных отношений возможна с помощью хэша:

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

условия

Если вы хотите добавить условия к включенным моделям, вам необходимо явно их указать. Например:

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

Будет выдавать ошибку, но это сработает:

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

Обратите внимание, что includes работает с именами ассоциаций, а references нуждается в фактическом имени таблицы.

joins(*args) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 467
def joins(*args)
  check_if_method_has_arguments!(:joins, args)
  spawn.joins!(*args)
end

Выполняет объединения с args. Указанные символы должны соответствовать именам ассоциаций.

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" "comments_posts"
#   ON "comments_posts"."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
left_joins(*args)
Псевдоним для: left_outer_joins
left_outer_joins(*args) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 484
def left_outer_joins(*args)
  check_if_method_has_arguments!(:left_outer_joins, args)

  args.compact!
  args.flatten!

  spawn.left_outer_joins!(*args)
end

Выполняет левые внешние объединения с args:

User.left_outer_joins(:posts)
=> SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
Также алиасирован как: left_joins
limit(value) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 705
def limit(value)
  spawn.limit!(value)
end

Устанавливает ограничение на количество извлекаемых записей.

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

User.limit(10).limit(20) # generated SQL has 'LIMIT 20'
lock(locks = true) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 739
def lock(locks = true)
  spawn.lock!(locks)
end

Устанавливает параметры блокировки (по умолчанию true). Более подробную информацию о блокировке см. в ActiveRecord::Locking.

none() Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 782
def none
  where("1=0").extending!(NullRelation)
end

Возвращает цепочечное отношение с нулевыми записями.

Возвращаемое отношение реализует шаблон Null Объект. Это объект с определённым поведением null и всегда возвращает пустой массив записей без запроса к базе данных.

Любое последующее условие, присоединённое к возвращаемому отношению, будет продолжать генерировать пустое отношение и не будет выполнять никаких запросов к базе данных.

Используется в случаях, когда метод или область могут вернуть ноль записей, но результат должен быть цепочечным.

Например:

@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
offset(value) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 728
def offset(value)
  spawn.offset!(value)
end

Указывает количество строк, которые следует пропустить перед возвратом строк.

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

Следует использовать с order.

User.offset(10).order("name ASC")
or(other) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 663
def or(other)
  unless other.is_a? Relation
    raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
  end

  spawn.or!(other)
end

Возвращает новое отношение, которое является логическим объединением этого отношения и отношения, переданного в качестве аргумента.

Два отношения должны быть структурно совместимы: они должны охватывать одну и ту же модель и отличаться только where (если не определён group) или having (если group присутствует). Ни одно из отношений не может иметь установленное limit, offset или distinct.

Post.where("id = 1").or(Post.where("author_id = 3"))
# SELECT `posts`.* FROM `posts`  WHERE (('id = 1' OR 'author_id = 3'))
order(*args) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 337
def order(*args)
  check_if_method_has_arguments!(:order, args)
  spawn.order!(*args)
end

Позволяет указать атрибут сортировки:

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

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

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
preload(*args) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 199
def preload(*args)
  check_if_method_has_arguments!(:preload, args)
  spawn.preload!(*args)
end

Позволяет предварительно загрузить args, аналогично includes:

User.preload(:posts)
# SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
END_OF_DOCUMENT_MARKER
readonly(value = true) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 796
def readonly(value = true)
  spawn.readonly!(value)
end

Устанавливает атрибуты только для чтения для возвращаемой связи. Если значение равно true (по умолчанию), попытка обновить запись приведет к ошибке.

users = User.readonly
users.first.save
=> ActiveRecord::ReadOnlyRecord: User is marked as readonly
references(*table_names) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 219
def references(*table_names)
  check_if_method_has_arguments!(:references, table_names)
  spawn.references!(*table_names)
end

Используется для указания того, что указанные table_names ссылаются на строку SQL и, следовательно, должны быть JOINed в любом запросе, а не загружаться отдельно. Этот метод работает только в сочетании с 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
reorder(*args) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 358
def reorder(*args)
  check_if_method_has_arguments!(:reorder, args)
  spawn.reorder!(*args)
end

Заменяет любое существующее упорядочение, определенное в связи, указанным порядком.

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'.

reverse_order() Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 936
def reverse_order
  spawn.reverse_order!
end

Инвертирует существующую строку порядка в связи.

User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
rewhere(conditions) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 649
def rewhere(conditions)
  unscope(where: conditions.keys).where(conditions)
end

Позволяет изменить ранее заданное условие 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.

select(*fields) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 271
def select(*fields)
  return super if block_given?
  raise ArgumentError, 'Call this with at least one field' if fields.empty?
  spawn._select!(*fields)
end

Работает двумя уникальными способами.

Во-первых: принимает блок, чтобы его можно было использовать так же, как и +Array#select+.

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

Это создаст массив объектов из базы данных для области видимости, преобразует их в массив и проитерируется по ним с помощью +Array#select+.

Во-вторых: изменяет оператор SELECT для запроса, чтобы извлекались только определенные поля:

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

Хотя в приведенном выше примере кажется, что этот метод возвращает массив, он фактически возвращает объект relations и к нему можно добавить другие методы запросов, такие как другие методы в ActiveRecord::QueryMethods.

Аргументом метода также может быть массив полей.

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

Вы также можете использовать одну или несколько строк, которые будут использоваться без изменений в качестве полей SELECT.

Model.select('field AS field_one', 'other_field AS field_two')
# => [#<Model id: nil, field: "value", other_field: "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
Вызывает метод родительского класса
uniq(value = true)
Псевдоним для: distinct
unscope(*args) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 408
def unscope(*args)
  check_if_method_has_arguments!(:unscope, args)
  spawn.unscope!(*args)
end

Удаляет нежелательную связь, которая уже определена в цепочке связей. Это полезно при передаче цепочек связей и желании изменить связи без перестроения всей цепочки.

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) }
where(opts = :chain, *rest) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 619
def where(opts = :chain, *rest)
  if :chain == opts
    WhereChain.new(spawn)
  elsif opts.blank?
    self
  else
    spawn.where!(opts, *rest)
  end
end

Возвращает новую связь, которая является результатом фильтрации текущей связи в соответствии с условиями в аргументах.

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)

Объединения

Если связь является результатом объединения, вы можете создать условие, которое использует любую из таблиц в объединении. Для строчных и массивно-ориентированных условий используйте имя таблицы в условии.

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, который может быть объединен с not для возврата новой связи, которая отрицает условие where.

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

См. WhereChain для получения дополнительной информации о not.

пустое условие

Если условие — это объект любого типа, относящегося к пустому значению, то where — это операция без действия, которая возвращает текущую связь.

© 2004–2018 David Heinemeier Hansson
Licensed under the MIT License.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API