カレーはおやつに入りますか?

気の向くままに紹介記事やプログラミング技術について書いていきます

RailsのCookieStoreをnodeで復号する

背景

既に動いているRailsアプリケーションとfrontendの間にBFF用のnodeサーバを立てて、認証情報(currentUserIdの取得)はBFFで吸収させたい。

※ Session管理にCookie Storeを使っているRailsアプリケーションを想定しています。

書いた

RubyMarshal.loadRuby独自の機構なのでnode-marshalを利用した。

 

  import * as crypto from "crypto";
  import Marshal from "marshal";
   
  function decrypt(cookie: string): Object {
  const unescapedCookie = unescape(cookie);
   
  if (!process.env.RAILS_SECRET_KEY_BASE) {
  throw new Error("No Exists process.env.RAILS_SECRET_KEY_BASE");
  }
  const secretKeyBase = process.env.RAILS_SECRET_KEY_BASE;
  const encryptedCookieSalt = "encrypted cookie";
  const encryptedSignedCookieSalt = "signed encrypted cookie";
  const iterations = 1000;
  const keySize = 64;
  const secret = crypto.pbkdf2Sync(secretKeyBase, encryptedCookieSalt, iterations, keySize / 2, "SHA1");
  const signedSecret = crypto.pbkdf2Sync(secretKeyBase, encryptedSignedCookieSalt, iterations, keySize, "SHA1");
   
  const [data, digest] = unescapedCookie.split("--");
   
  // raise 'invalid message' unless digest == OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, sign_secret, data)
  // # you better use secure compare instead of `==` to prevent time based attact,
  // # ref: ActiveSupport::SecurityUtils.secure_compare
   
  const encryptedMessage = Buffer.from(data, "base64").toString();
  const [encryptedData, iv] = encryptedMessage.split("--").map(message => {
  return Buffer.from(message, "base64");
  });
   
  const decipher = crypto.createDecipheriv("aes-256-cbc", secret, iv);
  let dec = decipher.update(encryptedData);
  dec = Buffer.concat([dec, decipher.final()]);
   
  const marshal = new Marshal();
  const parsed = marshal.load(dec).parsed;
   
  return parsed;
  }
   
  export const setDecryptCookies = (cookieName: string) => {
  return (req, _, next) => {
  if (req.cookies && req.cookies[cookieName]) {
  // Base64 encode & 暗号化 された文字列をオブジェクトへ変換
  req.cookies[cookieName] = decrypt(req.cookies[cookieName]);
  next();
  } else {
  next();
  }
  };
  };

 

環境変数Railsアプリケーションで使っているSECRET_KEY_BASEを設定する必要がある。
expressのmiddlewareとして書いたので使うときは下のような感じになる。

import cookieParser from "cookie-parser";
import express from "express";

const COOKIE_NAME = "_sample_app_session_development"

// middleware
const app = express();
app.use(cookieParser()).use(setDecryptCookies(COOKIE_NAME));

参考にしました

encryption - How to decrypt a Rails 5 session cookie manually? - Stack Overflow
rails-cookie-parser/index.js at master · instore/rails-cookie-parser · GitHub

RailsのService層ってどう使っていくのがいいんだろうね

RailsのService層ってどう使っていくのがいいんだろうね?」って聞かれたときにすぐ答えられなかったのでまとめておきたいと思います。

※ Fat Modelの解決策としてTrailblazerが最近よく話題に上がりますが、私がまだ使ったことがないので触れない方向で行きます。

情報を漁る

まずは「Rails service」とググって検索して引っかかった記事を読みました。

 

