Quantcast
Channel: Rails – TechRacho
Viewing all 120 articles
Browse latest View live

任意のHTMLをクリップボードから手軽にHaml変換する方法

$
0
0

morimorihogeです。先日暴飲暴食をしてお腹を壊してから多少飲食を控えめにしてましたが、そろそろ復活してきた今日この頃。

Railsのビューといえば古くはERBテンプレートから、Rails 3近辺でHaml を使うのがメジャーになり、その後Hamlの記法は直感的ではない派からSlim がいい!という辺りに流れてきたのが昨今かと思います。

僕個人としてはどれでも良いと言えばいいのですが、html.erbだとタグの閉じ忘れなどの凡ミスをしてしまうことがあるので、Railsが分かるエンジニアのみがViewを弄る前提であればHamlかSlimが好みです。

Haml v.s. Slimは正直どっちでもいいしSlimの方が読みやすい派の気持ちもわかるのですが、一時期スタンダードを取ってしまってシェアが多いということで、僕の中ではHamlに軍配が上がるかな、という意見です。

HTMLをHaml変換するhtml2haml

Railsエンジニア(Haml書ける人達)だけで開発するのであれば良いのですが、世の中的にはHTMLコーディングまでは別の会社やHTMLコーダーが担当し、作成されたHTMLをRailsエンジニアがRailsテンプレートとして取り込むという流れも良くあります。

そんな時、HTMLをHamlに変換してくれるのがHtml2haml です。Github的にhaml公式のグループ に所属しているツールなので、変換の信頼性は高いと思います。

コマンドラインツールとしてできているので、bundleするというよりはローカルgemにインストールしてコマンドラインから実行するのがメジャーかなと思います(生成した結果を使うだけならRailsアプリ側にbundleする必要はないので)。

使い方

gem install html2haml

でインストールして

html2haml hoge.html

すれば標準出力に変換済みHamlが出力されます。

ファイルに保存せず変換したい

オプションなしで実行するならhtml2hamlはHTMLファイルを引数に取るのですが、Bootstrapベースのテンプレートで開発している時などは、Bootstrapの公式サンプル から一部をコピペして使いたいということがままあります。

そんなとき、わざわざ一時ファイルを作成してコマンドラインに食わせるのも面倒ですよね。

そんなときは標準入力から食わせます。html2hamlには-sで標準入力から取り込むオプションがありますので、-sオプションの後、HTMLを貼り付けてCtrl-Dすることで変換できます。

html2haml -s [Enter]
<div class="dropdown">
  <button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
    Dropdown
    <span class="caret"></span>
  </button>
  <ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
    <li><a href="#">Action</a></li>
    <li><a href="#">Another action</a></li>
    <li><a href="#">Something else here</a></li>
    <li role="separator" class="divider"></li>
    <li><a href="#">Separated link</a></li>
  </ul>
</div>
[Ctrl+D]

とすることで

.dropdown
  %button#dropdownMenu1.btn.btn-default.dropdown-toggle{"aria-expanded" => "true", "aria-haspopup" => "true", "data-toggle" => "dropdown", :type => "button"}
    Dropdown
    %span.caret
  %ul.dropdown-menu{"aria-labelledby" => "dropdownMenu1"}
    %li
      %a{:href => "#"} Action
    %li
      %a{:href => "#"} Another action
    %li
      %a{:href => "#"} Something else here
    %li.divider{:role => "separator"}
    %li
      %a{:href => "#"} Separated link

が得られます。

Hash記法をアロー形式からコロン形式にする

ちなみに、--ruby19-attributesオプションを使えば属性のハッシュ形式を懐かしいアロー形式("hoge" => "piyo")から見慣れたコロン形式(hoge: "piyo")に変換できます。

html2haml -s --ruby19-attributes

で食わせた結果は以下の通り。

.dropdown
  %button#dropdownMenu1.btn.btn-default.dropdown-toggle{"aria-expanded" => "true", "aria-haspopup" => "true", "data-toggle" => "dropdown", type: "button"}
    Dropdown
    %span.caret
  %ul.dropdown-menu{"aria-labelledby" => "dropdownMenu1"}
    %li
      %a{href: "#"} Action
    %li
      %a{href: "#"} Another action
    %li
      %a{href: "#"} Something else here
    %li.divider{role: "separator"}
    %li
      %a{href: "#"} Separated link

余談: hoge2huga的な変換ツールを作ってみよう

この手の「A形式のデータをB形式に変換する」といったコマンドは色々なものがあるのですが、その中でも割とメジャーなのが「#{A}2#{B}」形式の名前のツールです。A、Bはファイルの拡張子が使われます。

世の中にはたくさんのhoge2piyoという名前のツールがありますが、中身のソースを見てみると内部的にはより汎用的な変換ツールを使って作っていることが大半で、スクリプト自体はそのツールへのwrapperみたいになっていることが大半です。

僕も以前excel2json 的なものを作りましたが、この手のツールは言語に関わらずプログラミング練習にはほどよい規模だと思うので、修行してみたい人への課題にちょうど良いかもしれません。

特に調べたりせずに今ざっと思いついただけでも、以下のようなアイデアが考えられます。

pdf2png, pdf2jpg
ImageMagick あたりを使えばできそう。実用性もそれなりにありそう。
csv2xlsx, xlsx2csv
非エンジニアとやり取りしているとExcelでデータをもらうことはそれなりに多いので、あると便利です。RubyでExcelを扱う方法については Qiita: RubyでExcelファイルを扱うライブラリの比較 なんかが参考になると思います。
avi2jpg
FFmpeg は動画から指定フレームの画像を抜き出したりができたと思います(追加ライブラリがいるかもしれません)ので、多分作れそう。ただ実用的なものを作るには画像化する位置を指定するオプションを付けたりとかが必要かも。

とにかくデータを読んで変換して標準出力に出せば良いので、動作確認もやりやすいしお手軽ですので、何も作ったことがないという人はチャレンジしてみると良いと思います。

ちなみに、Haml2htmlはオプション-sで標準入力からの取り込みに対応していますが、多くのツールと同じくパイプで渡して使うこともできます。

今回と同じことはcatコマンドを使って

cat - | html2haml --ruby19-attributes

というのでもできるわけですね。

ちらっと見た感じでは、HTML2Slim には直接標準入力を待ち受けるオプションがなさそうでしたので、こちらのようなツールに標準入力を渡して使いたい場合には上記のようにcat -(標準入力を受け付けてそのまま標準出力に出す)を通せば良いと思います。

まとめ

そんなわけで普段使ってるツールの紹介でした。いつも使うという程ではないですが、知ってれば必要な時に引き出しから出して使う的な奴ですね。


週刊Railsウォッチ(20170310)クールなDocker監視ツールCtop、RailsがGoogle Summer of Code 2017に正式参加、Unicode 10.0.0ドラフト発表ほか

$
0
0

こんにちは、hachi8833です。先週のAWSに続いておととい3/8に東日本のAzureもコケたようです(参考: Qiita記事)が、全然使ってないので様子がわかりません。ざっくり検索した限りでは一般の反応が非常に少ないのが不思議です。

同じ頃に発生した「マルタ名所「アズール・ウィンドウ」が崩壊 強風と高波で」は全然関係ないニュースですが、思わずタイトルに釣られそうになりました。

季節の変わり目なので皆さまも体調と脳のコンディションにはお気をつけください。それでは今週もいってみましょう。

臨時ニュース: Unicode 10.0.0ドラフトが発表

Unicode Consortiumの皆さまお疲れさまです。まだまだTBDだらけですが、今年の6月に固めたい意向のようです。以下のドラフトが目につきました。黄色が目にまぶしいです。

セキュリティメカニズムではUnicode spoofingの防止策が提案されています。国際化ドメイン名(IDN)を悪用した攻撃などが想定されているようです(参考: Wikipedia_en: IDN homograph attack)。


Wikipedia_en: IDN homograph attackより

homographは本来「同形異義語(弓のbowとお辞儀のbowなど)」を意味しますが、ここでは違う言語のそっくりな文字に差し替えることなどを指しています。上の画像ではeとaがこっそりキリル文字に差し替えられています。単純ですがドメイン名をなまじ目視チェックするとひっかかってしまいそうです。

10.0の文字データベースはUNICODE CHARACTER DATABASE(Annex #44)に掲載される予定です(まだないっぽい)。

調べているうちに、絵文字の提案フォームを見つけました。どなたか勇者はいませんか。

GoogleのSummer of Code 2017にRuby on Railsが正式参加を認められた(Rails公式より)

当初Rails公式ニュース(/news)のトップに掲載されていたと思ったのですが、微妙に違う位置に移動しています。


https://summerofcode.withgoogle.com/より

大学生・院生など(18歳以上)を対象に、夏休みの自由研究ならぬオープンソースプロジェクトへの貢献を推進する企画だそうです。詳しく読んでませんが、賞金も出るようです。学生の参加資格には「米国との通商が禁じられている国に居住してないこと」とあるので、日本在住の日本人大学生でも参加できます。

これやってくれたらうれしいリストの中に「ActionViewテンプレートのeager loading化」というのがあります。言われてみればRailsのビューテンプレートはlazyなんですね。

RailsGirls企画もあります( ^ω^)ワクワク

⭐Ctop: topコマンド風にDockerコンテナを監視⭐


https://bcicen.github.io/ctop/より

一昨日にmorimorihogeさんが発見して社内Slackに流してくれました。実用はもちろん、この美しさだけで既に勝利してますね。

昨日夜の時点では公開後10日で☆80個でしたが、今見たら☆1,100個超えていました。

私どもからも久しぶりの⭐を進呈いたします。おめでとうございます。

機能追加: ルーティングのカスタムURLヘルパーとポリモーフィックマッピング(Rails公式より)

先週のTechRacho記事「5.1 beta1リリースノートに見るRails 5.1の姿」で既に#23138をご紹介していました。Railsの新機能関連IssueはTechRachoでだいぶ先回りしてしまったので今週は少なめです。

機能改善: Railsアプリの秘密情報を使ってより強力なAES-128-GCM暗号化アルゴリズムを利用できるようにする(Rails公式より)

これも「5.1 beta1リリースノートに見るRails 5.1の姿」でご紹介した#23038: Add encrypted secretsに関連しています。

改修はシンプルで、暗号化方式の変更のついでに従来OpenSSL::Cipher.new("aes-256-cbc")とリテラルで書いていたのをCIPHER = "aes-128-gcm"と定数化しています。

#pack("H*")

一同で上のコードをつっついていて、ふと#pack(“H*”)って何じゃらほいという話題になりました。

当初私は単なるzip/unzipみたいなものかなと想像していたのですが、どうやらさまざまなバイナリ的変換を行うメソッドのようです。Rubyリファレンスマニュアル: Array#packをこわごわ開いてみるとunsignedだのビッグエンディアンだのと生々しい記述だらけです。Hはhexadecimalだったんですね。

Webチームのkazzさんが「これはC言語由来だったはず」と教えてくれました。どうにかC言語のコードらしきものを見つけてみると#pragmaが使われていて、リファレンスにあるとおりシステム環境に依存することを実感できました。速そうですがあまり直接触りたくない感じです。

packはRubyのみならずPerl/PHP/Pythonなどでも広く変換に使われているようです。

RSpecマッチャをチェインしてテストコードを読みやすくする(OpenRubyより)

RSpecのマッチャを書くときの参考になります。短いのですぐ読めます。

# https://robots.thoughtbot.com/chain-rspec-matchers-for-improved-test-readability より
expect(page).to have_css("dd ul li", count: 2)
expect(page).to have_css("dd ul li", text: "I read the New York Times every day")
expect(page).to have_css("dd ul li", text: "I read the Washington Post every day")

マッチャを自作して、元のテストコード(上)を下のように書けるようにしたそうです。

# https://robots.thoughtbot.com/chain-rspec-matchers-for-improved-test-readability より
expect(page).to have_multiple_choice_responses(
  "I read the New York Times every day",
  "I read the Washington Post every day"
)

「こういうテストを大量に書かないといけないんでなければ、別に元のまんまでもいいじゃないかなーw」という声もありました。

Rails開発者に求められる8つの資質(OpenRubyより)

「またか」と思いつつ「今度こそいいこと書いてあるかも」とつい開いてしまいがちなPV集め系記事ですね(開いてしまいました)。以下の8つがリストアップされています。

  1. 開発への情熱
  2. フロントエンドの開発スキル
  3. コードの品質の高さ
  4. 自分でスキルを高められる
  5. 地のプログラミング能力の高さ
  6. データベース技術の知識
  7. コミュニケーション能力
  8. ベストプラクティスに沿って開発できる

記事末尾では以下のリンクも紹介されています。Quoraの方には「事前に手を打てる戦略性の高さ」というのもあり、私の年だと真田さん岸和田博士の得意のセリフ「こんなこともあろうかと」を連想してしまいました。失礼しました。

参考

Unibits gem: CLIで使う文字コードバリデータ(RubyFlowより)

GitHub Trendingでも上昇中の、Unicode系文字コードをCLIやRubyコード内でバリデーションするツールです。コードが壊れていると赤く表示されます。

表示が美しくてうれしくなります。Shift_JIS? EUC? 知らない子ですね。

作者のjanlelis氏は他にもUnicode関連のgemを大量にこしらえています。ここまで多いと恐怖すら感じますが、文字コード厨のわたくしは思わずフォローしてしまいました。

ところでエラーの赤以外のカラーリングの根拠がよくわからなかったので掘ってみると、unicolors gemなるものを使っています。しかしこのgemがネットのどこにも見当たりません。作者のローカルgemサーバーにいるのか、公開準備中なのかもしれません。

Rubyで強力なCLIツールを作る(RubyWeeklyより)

記事後半の「プラットフォーム間互換性をどうにかする」あたりが個人的に興味ありました。

Ruby標準のRbConfigモジュールを使って動作環境を取得できます。でも結局場合分けが必要なんですね。

RbConfig::CONFIG['target_os'] #=> "darwin16"(MacOSの場合)

コンソールのクリアコマンドがLinux/MacだとclearなのがWindowsだとclsなので、エスケープシーケンスを直接吐いて強引にクリアを実行するなど、マルチプラットフォームにしようとすると主にファイルパス周りで涙ぐましい努力が必要になってしまいますね。誰を恨めばいいのでございましょうか。誰を誰を誰を誰を

RubyのオブジェクトをHashキーにする(RubyWeeklyより)

Rubyで書かれた任天堂NES(≒ファミコン)エミュレータoptcarrotのコードで著者が見つけた問題点とその解決法を紹介しています。任意のRubyオブジェクトをHashキーにするテクニックです。

ポイントはHashでメモ化(memoization)を使ってMethodオブジェクトが無駄に複製されるのを防ぐことです。Rubyではこういったメモ化を||=で実に簡単に行えるのがいいよね、と盛り上がりました。ifで律儀に3行も使って書かなくてもよいので。

Rubyのメモ化の合言葉は「たてたてイコール」です。はいみなさんもご一緒に。

ところでoptcarrot、READMEがないお(´・ω・`)。

[動画] 12分で理解できるRails 5.1の変更点と新機能(RubyWeeklyより)

動画ですがコードも表示されています。内容的には「5.1 beta1リリースノートに見るRails 5.1の姿」で足りますが、WebPackを動かすところを手っ取り早く見たい方にはよいと思います。


#69 Ruby on Rails 5.1.0 Changes and New Featuresより

Ruby+OMRによるJITコンパイラの今後(RubyWeeklyより)

IBMのブログ記事です。OMRって何だろうと思ったら、Eclipseで任意の言語向けの実行環境を構築するための仮想マシンツールキットだそうです。なおOMRは略語ではなく単なるプロジェクト名だそうです(参考)。

公開されたrubyomr-preview/rubyの☆はまだそれほどありませんが、Forkは3000件を超えています。Forkが多すぎてクリックしても表示できませんでした(´・ω・`)。

JITコンパイラの成果はRuby 3X3に反映されると見込まれます。IBMがRuby 3×3を強力に支援していることがよくわかりました。

Railsチュートリアル英語版が怒涛のシリーズ化

Railsチュートリアルの原著者としておなじみのMichael Heartl氏が、いつの間にかシェル/HTML/CSS/JavaScriptなどをカバーする入門書籍を大量生産しています。CSSやJavaScriptはまだ執筆中ですが、Railsチュートリアル同様ネットでは無料で全文公開されています。

従来のRailsチュートリアルで「HTMLはこれ読め」「シェルはこれ読め」で泣く泣く済ませていた部分を全部自分でカバーしようとしているようです。ノリノリですね。

中でもAction Cableはよさそうです。


https://www.learnenough.com/action-cable-tutorialより

Grimore.js: 日本発のWebGLフレームワーク


https://github.com/GrimoireGL/GrimoireJSより

見出しのとおり、日本で開発されたJavaScriptのWebGLフレームワークなのでドキュメントもチュートリアルも全部日本語で読めます。こんな風に書くだけでグリグリ動き出します

<!-- https://grimoire.gl/example/example-008.html より-->
<goml width="fit" height="fit">
  <renderer camera=".camera" viewport="0,0,100%,100%">
    <render-scene/>
  </renderer>
  <scene>
    <camera class="camera" near="0.01" far="40.0" aspect="1.0" fovy="45d" position="0,12,17" rotation="-35,0,0" >
      <camera.components>
        <StareAt center="0,0,0" speed="0.007" zoom="5" zoomPhase="1.4"/>
      </camera.components>
    </camera>
  </scene>
</goml>

Rubyで学ぶSOLID設計

SOLIDについてはTechRacho記事「Rubyスタイルガイドを読む: クラスとモジュール(2)クラス設計・アクセサ・ダックタイピングなど」でも紹介しましたが、こちらにも再録しました。

そこそこ長いですが大半はコードなので、時間ができたときにどうぞ。

重要でない文字列をPostgreSQLのJSONB型を使ってデータベースに保存する

文中の「Just wing it」は英語圏の流行語です。

Tシャツのサイズ「”S,M,L,XL”」のような表示にしか使わない付加的な情報のために、わざわざモデルを作って多対多でリレーションして、みたいなことは避けたいと思うのが人情です。

カンマ区切りの文字列をカラムに保存しておいて#splitで切り分けて使うのがよくある回避方法ですが、PostgreSQLのJSONB型を使えば["S", "M", "L", "XL"]といったArrayをまるっと保存できるので#split要らずで便利だよ、という記事です。

なお同記事のコメントに「この場合JSONBいらなくね? PostgreSQLならarray: trueだけでできるお↓」とツッコミが入っていました。

# http://jeromedalbert.com/jsonb-plus-rails-to-store-glorified-strings-in-your-db/ より
class AddSizesToProducts < ActiveRecord::Migration
  def change
    add_column :products, :sizes, :text, array: true, default: []
  end
end

念のためStackoverflowを見るとPostgresのArray型は一応インデックス化できるようですが、そういう使い方はあまりよくない気がします。

これまたStackoverflowによると、MySQLにはArray型はないそうです。MySQLのタスクリストを見ると2007年以来アサインすらされていません(´・ω・`)。

Linguist gem: プログラミング言語の種類を自動判定(GitHub Trendingより)

↑GitHubリポジトリでプログラミング言語を自動判定するのに使われているそうです。皆さまのフィードバックをお待ちしています。

こんな使い方はいかが

これまたkazzさんから「受け取ったソースコードにどんな言語が使われているのかをさくっとチェックするのにいいかも」というアイデアが飛び出しました。

Linguist gemはgem install github-linguistでインストールできますが、CMakeがないとビルドに失敗します(参考: MacOSXでcmakeのコマンドライン版を使えるようにする)。

linguist --breakdownで無事チェックできました(∩´∀`)∩ワーイ。

OSPTF: オープンソースのペネトレーションテストフレームワーク


https://howucan.grより

ついさっきTwitterで見つけました。そのまんまの名前です。

ペネトレーションテストはサーバーのセキュリティチェックで使われますが、オープンソースなのがありがたいですね。

なおpenetration testはもともと防弾チョッキの性能を試すための銃撃テストを指します。

人気の割にビジネス利用が進まないGo言語

Go言語ファン3,595人を対象にしたアンケート調査によると、「Go言語を他の人にも勧めたい」「仕事でもプライベートでも使ってる」というラブラブな回答は非常に多かったにもかかわらず、「Goは会社の成功には欠かせない」と考えている人は皆無だったそうです。Go言語の使いみちもほとんどが「CLI」「HTTPサーバ」「マイクロサービス」でした。

私自身も、Go言語は主役ではなく、懐刀や頼りになる参謀という位置づけがふさわしい気がします。速度の必要なバッチ処理や単機能のマイクロサービス、マルチプラットフォームのシェルスクリプト的に使うのがいいのかも。なにしろクラスがないので。

Go言語でRuby gemのネイティブエクステンションを書く試みがいくつかあるのでそっち方面に期待したいのですが、cgoやMakefileを使わずにgemらしくきれいに書く方法はまだなさそうです。

ふと、Go言語に一番食われたのはPerlなのではないかと思ってしまいました。

Peardrop: LSI設計ツール

英国のスタートアップ企業です。最近めっきり見かけなくなった「何も足さない・何も引かない」Webページですね。漢(おとこ)らしいと思います。

と思ったらPeardropは2015年に解散してました

リチウムイオン電池の発明者が今度は電解液をガラスに替えて不燃性の高速充電バッテリーを考案(HackerNewsより)

発明者のJohn Goodenough氏は御年94歳です。

ところで一部業界では「ガラスは液体」派と「ガラスは固体」派が長らく争っているそうですが、最近はガラス固体説がやや有利なようです。

今週は以上です。

関連記事

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやRSSなど)です。

Rails公式ニュース

Ruby Weekly

OpenRuby

RubyFlow

160928_1638_XvIP4h

Hacker News

160928_1654_q6srdR

Github Trending

160928_1701_Q9dJIU

Rubyスタイルガイドを読む: クラスとモジュール(3)クラスメソッド、スコープ、エイリアスなど

$
0
0

こんにちは、hachi8833です。Rubyスタイルガイドを読むシリーズ、今回は「クラスとモジュール編」その3をお送りします。

Rubyスタイルガイド: クラスとモジュール(3)メソッドのスコープやエイリアスなど

クラス変数(@@で始まる変数)はできるだけ避ける

Avoid the usage of class (@@) variables due to their “nasty” behavior in inheritance.

クラス変数は、以下のように継承で予想外の動作を引き起こすことがあります。

class Parent
  @@class_var = 'parent'

  def self.print_class_var
    puts @@class_var
  end
end

class Child < Parent
  @@class_var = 'child'
end

Parent.print_class_var # => 'parent'ではなく'child'が出力されてしまう

As you can see all the classes in a class hierarchy actually share one class variable. Class instance variables should usually be preferred over class variables.

上のParentクラスとChildクラスが同じクラス変数@@class_varを共有していますが、場合によっては好ましくない動作を引き起こします。インスタンス変数(@で始まる変数)を初期化すればできることはクラス変数でやらないようにしましょう。

メソッドの公開範囲を用途に応じて適切に設定する

Assign proper visibility levels to methods (private, protected) in accordance with their intended usage. Don’t go off leaving everything public (which is the default). After all we’re coding in Ruby now, not in Python.

全メソッドをデフォルトのpublicのままにせず、必要なものはprivateprotectedを使って公開範囲を適切に絞り込みます。

このあたりはPythonのコーディングスタイルとは異なるようです。

Python においては、すべては、真の意味でプライベートではない; Pythonの内部的には、プライベート・メソッドやアトリビュートの名前はこっそりとバラバラにされてしまうため、もともと与えられたそれらの名前のみではアクセスができないかのように見えるのである。 MP3FileInfoクラスの__parse メソッドには、__MP3FileInfo__parse という名前によって、はじめてアクセスが可能である。 これが大変興味深いことは認めよう。しかし、このような方法でのアクセスは、実際のコードの中では決して行わない事を約束してほしい。 プライベート・メソッドは、その命名規則にもとづいて、プライベートになっている。 しかし、Python における他の多くのことと同じように、それがプライベートであることは単なる慣習上の決め事であり、強制ではないのである。 
5.9. プライベート関数 (Private Functions)より

Rubyのprivateメソッドは、そのクラス自身からの呼び出しのみが想定されます。privateメソッドはレシーバを指定できないので、関数形式でしか呼び出せません。このメカニズムを使って、privateメソッドはそれが属するクラス自身からしか呼べないようになっています。

Object#sendを使うと無理やり外部から呼び出すことも一応できますが、乱用は避けたいところです。「望ましくない使い方は、わざと使いにくくする」というRuby言語設計者のポリシーを感じてしまいました。

追伸: privateメソッドはサブクラスのインスタンスメソッドから呼べる

