8/26-調査

■index
  1. jbuilderについて_2
  2. リクエスト・レスポンス処理について(render/head)
  3. destroyについて
  4. RecordNotDestroyedについて
  5. destroyとdestroy!の!について
  6. params.permitについて
  7. oem_service_conとservice_conについて
  8. user_token_controllerについて
  9. ワンタイムパス・二段階認証・とかとか
  10. entity = self.class.controller_name.classify.constantize
  11. sendメソッドについて
  12. SecureRandomを活用したランダム文字列生成
  13. JWTについて_2
  14. before_action_2
  15. Admin/Userの違いという観点
  16. Active Jobについて
  17. Active Mailerについて
  18. app/views以下のjsonについて
  19. Rails APIモードについて


①jbuilderについて_2
  • json形式のデータを簡単に作る為のgem
  • app/views以下に〇〇.json.jbuilderのファイルを作成する
  • jbuilderのオブジェクトのjsonを使うと、JSON形式の文字列のデータを作成出来る
  • 概ね、indexとshowメソッドで使うことが多い
[使い方]

index.json.builder
json.array!(@users) do |user|
  json.extract! user, :id, :name, :email, :password
  json.url user_url(user, format: :json)
end
show.json.builder
json.extract! @user, :id, :name, :email, :password, :created_at, :updated_at

[参考]
リクエスト・レスポンス処理について
レスポンスについてcontrollerは記述している。種類は下記、
  • render:テンプレートの呼び出し・テキスト/スクリプトの出力など、結果出力
  • redirect_to
  • send_file
  • send_data
  • head:応答ヘッダーのみ出力
  • respond_to
使い方(コピペ)

renderメソッド

json

json形式で結果を出力する
レスポンスヘッダーにapplication/jsonも付与してくれる。
render json: @todos

### その他オプション

#### content_typeオプション

以下の様にcontent_typeをオプションで指定可能

