Ruby on Rails 14章ajax, サブクエリ

Ruby on Rails 14章 をやりました。

●ajax
htmlの場合、コントローラ側は,redirect_to user
のようにuser/show.html.erbなどにリダイレクトさせていた。

ajaxの場合はjsを返せばよい。

respond_to do |format|
format.html {redirect_to @user}
format.js
end

respond_toの評価はフォーム側のremote: true
で決まる。

●クライアントサイドのjs
create.js
format.jsで、create.jsが評価されるのだろう。

@user.followersなどがサーバサイドで評価された後、
その結果をレスポンスとしてそのまま返し、
クライアントサイドで結果がマッピングされる、と考えらえる。

$(“#follo_form”).html(“<%= escape_javascript(render(‘users/unfollow’)) %>”);
$(“#follo_form”).html(“<%= @user.followers.count %>”);

f:id:s06068ss:20190921053713j:plain

●feed
フォローユーザと自分のマイクロポストを表示する。

すでに,仮実装で自身のマイクロポストは出るようになっている。
デフォルトスコープの設定で、マイクロポストの降順ソートはなっているので、
自身以外のフォローユーザの投稿が表示対象になればよい。

Miropost.where(“user_id in (?) OR user_id = ?”, self.following.map(&:id), self.id)

followingは、もともとフォローのin/outを示すactive_relationshipsのリストを、
followed_id(ユーザID)のリストにマッピングしなおしたもので、
フォローしているユーザリストが返る。

この中から、mapでいうidをリスト化しなおすことは、下記でできる。

self.following.map(&:id)

さらに、active_recordが下記のようにサポートしてくれる。

self.following_ids

>> User.first.following.map(&:id)
User Load (0.2ms) SELECT “users”.* FROM “users” ORDER BY “users”.”id” ASC LIMIT ? “LIMIT”, 1
User Load (0.5ms) SELECT “users”.* FROM “users” INNER JOIN “relationships” ON “users”.”id” = “relationships”.”followed_id” WHERE “relationships”.”follower_id” = ? “follower_id”, 1
=> [2, 3, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51]
>>
>> User.first.following_ids
User Load (0.1ms) SELECT “users”.* FROM “users” ORDER BY “users”.”id” ASC LIMIT ? “LIMIT”, 1
(0.2ms) SELECT “users”.”id” FROM “users” INNER JOIN “relationships” ON “users”.”id” = “relationships”.”followed_id” WHERE “relationships”.”follower_id” = ? “follower_id”, 1
=> [2, 3, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51]

●inの上限
oracleだと1000。そうでなくてもunion allを1000回もやるなんていかがなものかしら。
sqlが確認できるように、フォローしているユーザのIDをメモリに持つにもなるので、
APサーバがいつかひっ迫するかも。

・とりあえず順を追って

>> u=User.first
User Load (0.2ms) SELECT “users”.* FROM “users” ORDER BY “users”.”id” ASC LIMIT ? “LIMIT”, 1

>> Micropost.where(“user_id in (?) or user_id = ?”, u.following_ids, u.id)
(0.3ms) SELECT “users”.”id” FROM “users” INNER JOIN “relationships” ON “users”.”id” = “relationships”.”followed_id” WHERE “relationships”.”follower_id” = ? “follower_id”, 1
Micropost Load (1.1ms) SELECT “microposts”.* FROM “microposts” WHERE (user_id in (2,3,5,6,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,

26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51) or user_id = 1) ORDER BY “microposts”.”created_at” DESC LIMIT ? “LIMIT”, 11

>> Micropost.where(“user_id in (:following_ids) or user_id = :user_id“, following_ids: u.following_ids, user_id: u.id)
Micropost Load (1.1ms) SELECT “microposts”.* FROM “microposts” WHERE (user_id in (2,3,5,6,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,

26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51) or user_id = 1) ORDER BY “microposts”.”created_at” DESC LIMIT ? “LIMIT”, 11

>> Micropost.where(“user_id in (:following_ids) or user_id = :user_id”, following_ids: “select followed_id from relationships where followed_id = :user_id”, user_id: u.id)
Micropost Load (0.4ms) SELECT “microposts”.* FROM “microposts” WHERE (user_id in (‘select followed_id from relationships where followed_id = :user_id’) or user_id = 1) ORDER BY “microposts”.”created_at” DESC LIMIT ? “LIMIT”, 11

●邪道だけど

モデルをガン無視することになるけれど、検索時やバッチ処理などで

ゴリゴリなsqlを組む場合

>> con=ActiveRecord::Base.connection

>> con.select_all(‘select id from users limit 1’)
(0.2ms) select id from users limit 1
=> #<ActiveRecord::Result:0x000000000456dc50 @columns=[“id”], @rows=12, @hash_rows=nil, @column_types={}>

u=con.select_all(‘select * from users limit 1’)

>> u
=> #<ActiveRecord::Result:0x000000000457e910 @columns=[“id”, “name”, “email”, “created_at”, “updated_at”, “password_digest”, “remember_digest”, “admin”, “activation_digest”, “activated”, “activated_at”, “reset_digest”, “reset_sent_at”],
@rows=1, “Example User”, “example@railstutorial.org”, “2019-09-15 15:24:14.005381”, “2019-09-15 15:24:14.005381”, “$2a$10$UgR.yS81aTsDfm4t9ylLBuPDr4Tq.kQWqkxzODAY7k9KjW8TwHVhG”, nil, “t”, “$2a$10$5B3G3pWS.WJHEXHv.taWN.DqVaaVoxxYU9Km34h0ZEcJsyU40fmrC”, “t”, “2019-09-15 15:24:13.916000”, nil, nil, @hash_rows=nil, @column_types={}>

使いづらいので、to_hashにすると扱いやすい。ここでマップの配列(List<Map>)になっている。

>> u.to_hash
=> [{“id”=>1, “name”=>”Example User”, “email”=>”example@railstutorial.org”, “created_at”=>”2019-09-15 15:24:14.005381”, “updated_at”=>”2019-09-15 15:24:14.005381”, “password_digest”=>”$2a$10$UgR.yS81aTsDfm4t9ylLBuPDr4Tq.kQWqkxzODAY7k9KjW8TwHVhG”, “remember_digest”=>nil, “admin”=>”t”, “activation_digest”=>”$2a$10$5B3G3pWS.WJHEXHv.taWN.DqVaaVoxxYU9Km34h0ZEcJsyU40fmrC”, “activated”=>”t”, “activated_at”=>”2019-09-15 15:24:13.916000”, “reset_digest”=>nil, “reset_sent_at”=>nil}]
>>

>> u.to_hash[0]
=> {“id”=>1, “name”=>”Example User”, “email”=>”example@railstutorial.org”, “created_at”=>”2019-09-15 15:24:14.005381”, “updated_at”=>”2019-09-15 15:24:14.005381”, “password_digest”=>”$2a$10$UgR.yS81aTsDfm4t9ylLBuPDr4Tq.kQWqkxzODAY7k9KjW8TwHVhG”, “remember_digest”=>nil, “admin”=>”t”, “activation_digest”=>”$2a$10$5B3G3pWS.WJHEXHv.taWN.DqVaaVoxxYU9Km34h0ZEcJsyU40fmrC”, “activated”=>”t”, “activated_at”=>”2019-09-15 15:24:13.916000”, “reset_digest”=>nil, “reset_sent_at”=>nil}

>> u.to_hash[0][“name”]
=> “Example User”