Rails query join association table with alias

2020-07-01 04:10发布

问题:

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?

回答1:

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