Runner in the High

技術のことをかくこころみ

モデルの中でセッションを使う

ときたまセッションに関連するロジックをモデルの中に作りたいときがある。ところが、原則的にRailsではセッションをモデルの中で操作することができない。

この問題を解決する最も簡単な方法は、モデルに生やしたメソッドへセッションのインスタンスを渡す引数を追加し、それを経由してロジックを書くというものだ。

class User < ActiveRecord
  def your_method(session, count)
    session[:items_count] = count
  end
end

こうすることで、やりたいことは実現できる。

...
def update
  user.your_method(session, count)
end
...

SRP(単一責任の原則)に則る

これによって、実際にやりたいことは実現されたが、上のコードはSRP(単一責任の原則)から見て必ずしも正しいものではない。 基本的にはActiveRecordパターンであるRailsのモデルには、そのモデルが1:1で紐づく入出力すなわちデータベース・テーブルへの、アクセスオブジェクトとしての役割がある。セッションも見方を変えれば、小さく揮発性の高いストレージのひとつであるが、ひとつのモデルにいくつもの永続化のロジックが混入するのは責務の観点からみて適切ではない。こうした実装は、テストを書く際やアプリケーションのスケールの際に問題になりやすい。

こうした問題を解決するひとつの方法として、セッションを扱うロジックをカプセル化し、責務を分離することで、疎結合な実装にすることができる。

class TemporaryCart
  def initialize(session)
    @session = session
  end
  
  def add_item
    @session[:temprary_cart][:items_count] += 1
  end
end

TempraryCardクラスを実装したことによって、コントローラは以下のようになる。

...
def update
  TemporaryCart.new(session).add_item
end
...

セッションの永続化モジュールがモデルから分離されたことで、見通しの良いコードベースになった。モデルがそれ自体の正しい責務を守ることは「驚き最小の原則」にも則るという面で利点がある。

参考: Setting a Session Variable in a Model - Stack Overflow

コントローラを名前空間で分離して責務の分割をする

Deviseなどを使って、ひとつ以上のスコープを持つアプリケーションを開発している際に、RESTfulなコントローラとビューが1対1対応をしていると、コントローラがどうしても複数のスコープが絡みついた見通しの悪いコードになりがちである。

たとえば、ECカートを実装する例を考えるとする。まず、ChargesControllerがあって、newアクションとcreateアクションはUserスコープによって決済を行う、いわゆるカートの画面を表示にまつわるもの。そして、showアクションはAdminスコープのみでアクセスできるよう認証を実施し、決済情報の詳細を見れるものである。

class ChargesController < ApplicationController 
  def show
    authenticate_admin!
    ... 
  end
  
  def new
    ...
  end
end

この段階ではコントローラはまだ小さいので、さほど問題は感じない。だが、開発をすすめていくうちに、ユーザーによる決済の執行の過程で、エラーが起きた際に、特別な処理をしなくてはならない必要性が出てきたため、コントローラの中にrescue_fromブロックを用意し、DRIな実装を試みようとした。

class ChargesController < ApplicationController
  rescue_from do
    # ここでnew/createでの例外を拾う
  end

  def show
    authenticate_admin!
    ...
  end

  def new
    # 例外が起こる可能性がある処理
  end
    
  def create
    # 例外が起こる可能性のある処理
  end
end  

このようなコードを書くと、今度はshowアクションの中にある例外も、同じくrescue_fromの中で拾われてしまい、Adminスコープとして必要な処理とUserスコープとして必要な処理がコントローラの中に混在してくことになる。また、authenticate_admin!のような認証系の処理も、今例の短いコードであればよいが、コードの量が多くなればなるほど、ひと目ではどのアクションがどのユーザースコープに限定されたものであるのか理解しづらくなる。

こうしたコントローラの問題は、アプリケーションが肥大化した際にコントローラ内部のスコープの責務がすぐにはわかりづらくなり、スケールする際にバグを生みやすいという点である。このような責務の混在を解消するためのアプローチのひとつとして、コントローラを名前空間で分離し、責務の分割を図るという方法が有効である。

