Module: ActiveRecord::Refined::QueryMethods

Defined in:
lib/active_record/refined.rb

Overview

The relation methods a block reaches, prepended to Active Record's own: where, select, having, order and group take a block beside what they take already, the joins take one for the ON, and from, from_cte, distinct_on and lateral are here for what Active Record has no spelling for. Without a block each is Active Record's own.

Examples:

Author.
  joins(:posts) { :posts[:author_id] == :authors[:id] }.
  where { :posts[:published] == true }.
  group { :authors[:id] }.
  having { count(:posts[:id]) > 1 }.
  order { count(:posts[:id]).desc }.
  select { [:name, count(:posts[:id]).as(:post_count)] }

Instance Method Summary collapse

Instance Method Details

#cross_joins(*args, as: nil, &block) ⇒ Object

CROSS JOIN: every row of one table against every row of the other, so there is no condition to give and no block to write it in.

Examples:

Post.cross_joins(:authors)
Post.cross_joins(:posts, as: :others)

Parameters:

  • as (Symbol, nil) (defaults to: nil)


1048
1049
1050
1051
1052
1053
1054
# File 'lib/active_record/refined.rb', line 1048

def cross_joins(*args, as: nil, &block)
  if block
    raise ArgumentError,
      "a cross join has no condition; joins is the one that takes a block"
  end
  joins(build_cross_join(args.first, as))
end

#distinct_on(*columns, &block) ⇒ Object

SELECT DISTINCT ON (columns): the first row of each group the order brings up. PostgreSQL has it; the portable shape is a row_number window in a subquery. Arel carries the node and refuses to write it elsewhere, the way it does a regexp, so there is nothing for this to check.

Examples:

Post.distinct_on { :author_id }.order { [:author_id, :likes.desc] }

Parameters:

  • columns (Array<Symbol>)

    the columns, unless a block gives them



927
928
929
# File 'lib/active_record/refined.rb', line 927

def distinct_on(*columns, &block)
  spawn.distinct_on!(*columns, &block)
end

#distinct_on!(*columns, &block) ⇒ Object

#distinct_on on the relation itself.



932
933
934
935
936
937
938
939
# File 'lib/active_record/refined.rb', line 932

def distinct_on!(*columns, &block)
  columns = Array(evaluate_block(&block)) if block
  if columns.empty?
    raise ArgumentError, "distinct_on needs a column or an expression"
  end
  self.distinct_on_values += columns
  self
end

#from(value, subquery_name = nil, as: nil) ⇒ Object

FROM, with a table named as a symbol and, with as:, selected under another name; anything else is Active Record's own from. A symbol names a table, which Active Record's own from only takes as a string. With as it is selected under another name; when that name is the model's own, from_cte says the same thing without repeating it.

Examples:

Post.from(:archived_posts, as: :posts)

Parameters:

  • value (Symbol, String, ActiveRecord::Relation)
  • as (Symbol, nil) (defaults to: nil)

    the name the table is selected under



872
873
874
875
876
877
878
879
880
881
882
# File 'lib/active_record/refined.rb', line 872

def from(value, subquery_name = nil, as: nil)
  unless value.is_a?(Symbol)
    if as
      raise ArgumentError, "as: needs the table named as a symbol"
    end
    return super(value, subquery_name)
  end
  arel_table = Arel::Table.new(value)
  arel_table = arel_table.alias(as) if as
  super(arel_table, subquery_name)
end

#from_cte(name) ⇒ Object

Selects a CTE in place of the model's own table, under the model's own name, so that the columns Active Record qualifies still resolve. The name has to be one with or with_recursive declares. The alias is not a choice -- Active Record keeps qualifying columns with the table name, so the model's is the only name that works -- which is why it is taken from the model rather than asked for. The name is checked against what with declares, so that a typo is not a query against a table nobody has. Checked when the SQL is built, since the CTE may be declared after this in the chain, or by a scope merged into it.

Examples:

Node.with_recursive(tree: [Node.where { :id == 1 }, Node.joins(...)]).from_cte(:tree)

Parameters:

  • name (Symbol)

    the CTE's name



898
899
900
901
902
903
904
905
# File 'lib/active_record/refined.rb', line 898

def from_cte(name)
  unless name.is_a?(Symbol)
    raise ArgumentError, "from_cte takes the CTE's name as a symbol"
  end
  relation = from(name, as: klass.table_name)
  relation.from_cte_value = name
  relation
end

#full_outer_joins(*args, as: nil, &block) ⇒ Object

FULL OUTER JOIN, as #right_outer_joins takes it. The MySQL family has none.

Parameters:

  • as (Symbol, nil) (defaults to: nil)


1036
1037
1038
1039
1040
# File 'lib/active_record/refined.rb', line 1036

def full_outer_joins(*args, as: nil, &block)
  check_full_outer_support
  outer_joins(:full_outer_joins, Arel::Nodes::FullOuterJoin,
              args, as, &block)
end

#group(*args, &block) ⇒ Object

GROUP BY, from a block: a column or an expression, an array of them, or one of BlockContext#grouping_sets, BlockContext#rollup and BlockContext#cube.

Examples:

Post.group { date_trunc("day", :created_at) }.select { [date_trunc("day", :created_at).as(:day), count(:*)] }

Yield Returns:



851
852
853
854
855
856
857
858
859
# File 'lib/active_record/refined.rb', line 851

