You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何includes查询无法访问别名列而joins可以?

Why includes Doesn't Let You Access Aliased Columns Like joins Does in Rails

Great question! The difference here boils down to how Rails handles query results from joins vs includes (even when you add references). Let’s break this down clearly:

How joins Works

When you use joins(:voter_election_history), Rails runs a single INNER JOIN query. The result set combines columns from both the voters and voter_election_histories tables, and Rails builds Voter objects directly from this combined data. Any custom aliases you define in the select clause (like ad_ed) get added as temporary attributes to the Voter instances—this is why voters.first.ad_ed works seamlessly here.

How includes Works

includes is built for eager loading to avoid N+1 query issues. By default, it runs two separate queries (one for Voter records, one for their associated VoterElectionHistory records). When you add references(:voter_election_histories), Rails switches to a single LEFT JOIN query, but its object-building logic stays the same:

  • It extracts columns belonging to the Voter model to construct the main Voter objects.
  • It extracts columns belonging to VoterElectionHistory to build associated objects and attach them to the voters.

The key catch: Custom aliases like ad_ed don’t belong to either model’s predefined attributes, so Rails ignores them when constructing the objects. That’s why calling voter.first.ad_ed throws an "undefined method" error—this attribute was never assigned to the Voter instance during setup.

Fixes to Access the Aliased Column

You have a few clean options to make ad_ed accessible when using includes:

1. Manually Assign the Aliased Attribute

After fetching the results, iterate through the voters and pull the ad_ed value from the raw attributes hash:

voters = Voter.includes(:voter_election_history)
              .select("voters.ed, voters.ad, concat_ws('/', voters.ad, voters.ed) AS ad_ed, voter_election_histories.e110315")
              .where("voter_election_histories.e110519 IS NULL")
              .order('voters.ed')
              .references(:voter_election_histories)

voters.each { |voter| voter.ad_ed = voter.attributes['ad_ed'] }

2. Define a Model Method for Flexibility

Add a method to your Voter model that either uses the precomputed alias (if available from the query) or calculates the value dynamically:

class Voter < ApplicationRecord
  has_many :voter_election_histories

  def ad_ed
    # Use the query's precomputed value if present, else calculate it on the fly
    attributes['ad_ed'] || "#{ad}/#{ed}"
  end
end

Now you can call voter.ad_ed regardless of whether you used joins or includes—it’ll prioritize the query’s precomputed value if available, otherwise generate it dynamically.

3. Use eager_load as a Cleaner Alternative

eager_load behaves like includes + references but always uses a single LEFT JOIN query. It still won’t auto-assign the alias, but the fixes above work just as well with it. The syntax is a bit cleaner:

voters = Voter.eager_load(:voter_election_history)
              .select("voters.ed, voters.ad, concat_ws('/', voters.ad, voters.ed) AS ad_ed, voter_election_histories.e110315")
              .where("voter_election_histories.e110519 IS NULL")
              .order('voters.ed')

内容的提问来源于stack exchange,提问作者bkunzi01

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:37:29