```rb
render plain: 'hello', content_type: 'text/comma-separated-values'

statusオプション

ステータスコードを指定可能
render json: todos, status: 200

headメソッド

コンテンツ本体だけではなく、ただステータスコードのみを返したいケースに利用
head 404
シンボルでも指定可能
head :ok # 200
head :not_found # 404
head :forbidden # 403
→コントローラーでインスタンス化してるやつって、Modelクラスだっけ
@customers = Customer.search(...)
とか、、、

destroyについて
  • レコードを削除するメソッドです
  • routes.rbで使用を定義、Viewで削除場所を定義、Controllerで動きを定義する
  • 削除するレコードidの取得
  • 削除処理
  • 例外処理
[実装]
①Controllerではアクションを定義する
@customer.costracts
@customer.note1 = nil
.....

②削除するレコードidの取得
customer.find_by!(id: params[:id])
③削除
@customer.destroy
④例外処理
ActiveRecord::RecordNotDestroyed < ActiveRecord::ActiveRecordError

  • destroyメソッドがfalseの場合発生するエラー

destroyとdestroy!の!について
  • 失敗時の挙動が、falseか例外かの違い
  • destroy : 削除できたら真の値(削除したインスタンス自身)、できなかったら偽の値(false)を返す。
  • destroy! : 削除できたら真の値(削除したインスタンス自身)、できなかったらActiveRecord::RecordNotDestroyed例外を発生させる。


params.permitについて

  • いわゆるstrong parameterという、Action Controllerのパラメータ
  • params.permitは、ホワイトリストである
  • なので、意図した情報以外の部分は除外する処理となる
  • params.permit(...)の...に記述以外を削除する


⑦oem_service_conとservice_conについて

  • 疑問点:なぜ、oem_service_controllerはindexのメソッドを直接定義しているのに、対してのservice_controllerは、servicableにincludeして、その中でメソッドを定義する、二層構造になっているのはなぜだ?同じなのに違う振る舞い
  • 解決:OEMに関しては、Adminが実行権限があって、一般ユーザーにはほとんどない。対して、サービスコントローラーは、Adminも一般ユーザーも同じつくり。なので、oemは一枚ペラにしており、serviceは二層構造にしている。そっちの方が記述がすっきりしているから。

⑧user_token_controllerについて

  • WebAPIに認証情報をつけたいときの実装
  • json {json:auth_token,role: @role}みたいに送信情報に情報を付与する
  • SPA+Rails+Loginについての良記事リンクあり

⑨ワンタイムパス・二段階認証・とかとか

  • auth関連ファイルを見直す必要あり
  • そして、概念をそもそも整理しないと感

⑩entity = self.class.controller_name.classify.constantizeについて

  • ActiveSupportのString拡張版である
  • 与えられたテーブル名に対応するクラス名を返す
  • classifyではStringなのでconstantizeで、クラスオブジェクトに変換する
  • なので、コントローラー名を取得してentityに格納している


11)entity = sendメソッドについて

  • 呼び出すメソッドを動的に決める、Rubyのメタプログラム
  • 文字列を用いた変数化
  • send("文字列orシンボルでのメソッド名")
[実例]
1)send("#{object.class.to_s.downcase}_path", object) 
2)send("current_#{entity}".downcase)
[参考]

12)SecureRandomを用いたランダム文字列生成
iat = Time.now.to_i
jti = "#{iat}/#{SecureRandom.hex(18)}"
  • SecureRandom.hex(18)でランダム文字列化
  • 頭をTimeをint化して入力値もランダム化する


13)JWTについて_2
概要
  • JWTは「Json Web Token」の略
  • JWTはjsonをトークン化する仕組み
  • 改ざん出来ないjsonと思ってもらえればOK
JWTがサポートしている署名アルゴリズム
  • HMAC : 共通鍵方式 <=使ってるのこれ
  • RSA : 公開鍵方式
  • ECDSA : 楕円曲線暗号
運用
  • 秘密鍵と公開鍵を作っておく
  • 送りたいデータを秘密鍵でエンコードし、レスポンスとして返す
  • 帰ってきたJWTをセッションに保存
  • 受信側は公開鍵を使ってトークンをデコードし、必要なデータを取り出す
  • 必要ならばトークンに有効期間を設けておく
JWTの構造
JWTは3つのパートから成り、全てbase64urlエンコードされている。
この3つのパートをドット(.)で繋げたものがJWTである
各パートについて
Header : このパラメータがJWTであることや、署名アルゴリズムを示す
{"typ":"JWT", "alg":"HS256"}

エンコード後
eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9
Claim Set : 内容
{"iss":"joe",
 "exp":1300819380,
 "http://example.com/is_root":true}

エンコード後
eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ
Signature : 上記のHeaderとClaimを繋げてRSA等で署名したもの
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
JWTは上記のHeader、Claim Set、Signatureをドット(.)で繋げると出来上がる
eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
使用例:サンプルコード(RSA)
(encode部分は同じだけど、鍵方式は別なので)
require 'jwt'

# 秘密鍵生成
rsa_private = OpenSSL::PKey::RSA.generate(2048)

# 公開鍵生成
rsa_public = rsa_private.public_key

# sample data
payload = {
  id: 1,
  name: 'tanaka',
  password: 'jau0328ura0jrdsf3'
}

# payload を暗号化 (秘密鍵でしかできない)
token = JWT.encode(payload, rsa_private, 'RS256')

# payload を復号化 (公開鍵でもできる)
JWT.decode(token, rsa_public, true, { algorithm: 'RS256' })

# 秘密鍵で復号化
JWT.decode(token, rsa_private, true, { algorithm: 'RS256' })


コード例
def generate_url(a)

    iat = Time.now.to_i
    jti = "#{iat}/#{SecureRandom.hex(18)}"

    jwt = JWT.encode(
        {
        iat: iat,
        jti: jti,
        name: a.email.split('@').first,
        email: a.email,
        },
        settings[:jwt_shared_secret],
    )

    "#{settings[:url]/access/jwt?jwt=#{jwt}"
end


ランダム値を生成、入力値を含める、設定値はsettingsに含めて別で送信



14)before_action/skip_before_action_2

  • ActionControllerのフィルタ機能である
  • 全適用する前にフィルタすることで高速化する(before_action)
  • 例えばログインのユーザ認証で、ログイン/非ログインの振り分け処理
  • しかし、すべてにおいてログイン認証ページに遷移するのはよくない(新規登録・作成など)場合がある
  • そのとき、フィルター除外としてskip_before_actionを使う
[下記引用]

8 フィルタ

フィルタは、コントローラにあるアクションの「直前 (before)」、「直後 (after)」、あるいは「直前と直後の両方 (around)」に実行されるメソッドです。
フィルタは継承されるので、フィルタをApplicationControllerで設定すればアプリケーションのすべてのコントローラでフィルタが有効になります。
「before系」フィルタは、リクエストサイクルを止めてしまう可能性があるのでご注意ください。「before系」フィルタのよくある使われ方として、ユーザーがアクションを実行する前にログインを要求するというのがあります。このフィルタメソッドは以下のような感じになるでしょう。
class ApplicationController < ActionController::Base
  before_action :require_login

  private

  def require_login
    unless logged_in?
      flash[:error] = "You must be logged in to access this section"
      redirect_to new_login_url # halts request cycle
    end
  end
end
このメソッドはエラーメッセージをflashに保存し、ユーザーがログインしていない場合にはログインフォームにリダイレクトするというシンプルなものです。「before系」フィルタによってビューのレンダリングやリダイレクトが行われると、このアクションは実行されません。フィルタの実行後に実行されるようスケジュールされた追加のフィルタがある場合、これらもキャンセルされます。
この例ではフィルタをApplicationControllerに追加したので、これを継承するすべてのコントローラが影響を受けます。つまり、アプリケーションのあらゆる機能についてログインが要求されることになります。当然ですが、アプリケーションのあらゆる画面で認証を要求してしまうと、認証に必要なログイン画面まで表示できなくなるという困った事態になってしまいます。したがって、このようにすべてのコントローラやアクションでログイン要求を設定すべきではありません。skip_before_actionメソッドを使えば、特定のアクションでフィルタをスキップできます。
class LoginsController < ApplicationController
  skip_before_action :require_login, only: [:new, :create]
end
上のようにすることで、LoginsControllernewアクションとcreateアクションをこれまでどおり認証不要にできました。特定のアクションについてだけフィルタをスキップしたい場合には:onlyオプションを使います。逆に特定のアクションについてだけフィルタをスキップしたくない場合は:exceptオプションを使います。これらのオプションはフィルタの追加時にも使えるので、最初の場所で選択したアクションに対してだけ実行されるフィルタを追加することもできます


15)Admin/Userの違いという観点
Adminと一般ユーザーはパーツで共通しているものが非常に多いため、共通点と相違点という観点で、時間を見つけてチェックしていきたい。
  • oem_controllerちがう
  • users_controllerちがう
  • adminはtokenない
  • zendeskも提供機能が違う


16)Active Jobについて
→本件では未使用につき概要のみの記述

  • バックグラウンドで実行するジョブの作成やキュー登録について
  • 具体的なジョブは、定期的なクリーンアップ、請求書発行やメール配信など、あらゆる処理です



17)Active Mailerについて
→本件では未使用につき概要のみの記述

  • アプリケーションでメールの送受信を行えるようにするための機能
  • メイラーの動作はコントローラときわめて似通っています。
  • メイラーはActionMailer::Baseを継承し、app/mailersに配置され、app/viewsにあるビューと結び付けられます。



18)app/views以下のjsonについて

  • jbuilderにてJSONを生成してフロントに送信している。
  • RailsのAPIモードという。詳細は後述する。
  • xxx.json.jbuilderファイルは全てで8つ。
  • そのうち6つがindex.json.jbuilderであり、他はshowとutm_hostnames
  • layoutファイルは消し忘れなので、考慮しない



19)RailsAPIモードについて
        Next Post Previous Post
        No Comment
        Add Comment
        comment url