def group(*args, &block)
  if block
    result = evaluate_block(&block)
    check_rollup_stands_alone(result)
    super(*to_arel_fields(result), &nil)
  else
    super
  end
end

#having(opts = nil, *rest, &block) ⇒ Object

HAVING, from a block: a condition over the aggregates of a group.

Examples:

Author.group { :country }.having { count(:*) > 1 }

Yield Returns:



824
825
826
827
828
829
830
# File 'lib/active_record/refined.rb', line 824

def having(opts = nil, *rest, &block)
  if block
    super(to_arel_condition(evaluate_block(&block)))
  else
    super
  end
end

#joins(*args, as: nil, &block) ⇒ Object

INNER JOIN, with the ON from a block: joins(:posts) { ... } joins the table named, joins(relation) { ... } a subquery -- a lateral one when the relation is marked #lateral. as: names the table within the query, which is what makes a self join expressible. Without a block it is Active Record's own joins.

Examples:

Author.joins(:posts) { :posts[:author_id] == :authors[:id] }
Employee.joins(:employees, as: :managers) { :managers[:id] == :employees[:manager_id] }

Parameters:

  • as (Symbol, nil) (defaults to: nil)


991
992
993
994
995
996
997
998
999
1000
# File 'lib/active_record/refined.rb', line 991

def joins(*args, as: nil, &block)
  if args.first.is_a?(ActiveRecord::Relation)
    super(build_lateral_join(args.first, Arel::Nodes::InnerJoin, as, &block))
  elsif block
    super(build_join_node(args.first, Arel::Nodes::InnerJoin, as, &block))
  else
    reject_join_alias(as)
    super(*args, &block)
  end
end

#lateralObject

Marks the relation for a LATERAL join, which lets the subquery see the row it is joined to -- the top few rows of each group, and the like. Said on the relation, since in SQL the keyword modifies the subquery rather than the join. SQLite and MariaDB have none.

Examples:

top = Post.where { :posts[:author_id] == :authors[:id] }.order { :likes.desc }.limit(1)
Author.left_outer_joins(top.lateral, as: :top) { true }.select { [:name, :top[:title]] }


961
962
963
# File 'lib/active_record/refined.rb', line 961

def lateral
  spawn.lateral!
end

#lateral!Object

#lateral on the relation itself.



966
967
968
969
# File 'lib/active_record/refined.rb', line 966

def lateral!
  self.lateral_value = true
  self
end

#left_outer_joins(*args, as: nil, &block) ⇒ Object

LEFT OUTER JOIN, as #joins takes it.

Examples:

Author.left_outer_joins(:posts) { :posts[:author_id] == :authors[:id] }

Parameters:

  • as (Symbol, nil) (defaults to: nil)


1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
# File 'lib/active_record/refined.rb', line 1006

def left_outer_joins(*args, as: nil, &block)
  if args.first.is_a?(ActiveRecord::Relation)
    joins(build_lateral_join(args.first, Arel::Nodes::OuterJoin, as, &block))
  elsif block
    joins(build_join_node(args.first, Arel::Nodes::OuterJoin, as, &block))
  else
    reject_join_alias(as)
    super(*args, &block)
  end
end

#order(*args, &block) ⇒ Object

ORDER BY, from a block: an ordering, or an array of them -- :age.desc, count(:*).desc.nulls_last, or a bare column.

Examples:

Author.order { [:country.asc.nulls_last, :age.desc] }

Yield Returns:



837
838
839
840
841
842
843
# File 'lib/active_record/refined.rb', line 837

def order(*args, &block)
  if block
    super(*to_arel_fields(evaluate_block(&block)), &nil)
  else
    super
  end
end

#right_outer_joins(*args, as: nil, &block) ⇒ Object

RIGHT OUTER JOIN, as #joins takes it, of a table or a relation; an association name is not among what it takes. The other two outer joins, which Active Record has no method for and Arel has the nodes for. The rules are joins': the block is the ON, as names the table within the query, a relation marked lateral joins as one. An association name is not among them -- what Active Record reads out of one is an inner or a left join and nothing else.

Examples:

Post.right_outer_joins(:authors) { :posts[:author_id] == :authors[:id] }

Parameters:

  • as (Symbol, nil) (defaults to: nil)


1028
1029
1030
1031
# File 'lib/active_record/refined.rb', line 1028

def right_outer_joins(*args, as: nil, &block)
  outer_joins(:right_outer_joins, Arel::Nodes::RightOuterJoin,
              args, as, &block)
end

#select(*fields, &block) ⇒ Object

SELECT, from a block: an expression, or an array of them, each aliased with as or left to its own name.

Examples:

Author.select { [:name, upper(:name).as(:shouted), count(:*).as(:n)] }

Yield Returns:



812
813
814
815
816
817
818
# File 'lib/active_record/refined.rb', line 812

def select(*fields, &block)
  if block
    super(*to_arel_fields(evaluate_block(&block)), &nil)
  else
    super
  end
end

#where(opts = nil, *rest, &block) ⇒ Object

WHERE, from a block: a condition built with the comparisons of BlockSyntax, combined with &, | and !.

Examples:

Author.where { :age >= 18 & :country.in?(%w[JP US]) }
Author.where { !:name.like?("A%") }

Yield Returns:



799
800
801
802
803
804
805
# File 'lib/active_record/refined.rb', line 799

def where(opts = nil, *rest, &block)
  if block
    super(to_arel_condition(evaluate_block(&block)))
  else
    super
  end
end