Skip to Content Skip to Search
Namespace
Methods
A
B
C
D
E
F
G
H
I
J
L
N
O
P
Q
R
S
T
U
V
W

Constants

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

Instance Public methods

and(other)

Returns a new relation, which is the logical intersection of this relation and the one passed as an argument.

The two relations must be structurally compatible: they must be scoping the same model, and they must differ only by where (if no group has been defined) or having (if a group is present).

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 1431
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)

Adds an SQL comment to queries generated from this relation. For example:

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 */

The SQL block comment delimiters, “/*” and “*/”, will be added automatically.

Some escaping is performed, however untrusted user input should not be used.

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

arel_column(field, references: true)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2337
def arel_column(field, references: true)
  field = field.name if is_symbol = field.is_a?(Symbol)

  field = model.attribute_aliases[field] || field
  from = from_clause.name || from_clause.value

  if model.columns_hash.key?(field) && (!from || table_name_matches?(from))
    table[field]
  elsif /\A(?<table>(?:\w+\.)?\w+)\.(?<column>\w+)\z/ =~ field
    arel_column_with_table(table, column, references: references)
  elsif block_given?
    yield field
  elsif Arel.arel_node?(field)
    field
  elsif is_symbol
    Arel.sql(model.adapter_class.quote_table_name(field), retryable: true)
  else
    Arel.sql(field)
  end
end

arel_column_aliases_from_hash(fields)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2644
def arel_column_aliases_from_hash(fields)
  fields.flat_map do |key, columns_aliases|
    table_name = key.is_a?(Symbol) ? key.name : key
    case columns_aliases
    when Hash
      columns_aliases.map do |column, column_alias|
        arel_column_with_table(table_name, column)
          .as(model.adapter_class.quote_column_name(column_alias.to_s))
      end
    when Array
      columns_aliases.map do |column|
        arel_column_with_table(table_name, column)
      end
    when String, Symbol
      arel_column(key)
        .as(model.adapter_class.quote_column_name(columns_aliases.to_s))
    end
  end
end

arel_column_with_table(table_name, column_name, references: true)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2325
def arel_column_with_table(table_name, column_name, references: true)
  self.references_values |= [Arel.sql(table_name, retryable: true)] if references

  if column_name.is_a?(Symbol) || !column_name.match?(/\W/)
    predicate_builder.resolve_arel_attribute(table_name, column_name) do
      lookup_table_klass_from_join_dependencies(table_name)
    end
  else
    Arel.sql("#{model.adapter_class.quote_table_name(table_name)}.#{column_name}")
  end
end

arel_columns_from_hash(fields)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2309
def arel_columns_from_hash(fields)
  fields.flat_map do |table_name, columns|
    table_name = table_name.name if table_name.is_a?(Symbol)
    case columns
    when Symbol, String
      arel_column_with_table(table_name, columns)
    when Array
      columns.map do |column|
        arel_column_with_table(table_name, column)
      end
    else
      raise TypeError, "Expected Symbol, String or Array, got: #{columns.class}"
    end
  end
end

assert_modifiable!()

# File activerecord/lib/active_record/relation/query_methods.rb, line 2081
def assert_modifiable!
  raise UnmodifiableRelation if @loaded || @arel
end

async()

# File activerecord/lib/active_record/relation/query_methods.rb, line 2055
def async
  spawn.async!
end

build_arel(aliases)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2085
def build_arel(aliases)
  arel = Arel::SelectManager.new(table)

  build_joins(arel.join_sources, aliases)

  arel.where(where_clause.ast) unless where_clause.empty?
  arel.having(having_clause.ast) unless having_clause.empty?
  arel.take(build_cast_value("LIMIT", limit_value)) if limit_value
  arel.skip(build_cast_value("OFFSET", offset_value.to_i)) if offset_value
  arel.group(*arel_columns(group_values)) unless group_values.empty?

  build_order(arel)
  build_with(arel)
  build_select(arel)

  arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
  arel.comment(*annotate_values) unless annotate_values.empty?
  arel.distinct(distinct_value)
  arel.from(build_from) unless from_clause.empty?
  arel.lock(lock_value) if lock_value

  arel
end

build_case_for_value_position(column, values, filter: true)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2521
def build_case_for_value_position(column, values, filter: true)
  node = Arel::Nodes::Case.new
  values.each.with_index(1) do |value, order|
    if value.is_a?(Array)
      if value.include?(nil)
        node.when(column.in(value.compact).or(column.eq(nil))).then(order)
      else
        node.when(column.in(value)).then(order)
      end
    else
      node.when(column.eq(value)).then(order)
    end
  end

  node = node.else(values.length + 1) unless filter
  Arel::Nodes::Ascending.new(node)
