ActiveRecordでselect('version() as version ') という具合にselectを追加すると、通常の結果に加えて、versionを取得することができますよね。

例えば

irb(main):001:0> spots = Spot.joins(:spot_datum).select('spots.*,spot_data.*,version() as version')
  Spot Load (5.0ms)  SELECT spots.*,spot_data.*,version() as version FROM "spots" INNER JOIN "spot_data" ON "spot_data"."spot_id" = "spots"."id"
=> #<ActiveRecord::Relation [#<Spot id: 1, name: "temp", created_at: "2017-11-12 23:55:07", updated_at: "2017-11-12 23:55:07">]>

こうやると、

irb(main):002:0> spots.first.version
=> "PostgreSQL 9.6.2 on x86_64-pc-linux-gnu, compiled by gcc (Debian 4.9.2-10) 4.9.2, 64-bit"

結果にversionが追加されます

  • joinする意味がないコードですけど、実際にはWHERE節でjoinしたspot_datumを使います
  • 実際にはpostgisを使って距離を計算した結果をとりたいのですが、簡単のためにversionにしています

さて、ここまではうまく行ったのですが、上記SQLだとN+1問題が発生してしまうので、includesを追加してみます

irb(main):003:0> spots = Spot.joins(:spot_datum).includes(:spot_datum).select('spots.*,spot_data.*,version() as version')
  SQL (2.9ms)  SELECT spots.*,spot_data.*,version() as version, "spots"."id" AS t0_r0, "spots"."name" AS t0_r1, "spots"."created_at" AS t0_r2, "spots"."updated_at" AS t0_r3, "spot_data"."id" AS t1_r0, "spot_data"."spot_id" AS t1_r1, "spot_data"."address" AS t1_r2 FROM "spots" INNER JOIN "spot_data" ON "spot_data"."spot_id" = "spots"."id"
=> #<ActiveRecord::Relation [#<Spot id: 1, name: "temp", created_at: "2017-11-12 23:55:07", updated_at: "2017-11-12 23:55:07">]>

するとなぜか、versionが取れなくなってしまいます。

=> #<ActiveRecord::Relation [#<Spot id: 1, name: "temp", created_at: "2017-11-12 23:55:07", updated_at: "2017-11-12 23:55:07">]>
irb(main):004:0> spots.first.version
NoMethodError: undefined method `version' for #<Spot:0x0056349f0aed38>

なんでダメなんでしょう?
N+1問題が起きないようにしつつ、versionも取得できる方法ってなんかないでしょうか?


irb(main):022:0> RUBY_VERSION
=> "2.3.2"
irb(main):023:0> Rails.version
=> "5.0.6"

# cat db/migrate/20151109073447_create_models.rb
class CreateModels < ActiveRecord::Migration
  def change
    create_table :spots do |t|
      t.string :name
    end

    create_table :spot_data do |t|
      t.references :spot, index: true, foreign_key: true
      t.string :address
    end
  end
end


# cat app/models/spot.rb
class Spot < ApplicationRecord
  has_one :spot_datum
end

# cat app/models/spot_datum.rb
class SpotDatum < ApplicationRecord
  belongs_to :spot
end