techracho.bpsinc.jp

  • アクションが複雑になる場合 (決算期の終わりに帳簿をクローズする、など)
    → 複雑な処理をmodelから分離させたい
  • アクションが複数のモデルにわたって動作する場合 (eコマースの購入でOrder, CreditCard, Customer を使用する、など)
    → どのmodelに書けばいいのかよく分からないのでとりあえずserviceに書いとけ感ある
  • アクションから外部サービスとやりとりする場合 (SNSに投稿する、など)
    → 外部サービスだしServiceっぽい!
  • アクションが背後のモデルの中核をなすものではない場合 (一定期間ごとに古くなったデータを消去する、など)
    → 複雑な処理をmodelから分離させたい
  • アクションの実行方法が多岐にわたる場合 (認証をアクセストークンやパスワードで行なう、など)。これはGoF (Gang of Four) のStrategyパターンです。
    → 複雑な処理をmodelから分離させたい

 

qiita.com

  • 一つのModelで複数のミドルウェアと通信する場合はService層に書く
    → 複雑なロジックをmodelから分離したい
  • 複数のModelが絡み合う処理はService層に書く
    → どのmodelに書けばいいのかよく分からないのでとりあえずserviceに書いとけ感ある
  • Service層では振る舞いだけを定義する(状態を持たないためModuleで設計する)
    → テストをしやすいようにする、1serviceに責務を負わせすぎないようにする

 

qiita.com

  • クラス名には動詞と目的語と「Service」を付ける
    → ソースを読んだときにすぐServiceだと気づけるようにするため、命名を悩まないようにするため
  • 引数は出来る限りnewで渡してインスタンス化する
    → 1serviceに1つの役割だけを持たせるため、
  • 1つのサービスにpublicなメソッドは、原則1つにする
    → 1serviceに1つの機能だけを持たせるため、上と一緒
  • 初期化したインスタンスはprivateのattr_readerで呼ぶ   → なるべく隠蔽したい、attr_readerにまとめることで可読性が上がる
  • 切り分けたメソッドは全てprivateなgetterメソッドとして実装する
    → 可読性、隠蔽の面から

 

qiita.com

  • クラス名は「動詞 (+ 目的語)」にする
    命名を悩まないようにするため、機能を明確化させるため
  • 外部に公開するメソッドは call という名前のクラスメソッドのみ
    → 1つの機能しか実装できないようにするため、命名しやすく、

 

techracho.bpsinc.jp

  • 命名規則を1つに定める 記事では名詞+動詞orだと動詞に違和感がある場合があるので動詞+名詞の方がいいかもって言ってる
    → 可読性
  • Service Objectを直接インスタンス化しないようにする
    → callをClass Methodとして実装することでステートレスなメソッドとして実装したい
  • Service Objectの呼び出し方法を1つに定める
    → call , runexecuteなどの意味を持たないメソッドを使うよう義務付けてクラス名でどんな機能なのか表現できるようにする、serviceを利用しようと思ったときに調べなくて済むようになる
  • Service Objectの責務を1つに絞り込む
    Manageなどの責務が曖昧な単語をserviceクラスの命名として使わないよう気をつける
    → 細かい粒度で作って疎にするため
  • Service Objectのコンストラクタを複雑にしない
    → 複雑なコンストラクタは責務が複雑な証拠。できるだけシンプルなserviceになるように心がける。
  • callメソッドの引数をシンプルにする 利用しやすいように、引数が2つ以上あるときはキーワード引数を使うとよい
  • 結果はステートリーダー経由で返す callによる副作用を期待するもの(DBに対する更新、メール/Slack送信など)だけではなく処理の結果を受け取りたいときはcallの返り値をservice objectそのものにすると柔軟な処理がかけて良い。
  • callメソッドの可読性を下げないようにする
    メソッドを分割して可読性を維持する
  • callメソッドをトランザクションでラップすることを検討する
    → 処理の中断によるバグをなくす
  • Service Objectが増えたら名前空間でグループ化する
    → 可読性向上

ServiceをCommandパターンで作るのをいい感じにサポートしてくれるGemが紹介されていた。