end

build_cast_value(name, value)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2109
def build_cast_value(name, value)
  ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
end

build_from()

# File activerecord/lib/active_record/relation/query_methods.rb, line 2113
def build_from
  opts = from_clause.value
  name = from_clause.name&.to_s || "subquery"
  case opts
  when Relation
    if opts.eager_loading?
      opts = opts.send(:apply_join_dependency)
    end
    opts.arel.as(name)
  when Arel::Nodes::Union, Arel::Nodes::UnionAll,
       Arel::Nodes::Intersect, Arel::Nodes::Except
    opts.as(name)
  else
    opts
  end
end

build_join_buckets()

# File activerecord/lib/active_record/relation/query_methods.rb, line 2164
def build_join_buckets
  leading_joins = []
  join_nodes = []

  unless left_outer_joins_values.empty?
    left_values = classify_join_values(left_outer_joins_values)
    raise ArgumentError, "only Hash, Symbol and Array are allowed" unless left_values.nodes.empty?

    left_values.cte_names.each do |cte_name|
      join_nodes << build_with_join_node(cte_name, Arel::Nodes::OuterJoin)
    end

    left_joins = left_values.association_names
    stashed_left_joins = left_values.join_dependencies

    if joins_values.empty?
      return JoinBuckets.new(leading_joins, left_joins, stashed_left_joins, join_nodes, Arel::Nodes::OuterJoin)
    end

    stashed_outer_join = construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
  end

  joins = joins_values.dup
  if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
    stashed_eager_load = joins.pop if joins.last.base_klass == model
  end

  joins.each_with_index do |join, i|
    joins[i] = Arel::Nodes::StringJoin.new(Arel.sql(join.strip)) if join.is_a?(String)
  end

  while joins.first.is_a?(Arel::Nodes::Join)
    join_node = joins.shift
    if !join_node.is_a?(Arel::Nodes::LeadingJoin) && (stashed_eager_load || stashed_left_joins)
      join_nodes << join_node
    else
      leading_joins << join_node
    end
  end

  values = classify_join_values(joins)

  values.cte_names.each do |cte_name|
    join_nodes << build_with_join_node(cte_name)
  end

  values.nodes.each do |node|
    raise "unknown class: %s" % node.class.name unless node.is_a?(Arel::Nodes::Join)
    join_nodes << node
  end

  named_joins = values.association_names
  stashed_inner_joins = values.join_dependencies

  # The join dependencies rooted at the model never depend on another join,
  # while the ones merged in from a relation of another model join to the
  # tables they bring in, so those come first.
  stashed_joins = [
    stashed_eager_load, stashed_outer_join, *stashed_inner_joins, *stashed_left_joins
  ].compact

  JoinBuckets.new(leading_joins, named_joins, stashed_joins, join_nodes, Arel::Nodes::InnerJoin)
end

build_join_dependencies()

# File activerecord/lib/active_record/relation/query_methods.rb, line 2072
def build_join_dependencies
  joins = joins_values | left_outer_joins_values
  joins |= eager_load_values unless eager_load_values.empty?
  joins |= includes_values unless includes_values.empty?

  values = classify_join_values(joins)
  [construct_join_dependency(values.association_names, nil), *values.join_dependencies]
end

build_joins(join_sources, aliases = nil)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2228
def build_joins(join_sources, aliases = nil)
  return join_sources if joins_values.empty? && left_outer_joins_values.empty?

  buckets = build_join_buckets

  leading_joins = buckets.leading_joins
  join_nodes    = buckets.join_nodes

  join_sources.concat(leading_joins) unless leading_joins.empty?

  unless buckets.named_joins.empty? && buckets.stashed_joins.empty?
    alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
    join_dependency = construct_join_dependency(buckets.named_joins, buckets.join_type)
    join_sources.concat(join_dependency.join_constraints(buckets.stashed_joins, alias_tracker, references_values))
  end

  join_sources.concat(join_nodes) unless join_nodes.empty?
  join_sources
end

build_order(arel)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2411
def build_order(arel)
  orders = effective_order_values
  arel.order(*orders) unless orders.empty?
end

build_select(arel)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2248
def build_select(arel)
  return arel.project(*arel_columns(select_values)) if select_values.any?

  context = model.schema_context
  if context.ignored_columns.any? || model.enumerate_columns_in_select_statements || context.only_columns.any?
    arel.project(*context.mapped_table.column_names.map { |field| table[field] })
  else
    arel.projections << table[Arel.star]
  end
