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)


1085
1086
1087
1088
1089
1090
1091
# File 'lib/active_record/refined.rb', line 1085

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



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

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

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

#distinct_on on the relation itself.



965
966
967
968
969
970
971
972
# File 'lib/active_record/refined.rb', line 965

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



905
906
907
908
909
910
911
912
913
914
915
# File 'lib/active_record/refined.rb', line 905

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



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

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)

Yield Returns:



1073
1074
1075
1076
1077
# File 'lib/active_record/refined.rb', line 1073

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:



884
885
886
887
888
889
890
891
892
# File 'lib/active_record/refined.rb', line 884

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:



857
858
859
860
861
862
863
# File 'lib/active_record/refined.rb', line 857

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)

Yield Returns:



1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
# File 'lib/active_record/refined.rb', line 1025

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).select { [:name, :top[:title]] }


994
995
996
# File 'lib/active_record/refined.rb', line 994

def lateral
  spawn.lateral!
end

#lateral!Object

#lateral on the relation itself.



999
1000
1001
1002
# File 'lib/active_record/refined.rb', line 999

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)

Yield Returns:



1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
# File 'lib/active_record/refined.rb', line 1041

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:



870
871
872
873
874
875
876
# File 'lib/active_record/refined.rb', line 870

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)

Yield Returns:



1064
1065
1066
1067
# File 'lib/active_record/refined.rb', line 1064

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:



845
846
847
848
849
850
851
# File 'lib/active_record/refined.rb', line 845

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:



832
833
834
835
836
837
838
# File 'lib/active_record/refined.rb', line 832

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