実装

Railsでは、ディレクトリの階層構造が名前空間を作るため、ディレクトリ構造は以下のようにUsersAdmins以下にそれぞれのChargesController.rbを作成する。

もしも、すでにDeviseでscoped_viewtrueにしてオーバライド用のコントローラを生成していれば、controllers以下にはスコープ名のディレクトリが存在しているはずなので、その中にコントローラを作ればよい。

- app
  + assets
  + channels
  - controllers
    - admins
      ...
      * charges_controller.rb
    - users
      ...
      * charges_controller.rb
    * application_controller.rb
  ...

コントローラへ名前空間を付与し、RESTfulに従いながらスコープに応じて必要なアクションを実装していく。

class Admins::ChargesController < ApplicationAontroller
  def show
    ...
  end
end
class Users::ChargesController < ApplicationController
  rescue_from do
    ... 
  end
  
  def create
    ...
  end
  
  def new
    ...
  end
end

上記のようにコントローラへ名前空間が付与されたため、ルーティングでは以下のコードのようにscope module: xxxなどを使う必要がある。

Rails.application.routes.draw do
  scope module: :users do
    resources :charges, only: %i(new create) 
  end
  
  scope module: :admins do
    resource :charges, only: %i(show) 
  end
end

これで、もとのChargesControllerは、Users::ChargesControllerAdmins::ChargesControllerに分割され、ひとつのコントローラ内の責務を適切に分割することができた。

まとめ

このテクニックは、実際に業務で遭遇した問題と、POSTDで寄稿された「DHHはどのようにコントローラを書くのか」という記事からインスピレーションを経た。このPOSTDの記事はいかにRESTfulのルールを維持しながら、Railsのコントローラを書くかというベストプラクティスのひとつであったが、その方策はDeviseを使う場合でも応用できる。

肥大化したRailsアプリケーションのリファクタリングのための最初の一歩は、(もしまだやっていなければ)きれいなコントローラを書くことだ。そのために、こうしたベストプラクティスをできるだけ多く取り入れて、気持ちの良い開発をしていければいいと思う。

その他

ひとつ思いつく別の解決策として、StandardErrorを継承したUserErrorAdminErrorなどのような独自例外を定義し、それぞれのスコープに関連するアクションからrescueブロックを用いて更に例外を投げ直すという手も考えられる。これであれば、コントローラの分割をする必要はない。だが、いずれにしてもアプリケーションが肥大化するにあたっては、コントローラの分割が最もよい手法であると思う。

【Quora】あなたが職場でやった悪いことを教えてください。

Read Josh Nymon's answer to What is the most immoral thing you have done in the workplace? on Quora

Josh Nymon

上司の机の上でセックスをしたことがある。当時僕はとある家族経営の店で働いていて、そこでは土曜日にひとりで商品を客に発送しないといけないことがたまにあった。その職場にはひどいコカイン中毒のジキルとハイドみたいな二重人格の上司がいて、そいつは「いい人」のときもあれば、僕達に平気で週50〜60時間オーバーの残業をさせる暴君にもなるやつだった。とはいえ、店を経営していた家族はけっこう気に入っていた。権威を振りかざすそいつみたいなタイプの上司がキライだっただけで。

そのはなしとは別に、僕は昔通っていた学校で一緒だった女の子と付き合っていた。彼女はだいたい車で8時間ぐらいかかる場所に住んでいて、ある週末、ぼくがひとりで働いているときに、たまたま彼女が僕の住んでいる街に来る予定が重なった。最後に彼女にあったのが数週間前だったというのもあって、ぼくは結構ムラムラしていた。恋する若人にとって三週間というのは永遠の別れと同じだ。丁度ぼくが仕事をきりあげかかっているときに、彼女は早めに着いたのでどこにいるか教えてほしいと連絡してきた。あまりにもムラムラしていて、正直数秒も待てる気がしなかったので、職場で会おうと伝えて、駐車場で彼女と落ち合い職場に招き入れた。お互いもう情動と性欲でいっぱいいっぱいになっていて、とりあえずどこかに窓のないスペースがないかどうか探し始めた。そのときたまたま見つかったのが上司の部屋で、当然わかるとおもうけれど、鍵もかかっていなかった。