end

build_with(arel)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2259
def build_with(arel)
  return if with_values.empty?

  with_statements = with_values.map do |with_value|
    build_with_value_from_hash(with_value)
  end

  with_recursive_value ? arel.with(:recursive, with_statements) : arel.with(with_statements)
end

build_with_expression_from_value(value, nested = false)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2275
def build_with_expression_from_value(value, nested = false)
  case value
  when Arel::Nodes::SqlLiteral, Arel::Nodes::BoundSqlLiteral
    Arel::Nodes::Grouping.new(value)
  when ActiveRecord::Relation
    if nested
      value.arel.ast
    else
      value.arel
    end
  when Arel::SelectManager then value
  when Array
    return build_with_expression_from_value(value.first, false) if value.size == 1

    parts = value.map do |query|
      build_with_expression_from_value(query, true)
    end

    parts.reduce do |result, value|
      Arel::Nodes::UnionAll.new(result, value)
    end
  else
    raise ArgumentError, "Unsupported argument type: `#{value}` #{value.class}"
  end
end

build_with_join_node(name, kind = Arel::Nodes::InnerJoin)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2301
def build_with_join_node(name, kind = Arel::Nodes::InnerJoin)
  with_table = Arel::Table.new(name: name)

  table.join(with_table, kind).on(
    with_table[model.model_name.to_s.foreign_key].eq(table[model.primary_key])
  ).join_sources.first
end

build_with_value_from_hash(hash)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2269
def build_with_value_from_hash(hash)
  hash.map do |name, value|
    Arel::Nodes::TableAlias.new(build_with_expression_from_value(value), name)
  end
end

cast_values_for_in_order_of(values, type_caster)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2610
def cast_values_for_in_order_of(values, type_caster)
  bad_value = Symbol # use some object that can not be a valid value

  values = values.map do |value|
    if value.is_a?(Array)
      cast_values_for_in_order_of(value, type_caster).without(bad_value)
    else
      serialized = type_caster.serialize(value) if type_caster.serializable?(value)

      if value.nil?
        nil
      elsif !serialized.nil?
        Arel::Nodes::Quoted.new(serialized)
      else
        bad_value
      end
    end
  end

  values.reject { |v| v == bad_value || (v.is_a?(Array) && v.empty?) }
end

check_if_method_has_arguments!(method_name, args, message = nil)

Checks to make sure that the arguments are not blank. Note that if some blank-like object were initially passed into the query method, then this method will not raise an error.

Example:

Post.references()   # raises an error
Post.references([]) # does not raise an error

This particular method should be called with a method_name (__callee__) and the args passed into that method as an input. For example:

def references(*args)

check_if_method_has_arguments!(__callee__, args)
...

end

# File activerecord/lib/active_record/relation/query_methods.rb, line 2589
def check_if_method_has_arguments!(method_name, args, message = nil)
  if args.blank?
    raise ArgumentError, message || "The method .#{method_name}() must contain arguments."
  else
    yield args if block_given?

    args.flatten!
    args.compact_blank!
  end
end

classify_join_values(values)

Sorts the values passed to joins and left_outer_joins by what they are: association names to build a join dependency from, join dependencies merged in from a relation of another model, names of CTEs the relation defines, and Arel nodes.

# File activerecord/lib/active_record/relation/query_methods.rb, line 2138
def classify_join_values(values)
  association_names = []
  join_dependencies = []
  cte_names = []
  nodes = []

  values.each do |value|
    case value
    when Symbol
      if cte_join?(value)
        cte_names << value
      else
        association_names << value
      end
    when Hash, Array
      association_names << value
    when ActiveRecord::Associations::JoinDependency
      join_dependencies << value
    else
      nodes << value
    end
  end

  JoinValues.new(association_names, join_dependencies, cte_names, nodes)
end

column_references(columns)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2479
def column_references(columns)
  references = nil

  columns.each do |column|
    column = column.expr if column.is_a?(Arel::Nodes::Ordering)

    table_name =
      case column
      when Arel::Attribute
        column.relation.name unless column.relation == table
      when String
        extract_table_name_from(column)
      end

    if table_name
      (references ||= []) << Arel.sql(table_name, retryable: true)
    end
  end

  references || FROZEN_EMPTY_ARRAY
end