github.com

  • commandパターンの為のレールを敷いてくれる
  • 引数のvalidationができる
  • runrun!をいい感じに使い分けることができる
  • services/ではなくinteractions/として別のディレクトリを切ることを推奨している
  • そもそもserviceという曖昧な名前を使うのがよくないのではと思っていたのでこれはよさそう

 

記事で主張していることが被ってきたのでまとめてみると

Railsでよく使われているServiceの役割

ARを継承したクラス(≒テーブル)を利用しないロジックをまとめる

  • 便利ツール群みたいなイメージ
  • Gemが提供している機能を使いやすいようにwrapする、外部サービスとの連携など

複雑なロジックをまとめておきcontroller側で簡潔に呼ぶ

  • 複雑なロジックなのでCommandパターンを使ってできるだけ分割して書くと色々メリットがある
    • 可読性の向上
    • ステートレス(callのclass method化)にすることにより、テストをしやすくなる
  • 複数リソースを扱うために使うのもここに該当
  • starategyパターンもここに該当

オープンソースを眺めてみる。

理想はだいたい分かったので実際どんな感じになっているか確かめるためにオープンなRailsアプリケーションはどうなっているのか眺めてみた。

github.com

  • #{動詞}#{目的語}Serviceという命名規則
  • 基本的にServiceクラスのpublic methodはcallのみ
    • 綺麗なCommandパターン
  • callの見通しがよくなるように処理をprivateメソッドを切り出している
    • privateメソッド内で他serviceを呼んでいて綺麗に分離できていてすごい
  • 便利ツール群はlib/にあった

github.com

  • #{動詞}#{目的語}Serviceという命名規則
  • 機能によってnamaspaceが切られている
  • 基本的にServiceクラスのpublic metodはexecuteのみ
  • レコードオブジェクトを操作するタイプのServiceはレコードオブジェクトをそのまま返していることが多い
  • google-apiのwrapはlib/でしていた
    • 便利ツール群はlib/に書いてあるみたい

github.com

  • #{名詞}#{動詞}er/orという命名規則
  • 色々なメソッドが生えている
  • たまにCommandパターンのserviceがあったりする
  • 正直よくわからない
  • 便利ツール群はlib/にあった
  • discourseって意外とイケてない?

まとめ

  • Serviceという言葉は曖昧なのでプロジェクトごとでしっかりとルールを決めよう。
    • Commandパターンで実装するのか、全部のせServiceクラスを作って実装するのか
    • 多くの記事がCommandパターンを用いた実装を推奨
    • でも、ある程度の経験がないとどのくらいの粒度で作ればいいのか悩むかも?
  • 複雑なロジックを整理するためにServiceを使うときはCommandパターン使って上手に分割しよう。
    • Serviceクラスを作るときはManageなどの責務の広そうな単語を使わないよう注意する。
    • Commandパターンのような一貫したルールがあるのとないのとでは初見でServiceのコードを見たときのインパクトが全然違う。
  • 今まで便利ツール群はServiceに置いてたけどlib/に置くのがベターっぽい。

コンピュータシステムの理論と実装 ―モダンなコンピュータの作り方

コンピュータシステムの理論と実装をやってみる

 

コンピュータシステムの理論と実装 ―モダンなコンピュータの作り方

 

NAND素子からテトリスのようなアプリケーションが動く環境を作るをコンセプトに書かれている本です。
全てシミュレータ上で行えるのとTDDで作っていけるので独学しやすいと思います。

以下が気になる人におすすめです。

  • 論理回路は勉強したけど、論理回路とコンピュータが頭の中で繋がっていない
  • なんでアセンブラで書いた処理を正しくコンピュータが行ってくれるんだろう
  • 結局コンパイルってどんな処理が行われているんだろう

メタプログラミングRuby

メタプログラミングRuby

 これを読みながら勉強中です。

結論

ナンセンスな標準メソッドの実装にパッチを当てたくなったときにrefineは真価を発揮する。

 

以下、結論に至るまでに道のりです。


 

refineとは

