Module: ActiveRecord::Refined::QueryMethods

Defined in:
lib/active_record/refined.rb

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 unlike the joins above there is no condition to give and no block to write it in.

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


514
515
516
517
518
519
520
# File 'lib/active_record/refined.rb', line 514

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

DISTINCT ON (...), which keeps the first row of each group the order brings up. PostgreSQL has it and the others do not; Arel carries the node and refuses to write it elsewhere, the way it does a regexp, so there is nothing for this to check:

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

The portable shape is a row_number window in a subquery, which the README shows.



422
423
424
# File 'lib/active_record/refined.rb', line 422

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

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



426
427
428
429
430
431
432
433
# File 'lib/active_record/refined.rb', line 426

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

#distinct_on_valuesObject

Active Record generates these for the values it knows about; this one is ours, and lives in the same place so that it survives a spawn.



437
438
439
# File 'lib/active_record/refined.rb', line 437

def distinct_on_values
  @values.fetch(:distinct_on, ActiveRecord::QueryMethods::FROZEN_EMPTY_ARRAY)
end

#distinct_on_values=(columns) ⇒ Object



441
442
443
444
# File 'lib/active_record/refined.rb', line 441

def distinct_on_values=(columns)
  assert_modifiable!
  @values[:distinct_on] = columns
end

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

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.



373
374
375
376
377
378
379
380
381
382
383
# File 'lib/active_record/refined.rb', line 373

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. 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: with_recursive(tree: [...]).from_cte(:tree)

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.



395
396
397
398
399
400
401
402
# File 'lib/active_record/refined.rb', line 395

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

#from_cte_valueObject



404
405
406
# File 'lib/active_record/refined.rb', line 404

def from_cte_value
  @values[:from_cte]
end

#from_cte_value=(name) ⇒ Object



408
409
410
411
# File 'lib/active_record/refined.rb', line 408

def from_cte_value=(name)
  assert_modifiable!
  @values[:from_cte] = name
end

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



502
503
504
505
506
# File 'lib/active_record/refined.rb', line 502

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



360
361
362
363
364
365
366
367
368
# File 'lib/active_record/refined.rb', line 360

def group(*args, &block)
  if block
    result = evaluate_block(&block)
    arel = Array(result).map {|node| to_arel_field(node) }
    super(*arel, &nil)
  else
    super
  end
end

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



342
343
344
345
346
347
348
# File 'lib/active_record/refined.rb', line 342

def having(opts = nil, *rest, &block)
  if block
    super(evaluate_block(&block).to_arel(table, klass))
  else
    super
  end
end

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

as names the table within the query, which is what makes a self join expressible: joins(:employees, as: :managers) { ... }.



470
471
472
473
474
475
476
477
478
479
# File 'lib/active_record/refined.rb', line 470

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 it see the row being joined to -- the top few rows of each group, and the like. In SQL the keyword modifies the subquery, not the join, so it is said on the relation: left_outer_joins(top_post.lateral, as: :top).



450
451
452
# File 'lib/active_record/refined.rb', line 450

def lateral
  spawn.lateral!
end

#lateral!Object



454
455
456
457
# File 'lib/active_record/refined.rb', line 454

def lateral!
  self.lateral_value = true
  self
end

#lateral_valueObject



459
460
461
# File 'lib/active_record/refined.rb', line 459

def lateral_value
  @values[:lateral]
end

#lateral_value=(value) ⇒ Object



463
464
465
466
# File 'lib/active_record/refined.rb', line 463

def lateral_value=(value)
  assert_modifiable!
  @values[:lateral] = value
end

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



481
482
483
484
485
486
487
488
489
490
# File 'lib/active_record/refined.rb', line 481

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



350
351
352
353
354
355
356
357
358
# File 'lib/active_record/refined.rb', line 350

def order(*args, &block)
  if block
    result = evaluate_block(&block)
    arel = Array(result).map {|node| to_arel_field(node) }
    super(*arel, &nil)
  else
    super
  end
end

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

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.



497
498
499
500
# File 'lib/active_record/refined.rb', line 497

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

#select(*fields, &block) ⇒ Object



332
333
334
335
336
337
338
339
340
# File 'lib/active_record/refined.rb', line 332

def select(*fields, &block)
  if block
    result = evaluate_block(&block)
    arel = Array(result).map {|node| to_arel_field(node) }
    super(*arel, &nil)
  else
    super
  end
end

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



324
325
326
327
328
329
330
# File 'lib/active_record/refined.rb', line 324

def where(opts = nil, *rest, &block)
  if block
    super(evaluate_block(&block).to_arel(table, klass))
  else
    super
  end
end