comparison_values_for_in_order_of(values, column)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2632
def comparison_values_for_in_order_of(values, column)
  values.map do |value|
    if value.is_a?(Array)
      comparison_values_for_in_order_of(value, column)
    elsif value.nil?
      nil
    else
      column.comparison_expression(Arel::Nodes.build_quoted(value))
    end
  end
end

create_with(value)

Sets attributes to be used when creating new records from a relation object.

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

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

You can pass nil to create_with to reset attributes:

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

cte_join?(join_name)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2130
def cte_join?(join_name)
  Symbol === join_name && with_values.any? { _1.key?(join_name) }
end

default_order(*args)

Defines a default order used when no other order is specified.

User.default_order('email DESC') # generated SQL has 'ORDER BY email DESC'

Subsequent calls to order on the same relation will replace default order. For example:

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

generates a query with ‘ORDER BY id ASC, name ASC’.

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

distinct(value = true)

Specifies whether the records should be unique or not. For example:

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 1744
def distinct(value = true)
  spawn.distinct!(value)
end

does_not_support_reverse?(order)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2400
def does_not_support_reverse?(order)
  # Account for String subclasses like Arel::Nodes::SqlLiteral that
  # override methods like #count.
  order = String.new(order) unless order.instance_of?(String)

  # Uses SQL function with multiple arguments.
  (order.include?(",") && order.split(",").find { |section| section.count("(") != section.count(")") }) ||
    # Uses "nulls first" like construction.
    /\bnulls\s+(?:first|last)\b/i.match?(order)
end

each_join_dependencies(join_dependencies = build_join_dependencies, &block)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2066
def each_join_dependencies(join_dependencies = build_join_dependencies, &block)
  join_dependencies.each do |join_dependency|
    join_dependency.each(&block)
  end
end

eager_load(*args)

Specify associations args to be eager loaded using a LEFT OUTER JOIN. Performs a single query joining all specified associations. For example:

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

Instead of loading the 5 addresses with 5 separate queries, all addresses are loaded with a single joined query.

Loading multiple and nested associations is possible using Hashes and Arrays, similar to 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"
#   ...

NOTE: Loading the associations in a join can result in many rows that contain redundant data and it performs poorly at scale.

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

excluding(*records)

Excludes the specified record (or collection of records) from the resulting relation. For example:

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)

This can also be called on associations. As with the above example, either a single record of collection thereof may be specified:

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

This is short-hand for .where.not(id: post.id) and .where.not(id: [post_one.id, post_two.id]).

An ArgumentError will be raised if either no records are specified, or if any of the records in the collection (if a collection is passed in) are not instances of the same model that the relation is scoping.

# File activerecord/lib/active_record/relation/query_methods.rb, line 1948
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)

Used to extend a scope with additional methods, either through a module or through a block provided.

The object returned is a relation, which can be further extended.

Using a Module ↑

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

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

You can also pass a list of modules:

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

Using a Block ↑

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

You can also use a block and a module list:

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 1798
def extending(*modules, &block)
  if modules.any? || block
    spawn._extending!(modules, &block)
  else
    self
  end
end

extract_associated(association)

Extracts a named association from the relation. The named association is first preloaded, then the individual association records are collected from the relation. Like so:

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

This is short-hand for:

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

extract_table_name_from(string)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2501
def extract_table_name_from(string)
  string.match(/^\W?(\w+)\W?\./) && $1
end

flattened_args(args)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2433
def flattened_args(args)
  args.flat_map { |e| (e.is_a?(Hash) || e.is_a?(Array)) ? flattened_args(e.to_a) : e }
end

from(value, subquery_name = nil)

Specifies the table from which the records will be fetched. For example:

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

Can accept other relation objects. For example:

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

Passing a second argument (string or symbol), creates the alias for the SQL from clause. Otherwise the alias “subquery” is used:

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

It does not add multiple arguments to the SQL from clause. The last from chained is the one used:

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

For multiple arguments for the SQL from clause, you can pass a string with the exact elements in the SQL from list:

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 1723
def from(value, subquery_name = nil)
  spawn.from!(value, subquery_name)
end

group(*args)

Allows to specify a group attribute:

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

Returns an array with distinct records based on the group attribute:

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, ...>]

Passing in an array of attributes to group by is also supported.

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 665
def group(*args)
  check_if_method_has_arguments!(__callee__, args)
  spawn._group!(args)
end

having(opts, *rest)

Allows to specify a HAVING clause. Note that you can’t use HAVING without also specifying a GROUP clause.

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

in_order_of(column, values, filter: true)

Applies an ORDER BY clause based on a given column, ordered and filtered by a specific set of 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