Rubyにはrefineというどんなクラスのメソッドでもローカルで再定義できる機能がある。

[1] pry(main)> module StringExtensions
[1] pry(main)*   refine String do
[1] pry(main)*     def reverse
[1] pry(main)*       "esrever"
[1] pry(main)*     end
[1] pry(main)*   end
[1] pry(main)* end
=> #<refinement:String@StringExtensions>
[2] pry(main)>
[3] pry(main)> module StringStuff
[3] pry(main)*   using StringExtensions
[3] pry(main)*   "my_string".reverse
[3] pry(main)* end
=> "esrever"
[4] pry(main)> "my_string".reverse
=> "gnirts_ym"

refineを含んでいるStringExtensionsをusingで呼び出した場所からStringStuffの終わりまで再定義が有効になる。
refineの構文についての細かい説明は省略するので上記のコードからふわっと理解してほしい。

 

Railsに潜る

refineを知った最初は「めっちゃ便利じゃん!」と思ったけど、よく考えてみると大抵のことはサブクラス作って親クラスのメソッドオーバーライドを行えば解決しそうだし、refineを使わないと解決できない問題が思いつかなかった。

なのでrefineの真価を発揮する場面をRailsソースコードから調べてみた。
すると1ヶ所だけヒットした。

% find . -type f -name "*.rb" | xargs grep 'refine' -n
./activesupport/lib/active_support/core_ext/enumerable.rb:142:    refine Array do

 

# https://github.com/rails/rails/blob/32431b37704c0aaec06ae1a23e0d6091d6542fd2/activesupport/lib/active_support/core_ext/enumerable.rb 一部抜粋


# Array#sum was added in Ruby 2.4 but it only works with Numeric elements.
#
# We tried shimming it to attempt the fast native method, rescue TypeError,
# and fall back to the compatible implementation, but that's much slower than
# just calling the compat method in the first place.
if Array.instance_methods(false).include?(:sum) && !(%w[a].sum rescue false)
  # Using Refinements here in order not to expose our internal method
  using Module.new {
    refine Array do
      alias :orig_sum :sum
    end
  }

  class Array
    def sum(init = nil, &block) #:nodoc:
      if init.is_a?(Numeric) || first.is_a?(Numeric)
        init ||= 0
        orig_sum(init, &block)
      else
        super
      end
    end
  end
end

Ruby 2.4でArray#sumが追加されたけど要素にNumeric以外を入れるとTypeErrorになっちゃうからその辺イイ感じにしちゃうよー って感じのコメントが書いてあるような気がする。

読んでいく。

if Array.instance_methods(false).include?(:sum) && !(%w[a].sum rescue false)

Array#sumが存在するかつ[‘a’].sumが例外を吐くときTrue

using Module.new {
    refine Array do
      alias :orig_sum :sum
    end
  }

if文中はArray#sumArray#orig_sumという別名を定義

class Array
    def sum(init = nil, &block) #:nodoc:
      if init.is_a?(Numeric) || first.is_a?(Numeric)
        init ||= 0
        orig_sum(init, &block)
      else
        super
      end
    end
  end

要素がNumericのときはArray#orig_sum(再定義前のArray#sum)を使い、それ以外のときはEnumerable#sumでイイ感じにする (今回の目的はArray#sumの解読ではないのでこれ以上の追跡は省略する)

再定義前のArray#sumを使ってnativeに任せられるところは任せている。 さらにif文を抜けるとArray#orig_sumは使えなくなるため無駄に汚染することもない。

 

 

でもrefineなんか使わなくても以下のように直接Arrayクラスでprivateなorig_sumを宣言してしまえばいいじゃないかと思う人がいるかもしれない。

class Array
    alias :orig_sum :sum
    private :orig_sum

    def sum(init = nil, &block)
      # ...
    end
end

refineを使ったときと使わないときの差を考えてみる。

 

refineを使用したとき

