Class: Huginn::Datatable::AssociationPath

Inherits:
Object
  • Object
show all
Defined in:
lib/huginn/datatable/association_path.rb

Overview

Resolves a multi-segment association chain ("company.people.name") into the concrete steps (tables, klasses and Arel link conditions) needed to build:

* a subquery for filtering/ranges
WHERE pk IN (SELECT DISTINCT pk FROM <chain> join ... WHERE ...)

* a correlated scalar subquery for ordering
ORDER BY (SELECT col FROM <chain> join ... WHERE <correlation> ...)

The link conditions are direction aware: a belongs_to puts the FK on the owner table while has_many/has_one put it on the child table. Association scopes are folded in as Arel predicates (never parsed SQL).

Defined Under Namespace

Classes: Step

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(model, field) ⇒ AssociationPath

Returns a new instance of AssociationPath.



27
28
29
30
31
# File 'lib/huginn/datatable/association_path.rb', line 27

def initialize(model, field)
  @model = model
  @segments = field.to_s.split(".")
  @column = segments.last
end

Instance Attribute Details

#columnObject (readonly)

Returns the value of attribute column.



21
22
23
# File 'lib/huginn/datatable/association_path.rb', line 21

def column
  @column
end

#segmentsObject (readonly)

Returns the value of attribute segments.



21
22
23
# File 'lib/huginn/datatable/association_path.rb', line 21

def segments
  @segments
end

Class Method Details

.call(model, field) ⇒ Object



23
24
25
# File 'lib/huginn/datatable/association_path.rb', line 23

def self.call(model, field)
  new(model, field)
end

.join_spec_for(names) ⇒ Object

Reverse engineers a join spec usable by ActiveRecord::Relation#joins for filter subqueries: .joins(:people: { company: ... }).



88
89
90
91
92
# File 'lib/huginn/datatable/association_path.rb', line 88

def self.join_spec_for(names)
  names.reverse.reduce(nil) do |acc, name|
    acc.nil? ? name.to_sym : { name.to_sym => acc }
  end
end

Instance Method Details

#association_namesObject

The association steps of the chain ("company", "people").



34
35
36
# File 'lib/huginn/datatable/association_path.rb', line 34

def association_names
  resolved_steps.map { |step| step.reflection.name.to_s }
end

#correlated_order_expressionObject

Correlated scalar subquery: ORDER BY (SELECT FROM ... WHERE [AND scope...] ORDER BY ASC LIMIT 1). Deterministic: the smallest matching value per parent row.



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/huginn/datatable/association_path.rb', line 58

def correlated_order_expression
  steps = resolved_steps
  return nil unless steps

  manager = Arel::SelectManager.new
  manager.from(steps.first.table)
  steps.drop(1).each do |step|
    manager.join(step.table).on(step.link_to_previous)
  end

  # The first step correlates the subquery to the outer (base) table.
  manager.where(steps.first.link_to_previous)
  scope_predicates.each { |predicate| manager.where(predicate) }
  manager.project(target_table[column])
  manager.order(target_table[column].asc)
  manager.take(1)

  Arel.sql("(#{manager.to_sql})")
rescue StandardError
  nil
end

#resolved_stepsObject

The walkable Arel filter/order chain, or nil when invalid.



51
52
53
# File 'lib/huginn/datatable/association_path.rb', line 51

def resolved_steps
  @steps ||= build_steps
end

#scope_constraintsObject

The Arel predicates folded in by the association scopes of every step. Public so subquery builders can replicate ActiveRecord's scoped joins.



82
83
84
# File 'lib/huginn/datatable/association_path.rb', line 82

def scope_constraints
  scope_predicates
end

#target_klassObject



38
39
40
# File 'lib/huginn/datatable/association_path.rb', line 38

def target_klass
  resolved_steps.last.klass
end

#target_tableObject



42
43
44
# File 'lib/huginn/datatable/association_path.rb', line 42

def target_table
  resolved_steps.last.table
end

#valid?Boolean

Returns:

  • (Boolean)


46
47
48
# File 'lib/huginn/datatable/association_path.rb', line 46

def valid?
  resolved_steps.present?
end