39535


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

例えば

[code]

    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">]>
    
[/code]

こうやると、

[code]

    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"
    
[/code]

結果にversionが追加されます

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

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

[code]

    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">]>
    
[/code]

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

[code]

    => #<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>
    
[/code]

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

* * *
[code]

    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
    
[/code]


CC BY-SA 3.0
2017-11-13T00:35:00.000
39527
2017-11-13T03:48:50.590
13276
question
ruby-on-rails rails-activerecord
join.includeしながら、selectで追加要素を取得したい

[code]

    spots = Spot.includes(:spot_datum).select('spots.*, version() as version')
    
[/code]

でいいです。先にspotsテーブルからSELECTして、それからspot_dataに対しIN句で取得する２つのSQLが発行されるはずです。（eager
loading）

`ActiveRecord`で取得した結果は`ActiveRecord::Base`モデルにマッピングされるので、この場合は`Spot`モデルにマッピングされます。`SpotDatum`にアクセスする場合は`spots.first.spot_datum`のようにしてアクセスすべきです。

そもそも、`select('spots.*,spot_data.*,`のような生SQL的発想はActiveRecordとは噛み合わないので、もしそのようなものをお望みなら素直に生SQLを書いて結果をハッシュで受けた方が無難だと思います。

[code]

    ActiveRecord::Base.connection.select_all('SELECT spots.* ...').to_hash
    
[/code]

参考  
\-
<http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html>  
\- <https://qiita.com/south37/items/b2c81932756d2cd84d7d>


CC BY-SA 3.0
2017-11-13T03:48:50.590
39535
2017-11-13T03:48:50.590
9608
39527
answer