irb(main):001:0> module ArrayExtension
irb(main):002:1>     refine Array do
irb(main):003:2*         alias :sumsum :sum
irb(main):004:2>     end
irb(main):005:1> end
=> #<refinement:Array@ArrayExtension>
irb(main):006:0> module Hoge
irb(main):007:1>     using ArrayExtension
irb(main):008:1>     [1,2,3].sumsum
irb(main):010:1> end
=> 6
irb(main):011:0> [1,2,3].sumsum
NoMethodError: undefined method 'sumsum' for [1, 2, 3]:Array
irb(main):012:0> [1,2,3].send(:sumsum)
NoMethodError: undefined method 'sumsum' for [1, 2, 3]:Array

usingで呼び出したmodule中のみでArray#sumsumが使えるがmodule外ではsendメソッドを使っても呼び出せない。

 

refineを使用してないとき

irb(main):001:0> class Array
irb(main):002:1>   alias :sumsum :sum
irb(main):003:1>   private :sumsum
irb(main):004:1> end
=> Array
irb(main):005:0> [1,2,3].sumsum
NoMethodError: private method 'sumsum' called for [1, 2, 3]:Array
irb(main):006:0> [1,2,3].send(:sumsum)
=> 6

Array#sumsumはprivateメソッドなので[1,2,3].sumsumはNoMethodErrorを吐くが、sendメソッドを使った場合Array#sumsumが呼び出せてしまう。

今回の目的は標準メソッドにパッチを当てることなのでパッチを当てる前のメソッドが呼び出せてしまうのは意に反している。

 

だから標準メソッドにパッチを当てたいときはrefineを使った方が良いよねっていうお話でした。

 

MacBook ProでCUDAのGPUモードが使えたり使えなかったりする話

MacBook ProでTheanoのGPUモードを使うために設定を済ませてimportするとエラーが発生。
GPUモードが使えない...??

>>> import theano
WARNING (theano.sandbox.cuda): CUDA is installed, but device gpu is not available
  (error: Unable to get the number of gpus available: CUDA driver version is insufficient for CUDA runtime version)

 

自分のmacのグラフィックスを確認すると
NVIDIA製ではないので使えないのは当たり前でした。

  チップセットのモデル: Intel Iris Graphics 6100  
  種類:  GPU  
  バス:  内蔵  
  VRAM(ダイナミック、最大):  1536 MB  
  製造元:  Intel (0x8086)

 

でも調べているとiMacではCUDAのGPUモードが使えている人はいる様子。
同じApple製品で違うメーカーのGPUが載ってるってこと?
なんでだろう。

ket-30.hatenablog.com

 

さらに調べているとこんな記事が

MacBook Pro現行モデルの技術仕様を見ると最上位機種のグラフィックスチップはIntel Iris Pro GraphicsとAMD Radeon R9 M370Xとなっており、NVIDIA GeForceは搭載されていません。

ですが、MacBook Pro (15-inch, Mid 2012) - 技術仕様を見ると、私が持っているMacBook Proは15インチ2.6GHzモデルなのでNVIDIA GeForce GT 650M、1GB GDDR5メモリが搭載されています。

GeForce GT 650M | NVIDIAには「プログラミング環境」の行に「CUDA」とあるのでCUDAが使えるようです。

引用: GeForce搭載の旧モデルMacBook ProでCUDAをセットアップする手順のメモ · hnakamur's blog at github

昔のMacBook ProではGeForceを選択できたみたい。

というわけでGeForceが載っているものと載っていないものを2011~2015年で調べてみました。

GeForceが載っている
  • Retina, 15-inch, Mid 2014
  • Retina, 15-inch, Early 2013
  • 15-inch, Mid 2012
