join.includeしながら、selectで追加要素を取得したい
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