Rails query join association table with alias

Andrew Starostin picture Andrew Starostin · Mar 31, 2015 · Viewed 10.9k times · Source

I have a model Edge that belongs to the other model Node twice through different foreign keys:

def Edge < ActiveRecord::Base
    belongs_to :first, class_name: 'Node'
    belongs_to :second, class_name: 'Node'
end

And I want to perform this query using ActiveRecord:

SELECT * FROM edges INNER JOIN nodes as first ON first.id = edges.first_id WHERE first.value = 5

I found the way to join association using .joins() method:

Edge.joins(:first)

But this produces query using a table name, not an association name, so in .where() method I have to explicitly use table name which breaks association abstraction.

Edge.joins(:first).where(nodes: {value: 5})

I can also explicitly use SQL query in .joins() method to define model alias:

Edge.joins('INNER JOIN nodes as first ON nodes.id = edges.first_id')

But this breaks even more abstraction.

I think there should be the way to automatically define table alias on join. Or maybe a way to write such function by myself. Something like:

def Edge < ActiveRecord::Base
    ...
    def self.joins_alias
        # Generate something like 
        # joins("INNER JOIN #{relation.table} as #{relation.alias} ON #{relation.alias}.#{relation.primary_key} = #{table}.#{relation.foreign_key}")
    end
end

But I couldn't find any information about accessing information about specific relation like it's name, foreign key, etc. So how can I do it?

Also it seems strange to me that such obvious feature is so complicated even through Rails is on its 4th major version already. Maybe I'm missing something?

Answer

RPinel picture RPinel · Apr 1, 2015

As for Rails 4.2.1, I believe you just cannot provide an alias when using joins from ActiveRecord.

If you want to query edges by the first node, you could do it just like you stated:

Edge.joins(:first).where(nodes: {value: 1})
SELECT "edges".* FROM "edges" INNER JOIN "nodes" ON "nodes"."id" = "edges"."first_id" WHERE "nodes"."value" = 1

But if you have to query using both nodes, you can still use joins like this:

Edge.joins(:first, :second).where(nodes: {value: 1}, seconds_edges: {value: 2})
SELECT "edges".* FROM "edges" INNER JOIN "nodes" ON "nodes"."id" = "edges"."first_id" INNER JOIN "nodes" "seconds_edges" ON "seconds_edges"."id" = "edges"."second_id" WHERE "nodes"."value" = 1 AND "seconds_edges"."value" = 2