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)


556
557
558
559
560
561
562
# File 'lib/active_record/refined.rb', line 556

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.



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

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

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



468
469
470
471
472
473
474
475
# File 'lib/active_record/refined.rb', line 468

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.



479
480
481
# File 'lib/active_record/refined.rb', line 479

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

#distinct_on_values=(columns) ⇒ Object



483
484
485
486
# File 'lib/active_record/refined.rb', line 483

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.



415
416
417
418
419
420
421
422
423
424
425
# File 'lib/active_record/refined.rb', line 415

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.



437
438
439
440
441
442
443
444
# File 'lib/active_record/refined.rb', line 437

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



446
447
448
# File 'lib/active_record/refined.rb', line 446

def from_cte_value
  @values[:from_cte]
end

#from_cte_value=(name) ⇒ Object



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

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

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



544
545
546
547
548
# File 'lib/active_record/refined.rb', line 544

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



402
403
404
405
406
407
408
409
410
# File 'lib/active_record/refined.rb', line 402

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



386
387
388
389
390
391
392
# File 'lib/active_record/refined.rb', line 386

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

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



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

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



492
493
494
# File 'lib/active_record/refined.rb', line 492

def lateral
  spawn.lateral!
end

#lateral!Object



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

def lateral!
  self.lateral_value = true
  self
end

#lateral_valueObject



501
502
503
# File 'lib/active_record/refined.rb', line 501

def lateral_value
  @values[:lateral]
end

#lateral_value=(value) ⇒ Object



505
506
507
508
# File 'lib/active_record/refined.rb', line 505

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

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



523
524
525
526
527
528
529
530
531
532
# File 'lib/active_record/refined.rb', line 523

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



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

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

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.



539
540
541
542
# File 'lib/active_record/refined.rb', line 539

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

#select(*fields, &block) ⇒ Object



378
379
380
381
382
383
384
# File 'lib/active_record/refined.rb', line 378

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

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



370
371
372
373
374
375
376
# File 'lib/active_record/refined.rb', line 370

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