Webチームのkazzさん(Java出身)が試してみたところ、スーパークラスのprivateなメソッドはサブクラスのインスタンスメソッドから呼び出せました

以下は私もRuby 2.4でダブルチェックしましたが、1.9でも動作は同じでした。

class Parent1
  private

  def priv1
    "I'm private"
  end
end

class Child1 < Parent1
  def call_priv1
    priv1
  end
end

Child1.new.call_priv1 #=> "I'm private"

もちろん、クラスのインスタンスをレシーバとしてprivateメソッドを呼ぶと普通にエラーになります。

class Parent1
  private

  def priv1
    "I'm private"
  end
end

Parent1.new.call_priv1 #=> NoMethodError

当初この「サブクラスのインスタンスメソッドからスーパークラスのprivateメソッドを呼び出せる」挙動について悩んでしまいましたが、morimorihogeさんが「これはまったく仕様どおりです」と「[Ruby] privateメソッドの本質とそれを理解するメリット」という記事を教えてくれました。

同記事を元に次のように理解しました。

  • privateのついたメソッドを呼び出す時は、レシーバは指定できないようになっている(Rubyではそれ以外の制限をかけていない
  • スーパークラスのprivateメソッドはサブクラスにもprivateメソッドとして継承される
  • したがって、継承したprivateメソッドはサブクラスのインスタンスメソッド内で呼び出せる

以下はネットで見つけたコード例を若干変更したものですが、こんなふうにスーパークラスのprivateメソッドをサブクラスでpublicに変更するといった利用法も考えられます。もし仮にスーパークラスのprivateメソッドはサブクラスに継承されないとしたら、このようなサブクラスでのメソッドスコープは変更できないことになりますね。

class Parent
  private

  def some_method
    "private in Parent"
  end
end

class Child < Parent
  public :some_method
end

Child.new.some_method #=> "private in Parent"

privateprotectedは対象となるメソッド定義とインデントを揃える

Indent the public, protected, and private methods as much as the method definitions they apply to. Leave one blank line above the visibility modifier and one blank line below in order to emphasize that it applies to all methods below it.

インデントを揃えて、privateprotectedの対象が明確になるようにします。
さらにprivateprotectedの前後には空行を置いて読みやすくします。

class SomeClass
  def public_method
    # (本文は略)
  end

  private

  def private_method
    # (本文は略)
  end

  def another_private_method
    # (本文は略)
  end
end

追伸: Rubyのprotectedメソッド

Rubyのprotectedはわかりにくいので有名です。Ruby 2.4リファレンスマニュアルより引用します。

public に設定されたメソッドは制限なしに呼び出せます。
private に設定されたメソッドは関数形式でしか呼び出せません。
protected に設定されたメソッドは、そのメソッドを持つオブジェクトが selfであるコンテキスト(メソッド定義式やinstance_eval)でのみ呼び出せます。
Ruby 2.4リファレンスマニュアルより

私の目から見て、上述のprivateprotectedの説明は「メカニズムの一部」を説明しているだけで、「目的」「使い方」について説明がない点に戸惑いを感じてしまいました。

この点については上述の「Ruby の private と protected 。歴史と使い分け」で非常に詳しく説明されていますが、特にJavaやC++からRubyにやってきた開発者がprotectedの定義の違いに戸惑うことが多いようです。

クラスメソッドはdef self.methodで定義する

Use def self.method to define class methods. This makes the code easier to refactor since the class name is not repeated.

クラス名を繰り返し書かなくてよくなるので、リファクタリングしやすくなります。

class TestClass
  # 不可
  def TestClass.some_method
    # (本文は略)
  end

  # 良好
  def self.some_other_method
    # (本文は略)
  end

  # 以下の書き方もOK: クラスメソッドを多数定義する場合向け
  class << self
    def first_method
      # (本文は略)
    end

    def second_method_etc
      # (本文は略)
    end
  end
end

メソッドをレキシカルクラススコープでエイリアスする場合はaliasを使うのが望ましい

Prefer alias when aliasing methods in lexical class scope as the resolution of self in this context is also lexical, and it communicates clearly to the user that the indirection of your alias will not be altered at runtime or by any subclass unless made explicit.

#aliasによるメソッドのエイリアスはレキシカル(≒静的)なクラススコープのコンテキストで作成されるので、selfもレキシカルに解決されます。また、そのエイリアスが実行時に変更されず、サブクラスによっても変更されない(サブクラスで明示的に変更する場合を除く)ことがAPI使用者に明確に伝わります。

class Westerner
  def first_name
    @names.first
  end

  alias given_name first_name
end

Since alias, like def, is a keyword, prefer bareword arguments over symbols or strings. In other words, do alias foo bar, not alias :foo :bar.
Also be aware of how Ruby handles aliases and inheritance: an alias references the method that was resolved at the time the alias was defined; it is not dispatched dynamically.

Rubyのaliasdefなどと同様キーワードなので、メソッド名やエイリアス名は文字列やシンボルではなく、むき出しで記述するようにします。

また、Rubyでエイリアスや継承がどのように扱われているかについても知っておきましょう。Rubyの#aliasによるエイリアス参照は、エイリアスの定義時に解決されます(動的には解決されません)。

class Fugitive < Westerner
  def first_name
    'Nobody'
  end
end

In this example, Fugitive#given_name would still call the original Westerner#first_name method, not Fugitive#first_name. To override the behavior of Fugitive#given_name as well, you’d have to redefine it in the derived class.

上述の例のFugitive#given_name呼び出しは、実際にはFugitive#first_nameではなく元のWesterner#first_nameを呼び出します。この動作をオーバーライドするには、以下のように派生クラスでaliasを使って再定義する必要があります。

class Fugitive < Westerner
  def first_name
    'Nobody'
  end

  alias given_name first_name # aliasしないと`Westerner#first_name`が呼ばれる
end

モジュール・クラス・シングルトンクラスのメソッドを実行時にエイリアスする場合は常にalias_methodを使う

Always use alias_method when aliasing methods of modules, classes, or singleton classes at runtime, as the lexical scope of alias leads to unpredictability in these cases.

前述のとおり、aliasはレキシカルなので定義時に参照が解決されます。以下のような動的なエイリアス作成にaliasを使っても期待どおりに動作しません。

module Mononymous
  def self.included(other)
    other.class_eval { alias_method :full_name, :given_name }
  end
end

class Sting < Westerner
  include Mononymous
end

クラスやモジュールの中で同じクラス・モジュール内の他のメソッドを呼ぶ場合は、呼び出しのself.を省略する

When class (or module) methods call other such methods, omit the use of a leading self or own name followed by a . when calling other such methods. This is often seen in “service classes” or other similar concepts where a class is treated as though it were a function. This convention tends to reduce repetitive boilerplate in such classes.

self.*メソッド名.*を省略する記法は、いわゆる「サービスクラス」のようにクラスを関数のように扱う場合によく見られます。この省略記法は、そうしたクラス内での冗長な呼び出しを削減するのに役に立ちます。

class TestClass
  # 不可 -- クラス名を変えるかメソッドを移動する方がよい
  def self.call(param1, param2)
    TestClass.new(param1).call(param2)
  end

  # 不可 -- 冗長
  def self.call(param1, param2)
    self.new(param1).call(param2)
  end

  # 良好
  def self.call(param1, param2)
    new(param1).call(param2)
  end

  # ...(他のメソッド定義)...
end

今回は以上です。スタイルガイドを読むシリーズ、次回は「例外処理」編をお送りします。ご期待ください。

関連記事

RubyのArray(配列)の使い方

$
0
0

morimorihogeです。そとがさむい。

たまには初心者向けの記事を書いてみようということで、Rubyの配列であるArrayクラスについて、基本的な部分に絞った内容を書いてみようと思います。PHP等の他言語からRubyを勉強している人なんかには参考になるのではないかと思います。
※超初心者向けです。確認環境は2.4.0準拠ですが1.9.3以降のそこそこ最近のRubyであればほとんど問題なく利用できると思います。

基本的には公式のArrayドキュメント に書いてあることばかりですが、リファレンス形式だと読みにくい、という人はどうぞ。
なお、引用サンプルではpry の出力結果を出していますが、これはIRBでも同じことができます。手元で動かしてみたい方はrubyが入っていればとりあえずirbして以下を追いかけてもらうと良いでしょう。

Arrayの初期化

RubyにおけるArrayは、リテラルを使って初期化できます。空配列であれば

arr_var = []

で良いですし、明示的にArray.newを使って

arr_var = Array.new

とすることもできます。

RubyのArrayはPHP等と同じように、中にどんなオブジェクトも要素として持つことができますので、

[1, "piyo", nil, nil]

といったArrayも定義可能です。また、Rubyらしい初期化方式だと、Array.newにブロックを渡して

[3] pry(main)> Array.new(5) {|index| "hoge_#{index}"}
=> ["hoge_0", "hoge_1", "hoge_2", "hoge_3", "hoge_4"]

みたいなこともできます。

Arrayの要素アクセス

要素へのアクセスは、他の言語でも一般的な添字を使った

arr_var[1]

といったアクセス(添字は0番目から始まる)もありますし、#fetchメソッドを使い

arr_var.fetch(1)

としても良いです。通常は添字アクセスだけ覚えていれば良いように思いますが、#fetchは第二引数に値が見つからなかった場合のデフォルト値指定ができるため、

[12] pry(main)> arr_var[10000]
=> nil
[13] pry(main)> arr_var.fetch(10000, 0)
=> 0

という感じで指定した添字の値がない場合にnil以外を受け取りたい、といったときに覚えておくと便利です。
また、Rubyらしいアクセス方式として、添字にRangeオブジェクト(n..m)を渡すことによって部分Arrayを取り出せます。n番目の要素からm番目の要素まで取り出す感じですね。

[60] pry(main)> arr_var = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
[61] pry(main)> arr_var[2..3]
=> [3, 4]

Arrayの大きさ取得 #size #length

Arrayの要素数を取得するには#sizeまたは#lengthを使います。最大の添字番号ではないので注意です

[53] pry(main)> arr_var = [1,2,3]
=> [1, 2, 3]
[54] pry(main)> arr_var.size
=> 3
[55] pry(main)> arr_var.length
=> 3

Arrayの要素に対する繰り返し

どんな入門書にも必ず載っている#eachとブロックを使えば良いですね。Rubyではcollection(集合)オブジェクトの中身を順番に取り出したければ基本#eachだと思っていて良いです。
Rubyにfor文は構文としてはありますが、ほとんど使われません。他言語から来た人が一番違和感を覚える所かもしれませんが、ブロックを使った記法に慣れるためにもforは使わず#eachを使うようにしていくのが良いと思います。

中括弧を使ったブロックの書き方では

arr_var.each{|val| puts "value: #{val}"}

do-endを使った書き方であれば

arr_var.each do |val|
  puts "value: #{val}"
end

とすれば良いです。上記二つは全く同じ意味になりますが、Ruby Style GuideというRubyの好ましい書き方まとめ に沿うのであれば、ブロックの中身が1行で終わるなら中括弧記法、2行以上あるならdo-end記法となります。
とりあえず動けばいいや、という話であればどちらでも構いませんが、よりRubyらしい書き方、美しい(他人が読みやすいとされる)コードを目指すのであれば少しずつ追いかけていくと良いと思います。

Arrayの要素削除

Arrayから特定の要素を削除したい場合には#deleteまたは#delete_atを使います。
#deleteは引数と==マッチする要素全てを削除します。添字番号を指定して削除するのは#delete_atです。間違えると悲しいことになるので気をつけましょう。

[14] pry(main)> arr_var = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
[15] pry(main)> arr_var.delete(4)
=> 4
[16] pry(main)> arr_var
=> [1, 2, 3, 5]
[17] pry(main)> arr_var = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
[18] pry(main)> arr_var.delete_at(4)
=> 5
[19] pry(main)> arr_var
=> [1, 2, 3, 4]

Ruby的なメソッドとしては#delete_ifがあります。これは要素を順番にブロック内で評価し、ブロックの評価結果(最後に評価された式)がtrueになった要素を削除します。
他の言語だとforやforeachループを書いて削除していく処理を、組み込みメソッドで書けるのがシンプルで良いですね。

[20] pry(main)> arr_var = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
[21] pry(main)> arr_var.delete_if{|var| 2 < var && var < 5}
=> [1, 2, 5]
[22] pry(main)> arr_var
=> [1, 2, 5]

Arrayのソート(並べ替え)

Arrayをソートしたい場合には#sortを使います。

[23] pry(main)> arr_var = [-1,3,0,-10,1]
=> [-1, 3, 0, -10, 1]
[24] pry(main)> arr_var.sort
=> [-10, -1, 0, 1, 3]

正しいソート結果を得たい場合には、要素の型を揃える必要があります。文字列と数値など、相互に比較できないものが混じるとエラーになります。

[25] pry(main)> arr_var = [-1,"5",2,"0",-10]
=> [-1, "5", 2, "0", -10]
[26] pry(main)> arr_var.sort
ArgumentError: comparison of Integer with String failed
from (pry):26:in `sort'

しかし、こんなときも#sortメソッドのブロックにオブジェクトの比較条件を渡してやることで、ソートさせることが可能です。
以下は#to_iメソッドを使うことで文字列も数値も全て数値に変換した上で比較するようにした例です。

[28] pry(main)> arr_var.sort{|a,b| a.to_i <=> b.to_i}
=> [-10, -1, "0", 2, "5"]

なお、逆順にしたければ#reverseを使えば良いです。逆順に#eachしたければ#eachの代わりに#reverse_eachを使うことができます。

[31] pry(main)> arr_var
=> [-1, "5", 2, "0", -10]
[32] pry(main)> arr_var.reverse
=> [-10, "0", 2, "5", -1]

スタックアクセス

Arrayをスタック的なデータ置き場として使いたい場合は#push#popを使えば良いです。要素が無い状態で#popすると、nilが返るようになっています。

[34] pry(main)> arr_var = []
=> []
[35] pry(main)> arr_var.push 'a'
=> ["a"]
[36] pry(main)> arr_var.push 'b'
=> ["a", "b"]
[37] pry(main)> arr_var.push 'c'
=> ["a", "b", "c"]
[38] pry(main)> arr_var.pop
=> "c"
[39] pry(main)> arr_var.pop
=> "b"
[40] pry(main)> arr_var.pop
=> "a"
[41] pry(main)> arr_var.pop
=> nil

その他よく使われる・便利なメソッド達

ここまでは他の言語でも一般的に実装されている機能ですが、ここからはあまり他言語では見ないメソッドを紹介してみます。

要素追加 <<

<<を使って要素の追加ができます。#pushと違い同時に1要素しか追加できませんが、Arrayに新しく要素を追加する、という意味合いでわかりやすいためか、Rubyではよく使われます。

[42] pry(main)> arr_var = []
=> []
[43] pry(main)> arr_var << 1
=> [1]
[44] pry(main)> arr_var << 2
=> [1, 2]
[45] pry(main)> arr_var << 3
=> [1, 2, 3]

1番目の要素を返す #first、最後の要素を返す #last

Arrayの先頭の要素を取るには添字アクセスで[0]を取ってももちろん良いのですが、最初と最後の要素については#first#lastによってアクセスすることができます(Rails環境だとActiveSupportが入るため、#secondなんかも使えますがRuby標準ではこの二つのみ)。

[46] pry(main)> arr_var = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
[47] pry(main)> arr_var.first
=> 1
[48] pry(main)> arr_var.last
=> 5

また、Arrayの末尾要素は「後ろから数えて1番目」なので添字[-1]でも取得することが可能です。

[50] pry(main)> arr_var[-1]
=> 5

入れ子になったArrayを全部展開する#flatten

入れ子になっているArrayをフラットなArrayに解きほぐして展開します。これは実際の処理結果を見るのが早いと思います。

[51] pry(main)> arr_var = [[1,2,3], 4,[5,[6,[7,8],9]],10]
=> [[1, 2, 3], 4, [5, [6, [7, 8], 9]], 10]
[52] pry(main)> arr_var.flatten
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

どんな時に使うかこれだけだとピンと来ないかもしれませんが、例えば公開APIを利用していて入れ子になったデータ構造を取得したときに、階層関係なく全ての要素について処理したいといった場合、何階層あるのかよく分からないデータをとりあえず処理したいといった場合にあると嬉しいメソッドです。

空かどうかを調べる#empty?

Arrayの中身が空かどうかを調べるには、要素数が0であるかを#lengthで調べても良いのですが、Rubyはより自然言語的な書き方が好まれることから#empty?を使うことが多いです。この辺りは文化的なものもあるので、他言語から移行してくる人には違和感があるかもしれませんが、こういうものだと思って慣れましょう。

[1] pry(main)> arr_var = [1,2]
=> [1, 2]
[2] pry(main)> arr_var.empty?
=> false
[3] pry(main)> arr_var = []
=> []
[4] pry(main)> arr_var.empty?
=> true

まとめ

そんなわけで、公式のArrayドキュメント からRubyを使う上でよく使うメソッド群を紹介してみました。
まだ紹介していないメソッドも多数ありますが、とりあえずここに挙げたものが使えれば他言語から移行してきた人でも最低限やりたいことはできるのではないかと思います。

[Rails 5] rails dev:cacheコマンドでdevelopmentモードでのキャッシュを簡単にオン・オフできる

$
0
0

こんにちは、hachi8833です。BigBinaryシリーズ、今回はRails 5でdevelopmentモードのキャッシュを簡単にオンオフできる機能を紹介します。

元記事

確認に使った環境

Rails 5の新機能: developmentモードのキャッシュを簡単に制御できるようになった

Rails 4まで

Rails 4までは、developmentモードのキャッシュを切り替えるためにいちいちconfig/environments/development.rbを開いて以下の設定のfalse/trueを切り替え、さらにサーバーを再起動しなくてはなりませんでした。

config.action_controller.perform_caching = false

ローカルキャッシュの動作を調べたりするときに地味に面倒です。

Rails 5以降

Rails 5では、developmentモードのキャッシュを簡単に作成できる新しいコマンドが導入されました。これにより、キャッシュの動作を切り替えて振る舞いの違いを簡単に確認できます。DHHの要請で追加された機能です。

訳注: Rails 5.0.2のconfig.action_controller.perform_cachingのデフォルト設定は以下のとおりです。

  • development: false(tmp/caching-dev.txtがある場合のみtrue
  • production: true
  • test: false

$ rails dev:cache
Development mode is now being cached.

上のようにrails dev:cacheコマンドを実行すると、tmp/ディレクトリにcaching-dev.txtファイルが作成されます。

キャッシュの仕組み

Rails 5で新規作成したRailsアプリでは、config/environments/development.rbに以下のコードが含まれるようになりました。以下は5.0.2で確認しました。

  # Enable/disable caching. By default caching is disabled.
  if Rails.root.join('tmp/caching-dev.txt').exist?
    config.action_controller.perform_caching = true

    config.cache_store = :memory_store
    config.public_file_server.headers = {
      'Cache-Control' => 'public, max-age=172800'
    }
  else
    config.action_controller.perform_caching = false

    config.cache_store = :null_store
  end

このコードでは、tmp/caching-dev.txtが存在する場合にのみ:mem_cache_storeでキャッシュを有効にします。

以下は当時のdev_cacheコマンドのソースです。

def dev_cache
  if File.exist? 'tmp/caching-dev.txt'
    File.delete 'tmp/caching-dev.txt'
    puts 'Development mode is no longer being cached.'
  else
    FileUtils.touch 'tmp/caching-dev.txt'
    puts 'Development mode is now being cached.'
  end

  FileUtils.touch 'tmp/restart.txt'
end

訳注: ただしその後コードがさらに変わり、現在はモジュールとしてrailties/lib/rails/dev_caching.rbにあります。

require "fileutils"

module Rails
  module DevCaching # :nodoc:
    class << self
      FILE = "tmp/caching-dev.txt"

      def enable_by_file
        FileUtils.mkdir_p("tmp")

        if File.exist?(FILE)
          delete_cache_file
          puts "Development mode is no longer being cached."
        else
          create_cache_file
          puts "Development mode is now being cached."
        end

        FileUtils.touch "tmp/restart.txt"
        FileUtils.rm_f("tmp/pids/server.pid")
      end

      def enable_by_argument(caching)
        FileUtils.mkdir_p("tmp")

        if caching
          create_cache_file
        elsif caching == false && File.exist?(FILE)
          delete_cache_file
        end
      end

      private
        def create_cache_file
          FileUtils.touch FILE
        end

        def delete_cache_file
          File.delete FILE
        end
    end
  end
end

新機能のメリットと注意

キャッシュのオン/オフを切り替えるためにサーバーを再起動する必要がなくなりました。dev:cacheコマンドを実行したときに内部で実行されるdev_cacheがよしなにやってくれます。tmp/restart.txtがtouchで作成されることをソースコードで確認できます。

この機能はunicorn、thin、webrickではサポートされないのでご注意ください。この点について、DHHのチームではpowを採用しており、powはtmp/restart.txtをtouchすると再起動するようになっているからではないかと(元記事の著者が)推測しています。実際、DHHはだいぶ以前にもspringの監視リストにtmp/restart.txtを追加するissueを作成しています。

キャッシュをオフにするには

rails dev:cacheコマンドはトグルになっているので、実行のたびにオンとオフが切り替わります。

$ rails dev:cache
Development mode is no longer being cached.

訳注

Pumaについて

原文ではRails 5のデフォルトのサーバーであるPumaについて言及がありません。Rails 5.0.2の素の環境で試してみたところ、少なくとも設定レベルではPumaで正常に動作していることを確認できました。

ビューで<%= Rails.cache %><%= Rails.configuration.cache_store %>を追加しただけの簡素な方法です。

  • rails dev:cacheでオンにした場合

  • rails dev:cacheでオフにした場合

rails dev:cacheを実行するとRailsが再起動されるので、2、3秒待つ必要があります。

Railsの起動オプションでdevelopmentキャッシュを指定する

デフォルトではdevelopmentキャッシュはオフになります。

rails start--no-dev-cachingを付けると、developmentキャッシュを明示的にオフにして起動できます。
rails start--dev-cachingを付けると、developmentキャッシュを明示的にオンにして起動できます。

不要な場合はdevelopmentキャッシュをオンにしないようにしましょう。

関連記事(BigBinaryシリーズ)

週刊Railsウォッチ(20170317)Railsパフォーマンスチューニング本、DBレコード存在チェックの最速メソッド、RubyのUnicode正規化ほか

$
0
0

こんにちは、hachi8833です。

「HackerNewsのランキングアルゴリズムをリバース・エンジニアリングした記事がHackerNewsから削除されたという記事」が昨日までHackerNewsでトップでした。ややこしいですね。

IPAが自ら脆弱性を報告した無料ゲーム「安全なウェブサイト運営入門」は書籍の名前かと思ってしまいました。

それでは今週もいってみましょう。

書籍「The Complete Guide to Rails Performance」(HackerNewsより)

(Amazon.co.jpにはエントリがないので同サイトの書影をリンク付きで引用しました)


The Complete Guide to Rails Performanceより

元記事は「私はいかにしてRails本で7万ドル稼いだか」的な記事ですが、その書籍の方をピックアップしました。読んでみないと何とも言えませんが、よさそうです。

ActionController::Parameters:にreverse_mergereverse_merge!を追加(Rails公式より)

改修箇所は以下です。

  # 現在のハッシュをother_hashにマージし、新しいActionController::Parametersインスタンスをすべてのキーとともに返す
  def reverse_merge(other_hash)
    new_instance_with_inherited_permitted_status(
      other_hash.to_h.merge(@parameters)
    )
  end

  # 現在のハッシュをother_hashにマージし、現在のActionController::Parametersインスタンスを返す
  def reverse_merge!(other_hash)
    @parameters.merge!(other_hash.to_h) { |key, left, right| left }
    self
  end

#merge#merge!のパラメータ順序をRubyのHash#mergeで確認すると以下のようになっていました。

  • merge(other) {|key, self_val, other_val| ... } -> Hash
  • merge!(other) {|key, self_val, other_val| ... } -> self

「これ欲しかった!」「strong parametersにデフォルト値を設定したいときにいいかも」という声がありました。

strong parametersの#permitは、paramsに入ってないkeyを#permitした場合にParameterオブジェクトの当該keyがnilになってしまいます。従来であればデフォルト値を与えたいときには自力でkeyに与える必要がありましたが、#reverse_merge!を使ってparamsのkeyにデフォルト値を与えられます。

たとえばモデル側で余計なことをしたくない(ビューの都合だけでデフォルト値を設定したいなど)場合に、モデルではなくコントローラ側でデフォルト値を与えられるのがよいかもという意見もありました。

個人的にはコントローラでparamsをそれ以上いじるのは避けたい気がしました。

Capybaraでドライバがスクショ非対応の場合にはスクショを撮らないようになった(Rails公式より)

Improvementに分類されてましたが、バグ修正ですね。修正もごくわずかでした。

Migrator.schema_migrations_table_nameを非推奨化(Rails公式より)

SchemaMigrationモデルが切り出されたことによる変更です。変更自体はアプリ開発者には直接関連しませんが、schema_migrationsという名前のテーブルが話題になりました。

今さらですが、Railsのデータベースにはschema_migrationsテーブルがあり、versionカラムにはそれまでマイグレーションに使ったファイル(db/migrate/以下)のバージョン番号がすべて保存されています。

マイグレーションが思うようにいかなくて、思い余ってマイグレーションファイルのバージョン番号を変更してしまう人がいるらしいのですが、「schema_migrationsテーブルの一貫性が損なわれるから絶対やめた方がいいよ」という話を聞きました。

マイグレーションには恥ずかしい履歴が残りがちなので、気持ちはわかります。

例外時にローカルキャッシュがクリアされない問題を修正(Rails公式より)

改修箇所はわずかでしたが、見終わってからRackとWardenの役割の違いについて話題になりました。

手短にまとめると、Wardenは認証用フレームワークであり、RackとRailsアプリの間の層で認証やセッション管理をつかさどります。Rackmorimorihogeさんいわく「薄いWebサーバー」であり、Rackの上でRailsやSinatraなどのアプリケーションが動作します。

Rackについては、Railsガイド: RailsとRackと技術評論社サイトの記事「Rackについて」、Wardenについては、「Devise を知るにはまず Warden を知るが良い」がそれぞれ参考になります。

サブディレクトリでのrake db:schema:loadの不具合を修正(Rails公式より)

実はRailsではサブディレクトリでもrails db:migrateを実行できるのですが、「何が起きるかわからないからコマンドはプロジェクトのルートディレクトリでしか実行しない」という意見がありました。私も同感です。

ActionController::Rendererでのasset_urlの不具合を修正(Rails公式より)

asset_urlが不完全だった(’https://’が含まれないことがあった)問題の修正です。

#28250ではやや緊張感のあるやりとりが繰り広げられていました。こんな写真が貼られてたり。もちろんお茶目でやっていると思います。


https://github.com/rails/rails/pull/28250より

最終的にPRのrebaseに手違いが見つかり、それを修正して無事commitされました。

has_many関連付けでブロックにselectしたときに新レコードが作成されない問題を修正(Rails公式より)

kamipoさんによる1行修正で完了しました。

#titlizeがアポストロフィに対応(Rails公式より)

改修箇所は、正規表現への\w(語の区切りを表す)の追加で終わりました。

humanize(underscore(word)).gsub(/\b(?<!['’`])[a-z]/) { |match| match.capitalize }   # 修正前
humanize(underscore(word)).gsub(/\b(?<!\w['’`])[a-z]/) { |match| match.capitalize } # 修正後

#titlelizeをどこかで見たと思ったらActiveSupport探訪シリーズ「[Rails5] Active Support::Inflectorの便利な活用形メソッド群」でお見かけしていました。その後お変わりありましたね。

ちなみに私はtitlelizeという造語のスペルを一発で入力できたことがありません。いつも間違えます。

#titlelizeのサンプルに使われている映画のタイトルが趣味全開ですね。さりげに新旧取り混ぜているのはコミッターの層の厚さゆえかシニアコミッターに気を遣ったのかはわかりませんが。

    #   titleize('man from the boondocks')   # => "Man From The Boondocks"
    #   titleize('x-men: the last stand')    # => "X Men: The Last Stand"
    #   titleize('TheManWithoutAPast')       # => "The Man Without A Past"
    #   titleize('raiders_of_the_lost_ark')  # => "Raiders Of The Lost Ark"

PostgreSQLのJSON型データのdeserializeが文字列を返す問題を修正(Rails公式より)

これもkamipoさんによる修正で、PostgreSQLのみが対象です。kamipoさんのコミットが膨大なので、昨年既に七福神入りしていたんですね。いつもありがとうございます🙇

簡潔・高効率かつRubyらしいコードを書く(RubyWeeklyより)

#attr_readerで初期化を簡潔に書く、ブロックをprocオブジェクトに置き換えるなどの方法などが紹介されています。追記に、Ruby 2.3ではprivateでの#attr_reader警告が表示されなくなったということについても触れられています(#10967 Is “warning: private attribute?” wrong?)。それほど短い記事ではありませんが、込み入ってはいないので読んでおくとよさそうです。

参考: 初期化を便利にするgem 2つ

ある程度複雑な初期化を楽に書くgemとして、morimorihogeさんが以下の2つを教えてくれました。

RubyのUnicode正規化とは

週刊Railsウォッチ(20170217)でマサカリが大量に飛んできた「Testing Ruby’s Unicode Support」記事のリベンジです。

Frankly, I was a bit fuzzy on Unicode normalization.

一転してUnicode正規化を熱心に調べています。短くもよくまとまった良記事です。後でじっくり読んでみます。

  • NF: “normalization form”の略
  • D: “decomposition”の略
  • C: “composition”の略
  • K: “kompatibility”の略 ┐(´∀`)┌

「RubyやRailsのコミュニティがUnicode正規化に思ったほど関心を寄せていないのに少しだけ驚いた」としめくくっています。ASCIIだけで事足りる英語圏ではある程度仕方がないかなとも思いますが、私としては、絵文字のおかげで近年こうした問題に以前よりも関心が高まっているような気はしています。

微細な最適化が効く(RubyWeeklyより)

長いです。最適化の主題となっているsigprocmaskって何だろうと思って一同で検索したところ、プロセスが応答するシグナルを変更するシステムコールでした。

ただ、新しい記事にもかかわらず、使っているのがRuby 1.8なので現在のRuby 2.x VMには符合しなさそうです。

なぜRuby 1.8なのか

以下は、同記事のRubyバージョンがえらく古い点についての私の推測です。

全文がHTMLで公開されている「Rubyソースコード完全解説」は2004年に公開されましたが、英語化されたのはかなり最近です。そして同書がRuby 1.7.xをベースにしている(前書きには1.8でもほとんど同じだろうと書かれている)ので、2.xではこうしたローレベルの最適化を追求しづらかったのではないかと思いました。

リポジトリ: Ruby Hacking Guide Translation

データベースレコードの存在チェックはexists?が最速

present?     #=>  2892.7 ms
any?         #=>   400.9 ms
empty?       #=>   403.9 ms
exists?      #=>     1.1 ms

だそうです。morimorihogeさんが「それぞれのメソッドがどんなSQLを吐いているかを知っておくことが重要」と指摘していました。記事中のサンプルコードでは#existだけにLIMIT 1が含まれているのが効いているようです。

著者は「いつもexists?でおk」と結論づけていますが、こうした速度はさまざまな条件によって影響される可能性があるので、参考として頭の隅に置いておこうと思います。

私はといえば、select 1 as oneはクエリの結果がいらない場合の定番のSQLであることを今さら知りました。

google-drive-ruby gem: RubyでGoogleスプレッドシートにアクセスできる(RubyFlowより)

表計算を何とかするgemです。

OAuth認証と組み合わせることで、RubyからGoogleスプレッドシートにアクセスして読み取りや更新を行えます。記事やリポジトリには記載されていませんが、Railsでも使えると思います。スプレッドシートを正しく運用できるのであれば、案件によっては合うかもしれません。

この種のgemを導入するときは、頻繁に更新されている(=放置されていない)ものを選ぶのが重要ですね。機会があれば使ってみたいですが、データをシートから取ってくるだけにしておきたいと思いました。

Rubyのモンキーパッチで怪我をしないためには(RubyFlowより)

Rubyはオープンクラスなのでモンキーパッチを簡単に当てられますが、よく切れるナイフと同様、自分の身を傷つけないように注意が必要と説いています。

  • ひとつのメソッドにモンキーパッチが2つ当たると、最初のモンキーパッチは上書きされてまったく効かなくなる
  • モンキーパッチで発生したエラーはクラス内で発生したように見えてしまう
  • モンキーパッチをオフにする方が面倒
  • モンキーパッチ実行前にクラスをrequireするのを忘れると、パッチではなくクラスの再定義になってしまう
  • モンキーパッチをモジュール化することもできるが、問題はさして変わらない: パッチはグローバルに効くので、別のライブラリでいつの間にか上書きされてしまう可能性がある

morimorihogeさんが、趣旨の近い記事としてクックパッド開発者ブログ「Ruby on Rails アプリケーションにおけるモンキーパッチの当て方」を挙げました。

Webアプリ・モバイルアプリ開発にRuby on Railsを選ぶ理由(RubyFlowより)

RubyFlowのリンクが壊れていたので、直接元記事にリンクしました。

  • オープンソースであり、コミュニティによるサポートが絶大
  • 改修が容易
  • 短期間でのリリースが可能
  • 開発ツールの再利用が利く
  • 開発者に優しい
  • 英語に近い感覚

タイトルと本文の量がほぼ同じぐらいですね。リンクはしませんが、About Usを見るとインドのアフマダーバードの会社でした。

EuRuKo 2017に参加すべき理由(RubyFlowより)

「〜する理由」記事が続きます。EuRuKoはヨーロッパで開催される大きなRubyセッションです。2016年度はブルガリアのソフィアでの開催、そして今年は9月にハンガリーの首都ブダペストでの開催となります。


https://euruko2017.org/より

  • コミュニティが主役
  • 交通の便がよい
    • Geekすぎないのもありがたい
  • 内容がよい
  • 会社にもメリットがある
  • 出張の名目として絶好

Matzももちろんキーノートスピーチを担当しますし、昨年はLTやCoding Golfも大いに盛り上がったそうです。

morimorihogeさんによると「会場が空港や駅から遠い」「宿がスムーズに取れない」「会場の周辺に居酒屋などがまったくない」カンファレンスはやっぱりつらいそうです。

日本であれば多少不便でも旅情を味わえたりしますが、海外(特にヨーロッパ)だと都市部であってもコンビニが少なかったりレストランも土日閉まってたりすることが多いので、参加にあたってはそのあたりをチェックしたいですね。

今週は以上です。

関連記事

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやRSSなど)です。

Rails公式ニュース

Ruby Weekly

OpenRuby

RubyFlow

160928_1638_XvIP4h

Hacker News

160928_1654_q6srdR

[Rails] RubyistのためのPostgreSQL EXPLAINガイド(翻訳)

$
0
0

こんにちは、hachi8833です。今回は「A Rubyist’s Guide to Postgresql’s Explain」の翻訳記事をお届けいたします。

EXPLAINはSQLの構文なので、本記事では元記事のタイトルとコードを除き大文字で表記します。

概要

原著者の許諾を得て翻訳・公開いたします。

なお、翻訳では元記事にないコードのハイライトをスクリーンショットとして追加しています。

RubyistのためのPostgreSQL EXPLAINガイド

PostgreSQLにはEXPLAINと呼ばれるささやかな機能があります。ささやかですが、「このところなぜかデータベースクエリが遅い」という問題を解決するうえで最強の武器にもなります。

EXPLAINのしくみは単純です。PostgreSQLにクエリをどのように実行するかを問い合わせると、PostgreSQLがクエリプランを表示してくれます。そのクエリを実際にEXPLAINで実行して、実際のパフォーマンスと比較することもできます。

なかなか使いやすそうだけど?

EXPLAINという機能があることは何となくご存じのRailsエンジニアもいると思います。実はRails 3.2というかなり早い時期から、クエリ実行時間が500msを超えたときに自動的にEXPLAINが実行されるようになっているのです。

問題があるとすれば、EXPLAINの出力が少々込み入ってることぐらいでしょう。出力例として、当サイトのRails開発ブログから出力結果を引っ張ってきました。

% User.where(:id => 1).joins(:posts).explain

EXPLAIN for: SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id" WHERE "users"."id" = 1
                                  QUERY PLAN
------------------------------------------------------------------------------
 Nested Loop Left Join  (cost=0.00..37.24 rows=8 width=0)
   Join Filter: (posts.user_id = users.id)
   ->  Index Scan using users_pkey on users  (cost=0.00..8.27 rows=1 width=4)
         Index Cond: (id = 1)
   ->  Seq Scan on posts  (cost=0.00..28.88 rows=8 width=4)
         Filter: (posts.user_id = 1)
(6 rows)

さて、この出力から一体何を読み取ればよいのでしょうか?

本記事ではこうした出力結果を解釈する方法をご紹介いたします。特に、クエリがRubyを用いたWeb開発にいかに大きな影響を与えるかという点に重点を置きます。

EXPLAIN出力の構文

既にRailsをお使いであれば、以下のようにActive Recordのクエリに.explainを追加するだけで簡単にEXPLAINを実行できます。

> User.where(id: 1).explain
  User Load (10.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1  [["id", 1]]
=> EXPLAIN for: SELECT "users".* FROM "users" WHERE "users"."id" = $1 [["id", 1]]
                                QUERY PLAN
--------------------------------------------------------------------------
 Index Scan using users_pkey on users  (cost=0.29..8.30 rows=1 width=812)
   Index Cond: (id = 1)
(2 rows)

#explainメソッドはお手軽ですが、PostgreSQLで直接EXPLAINを実行する場合に使える高度なオプションには残念ながらアクセスできません。

PostgreSQLでEXPLAINを直接実行したい場合は、psql -d データベース名でpostgres clientを開き、以下のように実行するだけで済みます。

explain select * from users where id=1;
                                QUERY PLAN
--------------------------------------------------------------------------
 Index Scan using users_pkey on users  (cost=0.29..8.30 rows=1 width=812)
   Index Cond: (id = 1)
(2 rows)

これで、PostgreSQLがクエリをどのように実行するかというプランの情報を得られます。クエリプランには、実行に要する最短時間の予測も含まれています。

クエリを実際に実行して最短時間の見積もりと比較するには、EXPLAIN ANALYZEを使います。

explain analyze select * from users where id=1;
                               QUERY PLAN
------------------------------------------------------------------------------------
 Index Scan using users_pkey on users  (cost=0.29..8.30 rows=1 width=812) (actual time=0.043..0.044 rows=1 loops=1)
   Index Cond: (id = 1)
 Total runtime: 0.117 ms

出力結果を読み取る

PostgreSQLはよくできていて、クエリを効率よく実行する可能な限り最適な方法を見つけてくれます。言い換えると、EXPLAINコマンドを実行するだけでPostgreSQLが「クエリプラン」を作って出力してくれるのです。

以下の出力結果を読み取ってみましょう。

# explain select * from users order by created_at limit 10;
                               QUERY PLAN
-------------------------------------------------------------------------
 Limit  (cost=892.98..893.01 rows=10 width=812)
   ->  Sort  (cost=892.98..919.16 rows=104 width=812)
         Sort Key: created_at
         ->  Seq Scan on users  (cost=0.00..666.71 rows=104 width=812)
(4 rows)

クエリプランは以下の2つの要素で構成されています。

  1. ノードリスト(node list): クエリ実行に必要な操作(action)の順序を示します
  2. パフォーマンス予測(performance estimate): リストの各項目の実行にかかるコストを示します

訳注: 以下に追加したスクリーンショットでは、黄色がノードリスト、青がパフォーマンス予測です。

ノードリスト

ノードリスト部分を読みやすくするために、先ほどのクエリプランからパフォーマンス予測を取り除いてみました。

 Limit
   ->  Sort (Sort Key: created_at)
         ->  Seq Scan on users

ノードリストは、言ってみればPostgreSQLがクエリ実行のために自動生成した一種のプログラムです。この場合はLimitSortSeq Scanという操作を行います。

また、子ノードの出力は親ノードにパイプで接続されます。

このノードリストを仮にRubyで書き換えると次のような感じになります。

all_users.sort(:created_at).limit(10)

PostgreSQLはクエリプランでさまざまな種類の操作を利用します。すべての操作の意味を知っておく必要はないと思いますが、よく使うものを以下にまとめました。

Index Scan
インデックスを利用してレコード(複数可)をフェッチします
Rubyで言うと、ハッシュで項目を検索するような感じです
Seq Scan
レコードセットに対してループを回してレコードをフェッチします
Filter
レコードセットから条件を満たすレコードのみを選択します
Sort
レコードセットをソートします
Aggregate
countmaxminといった操作に使われます
Bitmap Heap Scan
レコードのマッチをビットマップで表現します
論理積や論理和といった操作は、実際のレコードよりもビットマップの方が簡単になることがあります

もちろん、この他にもたくさんの操作があります。

パフォーマンス予測

ノードリスト上の各ノードの後ろには、以下のようにパフォーマンス予測が追加されます。

Limit  (cost=892.98..893.01 rows=10 width=812)

各項目の数字の意味は次のとおりです。

cost
操作の実行に要する作業量を表します
数値に単位はなく、cost間の比較でのみ意味を持ちます
rows
行数の予測値です
操作の実行に必要なループ処理を行う行数の予測値を表します
width
各行のサイズ予測値(単位はバイト)です

筆者の場合、最もよくチェックするのはrowsです。クエリがちゃんとスケールしているかどうかの確認にrowsが大変便利です。

  • rowsが1の場合: クエリは高い性能を発揮できます
  • rowsがテーブルのレコード数と同じ場合: 巨大なデータセットで性能が落ちる可能性があります

実際のパフォーマンス値

EXPLAIN ANALYZEでクエリを実際に実行すると、次のように数字が2セット表示されます。最初のものは上述のような予測値であり、次のものが実測値です。

訳注: 以下に追加したスクリーンショットでは、青が予測値、緑が実測値です。

# explain analyze select * from users order by created_at limit 10;
                                                       QUERY PLAN
------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=892.98..893.01 rows=10 width=812) (actual time=22.443..22.446 rows=10 loops=1)
   ->  Sort  (cost=892.98..919.16 rows=10471 width=812) (actual time=22.441..22.443 rows=10 loops=1)
         Sort Key: created_at
         Sort Method: top-N heapsort  Memory: 31kB
         ->  Seq Scan on users  (cost=0.00..666.71 rows=10471 width=812) (actual time=0.203..15.221 rows=10472 loops=1)
 Total runtime: 22.519 ms
(6 rows)

実測値の項目は次のとおりです。

actual time
操作の実行に要した時間をmsで表します
rows
実際に処理した行数です
loops
操作のループが発生した場合、1より大きな値になります

詳細出力

EXPLAINのデフォルト出力はある程度要約されていますが、必要に応じて詳細な出力も得られます。出力をJSONやYAML形式に整形することもできます。

# EXPLAIN (ANALYZE, FORMAT YAML) select * from users order by created_at limit 10;
                QUERY PLAN
------------------------------------------
 - Plan:                                 +
     Node Type: "Limit"                  +
     Startup Cost: 892.98                +
     Total Cost: 893.01                  +
     Plan Rows: 10                       +
     Plan Width: 812                     +
     Actual Startup Time: 12.945         +
     Actual Total Time: 12.947           +
     Actual Rows: 10                     +
     Actual Loops: 1                     +
     Plans:                              +
       - Node Type: "Sort"               +
         Parent Relationship: "Outer"    +
         Startup Cost: 892.98            +
         Total Cost: 919.16              +
         Plan Rows: 10471                +
         Plan Width: 812                 +
         Actual Startup Time: 12.944     +
         Actual Total Time: 12.946       +
         Actual Rows: 10                 +
         Actual Loops: 1                 +
         Sort Key:                       +
           - "created_at"                +
         Sort Method: "top-N heapsort"   +
         Sort Space Used: 31             +
         Sort Space Type: "Memory"       +
         Plans:                          +
           - Node Type: "Seq Scan"       +
             Parent Relationship: "Outer"+
             Relation Name: "users"      +
             Alias: "users"              +
             Startup Cost: 0.00          +
             Total Cost: 666.71          +
             Plan Rows: 10471            +
             Plan Width: 812             +
             Actual Startup Time: 0.008  +
             Actual Total Time: 5.823    +
             Actual Rows: 10472          +
             Actual Loops: 1             +
   Triggers:                             +
   Total Runtime: 13.001
(1 row)

上のようにEXPLAIN (ANALYZE, VERBOSE, FORMAT YAML) select ...を使うことで、さらに多くの情報を出力できます。

ビジュアル表示ツール

EXPLAINでは大量の出力が生成されるので、クエリが複雑になると解析作業だけでうんざりしてしまいます。

幸いなことに、解析に便利なフリーのビジュアル表示ツールがいろいろあります。こうしたツールを使ってEXPLAINの出力を解析し、使いやすいダイヤグラムを生成できます。さらに、潜在的なパフォーマンス上の問題までハイライトしてくれます。

以下は筆者が愛用している「Postgres EXPLAIN Visualizer (pev)」というWebサービスのスクリーンショットです。

訳注: 上のサービスには少なくともJSON形式の出力を貼る必要がありました。

訳注: このツールではクエリの問題点を以下のように赤くハイライトしてくれます。

クエリプランを出力して結果を読み取ってみよう

以下に実例を用意しましたので、実際にやってみましょう。Railsで広く使われている定番コードの中に、スケールしない残念なデータベースクエリを生成するものがいくつもあるのをご存知でしたでしょうか?

#countメソッドの残念なクエリ

以下のようなコードは、Railsアプリのビューで使われまくっています。

Total Faults <%= Fault.count %>

このコードでは次のようなSQLが出力されます。

select count(*) from faults;

さっそくEXPLAINでどうなっているか調べてみましょう。

# explain select count(*) from faults;
                            QUERY PLAN
-------------------------------------------------------------------
 Aggregate  (cost=1840.31..1840.32 rows=1 width=0)
   ->  Seq Scan on faults  (cost=0.00..1784.65 rows=22265 width=0)
(2 rows)

こ、これは…!ちっぽけな#countメソッドのクエリで、何と22,265回ものループが発生しています。

この回数はテーブルの行数そのものです。つまり#countメソッドは呼ばれるたびにレコードセット全体をループしているのです。

ソートの問題

フィールドを指定してソートしたリストを取り出す場合、以下のようなコードが非常によく使われています。

Fault.order("created_at desc").limit(10)

取り出すのはたったの10レコードですが、10レコードを取り出すためにテーブル全体のソートが発生します。以下のSortノードにも示されているように、22,265行を処理しなければならなくなります。

# explain select * from faults order by created_at limit 10;
                                 QUERY PLAN
----------------------------------------------------------------------------
 Limit  (cost=2265.79..2265.81 rows=10 width=1855)
   ->  Sort  (cost=2265.79..2321.45 rows=22265 width=1855)
         Sort Key: created_at
         ->  Seq Scan on faults  (cost=0.00..1784.65 rows=22265 width=1855)

インデックスを追加すれば、Sortよりはるかに高速なIndex Scanが使われるようになります。

# CREATE INDEX index_faults_on_created_at ON faults USING btree (created_at);
CREATE INDEX

# explain select * from faults order by created_at limit 10;
                                               QUERY PLAN
---------------------------------------------------------------------------------------------------------
 Limit  (cost=0.29..2.66 rows=10 width=1855)
   ->  Index Scan using index_faults_on_created_at on faults  (cost=0.29..5288.04 rows=22265 width=1855)
(2 rows)

まとめ

本記事が皆さまの役に立ち、EXPLAINコマンドでいろんなことを試してみようという意欲につながれば幸いです。アプリケーションのデータ量が増大したときにアプリがどうスケールするかを理解することは、開発における基本中の基本といってよいでしょう。

週刊Railsウォッチ(20170331)PostgreSQLの制約機能を使えるRein gemはビューも使えるほか

$
0
0

こんにちは、hachi8833です。

⭐ Rein: RDBMSの制約機能やビューを使えるgem(RubyWeeklyより) ⭐

RDBMSが持つ制約(constraint)機能などがRailsで利用しやすくなります。「PostgreSQLのみ」機能が多数あるので、事実上PostgreSQL専用に近そうです。

元記事はREADMEの抜粋のようなものなのでリポジトリを見るほうがよいでしょう。以下は同gemのREADMEにもとづいています。

Railsのマイグレーションファイルで以下のように記述できるようになります。

  • 外部キー(foreign key)制約
add_foreign_key_constraint :books, :authors     #作成
remove_foreign_key_constraint :books, :authors  #削除
  • inclusion制約(PostgreSQLのみ)
add_inclusion_constraint :books, :state, in: %w(available on_loan) #作成
remove_inclusion_constraint :books, :state                         #削除
  • 数値の制約(PostgreSQLのみ)
add_numericality_constraint :books, :publication_month,   #作成
  greater_than_or_equal_to: 1,
  less_than_or_equal_to: 12
remove_numericality_constraint :books, :publication_month #削除
  • Presence制約(PostgreSQLのみ)
add_presence_constraint :books, :isbn, if: "status = 'published'" #作成
remove_presence_constraint :books, :title                         #削除
  • 列挙(enumerated)型(PostgreSQLのみ)
create_enum_type :book_type, ['paperback', 'hardcover'] #作成
drop_enum_type :book_type                               #削除

データベースビューやスキーマも

一同でチェックしていると「こいつ…ビューも使えるぞ!」という喜びの声があがりました。

  • ビュー
create_view :available_books, "SELECT * FROM books WHERE state = 'available'" #作成
drop_view :available_books                                                    #削除

なおソースを覗いてみたところ、マテリアライズドビューではないようです。

  • 複数スキーマ(PostgreSQLのみ)
create_schema :archive  #作成
drop_schema :archive    #削除

果たしてどのぐらいイケているのかは試してみないとわかりませんが、こうしたgemなしでデータベース側の制約機能やビューをRailsで使おうとすると、生SQLとの格闘やマイグレーションでの整合性など何かと面倒になりがちだったので、うまくはまってくれれば助かりそうです。

既に似たようなコードを書いている方も多いと思いますので、車輪の再発明を減らす効果もあるかもしれません。

期待を込めて ⭐ を進呈いたします。おめでとうございます!

参考: PostgreSQL 9.0.4文書: 5.3. 制約

Ruby 2.2.7リリース(Ruby公式より)

Rubyの2.4.1とは別に、Rubyの2.2系のバグ修正として2.2.7がリリースされました。

ChangeLogがフラットすぎて読みづらいので、compareとってみました。やはりほとんどの修正が上位バージョンからのbackportですね。


https://github.com/ruby/ruby/より

Rails機能追加: belongs_to:defaultオプションを追加(Rails公式ニュースより)

belongs_toで以下のように書くと、自動的にbefore_validationコールバックが呼び出されるようになります。

belongs_to :person, default: -> { Current.person }

以下のようにいちいちチェックせずに済むのでちょっとだけ書きやすくなりますね。

belongs_to :person
before_validation -> { self.person ||= Current.person }

morimorihogeさんがチェックした修正箇所は以下のようになっているので、このdefault:||=含みですね。

# https://goo.gl/VeQJJ7 より
def default(record)
  writer(record) if reader.nil?
end

機能追加: RailsでRationalとComplexのduplicateをサポート(Rails公式ニュースより)

Rubyの次期バージョンでRationalとComplexのオブジェクトが複製可能になるとのことで、一足先にRails側で対応しています。

Complexes are not duplicable for RUBY_VERSION < 2.5.0:

ということなので、Ruby 2.5.0が出てからですね。

Rails側の修正のテストコードを整形してみました。

# https://goo.gl/nWJ7hg より
if RUBY_VERSION >= "2.5.0"
  RAISE_DUP = [method(:puts)]
  ALLOW_DUP = ["1",
               "symbol_from_string".to_sym,
               Object.new,
               /foo/,
               [],
               {},
               Time.now,
               Class.new,
               Module.new,
               BigDecimal.new("4.56"),
               nil,
               false,
               true,
               1,
               2.3,
               Complex(1),
               Rational(1)
               ]
   ...

RationalはTimeオブジェクトなどで内部的に使われていたりしますが、Complexってビジネスロジックにはまず出てこなさそうです。とはいうものの、理数系、特に電気工学では複素数が当たり前に使われるので、そうした方面での利用ではやはり助かると思います。

Rails修正: freezeしたオブジェクトでもTime#to_timeできるようになった(Rails公式ニュースより)

Rails 4以来の問題だったそうです。issueでは今回もpixeltrix氏から容赦ないツッコミが入っていますね。さらにpixeltrix氏自ら解決法まで示し、そして

knock yourself outは「どうぞご自由に」「やればー」みたいなニュアンスですね。

Rails修正: ActiveRecordの#orderにハッシュで正しく式を与えられるようになった(Rails公式ニュースより)

以下のようにハッシュのキーが式になっていると式がクエリにそのまま入ってしまう問題が修正されました。

Post.order("LENGTH(title)" => :asc).last
# SELECT  `posts`.* FROM `posts` ORDER BY `posts`.`LENGTH(title)` DESC LIMIT 1
#                                                  ↑残念

修正後のコードは以下です。項目がリテラルでない場合に展開していますね。

          # https://goo.gl/0juQNQ より
          when Hash
            arg.map { |field, dir|
              case field
              when Arel::Nodes::SqlLiteral
                field.send(dir.downcase)
              else
                arel_attribute(field).send(dir.downcase)
              end
            }

ハッシュのキーを式で動的に与えるのはあまりしないかもなー、という声もちらほらとありました。

新しいJSON APIにRailsを使うのをやめた理由(RubyWeeklyより)

Rails単体でまかなうアーキテクチャを変更し、RailsにマウントしたHanamiにAPI部分を任せる形になっています。初期のコードがいろいろひどくてリファクタリングが困難になったこともあり、新しいAPIをHanamiで試してみたようです。

That’s why we decided to implement the new API as a standalone Hanami application mounted inside Rails. A change in the web UI (Rails) isn’t reflected in the API (Hanami) and viceversa.

RailsとHanamiを共存させる手法は、この場合特に必然性があるかどうかはわかりませんが、他でも使えそうですね。

Hanamiをチラ見

ところで、HanamiはRailsよりも軽めで小ぶりなのかなという印象でしたが、必要そうなものはひととおりあるようですね。


https://github.com/hanami/hanamiより

ついでなので、それぞれのvendor/bundle/ruby/2.4.0/gems以下でfind . -type f -name '*.rb'| xargs cat| wc -lを実行してざっくり行数を取ってみました。

  • Rails 5.0.2: 391,259
  • Hanami 0.9.2: 161,059

Rails 5のWebSocketsアプリでActionCableのDoS攻撃脆弱性を見つけるまで(RubyWeeklyより)


ActionCable under stress: Finding a DoS vulnerability in Rails 5 WebSockets Appsより

非常に具体的で良い記事です。検証にはOS Xのネットワークツールも動員しています。

こちらは近日中に翻訳記事を公開いたしますのでご期待ください。

localtower: GUIでモデル、リレーション、マイグレーションを管理するgem(RubyWeeklyより)

このGUIで以下の操作を実行できます。

  • モデルの追加・削除・変更
  • カラム追加・削除・変更
  • 多対多リレーションの追加
  • マイグレーションの追加・変更・削除


https://github.com/damln/localtowerより

こういうのは個人的に好きなのですが、GUIだといろいろ痒いところに手が届かなさそうという声多数でした(´・ω・`)
ちょっと動かしてみたところ、作成したカラムは一律でインデックスがオンになりました。

スキーマ表示はきれいなので、顧客向けに整形するとかREADMEに貼るなどの使いみちはあるかもしれません。

教育用にも使えそうですが、生徒がGUIに味をしめてしまいそうなので考えどころですね。

少し似たツールとしては、以前のRailsウォッチでもご紹介したrails_db gemもありますね。

Ruby Heroes授与が終了(RubyWeeklyより)

2017年度のRubyConfでは行われないそうです。 Ruby HeroesはCode Schoolがスポンサーを務めていました。

別にお金などもらっていませんが、Code Schoolはいいですね。一部コンテンツが古いものがあるので頑張って欲しいです。

Rails 5.1のJS周りの詳細(RubyWeeklyより)

Rails 5.1でのYarn・Webpack関連情報がよくまとまっていて便利そうです。yarnでインストールする練習しておこう。

interferon: Ruby製のアラート用フレームワーク(RubyWeeklyより)

AirBnBで実際に使われているそうです。Ruby製ですが、特にRails用ということではなさそうでした。

なお、BPSのインフラチームはnagiosによるアラートをprometheusに移行すべく試験運用中です。push型のnagiousに対し、prometheusはpull型という特徴があります。

参考

Ruby 2D: クロスプラットフォーム2Dアプリ作成用 gem


http://www.ruby2d.com/learn/tech/より

アーキテクチャ図の左下にはしっかりSDLの文字も。Rubyでクロスプラットフォームなゲームをがっしがっし書きたい方向け。

# http://www.ruby2d.com/learn/get-started/ より
require 'ruby2d'

set title: "Hello Triangle"

Triangle.new(
  320,  50,
  540, 430,
  100, 430,
  ['red', 'green', 'blue']
)

show


www.ruby2d.comより

Uninterruptible: 軽いsocketサーバーを瞬時に再起動するgem

「Zero-downtime restarts for your trivial socket servers」とあるとおり、Un*x系のsocketプログラミングする人向けのgemですね。

READMEに挿入されている画像が、インフラエンジニアが日々仕事するときの心境を見事に表していていますね。ここに引用するのはさすがにやめておきます。

一同で記事をチェックしながら、morimorihogeさんがLinuxなどのUn*x系OSで使われているシグナルについておさらいしてくれました。

  • ターミナルのkillコマンドはプロセスにシグナルを送信するだけ: プロセス側が適切に応答しなければ意味がない
  • ただしSIGKILLkill -9)だけは例外: プロセスの実装に関係なくOSが強制的にプロセスを止める

参考: Ctrl+Cとkill -SIGINTの違いからLinuxプロセスグループを理解する

正規表現のよく使うパターン(HackerNewsより)

日付やURLやメアド、クレカ番号などにマッチする定番正規表現のメモです。私は動作確認してませんが、きっと便利です。HackerNewsでこれだけ人気が集まるということは、みんな正規表現で困ってるんですね。


mingrammer/commonregexより

Go言語用となってますが、Go標準のregexpライブラリは貧弱余分な機能がないのでたいていの正規表現でも動きそうです。

morimorihogeさんがピックアップしたIPv6用の正規表現↓に一同「これはひどいw」。

IPv6Pattern = \s*(?:(?:(?:[0-9A-Fa-f]{1,4}:){7}(?:[0-9A-Fa-f]{1,4}|:))|(?:(?:[0-9A-Fa-f]{1,4}:){6}(?::[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(?:(?:[0-9A-Fa-f]{1,4}:){5}(?:(?:(?::[0-9A-Fa-f]{1,4}){1,2})|:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(?:(?:[0-9A-Fa-f]{1,4}:){4}(?:(?:(?::[0-9A-Fa-f]{1,4}){1,3})|(?:(?::[0-9A-Fa-f]{1,4})?:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[0-9A-Fa-f]{1,4}:){3}(?:(?:(?::[0-9A-Fa-f]{1,4}){1,4})|(?:(?::[0-9A-Fa-f]{1,4}){0,2}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[0-9A-Fa-f]{1,4}:){2}(?:(?:(?::[0-9A-Fa-f]{1,4}){1,5})|(?:(?::[0-9A-Fa-f]{1,4}){0,3}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[0-9A-Fa-f]{1,4}:){1}(?:(?:(?::[0-9A-Fa-f]{1,4}){1,6})|(?:(?::[0-9A-Fa-f]{1,4}){0,4}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?::(?:(?:(?::[0-9A-Fa-f]{1,4}){1,7})|(?:(?::[0-9A-Fa-f]{1,4}){0,5}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(?:%.+)?\s*

そこから、IPv6は固定長ヘッダの採用によって低層デバイスでも容易に扱えるようになった一方、IPv6の省略表記一意に定まらないという絶妙に残念なルールのせいでパターンマッチング泣かせであることなどが話題にのぼりました。

  • ::の間がすべて0のフィールドが1つ以上連続している場合はその部分を::と省略してよい
    • ただしこの省略法は1つのアドレスで1回しか行ってはいけない
  • 上に該当しない場合、::の間に含まれる0は、0以外の桁がその部分にある限り省略してもよい

一応RFC5952でIPv6の推奨表記方法が述べられています。

参考: JPNIC IPv6アドレスの表記法とは

streisand: Ansible Playbook集(HackerNewsより)

HackerNewsで急上昇中のAnsible Playbookリストです。 ネイティブで対応できるサイトリストにはAmazon EC2、DigitalOcean、Google Compute Engine、Linode、Rackspaceがずらり。まだ増やすそうです。

Explainshell.com: コマンドのmanをシェル構文に沿ってビジュアル表示(HackerNewsより)


より

百聞は一見にしかずです。クールクール。

番外: iOS10.3のファイルパス問題について

これだけ日本語記事ですが、解説がとてもわかりやすくて助かりました。

Mac系OSには以前からファイル名のカタカナの濁点や半濁点が転送先で分離する問題がありますが、UTF-8への正規化(normalization)の残念なつくりが問題の根源だったとは。

# http://skyarts.com/blog/jp/skyarts/?p=31678 より
0xE30x830x94 <= ピ                //Normalization Form C (NFC)
0xE30x830x92 0xE30x820x9A <= ヒ ゜ //Normalization Form D (NFD)

そしてAppleによる修正の見通しは暗そう…Macで保存した既存の大量のファイルはどうなるのでしょうか。

今週は以上です。

関連記事

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやRSSなど)です。

Ruby 公式ニュース

Rails公式ニュース

Ruby Weekly

Hacker News

160928_1654_q6srdR


Rails 5のWebSocket対応アプリでDoS脆弱性を見つけるまで(翻訳)

$
0
0

こんにちは、hachi8833です。今回はPhusion BlogのActionCableシリーズからの翻訳記事をお送りいたします。

文中のWebSocketとActionCableについては以下をご覧ください。

概要

原著者の許諾を得て翻訳・公開いたします。

Rails 5のWebSocket対応アプリでDoS脆弱性を見つけるまで(翻訳)

本記事では、Puma上で動作するRails 5.0.0のActionCableについて述べます。PumaはRailsアプリのデフォルトのサーバーですが、低速度のクライアントによるDoS(サービス拒否: denial of service)にさらされる可能性があります。筆者たちはOS Xのネットワークトラフィック絞り込みツールを用いて攻撃をシミュレートし、そこに潜む脆弱性を明らかにしました。

以前のActionCable記事では、筆者たちがActionCableでストレステストを実施していくつもの問題を発見し、解決するまでを解説しました。私たちが発見した問題点は、修正の後Railsにマージされました。今回は、ストレステスト実施で発見された別の問題点についての記事となります。

発見した問題点の1つは数か月前にRailsにマージされ、最近リリースされたRails 5.0.1に組み込まれています。なお、Passengerはこの問題の影響をこれまで受けていません。

「遅いクライアント」がDoSを引き起こすメカニズム

ここからしばらく、「遅いクライアント」の基本原理と、それによって生じる影響について解説します。遅いクライアント問題について既に十分ご存知でしたら、この「遅いクライアント」セクションをスキップして次の「ActionCableが遅いクライアントから保護されるかをテストする」まで進んでいただいてかまいません。次のセクションでは、PumaとPassengerでそれぞれ動くRailsアプリへの攻撃をシミュレートする方法と、その結果PumaだけがDoS攻撃に対して脆弱であることが判明するまでを解説します。

遅いクライアントの背後に潜む理論

Webアプリケーションにとっての「遅いクライアント」は、インターネット接続の帯域幅が狭いユーザーを指します。スマホから接続しているユーザーや遠隔地からアクセスするユーザーの場合、インターネットの接続性が低下することがあります。そして遅いクライアントの中には、意図的に帯域幅を制限することでアプリの妨害を目論む、悪意のある攻撃者が潜んでいる可能性もあります。

アプリをブロックする遅いクライアントには2つのタイプがありますが、サービスを信頼できるものにするにはその双方に対処する必要があります。遅いクライアントの対処だけで終わらせるのではなく、遅い速いにかかわらずアプリの全ユーザーを正しく扱わねばなりません。遅いクライアントはデータの送信に時間がかかり、データの受信にもやはり時間がかかります。

つまり、ネイティブに書かれたWebアプリでは、リクエストを処理するスレッドやプロセスがデータ受信を完了するまでに数秒、ひどいときには数分待たされることすらありえるわけです。その間、ビジネスロジックの実行やデータベースへのクエリも滞ってしまいます。その後も、アプリが作成したレスポンスをクライアント受信完了するまでにまたしても秒単位・分単位で待たされることになるでしょう。

遅いクライアントによる実際の影響

Ruby製アプリの多くは、同期的なリクエスト/レスポンスI/Oモデルをワーカープロセス(またはワーカースレッド)で採用しています。この方式は一部において「遅いクライアント問題」の影響を受けやすくなっています。

最悪のシナリオはおそらくこのようになるでしょう: 100個のワーカープロセスを使うよう設定されているアプリケーション・サーバーがあり、1秒あたり数千ものリクエストを処理する設計になっているとします。ここでたったひとりの攻撃者が、極めて狭い帯域から数百ものリクエストをサーバーに送信するとどうなるでしょう。100個のワーカースレッドの一つ一つが攻撃者からのリクエストを受信すると、遅延は数分にもおよび、他の(正当な)リクエストがキューにたまってしまいます。

たったひとりの攻撃者が送信したリクエストのために、最終的に全プロセスがビジー状態のまま分単位で滞ってしまいます。数千個におよぶリクエストがキューで待ちぼうけを食らったり、あふれて消えてしまったりします。攻撃者がさらに執拗にリクエストを繰り返せば、最小限のリソースでアプリをいつまでも遅延させられるでしょう。

スレッドはプロセスよりもずっと軽いので、多少なりともこの問題を緩和できます。スレッドを増やして対応することもできますが、根本的な問題は変わりません。攻撃者が遅いゴミリクエストの数をさらに増やす結果になるでしょう。

イベントベースのI/Oバッファシステムによる問題の緩和

実際のRubyアプリサーバーでは、遅いクライアント問題からの保護は既に十分なされています。これには、I/O並列性(concurrency)の高いイベントベースの(evented)I/Oバッファシステムが使われています。

イベントベースのI/Oバッファシステムは、Rubyアプリのさまざまなサーバーでどのように使われているのでしょうか。

  • Unicorn: nginxと合わせてデプロイされるのが普通です。nginxはイベントベースのI/Oを採用し、リバースプロキシとして動作します。遅い送信、遅い受信のどちらからも保護します。
  • Puma: イベントベースのI/Oマルチプレクサを内蔵しており、これ自体が送信の遅いクライアントを防ぎます。受信の遅いクライアントに対して保護できないため、本番デプロイではPumaをnginxの背後に配置することをおすすめします。

  • Passenger: イベントベースのI/Oバッファシステムを内蔵しており、nginxの有無にかかわらず、遅いクライアントによる送信と受信の両方から保護できます。

従来の「遅いクライアント」対策がWebSocketと組み合わさった場合に発生する特殊な問題

バッファリングリバースプロキシとしてのnginxには一箇所弱点があります。クライアントからのリクエスト受信が完了するまで、そのリクエストをアプリに送信開始できないのです。同様に、アプリからのレスポンス受信が完了するまでは、そのレスポンスをクライアントに送信開始できません。通常これが問題になることはありませんが、WebSocketと組み合わさった場合に問題が生じます。WebSocketフレームは即座にクライアントに送受信されなければならないので、nginxはWebSocketに逆らって動作するわけにいきません。

すなわち、WebSocket(とWebSocketをベースにしているActionCable)が動作するためにはnginxのI/Oバッファシステムをオフにするしかなくなり、必然的にnginxによる「遅いクライアント」からの保護もオフになってしまいます。ActionCableは受信WebSocketデータ向けに独自にイベントベースのI/Oマルチプレクサを提供することで、この問題を何とかして和らげようとします。つまりActionCableによる保護は、遅いWebSocketクライアントからの送信を対象としていますが、遅いWebSocketクライアントからの受信は対象に含まれていないのです。

次のセクションではこの問題の実例を示すと同時に、Passengerがこの問題の影響をまったく受けないことも示します。PassengerはI/Oをバッファリングでき、かつデータを即座にクライアントに送信できるので、送受信ともに遅いWebSocketクライアントからアプリを保護できるのです。

ActionCableが遅いクライアントから保護されるかをテストする

理論についてはこのぐらいにして、実際の結果を見てみましょう。Railsアプリが遅いクライアント攻撃に対して脆弱かどうかを確認するため、ちょっとしたツールをこしらえました。このツールは、ActionCableを使うあらゆる接続済みクライアントに対してデータを連続送信します。

上の画像は、アプリを起動して4つのクライアントが接続している様子を示しています。各クライアントには自身の現在の遅延が表示されます。古いMacBookで動かしたときの平均遅延はおよそ10msecでした。この遅延にはJSONオブジェクトのレンダリングや解析に要する時間も含めている点にご注意ください。通常のアプリよりも遅延がずっと大きくなっていますが、これはテスト用に遅延をかなり大きくしてあるためです。

アプリが正常に動作していることを確認できたので、テスト条件に切り替えられる状態になりました。遅いクライアントを1つ導入して「遅いクライアント」状態を再現し、速いクライアント(=通常のクライアント)でのレスポンスタイムの変化を観察します。速いクライアントのレスポンスタイムに変化がなければ、問題は発生していないことになります。ほかの速いクライアントのレスポンスタイムが著しく変化すれば、「遅いクライアント問題」が発生していると断定できます。

OS Xのネットワークトラフィック絞り込みツール

今どき56kモデムなど見当たりませんし、ここオランダのインターネット接続は大変残念なことに実に高速です。そういうわけで、遅いクライアントのネットワーク条件をシミュレートするよりありませんでした。今どきのOSにはたいていその種のツールがありますが、OS Xの場合はpfが使えます。

最初に以下を実行して、pfが現在有効かどうかを表示します。

sudo pfctl -s

無効な場合は、以下を実行して有効にします。

sudo pfctl -e

続いて以下のような感じでダミーネットを1つ作ります。

(cat /etc/pf.conf && echo "dummynet-anchor \"mop\"" && echo "anchor \"mop\"") | sudo pfctl -f -

このコマンドは既存の/etc/pf.confをダミーネットで拡張し、この設定でpfが動作するようにします。以下の2つのコマンドでいつでも元の設定にロールバックできます。

sudo dnctl flush
sudo pfctl -f /etc/pf.conf

次に、絞り込むネットワークを特定します。この例のクライアントは、ポート3000でWebサーバーに接続する4つのChromeインスタンスです。以下のコマンドで送信ポート番号を検索します。

sudo lsof -i -n -P | grep TCP | grep 3000

必要なポート番号をこのコマンドで確認できたら、その中からひとつを選び(ここでは58983)、以下のコマンドに貼り付けて実行します。

echo "dummynet in quick proto tcp from any to any port 58983 pipe 1" | sudo pfctl -a mop -f -

これで、このポートからの全データがダミーネットワークを経由します。いよいよトラフィックの絞り込みに取りかかりましょう。

sudo dnctl pipe 1 config bw 56kbit/s

トラフィック絞り込みは瞬時に有効になり、秒単位の遅延が発生します。

結果

まず、サーバーがデフォルトのままのRailsアプリからテストしてみました。以前のデフォルトサーバーはWEBrickでしたが、現在は本番により適したPuma Webサーバーに変わりました。以下でアプリを起動します。

./bin/rails s

遅いクライアントを導入した結果、そのクライアント自身のみならず、すべての通信相手(訳注: サーバーと全クライアント)で遅延が発生しました。つまりPumaは遅いクライアント問題に対して脆弱であり、リバースプロキシで保護しなければWebSocketをインターネット上に公開するにはふさわしくないということです。残念なことに、上述の「従来の遅いクライアント対策がWebSocketと組み合わさった場合に発生する特殊な問題」があるために、もっともおすすめのリバースプロキシであるnginxでは保護できません。

Passengerを使うRailアプリを起動して遅いクライアントを導入した場合、接続する全クライアントでの遅延は発生しませんでした。Passengerは、リバースプロキシによるバッファがなくても「遅いクライアント問題」に関してインターネット上で安全を保てます。

Passengerが遅いクライアントを扱う仕組み

Passengerは遅いクライアントに対抗するために、アプリとネットワークの間にバッファを配置します。このバッファはアプリからのデータを即座に受信し、クライアントの受信が遅いときにはメモリやディスク上にデータを保存します。これにより、アプリは超高速なクライアントと接続しているかのように動作できます。

PassegerはイベントベースのI/Oアーキテクチャを内蔵しており、スレッドやプロセスがバッファ内のデータストリームの処理で手一杯にならないようになっています。つまり、接続ごとのオーバーヘットがほとんどなく、遅いクライアントを問題なく扱えます。さらに、Passegerはバッファ中にもデータをただちにクライアントに送信するので、クライアント側に遅延を感じさせません。

まとめ

今回の実験では、簡単なデモアプリを作り、ネットワークトラフィックの絞り込みを使って遅いクライアントによる影響とをシミュレートし、ActionCableサーバーの信頼性をチェックしました。実験の結果、Puma上で動作するActionCableアプリには、遅いクライアントがコストの高いワーカープロセスやスレッドを立ち上げることによるDoSのリスクがありました。

同じアプリをPassenger上で動かした場合は、Passengerが外部へのデータをバッファするため、遅いクライアントの影響を受けませんでした。Pumaのフロントにnginxをリバースプロキシとして配置してもこの問題を解決できません。nginxのバッファシステムはWebSocketと共存できないためです。

この問題はPumaチームとRailsチームに報告され、その結果レスポンスバッファをRailsに実装していただきました。このパッチは、最近リリースされたRails 5.0.1にマージされました。

元記事をHacker Newsに投稿する

Passengerは、Rubyアプリ、Pythonアプリ、Node.jsアプリ、Meteorアプリ、マイクロサービス、APIを高い信頼性、高いパフォーマンス、高い制御性でサービス提供できるようにします。Enterprise Editionをお求めいただくことで、さらに多くの機能とプレミアムサポートをご利用いただけますので、ぜひご覧ください。

Phusion社の新製品であるUnion Stationは、Passenger上で動作するアプリを監視、分析します。Union Stationはアプリのパフォーマンスボトルネックやエラーの検出と修正を支援します。今すぐ登録して無料試用いただけます

週刊Railsウォッチ(20170407)N+1問題解決のトレードオフ、Capybaraのテスト効率を上げる5つのコツほか

$
0
0

こんにちは、hachi8833です。HackerNewsにあがってたHOW BANK OF AMERICA GAVE AWAY MY MONEYをつい読んでしまいました。いかにもコントやショートショートにありそうな話ですが、実際に起きるとめちゃ怖いですね。たぶんそのうちどこかの海外ネタサイトに載ると思います。

Ruby 2.1のサポート終了(Ruby公式ニュースより)

発表日が4/1だったので、すわエイプリルフールかと思いきや、1年前の予告どおりのスケジュールでの発表でした。

昨年 3 月末の Ruby 2.1.10 のリリース後、Ruby 2.1 系列はセキュリティメンテナンス期間に入っていましたが、予定の 1 年が経過しましたので、2017 年 3 月 31 日をもって、公式サポートを終了します。 以後、単なるバグの修正はもちろん、セキュリティ上の問題が発見された場合も、Ruby 2.1 系列については新たなリリースは行われません。 現在、Ruby 2.1 系列を使用中のユーザーは、速やかに、より新しいバージョン系列へと移行されるようお願いします。
Ruby 2.1 公式サポート終了より

メンテナンスポリシーはソフトウェアによってさまざまですが、Rubyの場合、ライフサイクルをどのように決定するかというはっきりした原則が今のところ見当たらないようです。一同でチェックしながら、現在のバージョンのライフサイクルをいつ終了させるかはコミュニティ任せなのかもしれない、という話題になりました。

Railsのアップグレードはそれなりに手がかかりますが、Railsで使われるRubyのアップグレードはスムーズにできるので、古いRailsでもRubyだけは最新にしている方も多いと思います。私も先日オレオレRails4アプリでRubyだけは2.4.1にアップグレードしましたが、Rubyが新しくなるたびにじわじわと速くなっているのを実感できます。

rails newから--javascriptオプションが削除される(Rails公式ニュースより)

JavaScript周りの変更にともない、Rails 5.1で--javascriptオプションが削除されます。

削除されたコード


railties/lib/rails/generators/app_base.rbより

従来Railsで使われていたJS系gemには「xxx-rails」といった名前のものがよく目につきますが、Rails 5.1でyarn管理に移行した後、「xxx-rails」系gemの多くがdeprecatedになるのかもしれないといった話題になりました。

driven_byがオーバーライド可能に(Rails公式ニュースより)

Rails 5.1への導入が決まっているCapybaraを活用した新しいブラウザ表示テスト用クラスであるSystemTestCaseで、テスト中に動的にseleniumやrack_testといったドライバを#driven_byで切り替えられるようになりました。

# https://github.com/rails/rails/pull/28586より
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  driven_by :selenium, using: :chrome, screen_size: [1400, 1400], options: {url: "http://chrome:4444/wd/hub"}
end

class WithJavascriptTest < ApplicationSystemTestCase
end

class WithoutJavascriptTest < ApplicationSystemTestCase
  driven_by :rack_test  # ドライバの切り替え
end

複数ブラウザの表示とJavaScriptのテストはどうしても面倒になりがちですが、ブラウザ間の挙動の違いをSystemTestCaseでテストするのが少し楽になりそうです。

reverse_mergeのエイリアスwith_defaultが追加(Rails公式ニュースより)

Railsウォッチ(20170317)でお伝えしたreverse_mergereverse_merge!の追加(#28355)の続きです。

メソッド名にエイリアスを追加しただけというシンプルな内容ですが、reverse_mergeという生々しすぎて挙動を推測しづらいメソッド名に、strong_parametersにデフォルト値を追加できることがはっきりわかるwith_defaultsというエイリアスを追加したことで使いやすくなりました。

alias_method :with_defaults, :reverse_merge
...
alias_method :with_defaults!, :reverse_merge!

「こういう改修って大事だよね!」という声多数でした。名前は大事。

rails-ujs.jsのUMDモジュールサポートを修正(Rails公式ニュースより)

従来のjquery-ujsがjQuery依存脱却にともないrails-ujsと名称が変更されましたが、それに伴いUMDが使えなくなっていた問題が修正されました。今回知りましたが、UMD(Universal Module Definition)はクライアントとサーバーの両方で動作するJavaScriptモジュール定義のことでした。

jquery-ujsはRuby on Rails用に開発されたjQueryライブラリですが、jquery-railsと違い最新のjQueryを取ってくることまではしません。今に始まった話ではありませんが、JavaScript周りはgem名もいろいろあってわかりにくいですね。

参考

N+1問題とメモリの問題を同時に解決できない理由(RubyWeeklyより)

N+1問題についての解説記事です。よい感じです。

記事をチェックしていて、「bullet gemに怒られそうだけど、確かにN+1問題はどんな場合にもバッドプラクティスというわけではないよね」という話題になりました。元記事でも、N+1問題を解決したために、使いもしないオブジェクトが大量に生成されてメモリを逆に圧迫してしまう問題と、対処の方法について触れられています。

たとえば巨大なデータを扱うバッチ処理を小分けにするといった状況では、N+1問題を修正することが必ずしも善とは限らないことを教わりました。

Rubyの一部のコードにシンボル処理を追加して高速化(RubyWeeklyより)

Rubyのパフォーマンス測定とパッチ当ての話なのでC言語が中心です。一同でチェックしていて「よく見つけたなこれ」と驚きの声が上がりました。


Making Ruby’s Array.include? faster for symbolsより

最終的に、vm_insnhelper.cでシンボルがまったく処理されていないことを突き止め、以下のように修正しています。


Making Ruby’s Array.include? faster for symbolsより

その後修正は一発でmergeされました。殊勲賞あげたくなっちゃいました。

同記事で参照しているRuby C APIガイドもよい資料ですね。

ポルトガルのRubyカンファレンスは今年は休み(RubyWeeklyより)

「今年で終わりではないが、ポルトガルでのコミュニティが今ひとつ盛り上がってないから」だそうです。ざっとググってみたところ、Rubyはポルトガル語圏ではそれほど盛り上がっていないようです。

Rubyのサブクラスをregistryに変更してリファクタリング(RubyWeeklyより)

これもよい感じですね。一同でチェックしていて「継承より委譲」という言葉が出てきました。

その後、「学校や教科書でオブジェクト指向を教えるときに、最初に継承を教えるのはあまりよくないかも」「実際に使うのはコンポジションとかの方が多いし」「他の項目との関連もあるから、継承を先に教えるのはある程度仕方がないけどね」などと話題が広がりました。

Mobility: 多言語の訳文をクラスの属性として利用できるgem(RubyWeeklyより)

Railsウォッチ(20170217)でご紹介したsvenfuchs/i18n-active_recordと同様の、多言語対応gemです。

# https://github.com/shioyama/mobility より
class Word < ApplicationRecord
  include Mobility
  translates :name,    type: :string
  translates :meaning, type: :text
end

こんなふうに多言語をActiveRecordの属性として扱えるようになります。脱yamlの多言語化の流れが加速していることを実感します。

word = Word.first
I18n.locale = :en
word.name
#=> "mobility"
word.meaning
#=> "(noun): quality of being changeable, adaptable or versatile"
I18n.locale = :ja
word.name
#=> "モビリティ"
word.meaning
#=> "(名詞):動きやすさ、可動性"

Mobilityのチェック中に、Webチームリーダーのmorimorihogeさんが「これ、同じようなのを随分前にbabaさんと作ったことある!」と、その場で社内GitLabのコードを見せてくれました。

BPSが長年運営している漫画翻訳サイト「MANGA REBORN」はRuby on Railsで構築されていますが、当初から2人によってがっつりと多言語化を意識した設計がなされていることを、私たちもその場で初めて知りました、マジで。

MANGA REBORNのマスコットキャラクター「マリちゃん」です

見せてもらったコードでは、漫画の日本語/英語タイトルといった属性を多言語コンシャスでモデル設計に盛り込み、かつ#__send__などのメタプログラミングを駆使したコードになっていて、予想より遥かにシンプルでした。

morimorihogeさんいわく「当時ActiveRecordを知って『Railsってこんなことできるんだー!』と感激した勢いで作ったのを思い出した」そうです。

5年以上も前にRailsアプリでここまで多言語化を追求していたとは…自称多言語厨の私も脱帽でした。

deep_pluck: ネストした関連付けでもeager loadingせずに#pluckを使えるgem

Railsのカラムを配列で取るのに多用される#pluckを、ネストした関連付けでeager-loadingせずに行えます。パフォーマンスもいいですね。

# https://github.com/khiav223577/deep_pluck より
User.where(:name => %w(Pearl Kathenrie)).deep_pluck(
  :name,
  :email,
  'posts' => [:name, 'post_comments' => :comment],
  'contact' => :address,
)

これは知っていて損はないgemですね。今さらですがpluckは「むしる、つまみ取る」という動詞です。

LittleBoxes: 依存性注入gem


https://github.com/manuelmorales/little-boxesより

Rubyで依存性注入を行うフレームワークだそうです。

# https://github.com/manuelmorales/little-boxes より
module MyApp
  class MainBox
    include LittleBoxes::Box

    let(:port) { 80 }            # 注入
    letc(:server) { Server.new } # 依存関係を指定して注入
  end

  class Server
    include LittleBoxes::Configurable

    dependency :port
  end
end

box = MyApp::MainBox.new
# => #<MyApp::MainBox :port, :server>

box.server.port
# => 80

「JavaならわかるけどRubyで使うところあるかな?」という意見もありました。Javaではロガーを書くときなどにこうした依存性注入がよく使われるそうです。ご興味のある方はどうぞ。

geared_pagination: ページに応じて項目数が可変になるページネーションgem

1ページ目は30件、2ページ目は50件というふうにページによって件数を変えるgemだそうです。ネーミングからして、自動車などの変速ギヤのイメージですね。

「こういうのより、下までスクロールしたら件数が追加される方がいいと思う」「でもスマホだと一番下の [PC版に切り替える] がどんどん逃げて押せないサイトあるよねw」といった話題になりました。

ご興味のある方はどうぞ。

Capybaraのテスト効率を上げる5つのコツ(RubyFlowより)

短いですが良い記事です。CapybaraやRSpecはチートシートが無味乾燥だったりマッチャを覚えるのが面倒だったりするので、こういう具体的なノウハウ集は助かりますね。

ただ、JavaScriptのイベント完了を待つ手法は「それはちょっとなー」と一斉に声が上がりました。

Pluto: RSSフィードから静的Webサイトを生成するgem(RubyFlowより)


http://feedreader.github.io/より

少々変わり種のgemです。RSSフィードから静的なWebページを生成します。ERBを書いてmiddlemanばりのカスタマイズもできます。

# http://feedreader.github.io/ より
<% site.feeds.each do |feed| %>
  <%= feed.url %>  or  <%= feed.link %>
  <%= feed.title %>  or  <%= feed.name %>
  <%= feed.title2 %>
  <%= feed.feed_url %>  or  <%= feed.feed %>
<% end %>

どこかでちょっとした使いみちがありそうな気がします。過去ブログのアーカイブ化やデジタルサイネージなどに使いみちあるかもという意見がありました。

tty-progressbar: CLIプログレスバーgem(RubyFlowより)

RubyでCLIのプログレスバーを作るgemです。今どきらしく色をつけるなどのカスタマイズがいろいろできます。

Piotrrrrr
downloading [======================= ]

MathJaxのCDNが今月いっぱいで閉鎖


https://en.wikipedia.org/wiki/MathJaxより

たまたま見つけました。数式表示用JavaScriptライブラリMathJaxのCDNが閉鎖されます。代替としてcdnjs.comを推奨しています。

数式表示にMathJax CDNを使っている有名ブログサイトはあちこちにあった気がするので要注意ですね。

Jupyter 5.0リリース(HackerNewsより)

ブラウザ上でコードを実行・記録可能なドキュメントシステムとして、機械学習系を中心に急速に人気が高まっているJupyterのバージョン5.0がリリースされました。もともとPython用でしたが、あっという間にRubyを始めさまざまな言語に対応しました。

  • ファイルリストを画面上でソート
  • キーボードショートカットのカスタマイズ
  • Cell tag機能で、コード中のプレースホルダに画面から値を入力・実行できる↓


Jupyter Notebook 5.0より

研究以外にも、社内研修、教育、プレゼンなどに役に立つツールだと思います。

C言語1000行足らずでシンタックスハイライト付きのテキストエディタ書いたった(HackerNewsより)

手元でgit cloneしてmakeしたらちゃんと動きました。終了はCtrl-Q連打で。

それだけならよくあるサイトなのですが、本人によるソースコード解説がものすごく詳細なのに驚きました。コード量より文章量の方が明らかに多いですね。凄い。

Twitter Lite発表(HackerNewsより)

https://lite.twitter.com/でPCでもモバイルでも使えます。
確かに従来より軽快です。これいいですね。

教師なし感情ニューロンを開発(HackerNewsより)

これはマジで驚きです。Amazonレビューの文章をサンプルに用いて、次にどんな文字が来るかを感情ニューロンに予測させる研究中に、レビューに含まれる書き手の感情(ポジティブまたはネガティブ)を高い精度で検出できたのだそうです。教師なし(unsupervised)なので、人間が手取り足取り指示しなくてもデータを食わせるだけで違いを学習したということですね。


https://blog.openai.com/unsupervised-sentiment-neuron/より

以下のように、好意的な文を緑、否定的な文を赤でハイライトするアニメーションGIFも掲載されています。


https://blog.openai.com/unsupervised-sentiment-neuron/より

機械学習系の成果でよくあることですが、これも原理はまだよくわかっていないようです。
こういう機能が近々Rubygemなどで利用できるようになるかもしれません。

小ネタ: forty_twoドキュメントの引用が間違っていた(Rails公式ニュースより)

Silly method gets a silly doc fix,

「アホなメソッド名でアホなドキュメント修正」だそうです。fourty twoというメソッド名については以下をどうぞ。

参考: RailsのArray拡張にforty_twoがいる謎

今週は以上です。

関連記事

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやRSSなど)です。

Ruby 公式ニュース

Rails公式ニュース

Ruby Weekly

RubyFlow

160928_1638_XvIP4h

Hacker News

160928_1654_q6srdR

[Rails 5][Rails 4] ‘link_to’ APIドキュメント完全翻訳

$
0
0

こんにちは、hachi8833です。Rails 4と5の#link_toメソッドのAPIドキュメントを翻訳いたしました。

概要

Rails 3、4、5のform_forメソッドを最新の公式APIドキュメントでチェックしてみたところ、現時点でのRails 4とRails 5向けform_forメソッドのAPIドキュメントは完全に一致していました。

また、Rails 3のAPIオプション数はRails 4/5よりも少なくなっています。

Rails 3の#link_toAPIドキュメントについては以下をご覧ください。

link_to APIドキュメント

# API呼び出し(Rails 4、Rails 5共通)
link_to(name = nil, options = nil, html_options = nil, &block)

一連のオプションで作成されたURLと、指定のリンク名を使って、アンカー要素を作成します。正しいオプションについては、url_forのドキュメントを参照してください。

optionsハッシュの代わりにStringも渡せます。その場合は、Stringの値をリンクのhrefとしてanchor要素を生成します。

optionsハッシュの代わりに:backというシンボルを渡すと、リファラへのリンクを生成します。該当のリンク先がない場合は、JavaScriptによってリファラの代わりにバック用リンクが使われます。

namenilを渡すと、リンク自身の値がリンク名として使われます。

シグネチャ

link_to(body, url, html_options = {})
  # url はString(posts_pathなどのURLヘルパーメソッドも使える)

link_to(body, url_options = {}, html_options = {})
  # url_options(ただし:methodは除く)はurl_forに渡される

link_to(options = {}, html_options = {}) do
  # name
end

link_to(url, html_options = {}) do
  # name
end

オプション

data:
カスタムのdata-属性を渡すのに使います。
method:
HTTP verbをシンボルで渡します。この修飾子はHTMLフォームを動的に生成し、指定のHTTP verbで即座にフォームを処理のために送信します。
このオプションは、リンクをクリックしたときにレコード削除などの危険なアクション(検索ボットがサイトからこうしたリンクを探り当てることがあります)をPOST操作で行わせたいときに便利です。
サポートされている操作は:post:delete:patch:putです。ただし、ユーザーがJavaScriptを無効にしている場合は動作がフォールバックし、リクエストはGETで行われます。
href: '#'を指定すると、ユーザーがJavaScriptを無効にしている場合のリンクは無効になります。
アプリがPOSTの動作に依存している場合は、コントローラのアクションで#post?#delete?#patch?#put?を使ってリクエストオブジェクトのメソッドがどれであるかを確認する必要があります。
remote:
trueを指定すると、リクエストをURLに送信する際にリンクをたどるのではなく、Unobtrusive JavaScript(控えめなJavaScript)ドライバを使ってAjaxリクエストを送信します。ドライバはAjaxリクエストの完了をリッスンし、完了後にJavaScript操作を実行しますが、そのメカニズムはドライバの実装に依存します。

data属性に与えられるオプション

confirm: '質問文'
このオプションを指定すると、ユーザーへの問い合わせメッセージ(この場合は「質問文」)をUnobtrusive JavaScriptドライバで表示できるようになります。ユーザーがこれを了承するとリンクは通常どおりに実行され、了承しない場合は何も実行されません。
:disable_with
このパラメータの値は、フォーム送信時の送信ボタンが無効になっているときの送信ボタン名として使われます。この機能はUnobtrusive JavaScriptドライバによって提供されます。

#link_toの動作は#url_forに依存しています。これにより、#link_toでは従来の「コントローラ/アクション/id」スタイルの引数と、新しいRESTfulルーティングのどちらも利用できます。現在のRailsでは可能な限りRESTfulルーティングスタイルを使うことが推奨されていますので、アプリのルーティングをリソースベースで行っている場合は、次のように書けます。

link_to "Profile", profile_path(@profile)
# => <a href="/profiles/1">Profile</a>

次のようにもっと簡潔に書くこともできます。

link_to "Profile", @profile
# => <a href="/profiles/1">Profile</a>

冗長になりますが、以下のようにリソース指向でない従来の方法で書くこともできます。

link_to "Profile", controller: "profiles", action: "show", id: @profile
# => <a href="/profiles/show/1">Profile</a>

同様に、以下の書き方もおすすめです。

link_to "Profiles", profiles_path
# => <a href="/profiles">Profiles</a>

以下は上よりも冗長であり、おすすめしません。

link_to "Profiles", controller: "profiles"
# => <a href="/profiles">Profiles</a>

リンク先がnameパラメータに合わせにくい場合は、次のようにブロックも使えます。ERBの例を示します。

<%= link_to(@profile) do %>
  <strong><%= @profile.name %></strong> -- <span>Check it out!</span>
<% end %>
# => <a href="/profiles/1">
       <strong>David</strong> -- <span>Check it out!</span>
     </a>

CSSのidやclassも以下のように簡単に生成できます。

link_to "Articles", articles_path, id: "news", class: "article"
# => <a href="/articles" class="article" id="news">Articles</a>

RESTfulでない従来のスタイルの場合は、以下のようにリテラルハッシュが必要です。

link_to "Articles", { controller: "articles" }, id: "news", class: "article"
# => <a href="/articles" class="article" id="news">Articles</a>

リテラルハッシュがないと、次のような誤ったリンクが生成されます。

link_to "WRONG!", controller: "articles", id: "news", class: "article"
# => <a href="/articles/index/news?class=article">WRONG!</a>

#link_toでは、anchor:でリンクにアンカーを追加したり、query:でリンクにクエリ文字列を追加することもできます。

link_to "Comment wall", profile_path(@profile, anchor: "wall")
# => <a href="/profiles/1#wall">Comment wall</a>

link_to "Ruby on Rails search", controller: "searches", query: "ruby on rails"
# => <a href="/searches?query=ruby+on+rails">Ruby on Rails search</a>

link_to "Nonsense search", searches_path(foo: "bar", baz: "quux")
# => <a href="/searches?foo=bar&baz=quux">Nonsense search</a>

link_to (:method)固有のオプション(:post:delete:patch:put)は以下のような方法でのみ使えます。

link_to("Destroy", "http://www.example.com", method: :delete)
# => <a href='http://www.example.com' rel="nofollow" data-method="delete">Destroy</a>

data:オプションを使って、カスタムのデータ属性を渡すこともできます。

link_to "Visit Other Site", "http://www.rubyonrails.org/", data: { confirm: "Are you sure?" }
# => <a href="http://www.rubyonrails.org/" data-confirm="Are you sure?">Visit Other Site</a>

他にも、target:rel:type:などで任意のリンク属性を指定することもできます。

link_to "External link", "http://www.rubyonrails.org/", target: "_blank", rel: "nofollow"
# => <a href="http://www.rubyonrails.org/" target="_blank" rel="nofollow">External link</a>

関連記事

Rails 4、Rails 5

Rails 3

[Rails 5] モデルの継承元がActiveRecord::BaseからApplicationRecordに変更された

$
0
0

こんにちは、hachi8833です。BigBinaryシリーズのRails 5翻訳記事をお送りいたします。

元記事

訳文ではバージョンやリンクなどを現時点の内容に更新しています。

確認に使った環境

モデルの継承元がActiveRecord::BaseからApplicationRecordに変更された(翻訳)

2016年6月にRails 5.0.0がリリースされました。重要な変更点のひとつとして、5.0.0以降ではすべてのモデルがApplicationRecordを継承するようになった点が挙げられます。

class Article < ActiveRecord::Base # Rails 4
end

class Article < ApplicationRecord  # Rails 5
end

ActiveRecord::Baseはどのように変わったのでしょうか?

実際にはさほど大きな変更はありません。Rails 5アプリでは、以下のapplication_record.rbファイルが自動的にapp/models/ディレクトリに追加されます。

# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
end

※この仕組みは、各コントローラがActionController::BaseではなくApplicationControllerを継承しているのと似ています↓。

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
end

# 各コントローラ
class ArticlesController < ApplicationController
  ...
end

Rails 5アプリで必要となるあらゆるカスタマイズや拡張は、このApplicationRecordに対して行うことになります。

たとえば、Active Recordに何か機能を追加したいとしましょう。Rails 4.2ではたとえば以下のようにします。

module MyAwesomeFeature
  def do_something_great
    puts "なんかめんどくさいことをする"
  end
end

ActiveRecord::Base.include(MyAwesomeFeature)

しかしこの方法では、ActiveRecord::BaseMyAwesomeFeatureが常にインクルードされてしまうので、ActiveRecord::Baseを継承するすべてのクラスで、必要の有無にかかわらずMyAwesomeFeatureがインクルードされてしまいます。

特に、Railsアプリでプラグインやエンジン(mountable engine)を使っていると、ActiveRecord::Baseに適用したモンキーパッチがこうしたプラグインやエンジンにまで影響してしまうことがあります。

Rails 5でApplicationRecordが導入されたことにより、ApplicationRecordを継承するモデルに対してのみカスタマイズを加えられるようになりました。これにより、(プラグインやエンジンを除外して)カスタマイズをRailsアプリのみに限定できます。

class ApplicationRecord < ActiveRecord::Base
  include MyAwesomeFeature

  self.abstract_class = true
end

Rails 4からの移行

Rails 5アプリでは、デフォルトでapplication_record.rbファイルがmodelsディレクトリに置かれます。

Rails 4アプリをRails 5に移行する場合は、以下の内容でapplication_record.rbファイルを作成し、さらにすべてのモデルで継承元をActiveRecord::BaseからApplicationRecordに変更します。

# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
end

関連記事(Rail 5新機能)

[Devise How-To]ユーザー登録ページへのルーティングをカスタマイズする(翻訳)

$
0
0

こんにちは、hachi8833です。Devise How-Toシリーズ、本日2本めです。

概要

原文の更新や誤りにお気づきの場合は、ぜひ@techrachoまでお知らせください。更新いたします。

[How-To] ユーザー登録ページへのルーティングをカスタマイズする(翻訳)

Customer::PrivateCustomer::Publicという2つのDeviseユーザーモデルがあるとします。

models/customer/private.rb

models/customer/public.rb

コントローラの設定

それぞれのユーザーのアクションについてスコープを設定するために、以下のコントローラを作成します。

# app/controllers/customer/private/registrations_controller.rb
class Customer
  class Private
    class RegistrationsController < Devise::RegistrationsController
    end
  end
end

ビューの設定

以下のような2つのビューを作成する必要があります。

views/customer/private/registrations/new.html.haml

views/customer/public/registrations/new.html.haml

You will likely want to have a _form.html.haml partial for each.

それぞれのビューには_form.html.hamlというパーシャル(部分テンプレート)があるとします。

Deviseの登録ルーティングを設定する

ルーティングファイル(conf/routes.rb)に以下を記述します。

  devise_for :private_customers, :class_name => 'Customer::Private', :controllers => {:registrations => "customer/
private/registrations", :sessions => 'main' } do
    get   "private_customer/sign_up" => "customer/private/registrations#new", :as => :private_customer_signup
    get   "private_customer/sign_in" => "main#index", :as => :private_customer_signin
  end

上はprivate_customers用です。public_customersについても同じように設定します。

あとはビューにlink_to 'register', private_customer_signup_pathと記述すればビューで使えるようになります。

関連記事(Devise)

[Devise How-To] OmniAuth: 概要(翻訳)

$
0
0

こんにちは、hachi8833です。Devise How-TO翻訳シリーズは、需要の多いOmniAuth関連を当面優先してお送りいたします。どうぞよろしくお願いします。

DeviseでOmniAuthを使うことで、Facebook認証、Twitter認証、Googleアカウント認証、GitHubアカウント認証などを導入できます。本翻訳記事の第一弾は、その基礎にあたる部分の解説です。

概要

読みやすさのため、config.omniauthのコードを途中で改行しています。

原文の更新や誤りにお気づきの場合は、ぜひ@techrachoまでお知らせください。更新いたします。

OmniAuth: 概要(翻訳)

Deviseはバージョン1.2からOmniAuthとの統合をサポートしています。このWikiページでは「とあるOAuthプロバイダ」を例に使って連携の基本を解説します。

Deviseバージョン1.5はOmniAuth 1.0以降をサポートするため、本チュートリアルではこれを用います。

始める前に

アプリケーションにはconfig.omniauthによってomniauth providerミドルウェアが追加されることを理解しておいてください。つまり、このproviderミドルウェアをconfig/initializers/omniauth.rbに再度追加してはいけないということです。もし再追加すると両方ともクラッシュし、認証が常に失敗してしまいます。

Facebookの例

最初にアプリにOmniAuth gemを追加します。この場合はGemfileに以下のように記述します。

gem 'omniauth-facebook'

ここではFacebookを例として使いますが、他のどんなOmniAuth gemを使ってもかまいません。gem名は一般に「omniauth-プロバイダ名」の形式になっています。プロバイダ名はfacebookだったりtwitterだったりします。完全なプロバイダリストについてはList of Strategiesをご覧ください。

次に、Userモデルにproviderカラム(文字列)とuidカラム(文字列)を追加します。

rails g migration AddOmniauthToUsers provider:string uid:string
rake db:migrate

次に、使うプロバイダをconfig/initializers/devise.rbで宣言する必要があります。

config.omniauth :facebook,
                "APP_ID",
                "APP_SECRET"

注意: v2.0.1にはコールバックURLエラーの問題があります。回避するには、configにcallback_urlを追加する必要があります。

config.omniauth :facebook,
                "APP_ID",
                "APP_SECRET",
                callback_url: "CALLBACK_URL"

上のAPP_IDAPP_SECRETは、アプリのidとsecret(秘密情報)に置き換えます。CALLBACK_URLhttps://myapp.com/users/auth/facebook/callbackのような形式にする必要があります。

パーミッションやリクエストされたスコープを変更するには、the omniauth-facebook gemのREADMEをご覧ください。

strategyの設定が終わったら、モデル(app/models/user.rbなど)に以下を記述してOmniAuthを有効にします。

devise :omniauthable, :omniauth_providers => [:facebook]

注意: Railsサーバーを実行している場合は、Deviseイニシャライザに:omniauthableを追記するなどして変更した後にRailsを再起動しないとエラーになります。

現時点では、Deviseでomniauthableにできるモデルは1つだけです。OmniAuthで複数のモデルを使いたい場合は、OmniAuth with multiple modelsをご覧ください。

Userモデルをomniauthableにすると、config/routes.rbにdevise_for :usersを記述することで以下のURLメソッドがDeviseによって作成されます。

  • user_omniauth_authorize_path(provider)
  • user_omniauth_callback_path(provider)

Deviseは*_urlというメソッドは作成しません(そのディレクトリより上でコールバックヘルパーを使うことはありえないため)。

Facebookによる認証を提供するには、レイアウトに上の1つ目のメソッドを以下のように追加します。

<%= link_to "Sign in with Facebook", user_facebook_omniauth_authorize_path %>

注意: user_omniauth_authorize_pathメソッドに渡されるシンボルは、Deviseのconfigブロックに渡されるプロバイダのシンボルと一致します。どのシンボルを渡せばよいかについては、omniauth-*のREADMEをご覧ください。

上のリンクをクリックすると、ユーザーはFacebookにリダイレクトされます(リンクが表示されていない場合は、サーバーを再起動してください)。

Facebook側で認証情報が挿入されると、再度リダイレクトしてアプリケーションのコールバックメソッドに戻ります。コールバックを実装する場合に最初に必要なのは、config/routes.rbを開いて、Omniauthコールバックを実装するコールバックをDeviseに伝えます。

devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }

次は、以下のコードのみを含むapp/controllers/users/omniauth_callbacks_controller.rbを追加します。

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
end

実装するコールバックのアクションは、プロバイダと同じ名前にする必要があります。facebookプロバイダのアクションの場合、たとえば以下のようなコードをコントローラに追加できます。

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def facebook
    # 以下のメソッドをモデル(app/models/user.rbなど)で実装する必要がある
    @user = User.from_omniauth(request.env["omniauth.auth"])

    if @user.persisted?
      sign_in_and_redirect @user, :event => :authentication #@userが無効だと例外がスローされる
      set_flash_message(:notice, :success, :kind => "Facebook") if is_navigational_format?
    else
      session["devise.facebook_data"] = request.env["omniauth.auth"]
      redirect_to new_user_registration_url
    end
  end

  def failure
    redirect_to root_path
  end
end

このアクションについていくつか補足します。

  1. FacebookからOmniAuthで取得したすべての情報はrequest.env["omniauth.auth"]のハッシュでアクセスできます。取得できる情報の種類については、OmniAuthのドキュメントとと各gem(Facebookの場合はomniauth-facebook)のREADMEをご覧ください。
  2. 有効なユーザーが見つかると、Deviseのsign_inメソッドまたはsign_in_and_redirectメソッドでサインインできます。:event => :authenticationはオプションであり、Wardenのコールバックを使う場合にのみ渡す必要があります。

  3. Deviseのデフォルトのメッセージの中からflashメッセージに流用することも、使わないこともできます。

  4. ユーザーがサイトから離れたら、OmniAuthのデータをセッションに保存します。データの保存では、devise.が名前空間のキーとして使われます。これにより、ユーザーがサインインに使ったセッションからdevise.で始まるすべてのデータがDeviseによって削除され、セッションが自動的にクリーンアップされます。最後に、ユーザーを元の登録フォームページにリダイレクトします。

コントローラを定義したら、モデル(app/models/user.rbなど)で以下のようにfrom_omniauthメソッドを実装します。

def self.from_omniauth(auth)
  where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
    user.email = auth.info.email
    user.password = Devise.friendly_token[0,20]
    user.name = auth.info.name   # Userモデルにnameカラムがあるとする
    user.image = auth.info.image # Userモデルにimageカラムがあるとする
    # Deviseのconfirmableを使い、かつプロバイダがメールのバリデーションを行っている場合は、
    # 以下の行のコメントを解除して確認メールをスキップする
    # user.skip_confirmation!
  end
end

このメソッドは、providerフィールドとuidフィールドを指定して既存のユーザーを検索します。ユーザーが見つからない場合は、ランダムなパスワードなどの情報を追加して新しいユーザーを作成します。

なお、first_or_createメソッドを使うと、ユーザー作成時にproviderフィールドとuidフィールドが自動的に作成されることにご注意ください。first_or_create!メソッドも基本的に同じですが、ユーザーレコードのバリデーションに失敗するとExceptionがraiseされる点が異なります。

訳注: first_or_createメソッドやfirst_or_create!メソッドは以下のようにActiveRecord::Queryingdelegateで実装されていますが、APIドキュメントはありません。

# https://github.com/rails/rails/blob/5-1-stable/activerecord/lib/active_record/querying.rb#L5 より
module ActiveRecord
  module Querying
    ...
    delegate :first_or_create, :first_or_create!, :first_or_initialize, to: :all

また、DeviseのRegistrationsControllerはリソースをビルドする前にデフォルトでUser.new_with_sessionを呼び出します。つまり、ユーザー登録(サインアップ)前にユーザーが初期化された場合にセッションのデータをコピーする必要がある場合は、モデルでnew_with_sessionのみを実装すればよいということです。

以下のコード例では、Facebookのメールが取得できる場合にメールをコピーします。

class User < ApplicationRecord
  def self.new_with_session(params, session)
    super.tap do |user|
      if data = session["devise.facebook_data"] && session["devise.facebook_data"]["extra"]["raw_info"]
        user.email = data["email"] if user.email.blank?
      end
    end
  end
end

最後に、ユーザーがFacebookでのユーザー登録(サインアップ)をキャンセルできるようにするには、ユーザーをcancel_user_registration_pathにリダイレクトするようにします。これにより、Deviseが開始したセッションデータはすべて削除され、上のnew_with_sessionフックがその後呼ばれることはなくなります。

ログアウト用リンク

# config/routes.rb
devise_scope :user do
  delete 'sign_out', :to => 'devise/sessions#destroy', :as => :destroy_user_session
end

上のコードだけでできます。動作がうまく統合されるようになったら、以下を参考に統合テストを書きましょう。

OmniAuthだけで認証する

他の認証方法を使わずOmniAuthだけで認証する場合、new_user_sessionという名前のルーティングを定義する必要があります(未定義の場合はrootが使われます)。

この場合のルーティング例を以下に示します。OmniAuthでデータベースや他の認証方法を併用している場合、以下は不要です。

devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }

devise_scope :user do
  get 'sign_in', :to => 'devise/sessions#new', :as => :new_user_session
  get 'sign_out', :to => 'devise/sessions#destroy', :as => :destroy_user_session
end

上のルーティングを使う場合、sessionsコントローラには何も処理を追加しません。たとえばプロバイダの認証リンクを表示できればよいのであれば、これで十分です。

注意: Deviseでsign_out_via = :deleteを設定している場合は、sign_outルーティングがDELETEリクエストにマッチするようコードの修正が必要になることがあります。

また、:database_authenticatableを使っていない場合は、以下のようにnew_session_path(scope)ヘルパーメソッドを定義して、失敗した場合に正しくリダイレクトする必要があります。

class ApplicationController < ActionController::Base
# ...
  def new_session_path(scope)
    new_user_session_path
  end
end

トラブルシューティング

ユーザーがメールアドレスの利用を許可していなかった場合

Facebookの新しいログインダイアログでは、ユーザーはメールアドレスの入力を拒否できるようになっています。通常、Deviseの登録ではメールが必要になります。こういう場合の手っ取り早い解決方法は、メールアドレスがない場合は再度リクエストするというものです。

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def facebook
    if request.env["omniauth.auth"].info.email.blank?
      redirect_to "/users/auth/facebook?auth_type=rerequest&scope=email"
    end
  end
end

Facebookから返されたメールアドレスが空になっている場合

2015年7月8日以降、FacebookはAPIをv2.4に更新しました。このため、emailフィールドにアクセスするにはinfo_fieldsの追加が必要です。

config.omniauth :facebook,
                "APP_ID",
                "APP_SECRET",
                scope: 'email',
                info_fields: 'email,name'

参考: @techmonsterによる解決方法

OpenSSLエラー

以下のようなOpenSSLエラーが発生することがあります。

OpenSSL::SSL::SSLError (SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed):

この場合、CA証明書ファイルの保存場所をOmniAuthに対して明示的に指定する必要があります。これには、certified gemを使う方法と、以下の方法があります。後者は実行しているOS環境によって異なります。

config.omniauth :facebook,
                "APP_ID",
                "APP_SECRET",
                :client_options => {:ssl => {:ca_path => '/etc/ssl/certs'}}

Herokuの場合、CAファイルは/usr/lib/ssl/certs/ca-certificates.crtに保存されます。

Engine Yard Cloud serversの場合、CAファイルは/etc/ssl/certs/ca-certificates.crtに保存されます。

証明書の設定には、:ca_fileキーを使います。

config.omniauth :facebook,
                "APP_ID",
                "APP_SECRET",
                :client_options => {:ssl => {:ca_file => '/usr/lib/ssl/certs/ca-certificates.crt'}}

OAuth gemを使うstrategy(omniauth-oauthなど)を利用する場合、次の方法で証明書ファイルを指定します。

config.omniauth :facebook,
                "APP_ID",
                "APP_SECRET",
                :client_options => {:ca_file => '/usr/lib/ssl/certs/ca-certificates.crt'}

Mac OS Xでは証明書はファイルシステムではなくMacのキーチェーンアプリに保存されるので、Mac OS上での開発中に限れば、以下のように単に証明書の検証を無効にするのが最も簡単です。

require "omniauth-facebook"
config.omniauth :facebook,
                "APP_ID",
                "APP_SECRET",
                :client_options => { :ssl => { :verify => !Rails.env.development? } }

このエラーの詳しい議論については、#260をご覧ください。

strategyクラスを読み込めない場合

Deviseが何らかの理由でアプリのstrategyクラスを読み込めない場合、以下のように:strategy_classオプションで明示的に指定できます。

config.omniauth :facebook,
                "APP_ID",
                "APP_SECRET",
                :strategy_class => OmniAuth::Strategies::Facebook

関連記事(Devise)

[Devise How-To] OmniAuth認証を複数のモデルで共用する方法(翻訳)

$
0
0

こんにちは、hachi8833です。引き続きDevise How-ToのOmniAuthシリーズをお送りします。

概要

読みやすさのため、横に長いコードを途中で改行しています。

原文の更新や誤りにお気づきの場合は、ぜひ@techrachoまでお知らせください。更新いたします。

OmniAuth: 概要(翻訳)

現時点では、DeviseのOmniauthableモジュールはそのままでは1つのモデルでしか利用できません。しかしご安心ください。OmniauthableはOmniAuthの単純なラッパーにすぎないので、方法はあります。

OmniAuthを複数モデルで認証に使えるようにするには、次の手順を進めます。

1. モデルから:omniauthable引数を削除する

2. Deviseの設定ファイルからOmniAuthの設定を削除して新しいファイルに移動する

設定を切り出す前のdevise.rbが以下のようになっているとします。

Devise.setup do |config|
  config.omniauth :twitter, ENV['TWITTER_KEY'], ENV['TWITTER_SECRET']
end

上を削除して、以下のように新しいomniauth.rbファイルに保存します。

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :twitter, ENV['TWITTER_KEY'], ENV['TWITTER_SECRET']
end

3. OmniAuth独自のルーティングを作成する

authentications_controller.rbというコントローラがある場合、次のようなルーティングを使います。

  • Rails 3、Rails 4の場合
get "/auth/:action/callback", :to => "authentications",
                              :constraints => { :action => /twitter|google/ }
  • Rails 5の場合
get "/auth/:action/callback", :controller => "authentications",
                              :constraints => { :action => /twitter|google/ }

OmniAuthによるすべての認証を同じcreateアクションにルーティングしたい場合は、次のようにします。この方法はプロバイダの種類に依存しません。

get "/auth/:provider/callback" => "authentications#create"

マッピングエラーが発生する場合は、以下のようにルーティングをdevise_scopeでラップします。

devise_scope :user do
  get "/auth/:provider/callback" => "authentications#create"
end

4. OmniAuthのon_failureフックを設定する

この設定は、以下のようにOmniAuthの設定用ブロックの中で行えます。

Rails.application.config.middleware.use OmniAuth::Builder do
  on_failure { |env| AuthenticationsController.action(:failure).call(env) }
end

あるいは、以下のように設定用ブロックの外でも行えます。

OmniAuth.config.on_failure = Proc.new { |env| AuthenticationsController.action(:failure).call(env) }

Deviseにはこの2番目のオプションが実装されていますが、omniauth.rbファイルが設定済みなので、1番目のオプションの方がシンプルに書けます。

この設定では、認証の失敗(ユーザーのログインのキャンセルなど)はすべてauthentications_controller.rbコントローラのfailureアクションにリダイレクトされます。

5. OmniAuthでの認証エラー処理

このライブラリのエラーは残念ながらあまり標準化されていないため、Devise自身で行っているエラー処理と同様に多くの条件分岐を処理するしかありません。ここから使えそうなコードをコピペして何とかすることになります。

RailsCastsの「Simple OmniAuth」も設定の参考になります。

関連記事(Devise)

OminiAuth

その他


週刊Railsウォッチ(20170519)Rails 5.1.1/5.0.3リリース、RailsでAPIサーバーを作る、HackerNewsでトップになるチャンス?ほか

$
0
0

こんにちは、hachi8833です。fishをそおっとインストールしてみましたがとりあえず元に戻しました。

番外

  • サイエンスライターの森山和道さんの「あれこれ新聞」にTechRacho記事を掲載いただきました。ありがとうございます!
  • パーフェクトRuby(改訂2版)が発売されました。Ruby 2.4対応だそうです。おめでとうございます。早速ポチりました。

早くもRails 5.1.1と5.0.3がリリース(Rails公式ニュースより)

バグフィックスが中心なので急いでインストールする必要はないとのことです。

5.0.2 -> 5.2.35.0.3のコミット
5.1.0 -> 5.1.1のコミット

今回は5.1.1の修正点を中心に追いました。Railsウォッチで取り上げたもの、バージョン変更、スペルミスなどのコミットは省略しました。

#29029: rake -Tでtest環境ではなくdevelopment環境をデフォルトで読み込むよう修正

# railties/lib/rails/test_unit/railtie.rb
 require "rails/test_unit/line_filtering"

  if defined?(Rake.application) && Rake.application.top_level_tasks.grep(/^(default$|test(:|$))/).any?
-   ENV["RAILS_ENV"] ||= "test"
+   ENV["RAILS_ENV"] ||= Rake.application.options.show_tasks ? "development" : "test"
  end

rake -Tそのものは、定義されているrakeタスク一覧を出力します。テスト環境のrakeタスクと知らなかったらはまりそうです。

その場でmorimorihogeさんから、bundle exec rake middlewareでRailsのミドルウェアをリストアップできることを教わりました。「このミドルウェアの読み込み順序が変わると問題が発生することあるんですよ」とのことです。

#29034: Rescuable#rescue_with_handlerで例外の原因チェーン(cause chain)を扱えるよう修正

Ruby 2.4.1だとこの現象を再現できなかったそうです。cause chainという言葉をどう扱おうかと思いましたが、ひとまず「原因チェーン」にしてみました。

# activesupport/lib/active_support/rescuable.rb
-     def rescue_with_handler(exception, object: self)
+      def rescue_with_handler(exception, object: self, visited_exceptions: [])
+        visited_exceptions << exception
+
          if handler = handler_for_rescue(exception, object: object)
            handler.call exception
            exception
          elsif exception
-          rescue_with_handler(exception.cause, object: object)
+          if visited_exceptions.include?(exception.cause)
+            nil
+          else
+            rescue_with_handler(exception.cause, object: object, visited_exceptions: visited_exceptions)
+          end
         end

#29040: ActionController::Parameters#deleteにブロックを渡せるよう再修正

# actionpack/lib/action_controller/metal/strong_parameters.rb
-    def delete(key)
-      convert_value_to_parameters(@parameters.delete(key))
+    def delete(key, &block)
+      convert_value_to_parameters(@parameters.delete(key, &block))
     end

もともと#20868Hash#deleteを継承してブロックを渡せるようになっていたのが、どこかで継承されなくなっていたとのことです。

#29043: FinderMethods#exists?でemptyの場合のeager loadingを抑制

kamipoさんによる#29025の修正です。

# activerecord/lib/active_record/relation/finder_methods.rb
-      return false if !conditions
+      return false if !conditions || limit_value == 0
+
+      relation = self unless eager_loading?
+      relation ||= apply_join_dependency(self, construct_join_dependency(eager_loading: false))

-      relation = apply_join_dependency(self, construct_join_dependency(eager_loading: false))
       return false if ActiveRecord::NullRelation === relation

#28995: Capybaraがマイナーバージョンアップした場合にも追従できるよう変更

2.13.0でロックされてたんですね。

-gem "capybara", "~> 2.13.0"
+gem "capybara", "~> 2.13"

つっつきボイス「これ悩ましい問題だよね」「semantic versioningに対して挙動の違うgemがあるとこうやってハマる」

#29002: テストコードの引数に丸かっこを追加

# activerecord/test/cases/quoting_test.rb
      def test_quote_with_quoted_id
-       assert_deprecated /defined on \S+::QuotedOne at .*quoting_test\.rb:[0-9]/ do
+       assert_deprecated(/defined on \S+::QuotedOne at .*quoting_test\.rb:[0-9]/) do
        assert_equal 1, @quoter.quote(QuotedOne.new)
      end

-       assert_deprecated /defined on \S+::SubQuotedOne\(\S+::QuotedOne\) at .*quoting_test\.rb:[0-9]/ do
+       assert_deprecated(/defined on \S+::SubQuotedOne\(\S+::QuotedOne\) at .*quoting_test\.rb:[0-9]/) do
        assert_equal 1, @quoter.quote(SubQuotedOne.new)
      end
    end

テストのワーニング解消です。その場では「正規表現の開始終了文字/でワーニングが出ていたのかな?」「それともブロックの有無で出るのかな?」という話題どまりでしたが、BPS Webチームのakioさんが翌日再確認したところ、前者の引数の/正規表現/を丸かっこで囲まないことに対してのワーニングでした。

  • //の場合: 「ambiguous first argument; put parentheses or a space even after `/’ operator」
  • (//)の場合: warningなし
  • %r//の場合: warningなし

akioさんいわく「Railsの修正も%r//を使った方がスマートだったかなー」とのことでした。ありがとうございます!

5a6091: テストコードでrubygems 2.6.12を一時的に差し止め

rubygemsのバグですが、原因不明なのでとりあえず2.6.11を使うようにしました。「rubygemsで起きるとはつらいねー」と思わず声が上がりました。

-  - "gem update --system"
+  - "gem update --system 2.6.11"

#28978#28337: Railsガイドのディレクトリパスがおかしいのを修正

ロケールが重複していたのを修正しています。韓国の開発者のようです。

Before:

source_dir: "source/ko/ko"
output_dir: "output"

After:

source_dir: "source/ko"
output_dir: "output/ko"

#28943: rails new-pオプションより環境変数が優先されていたのを修正

         def port
-          ENV.fetch("PORT", options[:port]).to_i
+          options[:port] || ENV.fetch("PORT", DEFAULT_PORT).to_i
         end

ポート番号を環境変数で指定すると-pオプションが効かなくなっていた問題の修正です。

つっつきボイス-pは臨時の指定だからやっぱりこちらが効いてくれないとねー」

#28939:マイグレーションを忘れたときのメッセージを見やすく変更


https://github.com/rails/rails/pull/28939より

マイグレーションを忘れてRailsを起動したりすると表示されていた大量のバックトレースを抑制しました。

つっつきボイス「これにびびった初心者多いだろうねw」

#28941: package.jsonが誤って削除されることがあるのを修正

# railties/lib/rails/generators/rails/app/app_generator.rb
      def create_vendor_files
         build(:vendor)
-
-        if options[:skip_yarn]
-          remove_file "package.json"
-        end
       end

つっつきボイス「Issueタイトルが「Remove unnecessary package.json deletion」なのでdeletionのremoveとかややこしいなー」「コードを見れば一目瞭然だけどね」

#28920: remove_possible_methodrequire漏れだったのを修正

# activesupport/lib/active_support/core_ext/date_time/compatibility.rb
require "active_support/core_ext/date_and_time/compatibility"
+require "active_support/core_ext/module/remove_method"

先週の#28835に続くrequire漏れ修正です。ActiveSupportが巨大なのでなかなか目が届かなさそうです。

Rails: action_controller_baseaction_controller_apiを追加(Rails公式ニュースより)

ここからはいつもの進行です。

ActiveSupportには#run_load_hooksというメソッドがあり、起動時の読み込みを絞り込むのに使われていることを知りました。

このメソッドで指定できるライブラリの中にaction_controllerもありますが、もう少し細かく指定できるようaction_controller_baseaction_controller_apiを追加したということです。

# actionpack/lib/action_controller/api.rb
ActiveSupport.run_load_hooks(:action_controller_api, self)

# actionpack/lib/action_controller/base.rb
ActiveSupport.run_load_hooks(:action_controller_base, self)

Rails: フィクスチャのアクセサメソッドに引数を渡さない場合にすべてのフィクスチャを返すよう変更(#railsnews)より)

# activerecord/lib/active_record/fixtures.rb
...
-              instances.size == 1 ? instances.first : instances
+              return_single_record ? instances.first : instances
...

従来は空の配列[]が返されていました。

Rails: HashWithIndifferentAccessfetch_valuesを実装(Rails公式ニュースより)

# activesupport/lib/active_support/hash_with_indifferent_access.rb
+    def fetch_values(*indices, &block)
+      indices.collect { |key| fetch(key, &block) }
+    end if Hash.method_defined?(:fetch_values)

Issueによると、Ruby 2.3.0という比較的最近の時期にHash#fetch_valuesが実装された(#10017)ので、それに合わせてActiveRecordのHashWithIndifferentAccessクラスでも使えるようにしたそうです。

このクラスは以下のようにハッシュのキーに文字列でもシンボルでも与えられるようにするものです。indifferentは人間に対して使うと「無関心」「無頓着」というニュアンスですが、ここでは「寛容」のニュアンスですね。

rgb['white'] = '#FFFFFF'
rgb[:white]  # => '#FFFFFF'
rgb['white'] # => '#FFFFFF'

Rails: 有効なコネクションを初期化後にクリアするよう修正(Rails公式ニュースより)

# activerecord/lib/active_record/railtie.rb
+    initializer "active_record.clear_active_connections" do
+      config.after_initialize do
+        ActiveSupport.on_load(:active_record) do
+          clear_active_connections!
+        end
+      end
+    end

スレッドが変わってもコネクションを使いまわせるようにするためだそうです。

Rails: JSレスポンスパーサーのバグを修正(Rails公式ニュースより)

今度はCoffeeScriptのAjax周りです。document.bodyではなくdocument.headに追加するように修正されています。

# actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee
   if typeof response is 'string' and typeof type is 'string'
      if type.match(/\bjson\b/)
        try response = JSON.parse(response)
-    else if type.match(/\bjavascript\b/)
+    else if type.match(/\b(?:java|ecma)script\b/)
        script = document.createElement('script')
-      script.innerHTML = response
-      document.body.appendChild(script)
+      script.text = response
+      document.head.appendChild(script).parentNode.removeChild(script)
      else if type.match(/\b(xml|html|svg)\b/)
        parser = new DOMParser()
        type = type.replace(/;.+/, '') # remove something like ';charset=utf-8'

Rails: dependent: :destroyを指定した場合のbefore_destroyの挙動についてドキュメントを修正(Rails公式ニュースより)

以下が追加されました。Railsガイド「Active Record コールバック」の3-3. オブジェクトのdestroyに該当します。

注: before_destroyコールバックは、dependent: :destroyにともなってレコードが削除される前に実行されなければならないので、dependent: :destroy関連付けより前に実行するか、prepend: trueオプションを指定すること。

参考: Rails API Active Record Callbacks

Rails: input文字列のfreezeを修正(Rails公式ニュースより)


https://github.com/rails/rails/pull/28729/filesより

コメントから:

  • 「条件節やfreezeのtrue/falseまでdupしないといけないのはちとイケてない」
  • 「同意、でもブランチでの変更量は割りと少なくてすみそう」

k0kubunさんがRubyコミッターに

前からコミッターだったような気がしていましたが、今回からだったんですね。おめでとうございます!

Google Summer of Code 2017に参加する学生が決定(Rails公式ニュースより)

以下の2名に決まったそうです。おめでとうございます。

Marko Bogdanović
Assain

Sinatra 2.0がリリース(RubyWeeklyより)

Changelogがどこにあるかややわかりにくいのですが、https://github.com/sinatra/sinatra/blob/v2.0.0/CHANGELOG.mdにありました。

普段Sinatraを追っていませんが、Changelogの最後にIndifferentHashというものを見つけました。
上でも取り上げたActiveSupport::HashWithIndifferentAccessがちょうどBPSの社内勉強会でも一瞬話題になったので気になりました。

SinatraではSinatra::IndifferentHashなんですね。APIドキュメントの冒頭が泣かせます。

  # A poor man's ActiveSupport::HashWithIndifferentAccess, with all the Rails-y
  # stuff removed.

つっつきボイス「ActiveSupportでかいからね」「Sinatra的には使ったら負け、なのかな?」「SinatraはRailsのマウンタブルエンジンにもできるよ」

RailsPanel: Rails開発を支援するChrome拡張機能(RubyWeeklyより)


https://github.com/dejan/rails_panelより

RailsPanelは以前から人気の高いツールです。SQL文を見やすく整形してくれるあたりがうれしいですね。私も以前インストールしたことがある気がするのですが、クリーンインストールのときに消してしまったようです。

Railsアプリにmeta_request gemを追加して[Chromeウェブストア]からインストールするだけで使えるようになります。

group :development do
  gem 'meta_request'
end

ActiveAdminが1.0でRails 5.1に対応(RubyWeeklyより)


https://activeadmin.info/より

つっつきボイス「管理画面系gemはカスタマイズしようとすればするほどハマる傾向があるなー」「カスタマイズなしで使える案件ってたぶんないでしょw」

Rackが404で返すヘッダにX-Runtimeがあるのヤバくね?(RubyWeeklyより)

HTTP/1.1 404 Not Found
Cache-Control: private, no-cache
Content-Type: text/html;charset=utf-8
Date: Mon, 29 Aug 2016 16:49:52 GMT
Request-Id: b3ca4a2c-1adf-4244-4cb0-17fbbc9deb06
Server: nginx/1.8.1
Strict-Transport-Security: max-age=31536000; includeSubDomains
Vary: Accept-Encoding
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-Runtime: 0.018023806                  <-コレ
X-Xss-Protection: 1; mode=block
Content-Length: 0
Connection: keep-alive

このヘッダを生成しているのが、Railsを縁の下から支えているRackです。

https://github.com/rack/rack/blob/master/lib/rack/runtime.rb#L12 より
  class Runtime
    FORMAT_STRING = "%0.6f".freeze # :nodoc:
    HEADER_NAME = "X-Runtime".freeze # :nodoc:
...

ユーザーが存在する場合としない場合でX-Runtimeが1桁近く違うので、これを使ってタイミング攻撃(この場合は辞書を食わせるなどしてユーザー名を特定)ができるのではないかという主張です。

# ユーザーが存在する場合
X-Runtime: 0.093990008
X-Runtime: 0.088857339
X-Runtime: 0.09251876
X-Runtime: 0.089843447
X-Runtime: 0.091845765
X-Runtime: 0.101260549

# ユーザーが存在しない場合
X-Runtime: 0.027787351
X-Runtime: 0.018023806
X-Runtime: 0.020997733
X-Runtime: 0.021645124
X-Runtime: 0.016645836
X-Runtime: 0.021828831
X-Runtime: 0.022211143

著者はこの問題をHerokuで見つけましたが、Heroku、BugCrowdともにあまり取り合ってもらっていない感じです。

つっつきボイス「影響の大きさ次第かな」「productionでX-Runtimeを表示しないぐらいはしてもいいかも」

引数にtrue/falseフラグを与えるのはコードが臭う前兆(RubyWeeklyより)

記事はそのまんまですが、そこからRubyMineの最近の機能であるparameter name hinting↓の話題に発展しました。

つっつきボイス「この機能、嫌いじゃない」「見た目のインデントが崩れるのいやだからオフにしてる」「デフォルトではパラメータ名と引数名が一致していれば表示されない」「挿入時にカーソルが置きにくい」

参考: Parameter Names Hinting

チュートリアル: RailsアプリをElastic Beanstalkにデプロイする(RubyWeeklyより)

元記事:

よくある記事ですが一応。

Active Model SerializerでRails APIサーバーを作る(RubyFlowより)

rails new--apiしてからカバレッジまでをひととおりカバーしています。

つっつきボイス「これ結構役に立ちそう」「Active Model Serializerについてあまり書かれてないけどねw」

Ralyxa: Amazon Alexaにアクセスするフレームワーク

Sinatraベースだそうです。AlexaはAmazon Echoのバックエンドにも使われているクラウド音声認識サービスです。
日本語版がいつ使い物になるかですね。

Rubyでバイナリデータを扱う(RubyFlowより)

Rubyによるビット操作、エンコードの検出、ビッグエンディアン・リトルエンディアンなどを解説しています。

そこから、Rubyはバイナリデータのハンドリングがイケているという話題になりました。私はテキスト処理がほとんどだったのでこのあたりは知りませんでした。

morimorihogeさんが「RubyでShiftJISのファイルを扱う(1.9.3, 2.0系対応版)」という記事を書いたことを思い出しながら、もっと前にJavaでやったときには「素のJavaにunsignedがないのでビット操作が不自由だったなー」「それ用のライブラリを使えば違ったかもしれないけど」と当時を回想していました。

他の記事もよい感じです。

Railsのテンプレートレンダリングのしくみ(RubyFlowより)

Railsで必ずといっていいほどお世話になっているビューテンプレートですが、実装はかなり生い茂っていることでも有名です。このあたりの解説はあまり見ないのでありがたいです。

スライドを見ていて「これ、Crafting Rails 4 Applicationsと構成が似ている気がするw」とmorimorihogeさんが気づきました。私も一応読んだはずなのに元ネタに気づけませんでした。そういえばこの本、未だに日本語化されていませんね。

著者のStan Loは台湾の人ですが、もしやと思ったら先週のウォッチで紹介したGobyの人でした。スライドを最後まで見るとわかります。

先のk0kubunさんもそうですが、RubyとGoを両方やっている人って意外にいますね。Goのpppecoなどもk0kubunさん作です。

JRubyのバグを見つけた話(RubyFlowより)

なにしろJRubyなのでJavaコードがほとんどです。

インテルが不揮発性メインメモリアーキテクチャのデモを公開

今朝morimorihogeさんがこのニュースを社内のSlackに流して一気に盛り上がりました。そのときの記事は日本語でしたが、意地で英語記事のリンクを貼りました。どんなふうに使おうかと夢は広がる一方です。

同記事には、まさにそのときのためにあるとしか思えないPersistent Memory Programmingというサイトも紹介されています。


http://pmem.io/2014/08/27/crawl-walk-run.htmlより

そして意外にも、このニュースが現時点でHacker Newsにまったく上がっていません。珍しいですね。

今から数時間以内ぐらいにHacker Newsに英語でタレこめばトップ取れるかもしれません。チャレンジャーは誰だ。

番外: 言語には人間性が反映される(RubyFlowより)

主にJRuby方面でご活躍のAlex Coles氏が、Sheffield Ruby User Groupの4月のカンファレンスでRubyや言語一般について自由に話している、雑学的に楽しい内容です。40分近くありますが、字幕も文字起こしもあり、日本人には聞き取りやすい英語だと思います。

個人的に、左のほっぺたのところのタトゥーが何の絵なのかが気になってしまいました。描いてる途中?

Wikipedia-ja: シェフィールド


今週は以上です。

関連記事

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやRSSなど)です。

Rails公式ニュース

Ruby Weekly

OpenRuby

RubyFlow

160928_1638_XvIP4h

[Devise How-To] OmniAuth: 結合テスト(翻訳)

$
0
0

こんにちは、hachi8833です。引き続きDevise How-ToのOmniAuthシリーズをお送りします。

概要

読みやすさのため、横に長いコードを途中で改行しています。

原文の更新や誤りにお気づきの場合は、ぜひ@techrachoまでお知らせください。更新いたします。

OmniAuth: 結合テスト(翻訳)

OmniAuthには、結合テスト(integration test: 統合テストとも)で認証フローをモックに差し替える方法がいくつか用意されています。

OmniAuth.config.test_mode

OmniAuthのテストモードを有効にするには、以下のように設定します。

OmniAuth.config.test_mode = true

訳注: 設定の記述場所はspec/support/omniauth.rbspec/support/omniauth.rbなどです。

テストモードが有効になると、後述の認証ハッシュのモックを用いるためにOmniAuthへのリクエストはすべてスキップされます。たとえば/auth/providerへのリクエストは即座に/auth/provider/callbackにリダイレクトされます。

OmniAuth.config.mock_auth

#mock_authを使うと、結合テスト中に認証プロバイダごとの認証ハッシュやデフォルトの認証ハッシュを返すことができます。以下は設定例です。

OmniAuth.config.mock_auth[:twitter] = OmniAuth::AuthHash.new({
  :provider => 'twitter',
  :uid => '123545'
  # (省略)
})

:defaultをキーを設定すると、認証プロバイダが指定されていない場合にデフォルトの認証プロバイダを返します。モック認証ハッシュが設定されてテストモードになると、OmniAuthへのすべてのリクエストに対してモックの認証ハッシュが返されるようになります。

OmniAuth.config.add_mock

#add_mockメソッドは、以下のように認証プロバイダの新しいモックをその場で追加するときにも使えます。設定した情報はデフォルトの情報と自動的にマージされるので、正しいレスポンスが返されます。

OmniAuth.config.add_mock(:twitter, {:uid => '12345'})

モックが失敗する場合

認証プロバイダのモックを以下のようにハッシュではなくシンボルで設定すると、モックが失敗してエラーメッセージが表示されます。

OmniAuth.config.mock_auth[:twitter] = :invalid_credentials

失敗の場合、 /auth/failure?message=invalid_credentialsにリダイレクトされます。

デフォルトのOmniAuthは、developmentモードとtestモードで無効な認証情報に対して例外をraiseします。developmentモードとtestモードで失敗したときに/auth/failureエンドポイントにリダイレクトしたい場合は、以下のコードを追加します。

OmniAuth.config.on_failure = Proc.new { |env|
  OmniAuth::FailureEndpoint.new(env).redirect_to_failure
}

クリーンアップ

テストとテストの合間にOmniAuthを特定の状態にリセットするには、テストスイートレベルの設定(setupbefore(:each)など)で以下を実行します。

OmniAuth.config.mock_auth[:twitter] = nil

コントローラの設定

OmniAuthのテストでは、コントローラで以下の2つの環境変数を設定する必要があります。以下はTwitter OmniAuthをRSpecでテストする場合のサンプルです。

before do
  request.env["devise.mapping"] = Devise.mappings[:user] # Deviseを使う場合
  request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter]
end

新しいバージョンのRSpecではrequestオブジェクトにアクセスできません。request specを使う場合は以下のようにします。

before do
  Rails.application.env_config["devise.mapping"] = Devise.mappings[:user] # Deviseを使う場合
  Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter]
end

上のようにすることで、以下のサンプルのようなルーティングエラーを防止できます。

Failure/Error: get :twitter
AbstractController::ActionNotFound:
  Could not find devise mapping for path "/users/auth/twitter/callback".
  Maybe you forgot to wrap your route inside the scope block? For example:
    devise_scope :user do
      match "/some/route" => "some_devise_controller"
    end

関連記事(Devise)

OminiAuth

その他

週刊Railsウォッチ(20170526)増えすぎたマイグレーションを圧縮するsquasher gem、書籍「Complete Guide to Rails Performance」ほか

$
0
0

こんにちは、hachi8833です。いつの間にかもう5月が終わろうとしています。

それでは今週のRailsウォッチいってみましょう。

Rails: 今週の改修(Rails公式より)

今回はRailsコミット系を1トピックにまとめてみました。タイトルは一応韻を踏んでいます。

リサイクル可能なキャッシュキーを追加

DHH提案の新しい内部機能です。コミットを見るとけっこう大きな改修になっています。

# actionpack/lib/abstract_controller/caching/fragments.rb#L86
+      def combined_fragment_cache_key(key)
+        head = self.class.fragment_cache_keys.map { |k| instance_exec(&k) }
+        tail = key.is_a?(Hash) ? url_for(key).split("://").last : key
+        [ :views, (ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]), *head, *tail ].compact
+      end

DHHのPRメッセージの中ほどをざっくり日本語にしてみました。

この問題は、明確なバージョンを分離してキーを安定化することで解決できる。従来だと”projects/1-20170202145500″のようにキーを組み合わせていたが、”products/1″や関連するバージョン(”20170202145500″など)といった組み合わせてないキーを安定して利用できるようになる。”projects/1″にどれほど頻繁にアクセスしようとも、単に同じキャッシュキーに書き込まれる。つまりこれがリサイクル部分になる。

つっつきボイス: 「#combined_fragment_cache_key自体はそもそもかなりraw levelなメソッドだし、キャッシュにはCacheHelper経由でアクセスするのが普通だから、普通なら直接触ることはないよ、ということか」「less-frequently-accessed-but-still-valid keysって書いてあるのがポイントかな。アクセス頻度は低くてもcache outしたくないようなデータをどうにかしたいと」「fetchにコストや時間がかかるようなデータなら永続化してもいいくらいのことってありますね」「cache versionとcache keyの管理を分離したい、という感じ」

rails consoleコマンドからirbにオプションを渡せるよう再修正

Rails 5からの機能だったのがいつの間にか消えていたので修正したそうです。

つっつきボイス:「↓これかー」「誰だっARGV.clearなんか足したのはw」

# railties/lib/rails/commands/console/console_command.rb#L82
-        ARGV.clear # Clear ARGV so IRB doesn't freak.

データベースのダンプで’SchemaDumper.ignore_tables’が効くように修正

# activerecord/lib/active_record/tasks/mysql_database_tasks.rb#L60
         args.concat(["--routines"])
         args.concat(["--skip-comments"])
         args.concat(Array(extra_flags)) if extra_flags
+
+        ignore_tables = ActiveRecord::SchemaDumper.ignore_tables
         if ignore_tables.any?
+          args += ignore_tables.map { |table| "--ignore-table=#{configuration['database']}.#{table}" }
+        end
+
         args.concat(["#{configuration['database']}"])

         run_cmd("mysqldump", args, "dumping")

MySQLのほか、PostgreSQLやSQLiteも同様に修正されています。

つっつきボイス:「この機能、あれば使うかも」

キャッシュ機能にunless_existオプションを追加

# activesupport/lib/active_support/cache/strategy/local_cache.rb#L117
          def write_entry(key, entry, options)
-            local_cache.write_entry(key, entry, options) if local_cache
+            if options[:unless_exist]
+              local_cache.delete_entry(key, options) if local_cache
+            else
+              local_cache.write_entry(key, entry, options) if local_cache
+            end
+
             super
           end

unless_exist、つまりキャッシュが既にあれば上書きしないという処理です。

つっつきボイス: 「この修正ってキャッシュのatomicityがちゃんと保証されているのかな?」「できるかどうかはキャッシュエンジンに依存しそうだし」「unless_existチェックが終わってから書き込むまで間に別のプロセスが書き込むとかありえる」

Rubyのバックエンドパフォーマンス改善(RubyWeelkyより)

RailsConf 2017で行われたパネルディスカッションです。40分ほどの動画と概要が紹介されていて、パフォーマンスについていろいろ参考になります。

途中にこんなことも書いてあります。

Always

Be

Benchmarking

元記事では最後にThe Complete Guide to Rails Performanceをおすすめしています。3月17日のRailsウォッチでもこの書籍のことを簡単に取り上げました。

morimorihogeさんが「お、これはよさそう。『FASTとは何か』の定義から始まってて、ちゃんとしてそうに見える」と早速購入方法を調べ始めました。


http://schneems.com/2017/05/17/ruby-backend-performance-getting-started-guide/より

みっちり読む甲斐がありそうです。まともに買うと99ドルですが、頑張ってクーポンを探すともっと安く買えそうです。

補足

ご多分に漏れず、RailsConf 2017の動画がごっそりYouTubeにアップされていますね。後でチェックしてみようっと。


https://www.youtube.com/results?search_query=RailsConf+2017より

ハイパーメディアクライアントをRubyで書く(RubyWeelkyより)


https://robots.thoughtbot.com/writing-a-hypermedia-api-client-in-rubyより

「ハイパーメディアって何ぞや?」という点が気になりました。http://steveklabnik.github.com/hypermedia-presentation/のスライドがポイントのようですが、おっきな字ばっかりでよくわかりません。


http://www.steveklabnik.com/hypermedia-presentation/#1より

つっつきボイス: 「高橋メソッドはこうやって読むには向いてないなー」

akioさんが同記事の中から見つけた「HAL – Hypertext Application Language」の方がどうやらずっとよくまとまっていますね。HAL、覚えておこう。

Polyfill: 古いRubyで新しい機能を使えるようにするgem(RubyWeelkyより)

JavaScriptの方のPolyfillsを思い出してしまいました。Ruby 2.4、2.3、2.2をターゲットにしています。

つっつきボイス:「ここまでして古いRuby使わなきゃいけないんだろうかw」「Kernelにパッチ当てて新しいRubyのメソッドを使うよりはましなのかな?」

morimorihogeさんが「コード側でRubyバージョンをRUBY_VERSIONで調べて切り替えていたら、おそらくこのgemは効かないのではないか」と指摘しました。「Rubyバージョンをrespond_to?でチェックしているコードならこのgemとやっていけるだろうけど」「いったんconflictしたら根が深そう」とも。

Railsは最新のRubyが推奨されるので、よほど古いRailsアプリでなければ普通にRubyをアップグレードする方がよさそうですね。

RubyでUnicodeを修正する


https://blog.daftcode.pl/fixing-unicode-for-ruby-developers-60d7f6377388より

元記事の著者は名前を見るだけで一発でポーランド人とわかります。Lの小文字に斜めの線が入っているような文字が特徴です。

Unicode絵文字が普及したおかげで、日本を含むアジア方面ではおなじみの文字コードの苦労がやっと海外でも共有され始めているようです。私も珍しく「ふふ、ふふふ、もっと、もっと苦しむがよい」という気持ちが湧き上がってしまいました。

lltsv: LTSVパーサー

morimorihogeさんが見つけました。JSONよりお手軽そうです。

私も、個人的にCSVよりもタブ区切りテキストを好んでいる(=表計算にそのまま貼り付けられるから)ので、LTSVに期待しちゃいます。

Squasher: 増えすぎたマイグレーションを圧縮するgem(RubyFlowより)

年月を経てずらりと増えたマイグレーションを1つのコマンドに集約するgemです。★600越えと人気のほどがうかがえます。
うまく使うとマイグレーションファイルの整理に役立ちそうです。
「実行前にはspringとかzeusは止めておきましょう」と注意書きがあります。

つっつきながら、「これとよく似たgemがあったはずだけどなー」という話になり、調べてみたところridgepoleというgemでした。こちらはRails標準のマイグレーションの代わりに使う、より本格的なスキーマ管理向けだそうです。

参考: ridgepole {名} : 《建築》棟木

つっつきボイス:「ridgepole、そういえば某プロジェクトで知らないうちに入ってたことありましたよ」

Napsack: CIでテストをノードに分割して比較可能にするgem

Pro版もあるそうです。CIや監視がらみはPro版あり多いですね。類似のgemにrrrspecがあることを教わりましたが、また違うアプローチのようです。

Rubyで学ぶグラフ理論の初歩(RubyFlowより)

あきれるほど短い記事なので、グラフ理論のとっかかりによいと思います。心なしか字も大きめなので読みやすいと思います。あくまでとっかかりですが。

つっつきボイス:「Practical Graph Theoryと名乗るほどの内容じゃないかなー」

Electrino: Chromiumの代わりにChromeブラウザを使うJSアプリフレームワーク

なぜ今までなかったんだと思ってしまいました。20日で★2000越えの快挙です。「まだ機能がろくにないけど」と謙遜気味のREADMEです。

名前からElectronの縮小版ということがわかります。Chromiumエンジンを丸呑みしているElectronだと100MB越えなのに、Electrinoだと100kBクラスですよ、閣下。

つっつきボイス:「cookieやプロファイルはブラウザと共用するのかな」「それはないな、プロファイルはちゃんと仕切られるはず」

私はnativefierというElectronでWebページをアプリ化するのをよくやりますが、Electrinoに乗り換えてほしいものです。

Go言語1.8.2、1.8.3セキュリティアップデート

1.8.2では暗号化の楕円関数あたりが修正されましたが、直後に1.8.3がリリースされました。めまぐるしいです。

HPEが160テラバイトのメモリ空間を持つ「The Machine」のプロトタイプを発表

テラバイト、ペタバイト、エクサバイト、ゼタバイトの次はヨタバイトなんですね。


今週は以上です。

関連記事

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやRSSなど)です。

Ruby 公式ニュース

Rails公式ニュース

Ruby Weekly

RubyFlow

160928_1638_XvIP4h

[Rails 5.1] 新機能: delegate_missing_toメソッド(翻訳)

$
0
0

こんにちは、hachi8833です。今回のBigBinaryシリーズは、週刊Railsウォッチでも紹介していなかったRails 5.1の新機能です。

DHHはこのメソッドをdecoratorで使うことを想定してるとのことです。

概要

新機能: delegate_missing_to(翻訳)

#method_missingを使うときには、#respond_to_missing?も併用するべきです。しかし、#method_missing#respond_to_missing?を両方使うとコードが冗長になるのも確かです。

DHHが自ら上げた#23824で、冗長なコードの典型的な例を見ることができます。

(訳注: このコード例はそのままAPIドキュメントでも使われています)

class Partition
  def initialize(first_event)
    @events = [ first_event ]
  end

  def people
    if @events.first.detail.people.any?
      @events.collect { |e| Array(e.detail.people) }.flatten.uniq
    else
      @events.collect(&:creator).uniq
    end
  end

  private
    def respond_to_missing?(name, include_private = false)
      @events.respond_to?(name, include_private)
    end

    def method_missing(method, *args, &block)
      @events.public_send(method, *args, &block)
    end
end

DHHは、こうしたコードを改善するために新しいModule#delegate_missing_toメソッドの利用を提案しています。利用例は次のとおりです。

class Partition
  delegate_missing_to :@events

  def initialize(first_event)
    @events = [ first_event ]
  end

  def people
    if @events.first.detail.people.any?
      @events.collect { |e| Array(e.detail.people) }.flatten.uniq
    else
      @events.collect(&:creator).uniq
    end
  end
end

SimpleDelegatorクラスでは不足な理由

BigBinary社では従来RubyのSimpleDelegatorクラスを使っていました。このクラスの問題は、このdelegatorは実行時にどんな種類のオブジェクトでも使われる可能性があるため、呼び出しが委譲される先のオブジェクトを静的に確定できないという点です。

DHHはこのパターンについて次のように言っています

この程度のシンプルな機能のために継承ツリーをハイジャックしなくても済む方がいい。

(訳注: 続きはこうなっています)

継承とsuperを使うのもどうかと思う。#delegate_missing_toなら意図が明確になる。

#delegateメソッドでは不足な理由

Module#delegateメソッドを使う手もあります。しかしその場合は全メソッドをホワイトリスト化しなければなりませんし、ホワイトリストが非常に長くなってしまうこともあります。以下は私たちの実際の案件から引用した実例コードです。

delegate :browser_status, :browser_stats_present?,
         :browser_failed_count, :browser_passed_count,
         :sequential_id, :project, :initiation_info,
         :test_run, :success?,
         to: :test_run_browser_stats

すべてを委譲するならdelegate_missing_to

状況によってはあらゆるmissing methodを委譲したくなることがありますが、#delegate_missing_toならそうした作業をすっきり行えます。なお、この委譲は委譲先のオブジェクトのpublicなメソッドに対してのみ有効であることにご注意ください。

詳しくは#23930のPRをご覧ください。

訳注

現在の5.1-stableの#delegate_missing_toは以下のようになっています。

# 5-1-stable/activesupport/lib/active_support/core_ext/module/delegation.rb#L262
  # オブジェクト内の呼び出し可能なものはすべて対象にできる
  # (例: インスタンス変数、メソッド、定数)
  def delegate_missing_to(target)
    target = target.to_s
    target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target)

    module_eval <<-RUBY, __FILE__, __LINE__ + 1
      def respond_to_missing?(name, include_private = false)
        # 見落としのように見えるかもしれないが、privateなものは委譲されないので
        # あえてinclude_privateを渡さないようにしている
        #{target}.respond_to?(name) || super
      end
      def method_missing(method, *args, &block)
        if #{target}.respond_to?(method)
          #{target}.public_send(method, *args, &block)
        else
          super
        end
      end
    RUBY
  end

#23824では#delegate_missing_toが何でも委譲することの是非などについて多少議論が紛糾していました。DHHは最後に「このパターンはdecorator用である」と述べています。

関連記事

週刊Railsウォッチ(20170609)ついにtherubyracerからmini_racerへ、注意しないとハマるgem、5.1でのVue.jsとTurbolinksの共存ほか

$
0
0

こんにちは、hachi8833です。暦では梅雨ですが近年のこの時期、どうも天候がはっきりしませんね。

それでは今週のウォッチ、いってみましょう。

Rails: 今週の改修(Rails公式ニュースより)

ついにtherubyracerを置き換え: 後任はmini_racer

therubyracerのV8エンジンのバージョンが古いことはRailsウォッチでもお知らせしましたが、2013年のChrome 31並に古かったんですね。古いV8の脆弱性が見過ごせない状態になったのがきっかけだそうです。

PRでも喜びの声があがっています。「mini_racerの方が速いし安定してるし」

つっつきボイス: 「昔バージョン依存が強かった時代に泣かされた人、多かったよねー」「therubyracerって、入れろ入れろってしょっちゅうメッセージ出るとその誘いに乗っちゃったり」「Gemfileにもコメントアウト状態ですがありますね」「mini_racerでRails 5.1をインストールしたときにyarnって動くんだろか?やっぱりnodeも別途必要なんだろか?」

RailsのJavaScriptの悩みは尽きません。

class_attributeにデフォルト値を指定するオプションを追加

DHHがこんな感じでガンガン修正しました。

-        class_attribute :periodic_timers, instance_reader: false
-        self.periodic_timers = []
+        class_attribute :periodic_timers, instance_reader: false, default: []

つっつきボイス: 「確かに前はこれできなかったなー」「これでちょっと読みやすくなるかも」

前回の曜日と次の曜日を取れるAPIをActiveSupportに追加

pixeltrix氏(私は勝手に「先生」と呼んでます)が「next_weekとどう違うの?こういうのがいっぱい増えすぎてもうこっちも追えなくなってるんだよね」「たぶんこれはよくない」といったんピシャリとcloseしましたが、「いやいや、そうじゃなくって前回と次回の曜日です」「そうか、しかしメソッド名がよくないなー」といったやりとりを経てめでたくmergeされました。

私も当初読み違えてしまいましたが、morimorihogeさんが「テストコード見たら一発っすね」と大画面に映してくれました。

# activesupport/test/core_ext/date_time_ext_test.rb
  def test_next_occur
    datetime = DateTime.new(2016, 9, 24, 0, 0) # saturday
    assert_equal datetime.next_occurring(:monday), datetime.since(2.days)
    assert_equal datetime.next_occurring(:tuesday), datetime.since(3.days)
    assert_equal datetime.next_occurring(:wednesday), datetime.since(4.days)
    assert_equal datetime.next_occurring(:thursday), datetime.since(5.days)
    assert_equal datetime.next_occurring(:friday), datetime.since(6.days)
    assert_equal datetime.next_occurring(:saturday), datetime.since(1.week)
    assert_equal datetime.next_occurring(:sunday), datetime.since(1.day)
  end

  def test_prev_occur
    datetime = DateTime.new(2016, 9, 24, 0, 0) # saturday
    assert_equal datetime.prev_occurring(:monday), datetime.ago(5.days)
    assert_equal datetime.prev_occurring(:tuesday), datetime.ago(4.days)
    assert_equal datetime.prev_occurring(:wednesday), datetime.ago(3.days)
    assert_equal datetime.prev_occurring(:thursday), datetime.ago(2.days)
    assert_equal datetime.prev_occurring(:friday), datetime.ago(1.day)
    assert_equal datetime.prev_occurring(:saturday), datetime.ago(1.week)
    assert_equal datetime.prev_occurring(:sunday), datetime.ago(6.days)
  end

つっつきボイス: 「会話なんかだとよく『じゃ作業は次の土曜日にしましょうか』『この前の日曜は何してた?』みたいなこと言うけど、そういうことかー」「こういうメソッドはあれば使うかなー」「週の境目を気にしなくて済むのはいいかも」

補足: 曜日の雑学

お気づきの方も多いと思いますが、英語には「曜日」を1語でぴたりと表す用語がありません。「day of week」という何だか苦し紛れな言い方しかなくて、こういうときに本当に紛らわしいですね。「day of week」だとスマホでも場所取るし。コンピュータ関連ではDOWと略すこともあるらしいですがさっぱり定着しません。weekdayは「平日」なので使えそうで使えません。

日本語の曜日は言うまでもなく古代中国の七曜が由来で、もっぱら占い用でした。日本にもたらしたのは空海なんだそうで、七曜を使ったに違いない陰陽師がもてはやされたのはもっと後みたいです。

曜日の命名には主要な惑星・恒星がマッピングされていることがひと目でわかります。しかも「曜」という字はこの意味でしか使わない言葉なので取り違えも少ないと思います。

ひるがえって英語の曜日はというと、Sunday、Saturday、Mondayあたりはかろうじて「太陽、土星、月」かなという感じですが、Tuesday、Wednesday、Thursday、Fridayは北欧神話が起源というハイブリッドです。

月名も、日本は1月2月と数字化されているのでループで回しやすいのですが、英語圏は未だに「January、February、March」と、旧暦の「睦月如月弥生」と大して変わらない世界なんですね。

英語圏で月名を01や02で表すのはlsコマンドやログのように「そうするしかない場合にしぶしぶ使う」感じで、一般には英語圏で月を数字で表すのはえらく嫌がられます。英語には日本語のような「月」「日」「曜」「時」「分」「秒」といったサフィックスがなく(期間を表す場合は10 daysや20 secのように書けますが)、月なのか日なのか時なのか分なのかわかりにくいのでしょうがないかもしれません。

なお中国語では月曜火曜みたいな表記は随分昔に廃れてしまい、星期一(=月曜)、星期二(=火曜)のように数値で表します。惑星・恒星とのマッピングは捨てられました。合理的かなと思う一方、年月日とベタで2017/01/01(01)みたいに並べるとかえってわかりにくい気もしますが。

Vue.jsとTurbolinksを共存させる方法

検索で見つけました。vue-turbolinksをyarnで導入して以下のようにするのがポイントだそうです。なお以下のコードはvue-turbolinksのページから引用しました。

// Vueを使う場合
import TurbolinksAdapter from 'vue-turbolinks';

document.addEventListener('turbolinks:load', () => {
  var vueapp = new Vue({
    el: "#hello",
    template: '<App/>',
    mixins: [TurbolinksAdapter],
    components: { App }
  });
});
// 特定ページでのみVueを使いたい場合
import TurbolinksAdapter from 'vue-turbolinks';

document.addEventListener('turbolinks:load', () => {
  var element = document.getElementById("hello")
  if (element != null) {
    var vueapp = new Vue({
      el: element,
      template: '<App/>',
      mixins: [TurbolinksAdapter],
      components: { App }
    });
  }
});

morimorihogeさんがGitHubリポジトリでvue-turbolinksのソースを開けてみると、ソースはたったこれだけでした。

// https://github.com/jeffreyguenther/vue-turbolinks/blob/master/index.js より
function destroyVue() {
  this.$destroy();
  document.removeEventListener('turbolinks:before-cache', destroyVue.bind(this))
}

var TurbolinksAdapter = {
  beforeMount: function(){
    if (this.$el.parentNode) {
      document.addEventListener('turbolinks:before-cache', destroyVue.bind(this))
      this.$originalEl = this.$el.outerHTML;
    }
  },
  destroyed: function() {
    this.$el.outerHTML = this.$originalEl;
  }
};

export default TurbolinksAdapter;

つっつきボイス: 「Turbolinksがあるとこんな感じで他のJSライブラリのイベント発火とか面倒みないといけないのがつらいねー」「前に自力でやったなー、これ」

参考: RailsでVue.jsを使う方法

少し前のものも多いですが、Vue.jsとRails関連の英語記事やサンプルをメモします。

気をつけないとハマるかもしれないgem 5+1種(RubyFlowより)


blog.rubyroidlabs.comより

先週のウォッチで紹介した19種のおすすめgem記事と同じrubyroidlabs.comの記事です。

  • devise_token_auth
  • sidekiq-unique-jobs
  • axlsx
  • whenever
  • dwolla-ruby
  • rails_admin + will_paginate

つっつきボイス: 「言いがかりに近いのもある気がするなぁ、ちゃんと使ってないだけだったりとか」「axlsxはメモり食うかもしれないけどこの手のgemとしてはいい方だと思う: 巨大CSV食わす感覚で巨大XLSX食わせるものじゃないと思うし」「rails_adminは同意: 管理画面系はだいたいろくなことにならない」「kaminariがあるんだからwill_paginateいらないっしょ」

そこからRails以外も含めたジョブ管理の話題に広がりました。

つっつきボイス: 「sidekiq-unique-jobsに限らず、ジョブ管理はだいたい大変になるよね」「消えていいジョブなんて普通はないし」「リトライを繰り返した末あきらめたりとか、リトライ中の状態がわからなかったり」「ひどいのになると詰まったジョブを殺して新しいジョブを作ってリトライする凶悪なジョブ管理もあったりとか」「ActiveJobはジョブ管理そのものじゃなくてインターフェイスを統一するものだし」

参考: Railsガイド: Active Jobの基礎

RubyのサーバーをRedisでMutexっぽく同期してみたお

http://blog.cloud66.com/より

Twitterで見かけた記事です。
RubyにはMutexというクラスがありますが、これは単独のプログラム内でのロック用です。
ここではRedisのlock機能を使って、Rubyアプリ間でMutexと同じような感じでロックを実現してみたのだそうです。

# http://blog.cloud66.com/ruby-mutex-mayhem-part-2/より
# cross-process/cross-server mutex
class RedisMutex

  attr_accessor :global_scope,
                :max_lock_time

  LOCK_ACQUIRER = "return redis.call('setnx', KEYS[1], 1) == 1 and redis.call('expire', KEYS[1], KEYS[2]) and 1 or 0"
  KEY_SPACE_PREFIX = '__keyspace@0__:'
  DEL_OR_EXPIRE_EVENTS = Set.new(['del', 'expired'])

  def initialize(global_scope, max_lock_time)
    # the global scope of this mutex (i.e "resource")
    @global_scope = global_scope
    # max time in seconds to hold the mutex
    # (in case of greedy deadlock)
    @max_lock_time = max_lock_time
  end

  def synchronise(local_scope = :global, &block)
    # get the lock
    acquire(local_scope)
    begin
      # execute the actions
      return block.call
    ensure
      # release the lock
      release(local_scope)
    end
  end
  ...

つっつきボイス: 「どうせならdrbでやりましょうよw(本当は大変だけど)」「drbって知らなかったー」「Rubyのネットワークプログラミングって実はかなり強力なんですよね」「synchroniseとかJavaっぽいw」

今気づきましたが、synchroniseだと英国風の綴りですね。米国だとsynchronizeです。ヨーロッパだと英国風のスペルが使われがちです。

Rubyリファレンスマニュアル: library drbには、「dRuby でインターネット上に公開するサービスを作るべきではありません」みたいなことが書いてあったりしますが、これはたぶん「インターフェースをインターネットに全開放するサービスを作るべきではない」ということなんだと思います。インターフェースをネットにフルで開放してはいけないのはdRubyに限りませんよね。

Railsconf 2017で仕入れたパフォーマンス関連の話(RubyWeeklyより)

5月にアリゾナで開催されたRailsconf 2017についてはRailsウォッチでもお知らせいたしましたが、その中からパフォーマンスに関連するメモ書きがたっぷり掲載されています。Railsconf 2017はかなり充実していたことがうかがえます。

  • Bootsnap
  • フロントエンドのパフォーマンス
  • アプリサーバーのパフォーマンス
  • rack-freeze
  • snip_snip
  • Rubyのinline threshold
  • 「あなたのアプリの設定は間違っている」by Heroku
  • パフォーマンスパネル
  • HTTP/2
  • Ruby Performance Research Group

「来年もカラオケやるぞー」と締めくくってます。

つっつきボイス: 「これけっこういいなー、量多いけど」「翻訳があったら読みたい」

RedCard: Ruby実装やバージョンを検出・制限するgem(RubyWeeklyより)

実行中のRubyの実装やバージョンの取得や、バージョンが合わない場合にexceptionを投げるgemのようです。

# MRIとRubiniusを1.9に限定
RedCard.verify :ruby, :rubinius, "1.9"


if RedCard.check "1.9.3", "2.0"
  # 1.9.3や2.0でのみ動くコードをここに書く
end

# Ruby 1.9とRubiniusを必須にする
require 'redcard/1.9'
require 'redcard/rubinius'

つっつきボイス: 「Railsならbundler経由で実行するときにGemfileのruby-versionを見るから不要かなー」

EmberとRails Stackでスモークテストを書く

同じサイトの別記事です。Ember.jsは「A framework for creating
ambitious web applications.」と謳っているように、かなり凝ったことができるMVCベースの欲張りさんなJSフレームワークです。メガネのタヌキみたいなのがマスコットです。


https://www.emberjs.com/より

この記事ではcapybaraやSelenium-webdriverなどを使い、Ember.jsでやらかしがちなエラーをいち早くキャッチする方法などを解説しています。

# https://blog.rubyroidlabs.com/2017/03/smoke-rails-ember/ より
#features/login.feature
@javascript
Feature: Login
  When a user visits "/", they should see form to login
  Scenario: User views login-page
    When I visit "/"
    Then I should see "SIGN IN"

#features/steps/login.rb
When(/^I visit "(.*?)"$/) do |path|
  visit path
end
Then(/^I should see "(.*?)"$/) do |text|
  page.should have_content(text)
end

つっつきボイス: 「スモークテストって耳慣れない用語だなー」「テストすら動かないような状況を避けるためのテストってことか」

開発作業でソースコードのちょっとした直し壊しやビルドの失敗に気付かず、動かないソフトウェアを次の工程に送っても差し戻されるだけで時間の無駄になる。こうした事態を避けるには、コンパイルやビルドした直後にそのソフトウェアが動くことを確認するのが効果的だ。これをスモークテストという。スモークテストは最低限のテストなので、「起動する」「基本機能が動作する」などを簡易に確認するだけでよいが、コンパイルやビルドするごとに行う必要がある。
情報システム用語事典 スモークテスト(すもーくてすと)より

ConvoxとRack: AWS上でHeroku的な環境を作れる

https://convox.com/より

convox/rackはHeroku的なPaaSをオープンソースでAWS上に構築できるようです。

つっつきボイス: 「そういえばHerokuってAWS上にあるのにAWSの機能やサービスをまったく受けられないんだよね」「Herokuよりもっと細かくカスタマイズしたい人向けなのかな?」「オンプレミスでもできるんだったら使いみちあるかも」


そこから少々脱線して、「Heroku」はどう発音するのかという話題になりました。

つっつきボイス: 「ヘロクって発音しているのをみると気になるなー: はーおーくーと発音して欲しい」「HerokuってHeroとHaikuを合わせたんだったっけ」「her oh kuu、って感じか」「英語圏の感覚だとherとokuで区切るかも」

Wikipedia: Herokuの意味によると、意味のある語にしたくなかったので造語にしたそうです。


そこからさらに、「課金」という言葉の使われ方についても話題になりました。

つっつきボイス: 「『課金する』っていう言葉も使い方がおかしいのをみかけますよ」「たしかに、『ユーザーが課金する』って既におかしい」「『ユーザーが課金される』ならいいけど」

参考: 「課金」の誤用が気になる

DeviseInvitableとRails API(RubyFlowより)

devise_invitable gemはDeviseにユーザーをメールでサイトに招待してパスワード設定までやってもらう機能を追加します。

つっつきボイス: 「これも自前でやった気がする」「こういうのがあるなら使うほうが楽かな」

securityheaders.io: セキュリティ関連のヘッダーをチェックできる

TechRacho記事「Railsアプリの認証システムをセキュアにする4つの方法」で知りました。もちろん悪用、いたずら厳禁。stagingサーバーあたりで使うぶんにはよいかもしれません。


https://securityheaders.io/より

つっつきボイス: 「Hall of Shameってのがいいなー」「Hall of Fameのダジャレですねw」「みんなHall of ShameのばっかりクリックしてるからRecentも真っ赤なFばっかりw」「これ日本だったら営業妨害呼ばわりされそうですね」

Quora: Bootstrap 4はいつになったらリリースされるんでしょうか?

つっつきボイス: 「いやほんとうに、いつになったら出るのか知りたいですよ」「もう出ないもんだと思ってますw」「5/24/2017の時点で92%だって」「コミットログは激しく更新されてるけど」


そこから、Bootstrapは3にしておけば十分という話題になりました。

つっつきボイス: 「Bootstrap 3と4の違いは小さいから、3にしとくのがいい」「2と3の変わりようはひどかったし」

LocalStack: AWSクラウド環境をローカルで動かしてテストとかに使える


https://github.com/atlassian/localstackより

AWS Summit 2017に参加したmorimorihogeさんが、@t_wadaさんの発表で注目したAtlassian製のツールです。

  • AWSのFake Objectを使える
  • AWSのサービスをlocalhostで起動できる
  • AWS接続のEndPointを変更するだけでよい(Mock/Stubは自前の実装が必要)
  • 特定言語に依存しない

といった特徴があるそうです。現時点では以下のサービスに対応しています。

  • API Gateway (http://localhost:4567)
  • Kinesis (http://localhost:4568)
  • DynamoDB (http://localhost:4569)
  • DynamoDB Streams (http://localhost:4570)
  • Elasticsearch (http://localhost:4571)
  • S3 (http://localhost:4572)
  • Firehose (http://localhost:4573)
  • Lambda (http://localhost:4574)
  • SNS (http://localhost:4575)
  • SQS (http://localhost:4576)
  • Redshift (http://localhost:4577)
  • ES (Elasticsearch Service)(http://localhost:4578)
  • SES (http://localhost:4579)
  • Route53 (http://localhost:4580)
  • CloudFormation (http://localhost:4581)
  • CloudWatch (http://localhost:4582)

morimorihoge: 「Managedなサービスのテストがやりやすくなる」「IAMもあればいいのに」

LivingStyleGuide: MarkdownとSass/SCSSで書けるフロントエンド向けスタイルガイド作成用gem(OpenRubyより)

フロントエンドだってスタイルガイドが欲しい、という方向け。カラーテーブルやフォント、コードなどもこうやって潤いのある感じに書けるようです。RailsやMiddlemanでも使えます。

バックエンド作業をやってるとつい後回しになってしまったりしますが、フロントエンドのスタイルガイドもちゃんと作りたいですね。

つっつきボイス: 「プログラマーが普通にMarkdownで書いただけだと見た目って残念になっちゃうよねw」「クライアントに見せるときにも使えそう」「どのぐらいまで自動でよしなにやってくれるのかな」「Railsのデモサイトがリンク切れっぽいなw」

Friends: 友だちとの活動をトラックできるgem(GitHub Trendingより)

友だちと会ったときの活動内容を入力して、後で頻度を集計したり次は誰と遊ぶかをおすすめしたりするCLIソフトウェアのようです。★500個超え、かつ熱心に更新されています。

$ friends suggest
Distant friend: Marie Curie
Moderate friend: Grace Hopper
Close friend: George Washington Carver

$ friends graph --with George
Nov 2017 |█∙∙|
Dec 2017 |∙∙|
Jan 2018 |█████∙∙|
Feb 2018 |███∙∙|

つっつきボイス: 「よーやるなーw」「このエネルギーはどこから来るんだろか?」

Gogs: Goで書かれたGitリポジトリ(GitHub Trendingより)

GitHubやGitLabのようなGitリポジトリソフトウェアです。星が2万個近くあります。

つっつきボイス: 「車輪を再発明したい人っているんだなーw」「Goでやりたいメリットって何だろう」「シングルバイナリだからデプロイが楽なんじゃ?」


今週は以上です。

関連記事

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやRSSなど)です。

Rails公式ニュース

Ruby Weekly

OpenRuby

RubyFlow

160928_1638_XvIP4h

Hacker News

160928_1654_q6srdR

Github Trending

160928_1701_Q9dJIU

Viewing all 120 articles
Browse latest View live