GeForceが載ってない
  • Retina, 15-inch, Mid 2015
  • Retina, 13-inch, Early 2015
  • Retina, 13-inch, Mid 2014
  • Retina, 13-inch, Late 2013
  • Retina, 13-inch, Early 2013
  • Retina, 13-inch, Late 2012
  • 13-inch, Mid 2012
  • 17-inch, Late 2011
  • 13-inch, Late 2011
  • 15-inch, Late 2011
  • 17-inch, Early 2011
  • 15-inch, Early 2011
  • 13-inch, Early 2011

まとめ

MacBook ProでCUDAのGPUモードを使うなら2012~2014年モデルの15インチを買うべし。

 

その他のApple製品の技術仕様はこちらからどうぞ
アップル – サポート - 技術仕様

rake db:migrateとrake db:schema:loadの違い

rake db:migrate と rake db:schema:load はどちらもテーブルを作成、変更、削除するときに使うコマンドだけど違いがよく分かっていなかったのでメモメモ。

rake db:migrate

  1. migrateファイル群を元にschema.rbを作成
  2. schema.rbを元にSQLクエリを発行する

rake db:schema:load

  1. schema.rbを元にSQLクエリを発行する

 

 

要するにrake db:schema:load はmigrateファイルの変更は考慮しないよってことか。

ローカルでは rake db:migrete , CIツール上では rake db:schema:load と使い分けるのが一般的で、そうすることによってmigrateし忘れてpushしてもCIツール上のschema.rbのバージョンが同じになるようにしてるみたい。 賢いなあ。

【お勧め参考書】今月読んだおすすめの参考書

参考書

プログラミング

C言語によるアルゴリズムとデータ構造

色々な編入体験記に載っているのアルゴリズムの本です。 基礎固めに役立ちました。

プログラミングコンテストチャレンジブック

通称アリ本です。「C言語によるアルゴリズムとデータ構造」にはグラフ理論や有名なアルゴリズム(エラトステネスの篩、ダイクストラなど)が載ってなかったのでそれらを実装方法を主眼において読みました。ソースはC++ですがCが分かれば読めると思います。

 

アルゴリズムとデータ構造(青)

C言語によるアルゴリズムとデータ構造」にはグラフ理論や有名なアルゴリズム(エラトステネスの篩、ダイクストラなど)が載ってなかったのでそれらを理論的なところを主眼において読みました。この本とアリ本を行ったり来たりしながら勉強すると捗ると思います。 先生に「C言語によるアルゴリズムとデータ構造のワンランク上でダイクストラとか載ってる本貸してください」と言ったらこの本を貸してくれました。

 

アルゴリズム図鑑

基本的な探索、ソートの名前を聞いたらすぐに挙動をイメージできるように暇なときはこのアプリを使って確認してました。アニメーションを使って分かりやすく説明してくれてるのでとてもイメージしやすかったです。 300円課金すると全てのアルゴリズムが解放されるのですが課金する価値は十分にありました。

 

論理回路

論理回路入門

ざーっと一通り読んでから練習問題を解きました。 意外と時間がかかるので注意してください

 

情報理論

情報理論

この本は授業で使ってたのでそのまま使いました。

 

おすすめの読み方

 

こんな感じでした。
プログラミング
参考書をばーっと読む+写経する
過去問解く
解けない&時間がかかり過ぎるところを中心に参考書を読む
もう一度過去問を解く
を繰り返す
暇なときはアルゴリズム図鑑で復習する

論理回路
論理回路をばーっと目を通す
演習問題を解く
過去問を解く
もう一度過去問を解く
を繰り返す

情報理論
情報理論にバーっと目を通す
過去問を解く
もう一度過去問を解く
を繰り返す

僕の勉強のおおまかな流れは
プログラミング
3月下旬~4月上旬 
アルゴリズムとデータ構造 読む 1周
4月上旬~試験日
過去問を繰り返し解く、アリ本、アルゴリズムを必要に応じて読む

論理回路
4月中旬~5月中旬
毎週金曜 3時間前後
5月中旬~試験日
過去問を解く

情報理論
6月~試験日
気が向いたときに過去問を解く
分からなかったら情報理論を読む