To group values together, an array can be passed as a value.

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" IN (1, 5) THEN 1
#     WHEN "users"."id" = 3 THEN 2
#   END ASC

column can point to an enum column; the actual query generated may be different depending on the database adapter and the column definition.

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 can also include 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 can be set to false to include all results instead of only the ones specified in 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 850
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?

  spawn._in_order_of!(column, values, filter)
end

includes(*args)

Specify associations args to be eager loaded to prevent N + 1 queries. A separate query is performed for each association, unless a join is required by conditions.

For example:

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)

Instead of loading the 5 addresses with 5 separate queries, all addresses are loaded with a single query.

Loading the associations in a separate query will often result in a performance improvement over a simple join, as a join can result in many rows that contain redundant data and it performs poorly at scale.

You can also specify multiple associations. Each association will result in an additional query:

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)

Loading nested associations is possible using a Hash:

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

Conditions ↑

If you want to add string conditions to your included models, you’ll have to explicitly reference them. For example:

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

Will throw an error, but this will work:

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"]]

As the LEFT OUTER JOIN already contains the posts, the second query for the posts is no longer performed.

Note that includes works with association names while references needs the actual table name.

If you pass the conditions via a Hash, you don’t need to call references explicitly, as where references the tables for you. For example, this will work correctly:

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

NOTE: Conditions affect both sides of an association. For example, the above code will return only users that have a post named “example”, and will only include posts named “example”, even when a matching user has other additional posts.

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

invert_where()

Allows you to invert an entire where clause instead of manually applying conditions.

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

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

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

User.active
# WHERE `accepted` = true AND `locked` = false

User.active.invert_where
# WHERE NOT (`accepted` = true AND `locked` = false)

