ActiveModelSerializersを使ってみたメモ
事前準備
安定板って書いてあったので0.10.0を選択
gem 'active_model_serializers', '~> 0.10.0'
gemを追加しで$ bundle
GitHub - rails-api/active_model_serializers at 0-10-stable
rails g serializer Log(使いたいモデル名で読み替えてください)
app/serializers/log_serializer.rb
が生成されるのでここに設定を書きます。
使い方
モデルサンプル
class Log < ApplicationRecord belongs_to :user belongs_to :grnv_bar_info end
# ユーザーの立ち寄り情報 class CreateLogs < ActiveRecord::Migration[6.0] def change create_table :logs do |t| t.string :user_id t.string :grnv_bar_info_id t.text :memo t.date :drank_on t.timestamps end end end
# お店情報 class CreateGrnvBarInfos < ActiveRecord::Migration[6.0] def change create_table :grnv_bar_infos do |t| t.string :grnv_id t.string :address t.string :name t.text :image t.text :grnv_url t.string :opentime t.string :holiday t.string :tel t.timestamps end end end
log_serializer.rbを書く
class LogSerializer < ActiveModel::Serializer attributes :id, :grnv_bar_info_id, :drank_on attribute :created_at, key: :visited_on belongs_to :grnv_bar_info class GrnvBarInfoSerializer < ActiveModel::Serializer attributes :id, :name, :address, :grnv_url, :tel, :opentime, :holiday end end
アソシエーション先の情報も一緒に取りたい場合はなかにclassを書いて取りたい情報を定義する。 active_model_serializers/getting_started.md at v0.10.6 · rails-api/active_model_serializers · GitHub
api/v1/logs_controller.rb
module Api module V1 class LogsController < ApplicationController def index # logs = Log.all logs = logs.eager_load(:grnv_bar_info) render json: logs end end end end
logs = Log.all
で定義してもアソシエーション先の情報は取れるがN+1問題が発生するのでincludeしといた方が良さげ
Started GET "/api/v1/logs" for ::1 at 2021-02-14 20:48:28 +0900 Processing by Api::V1::LogsController#index as HTML Log Load (0.4ms) SELECT `logs`.* FROM `logs` ↳ app/controllers/api/v1/logs_controller.rb:6:in `index' [active_model_serializers] GrnvBarInfo Load (0.6ms) SELECT `grnv_bar_infos`.* FROM `grnv_bar_infos` WHERE `grnv_bar_infos`.`id` = 1 LIMIT 1 [active_model_serializers] ↳ app/controllers/api/v1/logs_controller.rb:6:in `index' [active_model_serializers] GrnvBarInfo Load (0.4ms) SELECT `grnv_bar_infos`.* FROM `grnv_bar_infos` WHERE `grnv_bar_infos`.`id` = 2 LIMIT 1 [active_model_serializers] ↳ app/controllers/api/v1/logs_controller.rb:6:in `index' [active_model_serializers] GrnvBarInfo Load (0.5ms) SELECT `grnv_bar_infos`.* FROM `grnv_bar_infos` WHERE `grnv_bar_infos`.`id` = 3 LIMIT 1 [active_model_serializers] ↳ app/controllers/api/v1/logs_controller.rb:6:in `index' [active_model_serializers] GrnvBarInfo Load (0.4ms) SELECT `grnv_bar_infos`.* FROM `grnv_bar_infos` WHERE `grnv_bar_infos`.`id` = 4 LIMIT 1 [active_model_serializers] ↳ app/controllers/api/v1/logs_controller.rb:6:in `index' [active_model_serializers] GrnvBarInfo Load (0.4ms) SELECT `grnv_bar_infos`.* FROM `grnv_bar_infos` WHERE `grnv_bar_infos`.`id` = 5 LIMIT 1 [active_model_serializers] ↳ app/controllers/api/v1/logs_controller.rb:6:in `index' [active_model_serializers] GrnvBarInfo Load (0.5ms) SELECT `grnv_bar_infos`.* FROM `grnv_bar_infos` WHERE `grnv_bar_infos`.`id` = 6 LIMIT 1 [active_model_serializers] ↳ app/controllers/api/v1/logs_controller.rb:6:in `index' [active_model_serializers] GrnvBarInfo Load (0.3ms) SELECT `grnv_bar_infos`.* FROM `grnv_bar_infos` WHERE `grnv_bar_infos`.`id` = 7 LIMIT 1 [active_model_serializers] ↳ app/controllers/api/v1/logs_controller.rb:6:in `index' [active_model_serializers] GrnvBarInfo Load (0.5ms) SELECT `grnv_bar_infos`.* FROM `grnv_bar_infos` WHERE `grnv_bar_infos`.`id` = 8 LIMIT 1 [active_model_serializers] ↳ app/controllers/api/v1/logs_controller.rb:6:in `index' [active_model_serializers] CACHE GrnvBarInfo Load (0.0ms) SELECT `grnv_bar_infos`.* FROM `grnv_bar_infos` WHERE `grnv_bar_infos`.`id` = 6 LIMIT 1 [["id", 6], ["LIMIT", 1]] [active_model_serializers] ↳ app/controllers/api/v1/logs_controller.rb:6:in `index' [active_model_serializers] GrnvBarInfo Load (0.5ms) SELECT `grnv_bar_infos`.* FROM `grnv_bar_infos` WHERE `grnv_bar_infos`.`id` = 9 LIMIT 1 [active_model_serializers] ↳ app/controllers/api/v1/logs_controller.rb:6:in `index' [active_model_serializers] GrnvBarInfo Load (0.9ms) SELECT `grnv_bar_infos`.* FROM `grnv_bar_infos` WHERE `grnv_bar_infos`.`id` = 10 LIMIT 1 [active_model_serializers] ↳ app/controllers/api/v1/logs_controller.rb:6:in `index' [active_model_serializers] CACHE GrnvBarInfo Load (0.0ms) SELECT `grnv_bar_infos`.* FROM `grnv_bar_infos` WHERE `grnv_bar_infos`.`id` = 8 LIMIT 1 [["id", 8], ["LIMIT", 1]] [active_model_serializers] ↳ app/controllers/api/v1/logs_controller.rb:6:in `index' [active_model_serializers] CACHE GrnvBarInfo Load (0.0ms) SELECT `grnv_bar_infos`.* FROM `grnv_bar_infos` WHERE `grnv_bar_infos`.`id` = 2 LIMIT 1 [["id", 2], ["LIMIT", 1]] [active_model_serializers] ↳ app/controllers/api/v1/logs_controller.rb:6:in `index' [active_model_serializers] CACHE GrnvBarInfo Load (0.0ms) SELECT `grnv_bar_infos`.* FROM `grnv_bar_infos` WHERE `grnv_bar_infos`.`id` = 1 LIMIT 1 [["id", 1], ["LIMIT", 1]] [active_model_serializers] ↳ app/controllers/api/v1/logs_controller.rb:6:in `index' [active_model_serializers] GrnvBarInfo Load (0.4ms) SELECT `grnv_bar_infos`.* FROM `grnv_bar_infos` WHERE `grnv_bar_infos`.`id` = 11 LIMIT 1 [active_model_serializers] ↳ app/controllers/api/v1/logs_controller.rb:6:in `index' [active_model_serializers] GrnvBarInfo Load (0.3ms) SELECT `grnv_bar_infos`.* FROM `grnv_bar_infos` WHERE `grnv_bar_infos`.`id` = 12 LIMIT 1 [active_model_serializers] ↳ app/controllers/api/v1/logs_controller.rb:6:in `index' [active_model_serializers] Rendered ActiveModel::Serializer::CollectionSerializer with ActiveModelSerializers::Adapter::Attributes (96.49ms) Completed 200 OK in 120ms (Views: 102.0ms | ActiveRecord: 13.4ms | Allocations: 43767) Started GET "/api/v1/logs" for ::1 at 2021-02-14 20:49:52 +0900 Processing by Api::V1::LogsController#index as HTML SQL (0.4ms) SELECT `logs`.`id` AS t0_r0, `logs`.`user_id` AS t0_r1, `logs`.`grnv_bar_info_id` AS t0_r2, `logs`.`memo` AS t0_r3, `logs`.`drank_on` AS t0_r4, `logs`.`created_at` AS t0_r5, `logs`.`updated_at` AS t0_r6, `grnv_bar_infos`.`id` AS t1_r0, `grnv_bar_infos`.`grnv_id` AS t1_r1, `grnv_bar_infos`.`address` AS t1_r2, `grnv_bar_infos`.`name` AS t1_r3, `grnv_bar_infos`.`image` AS t1_r4, `grnv_bar_infos`.`grnv_url` AS t1_r5, `grnv_bar_infos`.`grnv_tel` AS t1_r6, `grnv_bar_infos`.`opentime` AS t1_r7, `grnv_bar_infos`.`holiday` AS t1_r8, `grnv_bar_infos`.`tel` AS t1_r9, `grnv_bar_infos`.`created_at` AS t1_r10, `grnv_bar_infos`.`updated_at` AS t1_r11 FROM `logs` LEFT OUTER JOIN `grnv_bar_infos` ON `grnv_bar_infos`.`id` = `logs`.`grnv_bar_info_id` ↳ app/controllers/api/v1/logs_controller.rb:6:in `index' [active_model_serializers] Rendered ActiveModel::Serializer::CollectionSerializer with ActiveModelSerializers::Adapter::Attributes (10.15ms) Completed 200 OK in 53ms (Views: 42.4ms | ActiveRecord: 6.1ms | Allocations: 23893)
上がlogs = Log.all
下がlogs = Log.eager_load(:grnv_bar_info)
出力はこんな感じ
[ { "id": 1, "grnv_bar_info_id": "1", "drank_on": "2021-01-29", "visited_on": "2021-01-29T23:09:10.489Z", "grnv_bar_info": { "id": 1, "name": "スターバックスコーヒー 志木駅前店", "address": "〒352-0001 埼玉県新座市東北2-39-1 井下田第2ビル1F", "grnv_url": "https://r.gnavi.co.jp/d02rspn80000/?ak=nwVBjKQQ4uWSWaNOcvOMy9gHVwn5m6xty%2FXZK2fnwrE%3D", "tel": "048-476-0551", "opentime": "月~金 07:00~22:00 土・日・祝日 08:00~22:00", "holiday": "年中無休" } }, { "id": 2, "grnv_bar_info_id": "2", "drank_on": "2021-01-29", "visited_on": "2021-01-30T00:50:08.186Z", "grnv_bar_info": { "id": 2, "name": "肉バル&和風居酒屋HAL", "address": "〒352-0001 埼玉県新座市東北2-39-8 永代ビル4F", "grnv_url": "https://r.gnavi.co.jp/et3pc1r20000/?ak=nwVBjKQQ4uWSWaNOcvOMy9gHVwn5m6xty%2FXZK2fnwrE%3D", "tel": "050-5488-3333", "opentime": "月~日・祝前日・祝日 18:00~翌1:00(L.O.24:00、ドリンクL.O.24:30)", "holiday": "毎週木曜日 その他(2021年1月12日~2021年2月7日) ※緊急事態宣言により休業致します。" } }, ... ... ]
以上 なんか間違ってたりしたら教えてくれると嬉しいです。
liff.getIDToken()で取得したIDTokenを使ってRailsサーバーでユーザー情報を取得するメモ
LIFF用の初期設定
application.html.slimにLIFF用のCDNを埋めると勝手に最新版を使ってくれるらしくさらに設定がこれだけで済むから楽。
doctype html html head title | LiffIdTokenSample script charset="utf-8" src="https://static.line-scdn.net/liff/edge/2/sdk.js" = csrf_meta_tags = csp_meta_tag = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' = javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' body = yield
config/environments/developmentのhosts設定を無くしてます。理由は開発環境にngrokを使っててurlが結構頻繁に変わるからです。
Rails.application.configure do config.hosts.clear end
View(楽したいからslimを使ってます)
h1 |Profile button#getProfile |getProfile p#iss |iss: p#sub |sub: P#aud |aud: p#exp |exp: p#iat |iat: p#authTime |auth_time: p#nonce |nonce: p#amr |amr: p#name |name: p#picture |picture: p#email |email:
JavaScript
window.onload = function(){ const token = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); const getProfileButton = document.querySelector("#getProfile") const iss_field = document.querySelector("#iss") const sub_field = document.querySelector("#sub") const aud_field = document.querySelector("#aud") const exp_field = document.querySelector("#exp") const iat_field = document.querySelector("#iat") const authTime_field = document.querySelector("#authTime") const nonce_field = document.querySelector("#nonce") const amr_field = document.querySelector("#amr") const name_field = document.querySelector("#name") const picture_field = document.querySelector("#picture") const email_field = document.querySelector("#email") liff.init({ liffId: "自分のliffIDを入れてね" }) .then(() => { if (!liff.isLoggedIn()) { liff.login(); } }) .catch((err) => { console.log(err.code, err.message); }); getProfileButton.addEventListener('click', () => { let idToken = liff.getIDToken() let body =`idToken=${idToken}` let request = new Request('/users', { headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8', 'X-CSRF-Token': token }, method: 'POST', body: body }); fetch(request) .then(response => response.json()) .then(data => { console.log(data) iss_field.append(data.iss) sub_field.append(data.sub) aud_field.append(data.aud) exp_field.append(data.exp) iat_field.append(data.iat) authTime_field.append(data.auth_time) nonce_field.append(data.nonce) amr_field.append(data.amr) name_field.append(data.name) picture_field.append(data.picture) email_field.append(data.email) }) }) }
なんかごちゃごちゃ書いてありますが大方表示用のやつなので今回主に大事なとこはこれだけです。
let idToken = liff.getIDToken() let body =`idToken=${idToken}` let request = new Request('/users', { headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8', 'X-CSRF-Token': token }, method: 'POST', body: body }); fetch(request)
liff.getIDToken()
で取得したIDTokenをfetch
メソッドを使ってRailsに渡してます。
Controller
class UsersController < ApplicationController require 'net/http' require 'uri' def create idToken = params[:idToken] channelId = '自分のチャンネルIDを入れてね' res = Net::HTTP.post_form(URI.parse('https://api.line.me/oauth2/v2.1/verify'), {'id_token'=>idToken, 'client_id'=>channelId}) render :json => res.body end end
JSからfetchで渡したIDTokenをLINEにPOST通信でHTTPリクエストを投げてユーザー情報をもらって表示のためにフロントに返します。
IDTokenを検証するためのHTTPリクエスト
LINEにIDTokenを投げる時のHTTPリクエストのbody
にはid_token
とclient_id
(Channel IDです)の2個を最低限持たせる必要があるようです。あと任意でnonce
とかuser_id
を持たせられるようです。
post_form
RailsからHTTPリクエストを送るのにpost_form
ってやつが簡単そうだったので使ってみました。
Railsサーバー側で取得したユーザー情報をフロントで表示する。
fetch(request) .then(response => response.json()) .then(data => { console.log(data) iss_field.append(data.iss) sub_field.append(data.sub) aud_field.append(data.aud) exp_field.append(data.exp) iat_field.append(data.iat) authTime_field.append(data.auth_time) nonce_field.append(data.nonce) amr_field.append(data.amr) name_field.append(data.name) picture_field.append(data.picture) email_field.append(data.email) })
この辺でやってるやつです。Railsから渡したjsonの情報を表示してるだけです。
参考文献
RubyのHTTPリクエストをできるだけシンプルに実装する - Qiita library net/http (Ruby 3.0.0 リファレンスマニュアル) LINEログイン v2.1 APIリファレンス | LINE Developers 参考になりましたありがとうございました
最後に
解釈が間違っているところやここは違うやり方に変えた方がいいよなどありましたらお願いします。
hoge.js.slimを書く
js.slimの書き方があまり出てこなかったのでメモ
j renderの書き方と|
いっぱい書くの分かればいけそう
あとセミコロンをちゃんとつけよう
h1 index div id="id" |ここになんか書くンゴ
def index @id = id @hoges = Hoge.all end
|function functionName(){ | let writeSpace = document.getElementById("#{@id}"); | writeSpace.innerHTML = ("#{j(render partial: 'hoge', collection: @hoges)}"); |} |functionName()
はてなブログ開設しました
少しずつnoteの技術メモを移していこうと思います