次の月曜日、僕らがセックスをした机の上で昼飯をたべているのを見たときはほんとに笑えたよ。

(抄訳)

Chrome Extensionのbackground pageでconstを使うのに気をつけよ

 Chrome Extensionではbackgroundを使っている方々各位にお伝えしたいのがなんといつのまにやらconstで定義した変数がextension.getBackgroundPageメソッドから取れなくなっているということ

background.js
...
const NOTIFY_ID = "default";
const ITEMS_ID = "Items";
...
popup.js
...
var BG = chrome.extension.getBackgroundPage();

console.log(BG.NOTIFY_ID); // "default"
...

 これまではこれでぜんぜんよかったはずが、いまはなぜかconstで定義された変数がすべてundefinedになってしまう! getBackgroundPage関数からはwindowが帰ってきていて、background.jsの中で定義された変数やら関数やらは全てwindowに生やされる形になるのだけれど、どうやらconstはだめらしい。これのせいで公開してた拡張機能が動かなくなっていたのですごく憎い。まあ気づいたから良かったものの、、、
 ということでおとなしくvarを使って定義しましょう。

いま大学生がSpeeeのbizTechインターンに参加すべき5つの理由

 どうもみなさん、2015年夏のBizTechインターンに参加しました@IzumiSyです。現在は別の企業にてインターンをしているのですが、あまりにSpeeeのインターンが素晴らしすぎたのでアドベントカレンダーの一日を拝借して記事を書かせてもらいました。僕自身、趣味でJavaScriptばっかり書いている文系大学のろくでもないエンジニア志望学生ですが、そんな僕にエンジニアを目指す学生ならすぐにでも参加すべき理由を5つばかり紹介させてください。

 

1. RubyRails)力がアガる

 ご存知RubyKaigiのスポンサーもつとめているのがこのSpeee。会社全体で開発言語のRubyRails)化を推し進めていて、エンジニアの方たちもRubyに造詣が深いことは目に見えて明らか。僕もSpeeeのインターンに参加するまでRailsを満足に触ったことは正直なかったのですが、度重なるレビューの嵐で否応なしにRails力が向上します。とはいえ、そもそもRubyに限らず技術自体に明るい人しかいないのでJavaScriptでもScalaでもドンと来い!という人が実際にいます。その辺を考えると、そもそもRubyの力どころか技術力全般がアガると思ってます。ちなみにRuby化の話では@nisshieeさんの「Ruby初心者チームが「Ruby日本一」を目指して半年の話」が僕的にはおすすめです。

 

2. メンターがアツい

 個人的な話になりますが、夏のSpeeeのインターン含めこれまで5, 6社で短期〜中期インターンに参加していました。つまり少なくともそれだけの会社のメンターの方たちを見てきたわけですが、その中でも最もインパクトのあったのが、正直なはなし、Speeeの是澤さんでした。
 SpeeeというTechカンパニーの重鎮という感じがハンパないです。Speeeには技術顧問としてMatzさんと元クッ社の井原さんを招いていますが、とはいえアツさでは経歴・思想ともに個人的には是澤さんを推したいです。ちなみにSpeeeのインターンでは是澤さんから直接1on1でアツいフィードバックがもらえて、大抵このフィードバックでみんな一皮むけます。

 

3. どうしたら強くなれるかを知れる

 2と少し絡むはなしですが、Speeeのインターンで得られるのは、いわゆる1weekとか1dayのインターンのようなものとは違うと個人的には感じています。Speeeのインターンは、実際にプロダクトを作るためのビジネス的な視点だったり、自分が技術的・人間的に成長するための謙虚な精神をいかに養うか、のようなよりエンジニアとしての根底的な成長に関わってくるものだと思っています。
 「自分になにが足りないか、そのためには何をすべきか」という問題意識をもって、Speeeでは自分の成長への距離を常に計っていました。学生のうちに自分に何が足りなくて、何を足せばもっと(エンジニアとして)強くなれるのか、ということを知れるのは今後の自分が何をすればよいのか、という指針になるはずです。

 