Be careful because this inverts all conditions before invert_where call.

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` = true AND `locked` = false)
# File activerecord/lib/active_record/relation/query_methods.rb, line 1385
def invert_where
  spawn.invert_where!
end

joins(*args)

Performs JOINs on args. The given symbol(s) should match the name of the association(s).

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

Multiple joins:

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"

Nested joins:

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"

You can use strings in order to customize your joins:

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 1110
def joins(*args)
  check_if_method_has_arguments!(__callee__, args)
  spawn._joins!(args)
end

left_outer_joins(*args)

Performs LEFT OUTER JOINs on args:

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

limit(value)

Specifies a limit for the number of records to retrieve.

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 1515
def limit(value)
  spawn.limit!(value)
end

lock(locks = true)

Specifies locking settings (default to true). For more information on locking, please see ActiveRecord::Locking.

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

lookup_table_klass_from_join_dependencies(table_name)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2059
def lookup_table_klass_from_join_dependencies(table_name)
  each_join_dependencies do |join|
    return join.base_klass if table_name == join.table_name
  end
  nil
end

none()

Returns a chainable relation with zero records.

The returned relation implements the Null Object pattern. It is an object with defined null behavior and always returns an empty array of records without querying the database.

Any subsequent condition chained to the returned relation will continue generating an empty relation and will not fire any query to the database.

Used in cases where a method or scope could return zero records but the result needs to be chainable.

For example:

@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 1593
def none
  spawn.none!
end

offset(value)

Specifies the number of rows to skip before returning rows.

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

Should be used with order.

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

optimizer_hints(*args)

Specify optimizer hints to be used in the SELECT statement.

Example (for MySQL):

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

Example (for PostgreSQL with 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 1835
def optimizer_hints(*args)
  check_if_method_has_arguments!(__callee__, args)
  spawn._optimizer_hints!(args)
end

or(other)

Returns a new relation, which is the logical union of this relation and the one passed as an argument.

The two relations must be structurally compatible: they must be scoping the same model, and they must differ only by where (if no group has been defined) or having (if a group is present).

Post.where(id: 1).or(Post.where(author_id: 3))
# SELECT `posts`.* FROM `posts` WHERE (`posts`.`id` = 1 OR `posts`.`author_id` = 3)
# File activerecord/lib/active_record/relation/query_methods.rb, line 1465
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)

Applies an ORDER BY clause to a query.

order accepts arguments in one of several formats.

symbols ↑

The symbol represents the name of the column you want to order the results by.

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

By default, the order is ascending. If you want descending order, you can map the column name symbol to :desc.

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

Multiple columns can be passed this way, and they will be applied in the order specified.

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

strings ↑

Strings are passed directly to the database, allowing you to specify simple SQL expressions.

This could be a source of SQL injection, so only strings composed of plain column names and simple function(column_name) expressions with optional ASC/DESC modifiers are allowed.

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 ↑

If you need to pass in complicated expressions that you have verified are safe for the database, you can use Arel.

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

Custom query syntax, like JSON columns for PostgreSQL, is supported in this way.

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

order_column(field)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2505
def order_column(field)
  column = arel_column(field, references: false) do |attr_name|
    if attr_name == "count" && !group_values.empty?
      table[attr_name]
    else
      Arel.sql(model.adapter_class.quote_table_name(attr_name), retryable: true)
    end
  end

  if column.is_a?(Arel::Attributes::Attribute)
    predicate_builder.predicate_attribute(column)
  else
    column
  end
end

preload(*args)

Specify associations args to be eager loaded using separate queries. A separate query is performed for each association.

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)

Instead of loading the 5 addresses with 5 separate queries, all addresses are loaded with a separate query.

Loading multiple and nested associations is possible using Hashes and Arrays, similar to 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 364
def preload(*args)
  check_if_method_has_arguments!(__callee__, args)
  spawn._preload!(args)
end

preprocess_order_args(order_args)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2437
def preprocess_order_args(order_args)
  model.disallow_raw_sql!(
    flattened_args(order_args),
    permit: model.adapter_class.column_name_with_order_matcher
  )

  validate_order_args(order_args)

  # if a symbol is given we prepend the quoted table name
  order_args.map! do |arg|
    case arg
    when Symbol
      order_column(arg.to_s).asc
    when Hash
      arg.map do |key, value|
        if value.is_a?(Hash)
          value.map do |field, dir|
            order_column([key.to_s, field.to_s].join(".")).public_send(dir.downcase)
          end
        else
          case key
          when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
            key.public_send(value.downcase)
          else
            order_column(key.to_s).public_send(value.downcase)
          end
        end
      end
    else
      arg
    end
  end.flatten!

  order_args
end

process_select_args(fields)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2600
def process_select_args(fields)
  fields.flat_map do |field|
    if field.is_a?(Hash)
      arel_column_aliases_from_hash(field)
    else
      field
    end
  end
end

process_with_args(args)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2664
def process_with_args(args)
  args.flat_map do |arg|
    raise ArgumentError, "Unsupported argument type: #{arg} #{arg.class}" unless arg.is_a?(Hash)
    arg.map { |k, v| { k => v } }
  end
end

qualify_attribute_with_table_name(attr)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2563
def qualify_attribute_with_table_name(attr)
  attr.to_s.include?(".") ? attr : predicate_builder.resolve_arel_attribute(table_name, attr)
end

readonly(value = true)

Mark a relation as readonly. Attempting to update a record will result in an error.

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

To make a readonly relation writable, pass false.

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

references(*table_names)

Use to indicate that the given table_names are referenced by an SQL string, and should therefore be +JOIN+ed in any query rather than loaded separately. This method only works in conjunction with includes. See includes for more details.

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 405
def references(*table_names)
  check_if_method_has_arguments!(__callee__, table_names)
  spawn._references!(table_names)
end

regroup(*args)

Allows you to change a previously set group statement.

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`

This is short-hand for unscope(:group).group(fields). Note that we’re unscoping the entire group statement.

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

reorder(*args)

Replaces any existing order defined on the relation with the specified order.

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

Subsequent calls to order on the same relation will be appended. For example:

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

generates a query with ORDER BY id ASC, name ASC.

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

reselect(*args)

Allows you to change a previously set select statement.

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

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

This is short-hand for unscope(:select).select(fields). Note that we’re unscoping the entire select statement.

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

resolve_arel_attributes(attrs)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2539
def resolve_arel_attributes(attrs)
  attrs.flat_map do |attr|
    case attr
    when Arel::Predications
      attr
    when Hash
      attr.flat_map do |table, columns|
        table = table.to_s
        Array(columns).map do |column|
          predicate_builder.resolve_arel_attribute(table, column)
        end
      end
    else
      attr = attr.to_s
      if attr.include?(".")
        table, column = attr.split(".", 2)
        predicate_builder.resolve_arel_attribute(table, column)
      else
        attr
      end
    end
  end
end

reverse_order()

Reverse the existing order clause on the relation.

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

