RailsのCookieStoreをnodeで復号する
背景
既に動いているRailsアプリケーションとfrontendの間にBFF用のnodeサーバを立てて、認証情報(currentUserIdの取得)はBFFで吸収させたい。
※ Session管理にCookie Storeを使っているRailsアプリケーションを想定しています。
書いた
RubyのMarshal.load
はRuby独自の機構なのでnode-marshalを利用した。
環境変数で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」とググって検索して引っかかった記事を読みました。
- アクションが複雑になる場合 (決算期の終わりに帳簿をクローズする、など)
→ 複雑な処理をmodelから分離させたい- アクションが複数のモデルにわたって動作する場合 (eコマースの購入でOrder, CreditCard, Customer を使用する、など)
→ どのmodelに書けばいいのかよく分からないのでとりあえずserviceに書いとけ感ある- アクションから外部サービスとやりとりする場合 (SNSに投稿する、など)
→ 外部サービスだしServiceっぽい!- アクションが背後のモデルの中核をなすものではない場合 (一定期間ごとに古くなったデータを消去する、など)
→ 複雑な処理をmodelから分離させたい- アクションの実行方法が多岐にわたる場合 (認証をアクセストークンやパスワードで行なう、など)。これはGoF (Gang of Four) のStrategyパターンです。
→ 複雑な処理をmodelから分離させたい
- 一つのModelで複数のミドルウェアと通信する場合はService層に書く
→ 複雑なロジックをmodelから分離したい- 複数のModelが絡み合う処理はService層に書く
→ どのmodelに書けばいいのかよく分からないのでとりあえずserviceに書いとけ感ある- Service層では振る舞いだけを定義する(状態を持たないためModuleで設計する)
→ テストをしやすいようにする、1serviceに責務を負わせすぎないようにする
- クラス名には動詞と目的語と「Service」を付ける
→ ソースを読んだときにすぐServiceだと気づけるようにするため、命名を悩まないようにするため- 引数は出来る限りnewで渡してインスタンス化する
→ 1serviceに1つの役割だけを持たせるため、- 1つのサービスにpublicなメソッドは、原則1つにする
→ 1serviceに1つの機能だけを持たせるため、上と一緒- 初期化したインスタンスはprivateのattr_readerで呼ぶ → なるべく隠蔽したい、attr_readerにまとめることで可読性が上がる
- 切り分けたメソッドは全てprivateなgetterメソッドとして実装する
→ 可読性、隠蔽の面から
- 命名規則を1つに定める 記事では名詞+動詞orだと動詞に違和感がある場合があるので動詞+名詞の方がいいかもって言ってる
→ 可読性- Service Objectを直接インスタンス化しないようにする
→call
をClass Methodとして実装することでステートレスなメソッドとして実装したい- Service Objectの呼び出し方法を1つに定める
→call
,run
,execute
などの意味を持たないメソッドを使うよう義務付けてクラス名でどんな機能なのか表現できるようにする、serviceを利用しようと思ったときに調べなくて済むようになる- Service Objectの責務を1つに絞り込む
Manage
などの責務が曖昧な単語をserviceクラスの命名として使わないよう気をつける
→ 細かい粒度で作って疎にするため- Service Objectのコンストラクタを複雑にしない
→ 複雑なコンストラクタは責務が複雑な証拠。できるだけシンプルなserviceになるように心がける。- callメソッドの引数をシンプルにする 利用しやすいように、引数が2つ以上あるときはキーワード引数を使うとよい
- 結果はステートリーダー経由で返す
call
による副作用を期待するもの(DBに対する更新、メール/Slack送信など)だけではなく処理の結果を受け取りたいときはcall
の返り値をservice objectそのものにすると柔軟な処理がかけて良い。- callメソッドの可読性を下げないようにする
メソッドを分割して可読性を維持する- callメソッドをトランザクションでラップすることを検討する
→ 処理の中断によるバグをなくす- Service Objectが増えたら名前空間でグループ化する
→ 可読性向上
ServiceをCommandパターンで作るのをいい感じにサポートしてくれるGemが紹介されていた。
- commandパターンの為のレールを敷いてくれる
- 引数のvalidationができる
run
とrun!
をいい感じに使い分けることができる- services/ではなくinteractions/として別のディレクトリを切ることを推奨している
- そもそもserviceという曖昧な名前を使うのがよくないのではと思っていたのでこれはよさそう
記事で主張していることが被ってきたのでまとめてみると
Railsでよく使われているServiceの役割
ARを継承したクラス(≒テーブル)を利用しないロジックをまとめる
- 便利ツール群みたいなイメージ
- Gemが提供している機能を使いやすいようにwrapする、外部サービスとの連携など
複雑なロジックをまとめておきcontroller側で簡潔に呼ぶ
- 複雑なロジックなのでCommandパターンを使ってできるだけ分割して書くと色々メリットがある
- 可読性の向上
- ステートレス(
call
のclass method化)にすることにより、テストをしやすくなる
- 複数リソースを扱うために使うのもここに該当
- starategyパターンもここに該当
オープンソースを眺めてみる。
理想はだいたい分かったので実際どんな感じになっているか確かめるためにオープンなRailsアプリケーションはどうなっているのか眺めてみた。
#{動詞}#{目的語}Service
という命名規則- 基本的にServiceクラスのpublic methodは
call
のみ- 綺麗なCommandパターン
call
の見通しがよくなるように処理をprivateメソッドを切り出している- privateメソッド内で他serviceを呼んでいて綺麗に分離できていてすごい
- 便利ツール群はlib/にあった
#{動詞}#{目的語}Service
という命名規則- 機能によってnamaspaceが切られている
- 基本的にServiceクラスのpublic metodは
execute
のみ - レコードオブジェクトを操作するタイプのServiceはレコードオブジェクトをそのまま返していることが多い
- google-apiのwrapはlib/でしていた
- 便利ツール群はlib/に書いてあるみたい
#{名詞}#{動詞}er/or
という命名規則- 色々なメソッドが生えている
- たまにCommandパターンのserviceがあったりする
- 正直よくわからない
- 便利ツール群はlib/にあった
- discourseって意外とイケてない?
まとめ
- Serviceという言葉は曖昧なのでプロジェクトごとでしっかりとルールを決めよう。
- Commandパターンで実装するのか、全部のせServiceクラスを作って実装するのか
- 多くの記事がCommandパターンを用いた実装を推奨
- でも、ある程度の経験がないとどのくらいの粒度で作ればいいのか悩むかも?
- 複雑なロジックを整理するためにServiceを使うときはCommandパターン使って上手に分割しよう。
- Serviceクラスを作るときは
Manage
などの責務の広そうな単語を使わないよう注意する。 - Commandパターンのような一貫したルールがあるのとないのとでは初見でServiceのコードを見たときのインパクトが全然違う。
- Serviceクラスを作るときは
- 今まで便利ツール群はServiceに置いてたけどlib/に置くのがベターっぽい。
メタプログラミング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#sum
がArray#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が載ってるってこと?
なんでだろう。
さらに調べているとこんな記事が
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が載っている
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
- migrateファイル群を元にschema.rbを作成
- schema.rbを元にSQLクエリを発行する
rake db:schema:load
- 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月中旬~試験日
過去問を解く