4. ビジネスの視点を垣間見れる

 Speeeという会社自体がそもそも全体的に頭脳集団という印象が強いので、なんとも言えないのですが、数字やデータを効率的に使ってビジネスを推し進めている会社です。口外禁止と釘を差された内容もあるので、多くは語れませんがインターンに参加して中に入ってみると、いろんなビジネスをロジカルに広げているのがよく分かるはずです。(実は本当にいろんなことをやってます)
 インターンでもまた、このビジネスの視点というものを継承していて、プロダクトの開発から実際に設計をしてコードへ落とし込むまで、ひとつひとつをユーザー側の視点や意図に立ちながら客観的な物事の見方のもとに訓練されます。ここがまさに学生にとってはタフな部分なのですが...これが経験できるのがひとつデカいところです。

 

5. おいしいコーヒーが飲める

 Speeeにはコーヒー文化が強く根付いています☕
そして、僕がインターンをしている期間中にはなかったのですが、最近新しくスター◯ックスみたいなフロアが増えたらしいです。どうやらバリスタの人もいるらしいです。(http://speee-blog.jp/?p=279

f:id:IzumiSy:20161118174735j:plain


さらにこのフロア、技術力強化の一環でなんとオライリーの本が全部揃っているらしいです!!技術を勉強したい学生は行くしかない!!!!11

開発マシンとしてのChromebook

Feedforceさんとiidさんの2社でインターンに参加してました。

 iidさんのほうでは座学でリーンスタートアップを学んだのちに共感マップやリーンキャンバスを使って2週間のうちに企画→開発という業務の流れを体験し、Feedforceさんではアジャイル開発を用いながらGitとRailsに関するより技術よりの開発フローやKPTの回し方などを学びました。めっちゃ楽しかったですね!

 ところで、両方のインターンの参加者のほとんどがMacユーザーな中で、ひとりだけChromebookを使っていたのが僕です。(過去の参考記事: Acer C720のこと



正直Chromebookを使い始めたときは、このマシンじゃ開発は無理かな〜と思っていたのですが、探してみればブラウザ上で動かせる便利なサービスがいろいろあることがわかってきました。そういうサービスを活用しながら、インターン含め自分で開発をやるときにも結局Chromebookが使えるようになってきているので、Web開発に限ればChromebookもかなり優秀な開発マシンになりうる可能性が無きにしも非ずである、ということがこの記事で紹介出来ればと思います。

***

【必要なもの1】リモートサーバ (さくら, DigitalOcean, etc...)

 ChromeOS自体はLinuxと同じっぽいのですが、ローカルではroot権限もなくロクなことができないので、まずはリモートで作業をできるサーバを準備しましょう。僕は去年の秋あたりのOSCでさくらインターネットさんからもらったクーポンを使ってさくらのクラウドを使ってます。さくらでもなんでもリモートで作業ができる環境が揃ってしまえばこっちのもの。

【必要なもの2】SecureShell

 ChromebookでももちろんSSHターミナルは使えて、Chrome AppでSecure Shellというのがあるのでこれを使います。タブを開くとか言う便利なことはできないのでおとなしくtmuxかscreenを使う。ちなみにSecureShellでは日本語の入力ができないので、これを解決するエディタを使います(後述)

【必要なもの3】Codeanywhere (あるいはShiftedit)

 ブラウザ上で動くエディタの2大巨塔(?)のひとつ、CodeanywhereがUI的にもUX的にもおすすめ。SFTP接続をサポートしていて、かつ日本語入力のできるエディタを備えているのでここからリモートサーバに接続してゴリゴリとコードを編集する。ShiftEditも使えなくはないけどあんまり見た目がすきじゃない。なんか古臭い感じがする。リモートサーバからここまで揃ってしまえば、インターネットが使える限りどこでも開発ができるChromebookが完成したと言えます!!

【あったらいいもの】Koding(あるいはNitrous, Cloud9, etc...)

 Kodingに限らずNitrousとかCloud9みたいなコンテナを使った仮想の開発環境を提供するサービスが増えていて、そしてしかもこれが無料でつかえるというところに興奮を覚える。共用サーバじゃないからroot権限もあってGitももちろん使えるし無料でもそこそこのスペックあるし、この辺のサービスがもうちょっと安定してつかえるようになればそもそもリモートサーバは不要だし、ともすればCloud9なんかはSSHでの接続までサポートしているあたり、接続さえできればいいChromebookの大正義感が見え隠れしていると思う。Kodingを一番にしている理由は一番ごちゃごちゃしてなくてシンプルで使いやすいから。

***

 Chromebookが便利すぎて、もはやWindowsマシンなんてよっぽど何か理由があるときしか触らないくらいになってしまったので、もう開発もこれでやってしまえ!という気持ちになったので参考としてこの記事を書きました。ただ、チーム開発とかで周りの人たちと環境を揃えたりするときには構築がもしかしたらものぐさな人には手間かもしれないので、そういうときはムリしないでおとなしくMacマシンを使ったほうがいいかもですね!

loadstop イベントより細かい request.onCompleted を使う

 Chrome API の WebView にはページのロードが終了した時に発生する loadstop というイベントが存在する。これを用いたサンプルが公式のドキュメントにあるのだが、ここにひとつ注意したい点がある。

onload = function() {
var webview = document.getElementById("foo");
var indicator = document.querySelector(".indicator");

var loadstart = function() {
indicator.innerText = "loading...";
}
var loadstop = function() {
indicator.innerText = "";
}
webview.addEventListener("loadstart", loadstart);
webview.addEventListener("loadstop", loadstop);
}
 Googleの検索結果のページなどで起こりがちなことだが、実際にページのロードではなくJavaScriptによる動的なデータの書き換え?のような動作を行うページではこの loadstop の発生するタイミングがあまり芳しいものにはならない(webview 内で表示されているページのタイトルを取得したいと思った際に、Google検索では loadstop のタイミングでタイトルを取得してもうまくいかないなど)
 この公式のサンプルでは単にページがローディング中かどうかだけを判断するために loadstart と loadstop を使っているが、実際にはもっと細かい感覚でスクリプトなどの実行が行われているはずであり、より詳細なステータスを知る必要に迫られた際にはこれらのイベントでは不十分である。


→こうした問題を解決するために chrome.webRequest を使おうではないか。

{
...
"permissions": [
"https://*/",
"http://*/",
"webview",
"webRequest"
]
...
}

manifest.json の permission はこんな感じで書く。
また、前に書いた webview 内のページタイトル取得のコードと組み合わせるとこんな感じに。

var view = document.getElementById("web-view-test");
view.request.onCompleted.addListener(
function(details) {
view.executeScript({
code: "document.title"
}, function(r) {
console.log("Title: " + r[0]);
});
}, {
urls: [""]
}
);
 最初に触れた loadstop ではなく webRequest.onCompleted イベントのタイミングでタイトルを取得することで、Google検索のようにタイトルの取得が難しいページでも正確にタイトルを取得することができるようになる。これは、そもそも chrome.webRequest が低レイヤーなネットワーク処理の実装を提供するAPIであり、loadstop と比べるとかなり細かい感覚でイベントが発生するからである。(ちなみに、コード中で view.request.onCompleted という書き方をしているのは webview のプロパティである WebRequestEventInterface を直接呼んでいるため)
 ドキュメントそのものを全て詳しくは読んでいないので、全部は分からないけれど、この他にもリダイレクションやヘッダーの受信などで発生するイベントなどできることは盛り沢山なので、これは要チェックだと思う :D