reverse_sql_order(order_query)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2364
      def reverse_sql_order(order_query)
        if order_query.empty?
          order_columns = _order_columns
          if !order_columns.empty?
            return order_columns.map { |column| predicate_builder.predicate_attribute(table[column]).desc }
          end

          raise IrreversibleOrderError, <<~MSG.squish
            Relation has no order values, and #{model} has no order columns to use as a default.
            Set at least one of `implicit_order_column`, or `primary_key` on the model when no
            `order `is specified on the relation.
          MSG
        end

        order_query.flat_map do |o|
          case o
          when Arel::Attribute
            o.desc
          when Arel::Nodes::Ordering
            o.reverse
          when Arel::Nodes::NodeExpression
            o.desc
          when String
            if does_not_support_reverse?(o)
              raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
            end
            o.split(",").map! do |s|
              s.strip!
              s.gsub!(/\sasc\Z/i, " DESC") || s.gsub!(/\sdesc\Z/i, " ASC") || (s << " DESC")
            end
          else
            o
          end
        end
      end

rewhere(conditions)

Allows you to change a previously set where condition for a given attribute, instead of appending to that condition.

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

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

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

This is short-hand for unscope(where: conditions.keys).where(conditions). Note that unlike reorder, we’re only unscoping the named conditions – not the entire where statement.

# File activerecord/lib/active_record/relation/query_methods.rb, line 1341
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

sanitize_order_arguments(order_args)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2473
def sanitize_order_arguments(order_args)
  order_args.map! do |arg|
    model.sanitize_sql_for_order(arg)
  end
end

select(*fields)

Works in two unique ways.

First: takes a block so it can be used just like Array#select.

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

This will build an array of objects from the database for the scope, converting them into an array and iterating through them using Array#select.

Second: Modifies the SELECT statement for the query so that only certain fields are retrieved:

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

Although in the above example it looks as though this method returns an array, it actually returns a relation object and can have other query methods appended to it, such as the other methods in ActiveRecord::QueryMethods.

The argument to the method can also be an array of fields.

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

The argument also can be a hash of fields and aliases.

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">]

You can also use one or more strings, which will be used unchanged as SELECT fields.

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

If an alias was specified, it will be accessible from the resulting objects:

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

Accessing attributes of an object that do not have fields retrieved by a select except id will throw 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 479
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)

Sets the returned relation to strict_loading mode. This will raise an error if the record tries to lazily load an association.

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

structurally_compatible?(other)

Checks whether the given relation is structurally compatible with this relation, to determine if it’s possible to use the and and or methods without raising an error. Structurally compatible is defined as: they must be scoping the same model, and they must differ only by where (if no group has been defined) or having (if a group is present).

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

Post.joins(:comments).structurally_compatible?(Post.joins(:author))
# => false

Since and and or are focused on combining where-like clauses, a multi-value clause that this relation sets and the other one leaves unset is not counted as a difference.

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

structurally_incompatible_key?(set_values, values)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2690
def structurally_incompatible_key?(set_values, values)
  set_values.each_key do |method|
    return true if STRUCTURAL_VALUE_METHOD_LOOKUP[method] && structurally_incompatible_value?(method, values)
  end
  false
end

structurally_incompatible_value?(method, values)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2697
def structurally_incompatible_value?(method, values)
  v1, v2 = @values[method], values[method]

  # `and`/`or` are focused to combine where-like clauses, so it relaxes
  # the difference when other's multi values are uninitialized.
  # Likewise, `with_recursive` is relaxed when other's CTEs are not recursive.
  return false if (v1.is_a?(Array) || method == :with_recursive) && v2.nil?

  !(v1 == v2)
end

structurally_incompatible_values_for(other)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2677
def structurally_incompatible_values_for(other)
  values = other.values

  # A value set on neither relation can't differ, so only the values set
  # on either one are compared. When one differs, every incompatible
  # value is collected, in order, for the error message.
  if structurally_incompatible_key?(@values, values) || structurally_incompatible_key?(values, values)
    STRUCTURAL_VALUE_METHODS.select { |method| structurally_incompatible_value?(method, values) }
  else
    []
  end
end

