Rails 查询加入关联 table 与别名
Rails query join association table with alias
我有一个模型Edge
通过不同的外键两次属于另一个模型Node
:
def Edge < ActiveRecord::Base
belongs_to :first, class_name: 'Node'
belongs_to :second, class_name: 'Node'
end
我想使用 ActiveRecord 执行此查询:
SELECT * FROM edges INNER JOIN nodes as first ON first.id = edges.first_id WHERE first.value = 5
我找到了使用.joins()
方法加入协会的方法:
Edge.joins(:first)
但是这会使用 table 名称而不是关联名称生成查询,因此在 .where()
方法中我必须显式使用 table 名称,这会破坏关联抽象。
Edge.joins(:first).where(nodes: {value: 5})
我还可以在 .joins()
方法中显式使用 SQL 查询来定义模型别名:
Edge.joins('INNER JOIN nodes as first ON nodes.id = edges.first_id')
但这打破了更多的抽象。
我认为应该有在加入时自动定义 table 别名的方法。或者也许是一种自己编写此类功能的方法。类似于:
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
但是我找不到任何关于访问特定关系信息的信息,比如它的名称、外键等。那我该怎么做呢?
我也觉得奇怪,即使 Rails 已经是第 4 个主要版本,如此明显的功能却如此复杂。也许我遗漏了什么?
至于 Rails 4.2.1,我相信您在使用来自 ActiveRecord 的 joins
时不能提供别名。
如果你想通过第一个节点查询边,你可以像你说的那样做:
Edge.joins(:first).where(nodes: {value: 1})
SELECT "edges".* FROM "edges" INNER JOIN "nodes" ON "nodes"."id" = "edges"."first_id" WHERE "nodes"."value" = 1
但是如果你必须使用两个节点进行查询,你仍然可以像这样使用joins
:
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
当然你可以使用 table 别名(可能是因为 rails 5)的关系,有些像下面这样:
def Edge < ActiveRecord::Base
...
def self.joins_alias
# Generate something like
join_name = table.table_alias || table.name
table_to_join = ... # table name to join
alias_to_join = ... # table alias to join
joins("INNER JOIN #{table_to_join} as #{alias_to_join} ON #{alias_to_join}.external_id = #{join_name}.id")
end
end
我有一个模型Edge
通过不同的外键两次属于另一个模型Node
:
def Edge < ActiveRecord::Base
belongs_to :first, class_name: 'Node'
belongs_to :second, class_name: 'Node'
end
我想使用 ActiveRecord 执行此查询:
SELECT * FROM edges INNER JOIN nodes as first ON first.id = edges.first_id WHERE first.value = 5
我找到了使用.joins()
方法加入协会的方法:
Edge.joins(:first)
但是这会使用 table 名称而不是关联名称生成查询,因此在 .where()
方法中我必须显式使用 table 名称,这会破坏关联抽象。
Edge.joins(:first).where(nodes: {value: 5})
我还可以在 .joins()
方法中显式使用 SQL 查询来定义模型别名:
Edge.joins('INNER JOIN nodes as first ON nodes.id = edges.first_id')
但这打破了更多的抽象。
我认为应该有在加入时自动定义 table 别名的方法。或者也许是一种自己编写此类功能的方法。类似于:
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
但是我找不到任何关于访问特定关系信息的信息,比如它的名称、外键等。那我该怎么做呢?
我也觉得奇怪,即使 Rails 已经是第 4 个主要版本,如此明显的功能却如此复杂。也许我遗漏了什么?
至于 Rails 4.2.1,我相信您在使用来自 ActiveRecord 的 joins
时不能提供别名。
如果你想通过第一个节点查询边,你可以像你说的那样做:
Edge.joins(:first).where(nodes: {value: 1})
SELECT "edges".* FROM "edges" INNER JOIN "nodes" ON "nodes"."id" = "edges"."first_id" WHERE "nodes"."value" = 1
但是如果你必须使用两个节点进行查询,你仍然可以像这样使用joins
:
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
当然你可以使用 table 别名(可能是因为 rails 5)的关系,有些像下面这样:
def Edge < ActiveRecord::Base
...
def self.joins_alias
# Generate something like
join_name = table.table_alias || table.name
table_to_join = ... # table name to join
alias_to_join = ... # table alias to join
joins("INNER JOIN #{table_to_join} as #{alias_to_join} ON #{alias_to_join}.external_id = #{join_name}.id")
end
end