ActiveRecord syntax for multiple joins on same table?

In my ongoing quest to wean myself of find_by_sql(), I now need to know
how to do multiple joins into a single table using the spiffy new
ActiveRecord query syntax.

As an example, imagine that an Edge is defined by its two vertices:

create_table “vertices”, :force => true do |t|
t.float “x”
t.float “y”
t.float “z”
end

create_table “edges”, :force => true do |t|
t.integer “vertex_a_id”
t.integer “vertex_b_id”
end

An SQL query to enumerate all points connected by edges might look like:

SELECT a.x as x0, a.y as y0, a.z as z0, b.x as x1, b.y as y1, b.z as
z1
FROM edges
JOIN vertices as a on a.id = edges.vertex_a_id
JOIN vertices as b on b.id = edges.vertex_b_id

Any idea how to express this query in ActiveRecord query syntax?

  • ff

(As an aside, I find that AREL is pretty well documented, ActiveRecord
is not. And the relationship between the two, such as it is, is
entirely baffling. Maybe I’m just looking in the wrong places.)

[I just *gotta* stop answering my own posts! :)]

Okay – if you set up your associations properly, then the ActiveRecord
query syntax does the right thing. With one big flaw.

In the above Edge / Vertex example, declare associations as follows:

class Edge < ActiveRecord::Base
belongs_to :vertex_a, :class_name => ‘Vertex’, :foreign_key =>
‘vertex_a_id’
belongs_to :vertex_b, :class_name => ‘Vertex’, :foreign_key =>
‘vertex_b_id’
end

class Vertex < ActiveRecord::Base
has_many :vertices
end

Now, a simple pair of joins gets us 90% of the way to our desired
results:

Edge.joins(:vertex_a).joins(:vertex_b).to_sql
=> “SELECT edges.*
FROM edges
INNER JOIN vertices
ON vertices.id = edges.vertex_a_id
INNER JOIN vertices vertex_bs_edges
ON vertex_bs_edges.id = edges.vertex_b_id

Note that AR has cleverly invented an alias for the second join
(‘vertex_bs_edges’), and therein lies my gripe: how are you supposed to
know what alias AR has invented for you? Unless I"m mistaken, you need
to know the alias in order to write the SELECT statement.

Completing the Edge / Vertex example of the OP, you’d write the
following:

Edge.
joins(:vertex_a).
joins(:vertex_b).
select(‘vertices.x as x0’,
‘vertices.y as y0’,
‘vertices.z as z0’,
‘vertex_bs_edges.x as x1’,
‘vertex_bs_edges.y as y1’,
‘vertex_bs_edges.z as z1’)

Glarg. Maybe i’ll stick to find_by_sql for the time being.

Summary: The new query interface is pretty great, but the inability to
name your own table aliases seems like a blemish.

  • ff