table_name_matches?(from)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2358
def table_name_matches?(from)
  table_name = Regexp.escape(table.name)
  quoted_table_name = Regexp.escape(model.adapter_class.quote_table_name(table.name))
  /(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
end

table_name_qualified_unscope_values()

# File activerecord/lib/active_record/relation/query_methods.rb, line 1054
def table_name_qualified_unscope_values
  self.unscope_values.map do |scope|
    case scope
    when Hash
      scope.transform_values do |target_value|
        case target_value
        when Array
          target_value.map { |value| qualify_attribute_with_table_name(value) }
        when Symbol, String
          qualify_attribute_with_table_name(target_value)
        else
          target_value
        end
      end
    else
      scope
    end
  end
end

unscope(*args)

Removes an unwanted relation that is already defined on a chain of relations. This is useful when passing around chains of relations and would like to modify the relations without reconstructing the entire chain.

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

The method arguments are symbols which correspond to the names of the methods which should be unscoped. The valid arguments are given in VALID_UNSCOPING_VALUES. The method can also be called with multiple arguments. For example:

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

One can additionally pass a hash as an argument to unscope specific :where values. This is done by passing a hash with a single key-value pair. The key should be :where and the value should be the where value to unscope. For example:

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

This method is similar to except, but unlike except, it persists across merges:

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

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

This means it can be used in association definitions:

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

validate_order_args(args)

# File activerecord/lib/active_record/relation/query_methods.rb, line 2419
def validate_order_args(args)
  args.each do |arg|
    next unless arg.is_a?(Hash)
    arg.each do |_key, value|
      if value.is_a?(Hash)
        validate_order_args([value])
      elsif VALID_DIRECTIONS.exclude?(value)
        raise ArgumentError,
          "Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
      end
    end
  end
end

where(*args)

Returns a new relation, which is the result of filtering the current relation according to the conditions in the arguments.

where accepts conditions in one of several formats. In the examples below, the resulting SQL is given as an illustration; the actual query generated may be different depending on the database adapter.

String ↑

A single string, without additional arguments, is passed to the query constructor as an SQL fragment, and used in the where clause of the query.

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

Note that building your own string from user input may expose your application to injection attacks if not done properly. As an alternative, it is recommended to use one of the following methods.

Array ↑

If an array is passed, then the first element of the array is treated as a template, and the remaining elements are inserted into the template to generate the condition. Active Record takes care of building the query to avoid injection attacks, and will convert from the ruby type to the database type where needed. Elements are inserted into the string in the order in which they appear.

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

Alternatively, you can use named placeholders in the template, and pass a hash as the second element of the array. The names in the template are replaced with the corresponding values from the hash.

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

This can make for more readable code in complex queries.

Lastly, you can use sprintf-style % escapes in the template. This works slightly differently than the previous methods; you are responsible for ensuring that the values in the template are properly quoted. The values are passed to the connector for quoting, but the caller is responsible for ensuring they are enclosed in quotes in the resulting SQL. After quoting, the values are inserted using the same escapes as the Ruby core method Kernel::sprintf.

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

If where is called with multiple arguments, these are treated as if they were passed as the elements of a single array.

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

When using strings to specify conditions, you can use any operator available from the database. While this provides the most flexibility, you can also unintentionally introduce dependencies on the underlying database. If your code is intended for general consumption, test with multiple database backends.

Hash ↑

where will also accept a hash condition, in which the keys are fields and the values are values to be searched for.

Fields can be symbols or strings. Values can be single values, arrays, or ranges.

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

In the case of a belongs_to relationship, an association key can be used to specify the model if an ActiveRecord object is used as the value.

author = Author.find(1)

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

This also works with polymorphic belongs_to relationships:

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 conditions may also be specified in a tuple-like syntax. Hash keys may be an array of columns with an array of tuples as values.

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

Joins ↑

If the relation is the result of a join, you may create a condition which uses any of the tables in the join. For string and array conditions, use the table name in the condition.

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

For hash conditions, you can either use the table name in the key, or use a sub-hash.

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

No Argument ↑

If no argument is passed, where returns a new instance of WhereChain, that can be chained with WhereChain#not, WhereChain#missing, or WhereChain#associated.

Chaining with WhereChain#not:

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

Chaining with 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

Chaining with 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 Condition ↑

If the condition is any blank-ish object, then where is a no-op and returns the current relation.

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

with(*args)

Add a Common Table Expression (CTE) that you can then reference within another SELECT statement.

Note: CTE’s are only supported in MySQL for versions 8.0 and above. You will not be able to use CTE’s with MySQL 5.7.

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

You can also pass an array of sub-queries to be joined in a +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

Once you define Common Table Expression you can use custom FROM value or JOIN to reference it.

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

It is recommended to pass a query as ActiveRecord::Relation. If that is not possible and you have verified it is safe for the database, you can pass it as SQL literal using Arel.

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

Great caution should be taken to avoid SQL injection vulnerabilities. This method should not be used with unsafe values that include unsanitized input.

To add multiple CTEs just pass multiple key-value pairs

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

or chain multiple .with calls

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 571
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!(process_with_args(args))
end

with_recursive(*args)

Add a recursive Common Table Expression (CTE) that you can then reference within another SELECT statement.

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

See with for more information.

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