ひびきの技術メモ帳

駆け出しエンジニアのメモ帳

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日) ※緊急事態宣言により休業致します。"
}
},
...
...
]

以上 なんか間違ってたりしたら教えてくれると嬉しいです。

カレントディレクトリにRails newしたいメモ

GitHubで作ったREADMEのみのリポジトリをcloneしてRailsアプリの開発を始めたいと思いました。

でも$rails new sample_appと打つと今いるディレクトリの一個下にsample_appディレクトリが作られてその中にRailsのコードが追加されてしまいます。

今いるディレクトリにRailsのプロジェクトを作るには $rails new .と打つとできるよと教えてもらったのでメモ。

覚えておくとポートフォリオとかREADME先に作る感じの時に使えるかも。

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_tokenclient_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()