Quantcast
Channel: hachi8833の記事一覧|TechRacho by BPS株式会社
Viewing all 1838 articles
Browse latest View live

Rails tips: RSpecテストを遅くする悪い書き方3種(翻訳)

$
0
0

概要

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

Rails tips: RSpecテストを遅くする悪い書き方3種(翻訳)

テストが遅くなる原因はそれはもうさまざまで、コードに関係するものもあればそうでないものもあります。今回は、specを高速化して改善するちょっとした変更のコツをご紹介します。

1. truefalseを常に期待する場合はbe_truthybe_falseyを避ける

まずは以下のコードをちょっとご覧ください。

expect(true).to be_truthy
expect(1).to be_truthy
expect('string').to be_truthy
expect(nil).to be_falsey
expect(false).to be_falsey

どのexpectationもパスしますので、falsetrueが返っているとお考えになるかもしれませんが、何か不具合があってこれ以外のものが返されると、テストでキャッチできなくなってしまう可能性があります。

解決方法

値そのものを指定します。

expect(true).to eq(true)
expect(1).to eq(true)
expect('string').to eq(true)
expect(false)to eq(false)
expect(nil).to eq(false)

2. FactoryBot.buildは避ける

訳注: 原文のFactoryGirlはリンクを除いてFactoryBotに置き換えました。

FactoryBot.buildを呼び出してもデータベースにレコードは作成されないとお思いかもしれませんが、まあUse Factory Girl’s build_stubbed for a Faster Test Suiteをご覧ください。ファクトリー内で関連付けが宣言されていると、レコードが作成されてしまいます。たとえば次のようなファクトリーがあるとしましょう。

FactoryBot.define do
  factory :user do
    contact
    company
  end
end

そしてFactoryBot.build :userを呼び出すと、データベースにレコードが2件作成されます。テストの冒頭でファクトリーを初期化してexampleを10件実行すれば、レコードが20件も作成されます。このレコードが不要であれば、改善の余地が大いにあります。

解決方法

FactoryBotl.build_stubbedを使うことです。こちらはデータベースにレコードを作成することはありません。

3. スタブの代わりにModel.newするのは避ける

以下の例で考えてみましょう。シンプルなSampleClassクラスとSampleApiクラスがあります。

class SampleApi
  def login; end
end
class SampleClass
  def call
    api.login
  end

  private

  def api
    SampleApi.new
  end
end

そしてSampleClass#callメソッドをテストしたいとします。

require 'spec_helper'

describe SampleClass do
  describe '#call' do
    it 'calls API' do
      api = SampleApi.new
      allow(SampleApi).to receive(:new).and_return(api)
      allow(api).to receive(:login)

      sample_class = SampleClass.new
      sample_class.call

      expect(api).to have_received(:login).once
    end
  end
end

ここまではよさそうに見えます。お次はSampleApiに新しいメソッドを追加してそれを#loginメソッドより前に呼び出したいとします。やってみましょう。

class SampleApi
  def login;end
  def before_login_action;end
end
class SampleClass
  def call
    api.before_login_action
    api.login
  end

  private

  def api
    @api ||= SampleApi.new
  end
end

テストを実行してみるとやはりgreenのままです。クラスの実装を変更したのにこうなったというのは悪い知らせです。スタブ化されてないインスタンスを使うと、実行されたメソッドに対する制御が失われてしまいます。この手の問題を洗い出すのは大変で、テスト駆動開発(TDD)アプローチを行わない開発者がメソッドを更新した場合は特にそうです。

解決方法

代わりにinstance_doubleを使います。api = SampleApi.newapi = instance_double(SampleApi, login: double)に置き換えれば、ちゃんとエラーが出力されます。

Double "SampleApi (instance)" received unexpected message :before_login_action with (no args)

この解決法は元の書き方とくらべても決して遅くはなく、しかも完全にコントロール可能です。メソッドがn回実行されることを期待するメソッドに対してinstance_doubleを組み合わせれば、期待どおりの結果が得られます。

お知らせ: RSpec & TDDの電子書籍を無料でダウンロード

もっと稼ぎたい方や会社をさらに発展させたい方へ: テスティングのスキルの重要性にお気づきでしょうか?テストを正しく書き始めることが、唯一のファーストステップです。無料でダウンロードいただける私の書籍『RSpec & Test Driven Developmentの無料ebook』をどうぞお役立てください。

関連記事

Rails: テスティングアンチパターン –前編(翻訳)

Rails: テスティングアンチパターン –後編(翻訳)

TestProf: Ruby/Railsの遅いテストを診断するgem(翻訳)


MacのMagic Trackpadの不具合を修正する方法

$
0
0

こんにちは、hachi8833です。最早ないと生きていけないMacのトラックパッドですが、割りとピチパチプッチンと問題を引き起こしてくれます。

macOSでトラックパッドで不具合が起きたときのトラブルシューティング方法についてメモします。私のは古いMagic Trackpadですが、Magic Trackpad 2でも基本同じだと思います。

今回のトラブルは、「Bluetoothが接続できているのにトラックパッドがまったく反応しない」というものでした。

最初に試すべきこと3つ

1. トラックパッドの汚れを取り除く

自分はまだ遭遇していませんが、これだけで問題が解消することがあるそうです。

2. 「システム環境設定」のトラックパッドがらみの項目を単にオン/オフする

馬鹿馬鹿しいようですが、これで解決することがしばしばあります。macOSはどうもトラックパッドがらみの設定の扱いが微妙に下手くそな感じで、画面上ではオンになっているのにいつの間にか内部でオフになってたり、あるいはその逆になっていることがときどきあります。

その場合、チェックボックスの設定をオン/オフまたはオフ/オンすると、やっと書き込まれるようです。

変更対象は、「トラックパッド」と「アクセシビリティ/マウスとトラックパッド」です。

3. トラックパッドの電池を取り替える

今回の場合、トラックパッドに接続はされているのにまったくデータが流れず、バッテリー状態も更新されない(100%のまま)という現象が起きたのですが、後述の設定ファイル除去を行った後に電池をいったん外して入れ直すとやっとデータが流れるようになり、バッテリーが5%と表示されました。それで速攻電池を新品に交換し、上述の「項目オン/オフ」を行ってやっと解決しました。

おそらく電池が弱ってBluetoothの電磁波を送信するパワーが足りなかったか、ひっそりフリーズしてたのではないかと推測しています。おそらく実際には、後述の設定ファイルの問題と複合して起きていたかもしれません。

それでもうまくいかない場合

PRAMクリアとSMCリセットを試します。

PRAMクリア

  • 念のためMacの電源をいったんオフにし、10秒数えます。
  • コマンド(⌘)キーとoptionキーとPキーとRキーを押しながら、電源ボタンをオンにします。
  • そのまま押していると、おそらく2回再起動します。途中どのタイミングで手を離していいかいつも迷います。

SMCリセット

  • 念のためMacの電源をいったんオフにし、10秒数えます。
  • 左側のShiftキーとcontrolキーとoptionキーを押しながら、電源ボタンをオンにします。
  • どうなるとSMCリセットされるのか今ひとつわかりませんが、Macの電源ケーブルのパイロットランプが黄色になったり青になったりしています。

それでもうまくいかない場合

トラックパッドやBluetoothの設定ファイルやドライバファイルをいったんデスクトップあたりにどかし、再起動します。再起動すればこれらのファイルが再作成されます。

これらのファイルは、Macでユーザープロファイルを使っているかどうかで置き場所が変わるのでご注意ください。これらのファイルをどかすには基本的にroot権限が必要になります。

以下のディレクトリにあります。

  • ~/Library/Preferences
  • /Library/Preferences

私の場合は以下のファイルをどかしました。一部はない可能性もあります。

  • com.apple.AppleMultitouchTrackpad.plist
  • com.apple.Bluetooth.plist
  • com.apple.driver.AppleBluetoothMultitouch.mouse.plist
  • com.apple.driver.AppleBluetoothMultitouch.trackpad.plist

参考

関連記事

Mac: homebrew caskを一括アップデートできるhomebrew-cask-upgrade

macOSのアップデート失敗後にダウンロード前の状態に戻す

Railsのビューは頭悪そうなぐらいシンプルに保つべし(翻訳)

$
0
0

概要

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

Railsのビューは頭悪そうなぐらいシンプルに保つべし(翻訳)

Railsプロジェクトに長年携わっているうちに、最初は純真きわまりないビューだったのが、いつしかネストだらけの複雑怪奇なRubyコードと入り組んだHTMLを煮込んだようなものに変わり果てていました。こうなってしまうと、理解するのも大変なら改修するのも大変です。ビューの取るに足らないような部分をちょっぴり修正するだけでも、エンドツーエンドテストを辛抱強く書いてはバグがつぶされたかどうかを確認することになります。しかもこの種のテストはテストスイートの総実行時間に激しく影響するので、これ以上テストを増やしたくないと思うのが人情です。重要でも何でもないエッジケースならなおさらです。テストにはそうしたexpectationが全部詰まっているので、エンドツーエンドテストが増えれば増えるほどビューの改修が困難になります。

ビューの機能をどれだけ増やさずにいられるかが決め手です。ビューのロジックが複雑になればなるほど、テストも困難になります。複雑になれば、HTMLやらボタンのクリックやらCSSのセレクタなどなど、考えなければならないことがその分増えるからです。

この問題のシンプルな解決方法は、ロジックを別のクラスに移動することです。別クラスに切り出されたロジックならば単体テストも楽になります。単体テストは実行も早く、書くのも簡単です。

そこから抜粋したビューで考えてみましょう。少々込み入ったロジックも含まれています。

<div class="checkout">
  <% if @order.line_items.count > 0 %>
    <% if (@order.total - @order.paid) > 0 %>
      <div class="outstanding-amount"><%= number_to_currency(@order.total - @order.paid) %></div>
    <% end %>
    <div class="all-the-line-items"></div>

    <% if @order.cancelled_at.nil? && (@order.total - @order.paid) > 0 %>
      <%= link_to "Cancel your order", cancel_order_path(@order) %>
    <% end %>
  <% else %>
    Your order is empty!
  <% end %>
</div>

ここで何が行われているのかを読み取るには、考える必要があります。特にif条件を読み解くにはしばらく時間がかかるでしょう。

Railsプロジェクトの場合、ロジックの移動先の有力な候補はビューデコレータです。ロジックだけをここに移し、HTMLレンダリングから出来る限り切り離します。HTMLレンダリングのテストを書くのは、単純な戻り値テストを書くよりずっと面倒だからです。

1つのデコレータにすべてのロジックを詰め込まなければならないということはありません。1つのページだけを担当するデコレータを作成することも、ページの特定のセクションだけを担当するデコレータを作成するのも、ありです。1つのデコレータが、デコレータでない別のクラスのロジックを委譲しても構いません。デコレータについて詳しくは、Railsアンチパターン: Decoratorの肥大化に譲ります。

さて、上のビューのデコレータは以下のような感じになります。

class OrderDecorator < Draper::Decorator
  delegate_all

  def checkout_possible?
    line_items.count > 0
  end

  def can_be_cancelled?
    cancelled_at.nil? && !complete?
  end

  def complete?
    unpaid == 0
  end

  def unpaid
    total - paid
  end
end

ロジックをデコレータに切り出してみると、かなり読みやすいビューになりました。

<div class="checkout">
  <% if @order.checkout_possible? %>
    <% unless @order.complete? %>
      <div class="outstanding-amount">$ <%= number_to_currency(@order.unpaid) %></div>
    <% end %>
    <div class="all-the-line-items"></div>

    <% if @order.can_be_cancelled? %>
      <%= link_to "Cancel your order", cancel_order_path(@order) %>
    <% end %>
  <% else %>
    Your order is empty!
  <% end %>
</div>

このビューにはテストの必要なエッジケースがほとんどないことが完全に明らかです。誤りは肉眼で確認してもよいくらいです。複雑なエンドツーエンドテストを書く代わりにシンプルなテストを書けば済むので、時間も節約できます。テストスイートの実行も速くなるので、TDDサイクルが落ちることもさほどありません。ビューのロジックを排除すれば、油断なく見張り続ける必要もなくなるのでエネルギーの節約にもなります。

関連記事

Railsアンチパターン: Decoratorの肥大化(翻訳)

Rails: テストのリファクタリングでアプリ設計を改良する(翻訳)

RabbitMQはSidekiqと同等以上だと思う: 後編(翻訳)

$
0
0

概要

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

原文が長いため、3本に分割します。

RabbitMQはSidekiqと同等以上だと思う: 後編(翻訳)

特殊な機能

RabbitMQは、AMQPにさらなるマジックを加えてくれます。header exchangeの項でも述べたように、これはAMQPの標準にはない機能です。私が個人的に多用しているのはdirect reply-toです。direct reply-toは、プロデューサー(producer)とコンシューマーの間の同期的コミュニケーションのひとつの形態です。これによってプロデューサーがメッセージをパブリッシュしてコンシューマーの処理完了を待ち、結果を直接プロデューサーに返すことができるので、メッセージの処理結果をさらに処理する場合に便利です。たとえば、IoTデバイスではハートビート信号をサーバーにログ出力することで、接続中であることと正しく設定されていることを示し、スマートロックがかかっている場合にIoTデバイスのハートビートを非同期に処理できます。というのも、結果そのものはサーバーにとってもデバイスにとっても実際には重要ではないからです。しかしpingチェックは重要であり、データが古くなったことによるアクセス権エラーの発生を回避するために同期的に扱われる必要があります。

direct reply-toメッセージが使われている様子

他に私がよく使う機能にdead letteringというものもあります。これは、メッセージがexchangeやキューで拒否された場合に自動で再キューイングするものです。この機能は、エラーハンドリングや指数的バックオフ(exponential back-off)、スケジューリングされたメッセージ処理などで便利です。

訳注: dead letterは「配達不能郵便」の他に「空文、死文」という意味もあります。

dead letterキューが使われている様子

Alternate exchangeは、サービスの廃止処理(deprecation)を進めるのに便利です。これは、メインのexchangeがメッセージの受け取りを拒否した場合のメッセージ送信先となるexchangeを指定します

alternate exchangeが使われている様子

他にpriority queuespriority consumersという機能もあります。priority queueはコンピュータサイエンスで標準の「優先度付きキュー」のことで、キューに0から255までの優先度を与えて、優先度の高いメッセージから先に処理を開始するというものです。これは前述のdirect reply-toと同じ理由で便利ですが、direct reply-toは非同期です。一方priorityコンシューマーはフェイルオーバー形式です。priorityコンシューマーは、自分がアクティブな場合にメッセージを扱います。priorityコンシューマーがすべてアクティブでなくなると、それ以外のコンシューマーがメッセージを扱うようになります。

最後はTTLです。これはメッセージの生存期間(time to live)を指定するもので、メッセージの生存期間がTTL値を超えると自動的にキューで拒否されるようになります。ただしこの機能には1つ注意点があります。このルールを強制できるのは、メッセージがキューの先頭にある場合に限られます。たとえば、当初キューに2件のメッセージがあり、1件目のメッセージのTTLが300で2件目のメッセージのTTLが100だとしましょう。TTL=300の期限が切れるまでは、1件目のメッセージはキューの先頭にあり、どちらのメッセージもキュー上にあります。(1件目のTTL=300のメッセージの)期限が切れると、その瞬間2件目のメッセージが自動的にキューの先頭に移動し、それからその瞬間に自動的に期限切れになります(TTLを超えたので)。これは一見無害に見えますが、dead letteringによるアーカイブ化と組み合わせたときに(オフセット配信など)多数の問題が発生する可能性があります。

TTLメッセージにdead letteringを組み合わせた場合の様子

プラグイン

これは私にとってRabbitMQでもっとも重要な機能です。プラグインを用いてRabbitMQに欲しい機能を追加できます。その好例が管理コンソールで、これもプラグインであり、利用前に有効にして置かなければなりません。

RabbitMQはプラグインを用いることで、通信プロトコルとしてAMQPのみならずSTOMPMQTT、そしてWebSocketsをもサポートします。

その他に、相互通信可能な隔離されたクラスタやインスタンス上でRabbitMQを実行するフェデレーション(federation)プラグインもあります。しくみはMastodonのそれと似ていて、あらゆるユーザーは、どのサーバーにサインアップしたかにかかわらず、相互通信可能です。フェデレーションは莫大な負荷をさばく場合に便利です。たとえば、非常に多様なコンピュータから送信されるログをRabbitMQ経由で扱う場合、1つのフェデレーションクラスタ(federated cluster)はログを扱い、それ以外はすべて別のフェデレーションクラスタで扱うことができます。このように、クラスタの負荷に応じて2つのクラスタをそれぞれ独立にスケールすることができ、足手まといになる問題を回避できます(高負荷サービスに引きずられてシステム全体の速度が低下する場合)。

RabbitMQフェデレーションをIoTアプリに使った例

まとめ

SidekiqをRabbitMQに置き換えることで、デバッグのしやすさ、スケーリング、フォールトトレーランス、メモリ消費において多くのメリットを得られます。業界標準のメッセージキュープロトコルを多数サポートし、他の「バックグラウンドワーカー」ライブラリをまるっと置き換えるのに使えます。

ジョブ実行や永続性を保証したキューが必要であれば、Sidekiqの代わりにRabbitMQを使ってみましょう。cronジョブやunique jobのようにSidekiqにはあってもRabbitMQにまだない機能がありますが、クライアント側で追加は可能です。RabbitMQの提供する機能は過剰なぐらい多いので、最初は便利に思えなくても、将来モノリスがサービス指向アーキテクチャに成長するうえで役に立つようになるでしょう。

RabbitMQを始めるにあたり、まずはSneakers(バックグラウンドジョブ)やBunny(AMQPクライアント)をざっと眺め、基本コンセプトを読んで理解しておき、それからマニュアルを読みましょう。Ruby on Railsをお使いの方は、移行を楽にするSneakersとActiveJobを統合する方法をご覧ください。

Sidekiqももちろん優秀です!実行や永続性の保証が不要なプロジェクトや、既にSidekiq Proライセンスをお持ちの場合は、当面Sidekiqを使い続けることをおすすめします。私自身は、実行保証やローリング再起動といった機能が有料である点を残念に思いますが、Sidekiq Proライセンスを購入すれば、自力でホストしたRabbitMQインスタンスでは得られないサポートを得られ、さまざまな実ビジネス指向の機能も利用できるようになります

関連記事

Rails: RedisキャッシュとRackミドルウェアでパフォーマンスを改善(翻訳)

RailsのPostgreSQL上でマルチテナントのジョブキューシステムを独自構築する(翻訳)

Goby: Rubyライクな言語(3)Go言語の`defer`を減らしたら10%以上高速化した話など

$
0
0

こんにちは、hachi8833です。ゴールデンウィーク中日いかがお過ごしでしたしょうか。

Gobyを完全に理解してから書こうとするといつまでたっても書けないので、悩んでいることも含めて書くことにしました。

Go言語のdeferはかなり遅い

ごく最近、GobyのSlackで「ちょいとベンチマーク取ってみたら、改良できそうなところが目についた: Goのdeferはコストが高いので、これを減らすだけで高速化できそう」という書き込みがありました。

それを受けてst0012さんが早速VMのスタックからdeferを取り除きました。正直、私がVM部分のコードをまじまじと覗き込んだのはこれが初めてかもしれません。

...
-   *sync.RWMutex
+   sync.RWMutex
 }

 func (s *stack) set(index int, pointer *Pointer) {
    s.Lock()

-   defer s.Unlock()
-
    s.Data[index] = pointer
+
+   s.Unlock()
 }

 func (s *stack) push(v *Pointer) {
    s.Lock()
-   defer s.Unlock()

    if len(s.Data) <= s.thread.sp {
        s.Data = append(s.Data, v)
    } else {
        s.Data[s.thread.sp] = v
    }

    s.thread.sp++
+   s.Unlock()
 }
...

defer s.Unlock()を外してs.Unlock()に置き換えています。ついでに*sync.RWMutexのポインタもやめています。

ベンチマークの結果、基本的な演算が確かに速くなってます。Gobyの最適化は機能が揃ってからの予定ですが、こういうリファクタリングは歓迎ですね。

go test -run '^$' -bench '.' -benchmem -benchtime 2s ./... > .tmp_benchmarks
benchcmp current_benchmarks .tmp_benchmarks
benchmark                         old ns/op     new ns/op     delta
BenchmarkBasicMath/add-8          6500          5783          -11.03%
BenchmarkBasicMath/subtract-8     6461          5723          -11.42%
BenchmarkBasicMath/multiply-8     6484          5709          -11.95%
BenchmarkBasicMath/divide-8       6445          5768          -10.50%

benchmark                         old allocs     new allocs     delta
BenchmarkBasicMath/add-8          78             78             +0.00%
BenchmarkBasicMath/subtract-8     78             78             +0.00%
BenchmarkBasicMath/multiply-8     78             78             +0.00%
BenchmarkBasicMath/divide-8       78             78             +0.00%

benchmark                         old bytes     new bytes     delta
BenchmarkBasicMath/add-8          3792          3792          +0.00%
BenchmarkBasicMath/subtract-8     3792          3792          +0.00%
BenchmarkBasicMath/multiply-8     3792          3792          +0.00%
BenchmarkBasicMath/divide-8       3792          3792          +0.00%

と思ったら、さらに「deferをさらに減らしてもっと速くした」「スレッドをAPI化してVMから切り出してみようと思う」と追い打ちが来ました。何それ凄い。
まだ私は全然追いきれてませんが、「こういうのはスレッドあたりのミューテックスやdeferを1つだけにしておくのがいいんですよ」だそうです。

benchmarking 2fd6637efac2bf8c371f020ab1e95aa0f7606873
benchmarking 5cfab57be7564a02bf15dd8ac63b9e66685014d9
universal.x86_64-darwin17
benchmark                         old ns/op     new ns/op     delta
BenchmarkBasicMath/add-8          5950          5473          -8.02%
BenchmarkBasicMath/subtract-8     5884          5416          -7.95%
BenchmarkBasicMath/multiply-8     5967          5418          -9.20%
BenchmarkBasicMath/divide-8       5916          5502          -7.00%

benchmark                         old allocs     new allocs     delta
BenchmarkBasicMath/add-8          78             78             +0.00%
BenchmarkBasicMath/subtract-8     78             78             +0.00%
BenchmarkBasicMath/multiply-8     78             78             +0.00%
BenchmarkBasicMath/divide-8       78             78             +0.00%

benchmark                         old bytes     new bytes     delta
BenchmarkBasicMath/add-8          3792          3792          +0.00%
BenchmarkBasicMath/subtract-8     3792          3792          +0.00%
BenchmarkBasicMath/multiply-8     3792          3792          +0.00%
BenchmarkBasicMath/divide-8       3792          3792          +0.00%
Switched to branch 'thread-rework'

いずれにしろまだ作業中なので、今後が楽しみです。

最近のGoby

Ruby X Elixir Conf Taiwan 2018での発表

作者のst0012さんがRuby X Elixir Conf Taiwan 2018の2日目にGobyについて発表しました。なおst0012さんは台湾の方です。

スライドをWikiに設置

前回の記事に掲載したGobyの紹介スライドをWikiに配置しました。GitHubのMarkdownにはスライドや動画の埋め込みができないので、考えた挙句画像だけ貼り、そこからリンクでスライドに飛ばすようにしました。

「アンスコ数字形式1_000_000が欲しい」

#660で、1_000_000みたいなリテラル形式が欲しいというリクエストがありました。ついでに0b001001とか1.2e-3なども欲しいという意見もあり、「いずれは全部取り入れたいけど今じゃない」とst0012さんが締めました。

Ripperクラスを作ってみた

Gobyでいずれlinter的なことを標準で行うのであれば、RubyのRipperみたいな標準パーサーがいずれ必要になるだろうと思って、見よう見まねでよちよち作ってみました(#658)。Goby自身のlexerやparserに委譲するだけなのでそんなに大変ではないかなと思ったのですが、型変換が意外にめんどかった…

func builtInRipperClassMethods() []*BuiltinMethodObject {
    return []*BuiltinMethodObject{
...
            Name: "instruction",
            Fn: func(receiver Object, sourceLine int) builtinMethodBody {
                return func(t *thread, args []Object, blockFrame *normalCallFrame) Object {
                    if len(args) != 1 {
                        return t.vm.initErrorObject(errors.ArgumentError, sourceLine, "Expect 1 argument. got=%d", len(args))
                    }

                    arg := args[0]
                    switch arg.(type) {
                    case *StringObject:
                    default:
                        return t.vm.initErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, arg.Class().Name)
                    }

                    i, err := compiler.CompileToInstructions(arg.toString(), NormalMode)
                    if err != nil {
                        return t.vm.initErrorObject(errors.TypeError, sourceLine, errors.InternalError, classes.StringClass, errors.InvalidGobyCode)
                    }

                    return t.vm.convertToTuple(i)
                }
            },
...

func (vm *VM) convertToTuple(instSet []*bytecode.InstructionSet) *ArrayObject {
    ary := []Object{}
    for _, insts := range instSet {
        hInsts := make(map[string]Object)
        hInsts["name"] = vm.initStringObject(insts.Name())
        hInsts["type"] = vm.initStringObject(insts.Type())
        if insts.ArgTypes() != nil {
            hInsts["arg_types"] = vm.getArgNameType(insts.ArgTypes())
        }
        ary = append(ary, vm.initHashObject(hInsts))

        aInst := []Object{}
        for _, ins := range insts.Instructions {
            hInst := make(map[string]Object)
            hInst["action"] = vm.initStringObject(ins.Action)
            hInst["line"] = vm.initIntegerObject(ins.Line())
            hInst["source_line"] = vm.initIntegerObject(ins.SourceLine())
            anchor, _ := ins.AnchorLine()
            hInst["anchor"] = vm.initIntegerObject(anchor)

            aParams := []Object{}
            for _, param := range ins.Params {
                aParams = append(aParams, vm.initStringObject(param))
            }
            hInst["params"] = vm.initArrayObject(aParams)

            if ins.ArgSet != nil {
                hInsts["arg_set"] = vm.getArgNameType(ins.ArgSet)
            }

            aInst = append(aInst, vm.initHashObject(hInst))
        }

        hInsts["instructions"] = vm.initArrayObject(aInst)
        ary = append(ary, vm.initHashObject(hInsts))
    }
    return vm.initArrayObject(ary)
}

func (vm *VM) getArgNameType(argSet *bytecode.ArgSet) *HashObject {
    h := make(map[string]Object)

    aName := []Object{}
    for _, argname := range argSet.Names() {
        aName = append(aName, vm.initStringObject(argname))
    }
    h["names"] = vm.initArrayObject(aName)

    aType := []Object{}
    for _, argtype := range argSet.Types() {
        aType = append(aType, vm.initIntegerObject(argtype))
    }

    h["types"] = vm.initArrayObject(aType)
    return vm.initHashObject(h)
}

instructionメソッドの部分を抜粋してみました。配列の中にハッシュや別の配列が入る形になっています。Gobyの配列はGoのスライスを、ハッシュはmapを使っていますが、使い分けが少々ややこしいです。
プルリクが通るかどうかはわかりませんが、正味2日ほどで作れたのはIDE(JetBrainsのGoland)という強い味方のおかげです。感謝です。私がRubyのようにC言語で書くとしたら何年かかるかわかったものではありません。適当ですが、mattnさんなら3時間もあれば作っちゃうんじゃないかしら。

とはいうもののまだ改良の余地はあって、何とGo言語にはソースの(定数の値ではなく)定数名を直接取得する手段がないらしいので、一部を以下のような直書きでしのいでいます。コンパイラ言語だししょうがないか。

func convertLex(t token.Type) string {
    var s string
    switch t {
    case token.Asterisk:
        s = "asterisk"
    case token.And:
        s = "and"
    case token.Assign:
        s = "assign"
...
    default:
        s = strings.ToLower(string(t))
    }

    return "on_" + s
}

いろいろ調べたところ、GoのStringerというライブラリを使えば、型指定されている定数を元に定数名を取得する関数を生成できるようなのですが、試してみると定数値が1, 2, 3のような連続した数値(iotaとかいうのを使うと簡単にできる)でないとうまくいかない感じなので諦めました。

参考: go generateでコードを自動生成する

さらに調べたところ、go generateコマンドをコードのコメントに書いておけば任意のUnixコマンドをトリガできるらしいので、それを使ってGobyのtoken.goからこのコードを自動生成するスクリプトを書くのがよさそうです。そのうちやります。

ただしgo buildでは自動生成がトリガされないので、手動なりmakeなりシェルスクリプトなりでコンパイル前にgo generateを実行しておく必要があるようです。確かにセキュリティを考えればそんなことしない方がいいでしょうね(go getとかgo installで知らないコマンドが実行されたらコワイ)。

その後st0012さんから、「#662でローダブルなライブラリをVMから切り出す作業が進められていて、Ripperもそれと同じように配置すべきなので、そっちが終わったら再開しようか」とコメントがありました。(`Д´)ゞラジャー!!

関連記事

Goby: Rubyライクな言語(2)Goby言語の全貌を一発で理解できる解説スライドを公開しました!

Goby: Rubyライクな言語(1)Gobyを動かしてみる

Ruby: 高速/高性能ルーティングエンジンgem「Roda」README: 前編(翻訳)

$
0
0
  • 次記事: Ruby: 高速/高性能ルーティングエンジンgem「Roda」README: 中編(翻訳)

概要

MITライセンスに基いて翻訳・公開いたします。


roda.jeremyevans.net/より

長いので3本に分割します。
本記事では、原則としてroutesやroutingは「ルーティング」、rootは「ルート」と表記します。

Ruby: 高速/高性能ルーティングエンジンgem「Roda」README: 前編(翻訳)

Rodaとは、Rubyで高速かつメンテナンス性の高いWebアプリを構築するためのルーティングツリーWebツールキットです。

インストール

$ gem install roda

リソース

Webサイト
http://roda.jeremyevans.net
ソースコード
https://github.com/jeremyevans/roda
バグ
https://github.com/jeremyevans/roda/issues
Google Group
https://groups.google.com/forum/#!forum/ruby-roda
IRCチャット
irc://chat.freenode.net/#roda

目指すもの

  • シンプル
  • 高信頼性
  • 高拡張性
  • ハイパフォーマンス

シンプル

Rodaは、内部外部のいずれもがシンプルになるように設計されています。「ルーティングツリー」を採用したことで、従来よりもシンプルかつDRYなコードを書けます。

高信頼性

Rodaは「イミュータブル」をサポートおよび促進します。Rodaアプリはproductionでfrozenされるように設計されており、スレッド安全性の問題が発生する可能性を排除しています。
さらにRodaでは、アプリで使われるインスタンス変数や定数やメソッドとの名前衝突を避ける目的で、Rodaで使われるインスタンス変数や定数やメソッドの個数を抑えています。

高拡張性

Rodaは完全にプラグインベースで構成されるため、拡張性が極めて高くなっています。Rodaのどんな部分でも、自由自在にオーバーライドしたりsuperを呼んでデフォルトの振る舞いを得たりできます。

ハイパフォーマンス

Rodaではリクエストごとのオーバーヘッドを低く抑えており、ルーティングツリーや、内部データ構造のインテリジェントキャシングによって、よく知られている他のRuby製Webフレームワークよりも著しく高速に動作します。

使い方

ルーティングツリーの動作を示すシンプルなアプリです。

# cat config.ru
require 'roda'

class App < Roda
  route do |r|
    # GET / request
    r.root do
      r.redirect '/hello'
    end

    # /hello branch
    r.on 'hello' do
      # /helloブランチのすべてのルーティングで使う変数を設定
      @greeting = 'Hello'

      # GET /hello/world request
      r.get 'world' do
        "#{@greeting} world!"
      end

      # /hello request
      r.is do
        # GET /hello request
        r.get do
          "#{@greeting}!"
        end

        # POST /hello request
        r.post do
          puts "Someone said #{@greeting}!"
          r.redirect
        end
      end
    end
  end
end

run App.freeze.app

上で行われている内容をブロックごとに小分けにして説明します。

routeブロックは、新しいリクエストを受け取ったときに必ず呼ばれます。ここではRack::Requestのサブクラスにルーティングマッチ用のメソッドが若干追加された、そのサブクラスのインスタンスが生成されます。このブロックの引数名は慣習に則ってrとすべきです。

Rodaでのルーティングのマッチは、主にr.onr.isr.rootr.get``r.postを呼ぶことで行います。これらの「ルーティングメソッド」はどれも「マッチブロック」を1つ取れます。

各ルーティングメソッドは1つ以上の引数(マッチャー)を受け取り、現在のリクエストとマッチするかどうかを順に試行します。メソッドの引数がすべてマッチするとマッチブロックをyieldし、1つでもマッチしないとブロックをスキップして次に進みます。

  • r.on: 引数がすべてマッチするとマッチと判定します
  • r.is: 引数がすべてマッチし、かつマッチした部分より先のエントリがパスにない場合にマッチと判定します
  • r.get: 引数なしで呼び出されると、あらゆるGETをマッチと判定します
  • r.get:(1つ以上の引数で呼び出されると)、現在のリクエストがGETで、かつマッチした部分より先のエントリがパスにない場合にのみマッチと判定します
  • r.root: 現在のパスが/になっているGETリクエストのみをマッチと判定します

Rodaは、ルーティングメソッドがマッチして制御がマッチブロックにyieldされると、マッチブロックから戻るときに必ずRackレスポンスの配列(ステータス、ヘッダー、bodyを含む)を呼び出し元に返します。

マッチブロックが文字列を返し、レスポンスのbodyがまだそこに書き込まれていない場合は、ブロックの戻り値をレスポンスのbodyとして解釈します。どのルーティングメソッドともマッチせず、routeブロックが文字列を返す場合は、その文字列をレスポンスのbodyとして解釈します。

r.redirectはレスポンスを即座に返すので、r.redirect(path) if 条件のような書き方ができます。r.redirectが引数なしで呼ばれ、現在のリクエストメソッドがGETではない場合、現在のパスにリダイレクトします。

末尾に.freeze.appをオプションで追加できます。アプリをfreezeすると、アプリレベルの設定を変更しようとしたときにエラーがraiseされるので、アプリのスレッド安全性問題の可能性を事前に警告できます。.appは、リクエストごとのメソッド呼び出しを若干節約する、一種の最適化です。

ルーティングツリー

Rodaは「ルーティングツリーWebツールキット」と呼ばれていますが、その理由は、ルーティングが(サイトのURL構造に基いて)ツリーを形成するかたちでほとんどのサイトが構造化されるためです。一般に次が成り立ちます。

  • r.on: ツリーを異なる枝(branch)に枝分かれするのに用います
  • r.is ルーティングパスの終端を定めます
  • r.getr.post: 特定のリクエストメソッドを扱います

したがって、シンプルなルーティングツリーは次のような感じになります。

r.on 'a' do           # /a branch
  r.on 'b' do         # /a/b branch
    r.is 'c' do       # /a/b/c request
      r.get {}        # GET  /a/b/c request
      r.post {}       # POST /a/b/c request
    end
    r.get 'd' do end  # GET  /a/b/d request
    r.post 'e' do end # POST /a/b/e request
  end
end

(異なる枝が)同じリクエストを扱うこともできますが、リクエストメソッドの最初の枝分かれでルーティングツリーが構造化されます。

r.get do # GET
  r.on 'a' do         # GET /a branch
    r.on 'b' do       # GET /a/b branch
      r.is 'c' do end # GET /a/b/c request
      r.is 'd' do end # GET /a/b/d request
    end
  end
end

r.post do             # POST
  r.on 'a' do         # POST /a branch
    r.on 'b' do       # POST /a/b branch
      r.is 'c' do end # POST /a/b/c request
      r.is 'e' do end # POST /a/b/e request
    end
  end
end

このようになっていることで、GETリクエストの扱いとPOSTリクエストの扱いを簡単に分離できます。扱うPOSTリクエストのURLが少なく、GETリクエストのURLが多い場合はさらに簡単です。

ただし、パスによるルーティングを冒頭に配置し、リクエストメソッドによるルーティングを末尾に配置する方が、シンプルでDRYなコードになりやすくなるでしょう。このようなことが可能なのは、ルーティング中のどの時点でもリクエストを扱えるからです。たとえば、/aブランチではすべてのリクエストでAというパーミッションが必要で、/a/bブランチではBというパーミッションが必要だとすると、次のようにこれらを簡単にルーティングツリーで扱えます。

r.on 'a' do           # /a branch
  check_perm(:A)
  r.on 'b' do         # /a/b branch
    check_perm(:B)
    r.is 'c' do       # /a/b/c request
      r.get {}        # GET  /a/b/c request
      r.post {}       # POST /a/b/c request
    end
    r.get 'd' do end  # GET  /a/b/d request
    r.post 'e' do end # POST /a/b/e request
  end
end

ルーティング中の任意の時点でリクエストを自由に操作できる点が、Rodaの大きな強みのひとつです。

マッチャー

r.rootを除くあらゆるルーティングメソッドは、1つまたは複数のマッチャーを引数として取ることができます。マッチャーがすべてマッチすると、そのルーティングメソッドはマッチブロックをyieldします。以下はさまざまなマッチャーの動作を示すコード例です。

class App < Roda
  route do |r|
    # GET /
    r.root do
      'Home'
    end

    # GET /about
    r.get 'about' do
      'About'
    end

    # GET /post/2011/02/16/hello
    r.get 'post', Integer, Integer, Integer, String do |year, month, day, slug|
      "#{year}-#{month}-#{day} #{slug}"             #=> "2011-02-16 hello"
    end

    # GET /username/foobar branch
    r.on 'username', String, method: :get do |username|
      user = User.find_by_username(username)

      # GET /username/foobar/posts
      r.is 'posts' do
        # ブロックはクロージャなので、ここでユーザーにアクセスしてもよい
        "Total Posts: #{user.posts.size}"           #=> "Total Posts: 6"
      end

      # GET /username/foobar/following
      r.is 'following' do
        user.following.size.to_s                    #=> "1301"
      end
    end

    # /search?q=barbaz
    r.get 'search' do
      "Searched for #{r.params['q']}"               #=> "Searched for barbaz"
    end

    r.is 'login' do
      # GET /login
      r.get do
        'Login'
      end

      # POST /login?user=foo&password=baz
      r.post do
        "#{r.params['user']}:#{r.params['password']}" #=> "foo:baz"
      end
    end
  end
end

個別のマッチャーについて以下で解説します。文中の「セグメント」とは、/で始まるパスの一部を指します。つまり、/foo/bar//bazには/foo/bar//bazという4つのセグメントがあります。3番目の/は空のセグメントと見なされます。

文字列

文字列にスラッシュ/が含まれていない場合、/で始まり、その文字列のテキストを含む1つのセグメントにマッチします。

""         # "/"にマッチ
"foo"      # "/foo"にマッチ
"foo"      # "/food"にはマッチしない

文字列にスラッシュ/が1つ以上含まれている場合は、/で区切られた1つの追加セグメントにマッチします。

"foo/bar" # "/foo/bar"にマッチ
"foo/bar" # "/foo/bard"にはマッチしない

正規表現

正規表現は、スラッシュ/で始まり、/またはパスの終端で終わるパターンを検索することで、1つまたは複数のセグメントにマッチします。

/foo\w+/     # "/foobar"にマッチ
/foo\w+/     # "/foo/bar"にはマッチしない
/foo/i       # "/foo"や"/Foo/"にマッチ
/foo/i       # "/food"にはマッチしない

いずれかのパターンが正規表現でキャプチャされると、yieldされます。

/foo\w+/     # "/foobar"にマッチ(yieldされない)
/foo(\w+)/   # "/foobar"にマッチ("bar"をyield)

クラス

マッチャーはStringIntegerでサポートされます。

String
空でない任意のセグメントにマッチし、/で始まる場合を除いてそのセグメントをyieldする
Integer
09の任意のセグメントにマッチし、マッチした値を整数で返す

任意のセグメントを扱う場合は、StringIntegerの利用をおすすめします。

String     # "/foo"にマッチし、"foo"をyield
String     # "/1"にマッチし、"1"をyield
String     # "/"にはマッチしない

Integer    # "/foo"にはマッチしない
Integer    # "/1"にマッチし、1をyield
Integer    # "/"にはマッチしない

シンボル

シンボルは、空でない任意のセグメントにマッチし、先頭の/を除いた部分をyieldします。

:id # matches "/foo" yields "foo"
:id # does not match "/"

シンボルマッチャーによる操作はStringクラスのマッチャーと同じであり、かつて任意のセグメントマッチを行う方法として使われていました。新しいコードではStringクラスのマッチャーを使うことをおすすめします(直感的に書けるので)。

proc

procは、(falsenilを返すものを除き)あらゆるものにマッチします。

proc{true}    # あらゆるものにマッチ
proc{false}   # どれにもマッチしない

procはデフォルトではキャプチャを行いません。しかしキャプチャしたテキストをr.capturesに追加すれば可能です。

配列

配列は、その要素のどれか1つでもマッチした場合にマッチしたと判断されます。複数のマッチャーをr.onに渡す場合は、そのすべてにマッチしなければマッチしたと判断されません(AND条件)が、マッチャーの配列を渡す場合は、その中のどれか1つがマッチする必要があります(OR条件)。条件の評価は、マッチャーが最初にマッチした時点で終了します。

さらに、マッチしたオブジェクトがStringの場合、その文字列がyieldされます。これを用いれば、正規表現を使わずに複数の文字列マッチを簡単に取り扱えます。

['page1', 'page2'] # "/page1"か"/page2"にマッチ
[]                 # どれにもマッチしない

ハッシュ

ハッシュを使うと、リクエストで特殊なマッチメソッドを簡単に呼び出せます。Rodaでデフォルトで使える登録済みマッチャーについては後述します。一部のプラグインはハッシュマッチャーを追加します。hash_matcherプラグインを使うと、独自のハッシュマッチャーを簡単に定義できます。

class App < Roda
  plugin :hash_matcher

  hash_matcher(:foo) do |v|
    # ...
  end

  route do |r|
    r.on foo: 'bar' do
      # ...
    end
  end
end

:all

:allは、渡された配列のすべてのエントリとマッチした場合にマッチしたと判断します。

r.on all: [String, String] do
  # ...
end

つまり、上のコードは下と同等です。

r.on String, String do
  # ...
end

:allがハッシュマッチャーとしても存在している理由は、配列マッチャーの中でも使えるようにするためです。

r.on ['foo', {all: ['foos', Integer]}] do
end

上のコードは、/foo/foos/10とはマッチしますが、/foosとはマッチしません。

:method

:methodマッチャーは、リクエストメソッド(訳注: GETPOSTなどのHTTPメソッド)とマッチします。複数のリクエストメソッドを配列として渡すと、そのいずれかとマッチします。

{method: :post}               # POSTとマッチ
{method: ['post', 'patch']}   # POSTかPATCHにマッチ

falsenil

マッチャーにfalsenilを直接渡すと、あらゆるものにマッチしなくなります。

その他

これ以外の場合はエラーをraiseします。ただし、プラグインが別種のマッチャーを追加するなどで特定のサポートが追加された場合は、この限りではありません。


  • 次記事: Ruby: 高速/高性能ルーティングエンジンgem「Roda」README: 中編(翻訳)

関連記事

Ruby: 認証gem ‘Rodauth’ README(翻訳)

Railsのルーティングを極める(前編)

Railsのルーティングを極める (後編)

GitHub issueにリポジトリのコードをスニペットとして楽に埋め込み引用する方法

$
0
0

こんにちは、hachi8833です。休暇中にGobyちゃんで遊んでいるときに、GitHub issueにリポジトリのコードをスニペットとして埋め込み引用できるようになってたことに今頃気づいたのでメモします。マジ便利です。

1行だけ引用することも、行範囲を引用することもできます。

以下はGobyの#660より。コードスニペットが埋め込まれるとこんな感じになります。

編集ボックスを開くとこんな感じです。

私も早速#663で埋め込んでみました。おー楽ちん。

一応探してみたところ、昨年8月にIntroducing embedded code snippets | The GitHub Blogで公式にお知らせがありました。コードからissueを開いて埋め込み引用することもできるんですね。


同記事より

関連記事

「巨大プルリク1件vs細かいプルリク100件」問題を考える(翻訳)

いい感じのコードには速攻でLGTM画像を貼ってあげよう

Rails tips: 知らないと損する4つのバリデーションレベル(翻訳)

$
0
0

概要

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

Rails tips: 知らないと損する4つのバリデーションレベル(翻訳)

考えるまでもないことですが、アプリがユーザー入力を受け取ったらバリデーションが必要になります。Ruby on Railsアプリでバリデーションといえば真っ先に思い当たるのがモデルのバリデーションです。しかしそれ以外のレベルのバリデーションについてはどうでしょう。モデルのバリデーションがあれば完璧なソリューションになるのでしょうか?今回はRailsアプリの4つのレベルのバリデーションを簡単にご紹介しつつ、それぞれのメリットとデメリットについて説明したいと思います。お題として、Userモデルのemailカラムを使います。

1. モデルレベルのバリデーション

Railsアプリでよく見られるアプローチです。emailUserのレコードに確実に存在するようにするために、以下のバリデーションを定義できます。

class User < ActiveRecord::Base
  validates :email, presence: true
end

このデータ保護方法は間違っていませんが、これだけではメールが空のUserレコードをまだ作成できてしまう点を肝に銘じてください。User#saveUser#save!を呼んでも無効なレコードはデータベースに保存されませんが、以下のメソッドを呼べば保存されてしまうのです。

  • decrement!
  • decrement_counter
  • increment!
  • increment_counter
  • toggle!
  • touch
  • update_all
  • update_attribute
  • update_column
  • update_columns
  • update_counters

これらのメソッドを用いる場合、特にupdate_で始まるメソッドについては注意が必要です。では逆に、emailカラムのバリデーションが不要な場合はどうすればよいでしょうか?その場合は:ifオプションを渡すか、コントローラレベルのバリデーションを検討しましょう。

2. コントローラレベルのバリデーション

上述したように、特定の場合に限ってバリデーションを行いたいことがあります。:ifオプションや:unlessオプションを渡してもよいのですが、バリデーションルールが複雑になって読みづらくなったりテストがしにくくなるかもしれません。そこでコントローラレベルのバリデーションが選択肢として浮かび上がってきます。これを正しく行うには、Form Objectパターンの利用をおすすめします。ただし、コントローラレベルのバリデーションはモデルレベルのバリデーションに比べてメンテの難易度がぐっと上がります。Form Objectパターンは、モデルに多数のバリデーションがあり、ときどき必須にしたいバリデーションやときどきオプションにしたいバリデーションがあるような非常に大規模なアプリでとても有用です。

訳注: Form Objectについては以下の記事やForm Objectタグなどもどうぞ。

Rails: Form Objectと`#to_model`を使ってバリデーションをモデルから分離する(翻訳)

3. データベースレベルのバリデーション

データベースレベルのバリデーションは最も安全性が高く、これをかいくぐって無効な値を保存することはできません。よく使われるのはpresenceuniquenessで、どちらもUserモデルのemailカラムにうってつけです。以下のようにマイグレーションを作成して追加します。

class AddValidationOnUserEmail < ActiveRecord::Migration
  def change
    change_column :users, :email, :string, null: false
    add_index :users, :email, unique: true
  end
end

rake db:migrateを実行すればバリデーションをテストできます。モデルのバリデーションを呼び出さないupdate_columnメソッドを使ってみましょう。メールアドレスのないユーザーを試しに保存してみます。

user = User.find(user_id)
user.update_column(:email, nil) # => raises ActiveRecord::StatementInvalid: Mysql2::Error: Column 'email' cannot be null

ちゃんとエラーがraiseされました。今度はメールアドレスが重複しているユーザーを保存してみます。

user = User.find(user_id)
user_2 = User.find(user_2_id)

user.update_column(:email, user_2.email)
# => raises ActiveRecord::RecordNotUnique: Mysql2::Error: Duplicate entry

2つのバリデーションは種類が異なります。presenceバリデーションはカラム定義に渡せばよいのですが、uniquenessバリデーションの場合はデータベースに正しいインデックスを作成しなければなりません。このエラーは、無効なデータを保存しようとしたときにもraiseされます。

user = User.find(user_id)
user.update_column(:created_at, "string")
# => raises ActiveRecord::StatementInvalid: Mysql2::Error: Incorrect datetime value

ご覧いただいたように、パフォーマンス上の意味だけではなく、セキュリティのためにも、カラムを慎重に定義して正しくインデックスを作成することが重要です。

4. フロントエンドのバリデーション

最も安全性の低いバリデーションです。ブラウザでJavaScriptをオフにしたり、コードを使ってリクエストを直接送信したり、Postmanなどのブラウザ拡張を使ったりすれば、このバリデーションをバイパスできます。

データの保護はどんな場合であっても、バックエンド側のバリデーションで最初に行うべきです。しかしフロントエンドのバリデーションは、ユーザーエクスペリエンス向上には最も適しています。私は、フォーム送信を待たずにアプリがその場でフォームのエラーを表示してくれるのが好きです。

新着記事を見逃したくない方はTwitterをフォローしてください。もちろん「hello」だけでも構いません!

お知らせ: RSpec & TDDの電子書籍を無料でダウンロード

もっと稼ぎたい方や会社をさらに発展させたい方へ: テスティングのスキルの重要性にお気づきでしょうか?テストを正しく書き始めることが、唯一のファーストステップです。無料でダウンロードいただける私の書籍『RSpec & Test Driven Developmentの無料ebook』をどうぞお役立てください。

関連記事

Rails tips: カスタムバリデータクラスを作る(翻訳)

Rails: Form Objectと`#to_model`を使ってバリデーションをモデルから分離する(翻訳)


Ruby: 紛らわしい条件文を書かないこと(翻訳)

$
0
0

概要

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

Ruby: 紛らわしい条件文を書かないこと(翻訳)

Rubyの条件構文は「trueっぽい(truthy)」のです。条件内でnilと評価されるステートメントは、どんなものであってもfalseと等価であると見なされ、nilでないものはどんなものであってもtrueと見なされます。

次のような書き方をしないこと

条件をこじらせた書き方。

# 悪例1
unless something.nil?
  # 何かする
end

# 悪例2
if !something.nil?
  # 何かする
end

# 悪例3
if !!something
  # 何かする
end

以下のように書くこと

# 悪例1〜3のようにではなく、こう書くべし
if something
  # 何かする
end

こう書くべき理由

最初の2つの悪例(unlessとかif !)のように、否定条件内のステートメントの一部として#nil?チェックを行うのは、多くの場合冗長です。あらゆるnil値は「falseっぽい」(falsy)ので、#nil?を使わない肯定条件で同じ結果を得られます。

#nil?チェックを取り除いて、unlessifに置き換える(悪例1の修正)か!を取り除く(悪例2の修正)ことで、同じことをもっと明快なコードで表せます。

悪例3の!!という構文は、(truthyまたはfalseyである)あらゆる値を実際のtruefalseに変換するショートハンドです。しかしRubyの「trueっぽい」条件でこうした変換を行うのは冗長です。

冒頭のように書くべきでない理由

理解しづらくなります。nilそのものかどうかを「本当に」チェックしたいのであれば(おそらく空の配列を扱っていて、nilを通常と異なる方法で扱いたいとかでしょう)、問答無用で明示的なnilチェックを使いましょう。

関連記事

Rubyにおけるunlessとコードの読みやすさについて

Rubyスタイルガイドを読む: 文法(3)演算子とif/unless

Rails: DHHのYouTube動画を辛口レビュー「On Writing Software Well #2」(翻訳)

$
0
0

概要

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

お題の動画です↓。

Rails: DHHのYouTube動画を辛口レビュー「On Writing Software Well #2」(翻訳)

DHHの最近の動画シリーズについて思うところがあるんじゃないのかと何人かの方に尋ねられました。他にも、この動画に対する批判的な意見がないのが残念という声もありました。そこで、DHHの2本目の動画を題材に、私から辛口の意見を述べたいと思います。

シリーズ1本目の動画にしなかった理由は、この回が実によかったからです。コードがそうなるに至った理由を説き明かすコメント、これはコードベースのそうした部分に慣れ親しんでいない人にとって計り知れないほど有用です。DHHの説明も周到に練り上げられていて、私がツッコめる部分はどこにも見当たりませんでした(ここダジャレ!)。

訳注: commentingが「コメントを追加する」と「論評を加える」の両方の意味にかけているようです。

動画の第2回のキーワードは「コールバック」です。DHHがコールバックについてどんなことを語るのか、いたく興味を惹かれました。

以下の論評には、動画を3回見直したときの走り書きも含まれています。こういう本音ポロン出しを気に入っていただけた方は、ぜひ(原文)末尾のコメントにてお知らせください。

第一印象

  • コールバック来た。もうたくさん。コールバックって、欲しくもないときに発生すること多いし。単体テストでモデルのcreateを呼ぶときとかに、テストに全然関係ない振る舞いをコールバックが引き起こすし。びっくりさせるのは好きじゃないんで、この場合自分なら明示的に書くかな。
  • DHHの動画ではRailsのconcern(includeされてクラスに振る舞いを追加するモジュールのこと)の話題を扱うかもよという噂は小耳に挟んでたけど、もしそんな話を始めたら速攻で動画を一時停止してお気に入りの酒を持ってきてから、Railsアプリで使われてるconcernの話をするたびに一杯ずつひっかけるか。

1:35 「副作用」

  • 「副作用、最近は評判が今ひとつだけどね、特に関数型プログラミング方面で」そりゃそうでしょう。メソッドを呼んで「魔法のようなクソがランダムに発生」すれば予測は難しくなるし。コードが何をしているかを明示的に書いておけば、「今」だろうが「将来」だろうが理解に苦しまないで済むし。DHHにはこの「将来」という視点が欠けているし。

2:13Messagesコントローラ」

  • @bucket.recordの引数、いくらなんでも多すぎ。こんだけ引数使って何をしようと?key/valueごとに改行ぐらいしないと
@bucket.record(new_message,
  parent: @parent_recording,
  status: status_param,
  subscribers: find_subscribers,
  category: find_category
)

この方がまだマシだと思います。しかもキーが追加されたり削除されたりしたときでもGitのdiffが見やすくなります。こうしてみると、categoryは最近追加されせいでこんな末席にポイッと置かれたように私には見えます

正直少しばかり驚いたのは、ここで@parent_recordingbefore_actionとしてセットアップしているにもかかわらず、find_subscribersfind_categoryはそうではなく、明示的に呼び出しているのです。こういうのは一貫させて欲しいところですが、DHHがこのようにしたのは何か理由があるのでしょうか。これらのメソッドはコントローラの末尾にまとめられているのですが、この使い分けがよい選択なのかどうかを判断するうまいアイデアが思いつきません。

4:15 Mentionモデル

  • このモデルはなかなかいい。after_createafter_commitのあたりでちょっと寒気がしたけど(オレはPTSDかと)
  • このモデルはなぜかApplicationModelを継承していないのが目を引く。たぶんレガシーアプリか何かだろう。
  • callsign_matches?casecmpの使い方は( ・∀・)イイ!!
  • after_commit :deliverunlessは少々イラッと来たけど、すべてのロジックをメソッド内に閉じ込めておくのが好きなのかも。自分ならこう書くかな。
def deliver
 return unless mentioner == mentioned

...
end

5:55 Recording::Mentions concern

  • 飲むconcernだし。”concerning”(気がかりな、関連する、悩ましい)だからconcernだなんて笑えないジョークだし。

コールバック

  • コールバックをさらに投入してるし。remember_to_eavesdropはおそらくコントローラのメソッドチェインでさらに何かを設定するんだろうし。コントローラは変更があったかどうかをチェックして、そこからメンションを送信することを決定するんだろうし。

訳注: eavesdrop: 盗み聞きする、盗聴する

Current Attributes

もうひとつ気になるのはCurrentです。画面の下の方にちらっと見えるeavesdrop_for_mentionsで使われているやつです。

Currentについては既に「RailsのCurrentAttributesは有害である」で書きました。アプリのありとあらゆる場所で魔法のように使えるグローバル変数です。Current.userはどこで設定されるのでしょうか。そこに値があることをどうやって信じたらよいのでしょうか。

ジョブをトリガするロジックを抽象化する

私には、このeavesdrop関連全体が別の何らかのロジックでラップされているように感じられます(おそらくコントローラででしょう)。コードが実際的にそうしたラッパーを欲しがっています。おそらくBasecampとしては、テスト中にMessagesをデータベース内に永続化したり、このジョブコードを毎度毎度実行したりすることを望んでいないのでしょう。その代わりに、まさにこの場所でそれを行うことになります。これらの副作用でテストの速度が落ちそうです。

こういうのはService Objectとして抽象化して、そこでこのrecordingを作成してからこのEavesdroppingJobをトリガする方がよさそうです。これならcurrent_userをコントローラから渡せるというボーナスも付きますし、しかもですよ、CurrentAttributesグローバル変数などという代物も取っ払えるのです。

コードで表せばたぶんこんな感じです。

module Mention
  class EavesdropForMentions
    attr_reader :recording, :params, user
    def initialize(recording:, params:, user: )
      @recording = recording
      @params = params
      @user = user
    end

    def run
      return unless eavesdropping?

      Mention::EavesdroppingJob.perform_later recording, mentioner: user
    end

    private

    def eavesdropping?
      (active_or_archived_recordable_changed? || draft_became_active?) &&
      !Mention::Eavesdropper.suppressed? &&
      recording.has_mentions?
    end

    def active_or_archived_recordable_changed?
      # recordable + paramsを用いて変更をチェックするコード
    end

    def draft_became_active?
      # recordable + paramsを用いて変更をチェックするコード
    end
end

実際にBasecampの巨大アプリをいじったわけではないので、このコードが動くかどうかまではわかりません。わかっているのは、このコードは後でMention::EavesdroppingJobを実行する可能性のあるコードを見事にカプセル化していることと、Recordingを保存するといつ何どき副作用としてジョブがキューイングされるかもしれないという問題を回避できるということです。私のアプローチではこの2つを切り離したことで、それぞれが独立して発火できるようになっています。

本質的には(おそらく)コードをどっさり書けば同じことはできます。しかしモデルの保存を切り離せたのは自分の中では大勝利です。

Callbackを抑制する

さらなるグローバルステート(Mention::Eavesdropper.suppressed?)が出し抜けに現れました。これはどんな問題を引き起こす可能性があるでしょうか。果たしてこれがどの場所でオンオフされるのかを、このメソッドに到達するコードパスから追えるでしょうか。デバッグがつらそうな予感がします。

DHH自身はこれについて、ここはコールバックが発生して欲しくない場所かもしれないと考えているそうです(動画の11分目ぐらいまで)。そうですか。ならば上述のようにこれをコードのオプション部分とし、Mention::Eavesdropper.suppressed?などという「不気味な遠隔操作」はやめにすることです。

訳注: spookey-action-at-a-distanceはGeorge Musserの書籍のタイトルのもじりで、本来は量子力学におけるquantum entanglement(量子もつれ)という遠隔作用を表します。

コードを再編成して、このeavesdrop的振る舞いのトリガをオプション化すれば、このコードに取り組むときに余分な認知能力を使わずに済むのではないでしょうか。

has_mentions?

特記しておきたいのは(うふっ☺)モジュールの末尾にあるhas_mentions?です。これは少なくとも、何らかのメンションが行われたかどうかをチェックするという振る舞いを抽象化するようです。

訳注: special mentionの「mention」が「言及する」という意味であることにかけたシャレのようです。

13:23 Mention::EavesdroppingJob

ここで使っているCurrent.setは、Mention::Eavesdropperやこれに関連するものにアカウントを渡す方法としては相当しょぼい気がします。Eavesdropperクラス内でアカウントにアクセス可能だとしたら(以下のコードのように設定されていると仮定)、こんなふうにラップした理由が私には見えません。

class Mention
  class Eavesdropper
    attr_reader :recording

    def initialize(recording)
      @recording = recording
    end

    ...
  end
end

しかし(私の)このEavesdropperは少なくとも抽象化がなされていますし、concernではありません。concernはこのアプリではちょっとした「黄金のハンマー」的に使われているように感じられます。

13:45 全体をざっと眺める

DHHはコントローラーからRecording::Mentionsというconcernに画面を移動しました(飲みましたとも)。

第一印象としては、仮にこのアプリに不慣れな開発者がeavesdrop的振る舞いをデバッグすることがあれば、このRecording::Mentionsなるconcernの中でその振る舞いを見つけられると推測するだろうと思いました。

この種の「不気味な遠隔操作」や、私が数年前に書いてしまったこの手のコードのような代物は、書いたその時点ではとても冴えているように思えても、数か月後にもう一度見直してみれば「おいらは一体何をしようとしてたんだっけ?」となるのがオチです。

DHHは14:10のあたりで「間接的な操作がだいぶ増えたけれど、そのおかげでこのメソッドで何を行っているかという流れがとてもスッキリ見えるようになった」と語っています。私がこの動画でダントツに賛成できないのがこの発言です。この流れは「不慣れな」私の目にはまったくもって曖昧以外の何物でもありません。私はこのアプリの事情など知らないのですから。

このコードは「賢い」と同時に危険を孕んでいます。未来の自分が数か月後に再びこのコードを目にすれば、果たして今度もこのやり方で完璧にやっていけるだろうかと首をかしげるでしょう。

14:20 Mention::Eavesdropper

ここで私たちは、「単一責任の原則」という考えがいかに大事であるかを実際に体現したクラスを目にすることができます。

(興味深いことに、DHHは14:30までのあたりでコードを間違ってたどっています)

CurrentAttributesについては既に別記事で書いたので繰り返しませんが、やはりここで長い長い溜息が漏れてしまいます。グローバルステートというものがありとあらゆるアプリから消え去ってしまえばいいのにと思わずにいられません。

「グローバルな要素は、アプリからなくしてしまえばいいというものではないのです」勘弁して!まさかそれを実演するとは。「そういったものをたらい回ししたところで有用になるものでもない」この動画で2番目に賛成できない発言です。たらい回しすれば、それらがどこから来たのかということを自覚できますし、チェインを遡れば元々の定義にたどり着けるかもしれません。このCurrent.personなる謎の代物は、十分な理由もなしにあらゆるものを隠蔽してしまっています。

私の今の気持ちを余すところなく表すのはもうこの言葉ぐらいしかないかもしれません。
「グローバルステートよ、天に召されるがよいっ」と。

16:24  Mention::Scanner

Mention::Scannerのアプローチは割りとまともそうでほっとしました。このコードではEavesdropperに単に丸投げしていないのがよい点です。Mention::Scanner何らかの意味で関連があるからです。これは独立したconcernであり、このロジックをMention::Scannerに移動するのはよいアプローチです。

18:10 Mentionafter_commitフック

これは、単純にコントローラで@bucket.record呼び出しの後に呼び出せるのにと強く思います。

19:20 ProjectCopierと、suppressによる抑制

事前に「events」や「deliveries」を明示的に作成するオプションがこのコードにあれば、suppress_events_and_deliveriesの裏側の抑制用チェインは不要になるのではないでしょうか。やはりこれも残念な仕事だと感じてしまいます。こんなことをするぐらいなら、30分ほど頑張ってきちんと整ったコードを書けばよいのにと思います。

さらに、20:10あたりまでのDHHは、抑制可能な振る舞いがどこから来ているのかを見つけられずにいます。追うのがつらいコードには、並々ならぬ「コードの臭い」が潜んでいます。繰り返しますが、仮にこれらの「コールバック」が暗黙的ではなく明示的なものであれば、抑制はそもそも不要になるのではないでしょうか。それならばコードのフットプリントも縮小されて明快さも増し、ひいては理解しやすくなるのではないでしょうか。「このコードはAとBとCを行う」、そこにはマジックの介在する余地などありません。

20:39 まとめ

「これでスッキリしたんじゃないかな」いえいえとんでもない。私もRailsを10年やってますし、コードベース内でこのコードを見かけたら、より明示的な書き方、もっと取り組む値打ちのある書き方を模索することでしょう。

私にはこのコードが、先ほども申し上げた「賢いコード」のように感じられます。「見て見て、Rubyのスゴい機能を全部使ってるの、ボク賢いでしょ!」という具合です。はいはい、賢いですね☺

しかし何か月かが過ぎてすべてが完璧に動いた頃に、バグを踏むでしょう。そしてコードを見返して「一体どうやったら収まりがつくんだ」と頭を抱えることでしょう。マジックの使いすぎにもほどがあります。

しかしこれが「Rails Way™」というものなのでしょう。


20:46:「このロジックをまとめて詰め込む先は…さてコントローラかな?Service Objectかな?」そこではありません。トランザクションオブジェクトです(上述の5:55の私のコード例を参照)。recordingを作成し、かつMention::Scannerやdeliveriesを明示的にトリガするトランザクションオブジェクトです。

別のアプローチとしては、dry-transactionを使う方法も考えられます。このgemのDSLはとてもよくできていて、こういうのを設定するのに向いています。これを使えば、こんなふうに書けるでしょう。

class CreateRecording

step :create
step :scan_for_mentions
step :deliver_notifications

def create(bucket: bucket, ...)
  # @bucket.recordのコードはここ
end

def scan_for_mentions
  # MentionScannerはここ
end

def deliver_notifications
  # Deliveryコードはここ
end

何もかもが1つのクラスにきれいに収まっています。置くべき場所はこっちです。コントローラでもService Objectでもありません。しかも、このトランザクションに関わる手順がきちんと記述されているトランザクションオブジェクトには、マジックのかけらも見当たりません。手順は書いた順序のとおりに実行されますし、どの手順でもトランザクションを中断できます。

私はこのアプローチが好みです。元のコードベースではコールバックとその暗黙的な手法がこうした害をもたらしていたのです。私がその手法に手を出すことはまずありません。私なら、操作の順序が明確に示されるトランザクションオブジェクトを使うでしょう。

関連記事

Hanamiフレームワークに寄せる私の想い(翻訳)

Railsの`CurrentAttributes`は有害である(翻訳)

Rails tips: self JOIN時のテーブル名を工夫する(翻訳)

$
0
0

概要

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

Rails tips: self JOIN時のテーブル名を工夫する(翻訳)

通常、テーブルの命名を気にすることはありません。テーブル名は明らかだからです。しかし、たまに命名で一瞬考えてしまうことがあります。ここではいわゆるself JOIN(自己結合)を念頭に置いています。次の例で考えてみましょう。Userモデルにmanager_idカラムがあるとします。このカラムは同じテーブルを指しているので、Userモデルは管理職(manager)とユーザーの両方を扱います。

Userモデル

モデルは以下のようになっているとします。

class User < ActiveRecord::Base
  belongs_to :manager, class_name: "Person", foreign_key: :manager_id
  has_many :employees, class_name: "Person", foreign_key: :manager_id
end

問題

ここで、管理職と、管理者ごとの従業員数を表示したいとします。これは次のようなクエリでできます。

User.joins(:employees).group("users.name").count("employees_users.id")

お気づきのように、ここではemployees_usersという名前が使われています。ここではJOINを使っているため、同じテーブルをJOINする場合でもテーブルが2つになってしまいます。

パターン

このような場合に使えるパターンがあります。関連付け名の複数形とモデル名を組み合わせるだけでできます。

RSpec & TDDの電子書籍を無料でダウンロード

もっと稼ぎたい方や会社をさらに発展させたい方へ: テスティングのスキルの重要性にお気づきでしょうか?テストを正しく書き始めることが、唯一のファーストステップです。無料でダウンロードいただける私の書籍『RSpec & Test Driven Developmentの無料ebook』をどうぞお役立てください。

関連記事

Rails tips: モデルのクエリをカプセル化する2つの方法(翻訳)

Rails: ActiveRecord関連付けのpreload/eager-loadをテストする2つの方法(翻訳)

Rubyで経過時間を「正確に」測定する方法(翻訳)

$
0
0

概要

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

Rubyで経過時間を「正確に」測定する方法(翻訳)

Rubyで経過時間を算出したいときはどんなふうにやることが多いですか?

starting = Time.now
# 何か時間のかかることをする
ending = Time.now
elapsed = ending - starting
elapsed # => 10.822178

⚠ブブー、これは違います。理由は次のとおりです。

「時間は前に進むとは限らない」

RubyのTime.nowは、低レベルのOS(Operating System)設定に応じてLinuxのtime.hにあるgettimeofday関数やclock_gettime関数を使っています

gettimeofdayman pageから引用します。

エポック(epoch)以後の秒およびマイクロ秒の値を返します。

この関数は、秒数とシステムタイムゾーンを含む構造体を1つ返します。Ruby VMはこれを元に値を算出し、それらの情報を追加したTimeオブジェクトを1つ返します。これはLinuxドキュメントで多くの場合wall timeと呼ばれています(訳注: wallは「壁掛け時計」のアナロジーです)。

しかしこんなことも書かれています。

gettimeofday()の返す時間は、システム時間の不連続なジャンプに影響される(例: システム管理者がシステム時間を手動で変更した場合)。

手動管理のほかに、システムクロックの自動調整も時間に影響を与えます。たとえば、サーバーでNTPが利用されていると次のようになります。

参照時刻は、世界中どこでも同一であるのが理想です。同期が完了すれば、OSのクロックと参照時刻の間でいかなる不測のずれも生じるべきではない。したがって、NTPにはこの状況に対処するための特別な手法は存在しない。

さらにこんなことも書かれています。

ntpdは、微細なオフセットについてはローカルのクロックを通常どおりに調整します。

NTPのドキュメントでは、クロックの品質についてまるまる一節を使っています。

残念ながら、よくあるクロックハードウェアはどれも非常に正確であるとは言い難い。理由は単純で、時刻を刻む周波数の増加は決して正しくないからである。この周波数の増加がわずか0.001%の狂っただけでも、1日あたり最大1秒のずれが生じるであろう。

精度が完全でないのは、温度や気圧や、下手をすると磁気も含めたCPUの物理的条件によるものです。

つまり、OSがシステム時間を新たに設定しようとする場合、その新しい値が未来のある時刻を指すという保証にはならないのです。CPUのクロックが「速すぎる」場合、OSは時刻をリセットするために数秒ほど巻き戻しする決定を下すことがあります。

wall clockに欠陥が生じるもうひとつの理由は、一部のCPUがうるう秒を扱えないことにあります。Wikipediaのうるう秒のページには、うるう秒によって生じた歴史的なソフトウェアの問題事例が多数掲載されています。

🤓まとめ: システムクロックは常に揺らぐものであり、常に一方向に進むとは限りません。これを当てにして経過時間を算出すると、計算中に誤差が生じたり、最悪の場合は大規模な停止事故につながる可能性が大いにあります。

経過時間を正しく求める方法

増加が一方向に一定で進む経過時間を算出するにはどうすればよいのでしょうか?🤔

POSIXシステムでは、monotonic clock(単調増加クロック)なるものを導入することでこの問題を解決しました。概念上はタイマーに似ています(そのタイマーはあるイベントをきっかけに開始し、時間のゆらぎ問題に影響されない)。monotonic clockに時間を尋ねるたびに、前回のイベントからの経過時間を返します。macOSの場合、このイベントはシステムの起動時間に設定されます。monotonicの他にも、リアルタイム(realtime)、monotonic raw、virtualといった種類のクロックがいくつもありますが、それぞれが解決する問題は異なっています。

Ruby 2.1以降(MRI 2.1以降およびJRuby 9.0.0.0以降)、どのクロックからでも現在の値を取り出せる新しいメソッドが導入されました。その名もProcess.clock_gettimeというもので、名前はclock_gettimeというLinux関数をそのままいただいています(これもtime.hを使います)。

t = Process.clock_gettime(Process::CLOCK_MONOTONIC)
# => 2810266.714992
t / (24 * 60 * 60.0) # time / days
# => 32.52623512722222

上の変数tは、システム起動時からの経過時間を秒で表したものになります。

$ uptime
14:32  up 32 days, 12:29, 2 users, load averages: 1.70 1.74 1.67

Rubyでこれを用いて経過時間を測定するにはどうすればよいでしょうか?

starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
# 何か時間のかかることをする
ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
elapsed = ending - starting
elapsed # => 9.183449000120163 seconds

まとめ

Rubyのエコシステムをちょいと眺めただけでも、経過時間の算出方法がまるで間違っている事例が文字どおり数千件単位で見つかります。これを機会にオープンソース💚💎 に貢献して問題を解決してみてはいかがでしょうか😉

お忘れなく: wall clockは時刻を知らせるものであり、monotonic clockは時間を測定するためのものです⏰

おまけ: 社内Slackより

参考: iOS の時間関数の精度 - Qiita

関連記事

Rails: 多機能ベンチマークgem「derailed_benchmarks」README(翻訳)

Rails+PostgreSQL: EXTRACT関数で期間をスマートに扱う(翻訳)

来年のRubyKaigiに備えて今のうちに筋力と体力をつけよう

$
0
0

こんにちは、hachi8833です。

エンジニアの皆さま、体力づくりやってますか? 特にお子様が生まれると一気に倍の体力を要求されますよね。RubyKaigiのような特濃のイベントでは自らの体力とスタミナに否応なしに直面します。

対象

ITエンジニアを含むすべてのデスクワーカー。

運動の効能

既にいろんなところで散々語られているように、「継続的な」運動にはさまざまな効能があることが多くの研究から明らかになりつつあります。ありがたいことに最近の検索結果にはゴミ健康情報がめっきり出なくなったので、各自ググってみてください。

  • 根気や集中力の増加
  • 頭が冴える
  • 睡眠の質の改善
  • 肩こりや腰痛などの症状改善
  • なぜか多幸感につながる
  • うつ症状に対して薬やカウンセリングに比して効果が高く、しかも持続する

参考: うつ病の運動療法について | あべクリニック 運動療法

何で見たかは忘れましたが、カナダではうつ病の治療プログラムで薪割りをさせるところがあるそうです。想像ですが、薪割りは成果が目に見えてわかるのがよさそうですね。

軽く汗ばむぐらいに運動することで、汗腺が活発になって老廃物が押し出されるという効能もあります。めったに運動しない人が急に体を動かすと自分でもギョッとするぐらいのヤバめの体臭が漂うのはこのためで、普段から軽く汗を流すことで軽減できます。

追記: 体力は知力を支えるのに不可欠

一般に世間では数学者というと,青白い顔をして,やせ細った弱々しい体で,頭脳ばかりを使っているというイメージをもつ人が多いようです。特に日本ではその傾向があるようですが,私が外国で会った一流の数学者たちは,みな相当な体力の持ち主ばかりでした。
数学は体力だ! - 筑波大学 理工学群数学類/大学院数学専攻より


数学と体力については,もう一つ印象に残ってる話があります。あるときパリの喫茶店でコーヒーを飲んでいたら,そこへ若い日本人数学者が6人,興奮しながらやってきて「数学は一に体力,二に体力,三,四なくて五に体力だ」と口々に話していました。全員,ルレイ先生の講義を聞いてきた直後で,70才になるルレイが熊のようにノシノシと教壇を行ったり来たりしながら,すごい迫力で講義する姿に,すっかり感動して,つくづく数学は体力だと思ったそうです。
同上

方法や種類より、毎日続けられるものを見つけよう

運動は、ときどきガッツリやるよりも、毎日少しずつが効果的です。これは語学の学習にも言えることです。

どんな運動をやるかは人それぞれでまったく構いませんが、それが何であれ、継続できるものであることが重要です。継続だけが問題と言ってもよいでしょう。

そのためには、いろんな運動をやって試し、続けられるものがあればそれを続けるのが一番です。性に合わないと思ったらとっとと方法を変えましょう。我慢して無理に続ける方がよくないかもしれません。

よく言われるのが、「21日間継続できると、その後継続できる可能性が飛躍的に高まる」という経験則です。21日という数字そのものは割とどうでもよくて、「それだけの期間継続できた!」といううれしさから続けたい欲を呼び起こすのがポイントです。神社への願掛けは21日間行うという古来の慣習がありますが、偶然でしょう。

参考: 「21日で習慣が身につく」は必ずしも真実ではない:研究結果 | ライフハッカー[日本版]

あと、一回や二回さぼってもくよくよしないことです。「今日サボるために普段からやってるんだ」ぐらいに思うほうが気が重くならずに済みます。

私はこうやっています: その1

私の場合、だいぶ以前にぎっくり腰をやってしまったのがきっかけでした。

私: 「ぎっくり腰の予防方法ってあるんでしょうか?」
医者: 「うーん、腰の周りの筋肉を鍛えるしかないでしょうね」

以来、駅の上り階段は必ず足で駆け上るようにしています。幸い、通勤経路の半分が新し目の地下鉄なので駅が深くて階段が長く、実に手頃です。今では無意識にエスカレーターではなく階段を登るまでになりました。

その代り、下りはあえてエスカレーターにしています。下り階段を駆け下りて足をくじいたりする可能性を避けるためです。変なところで用心深い。

運動=スポーツではない!

運動とスポーツは割と混同されがちですが、スポーツによる運動が必ずしもポジティブな効能を持つとは限りません。

  • スポーツは本質的に競技であり、勝ち負けが伴うので無理が生じやすい
  • 多くのスポーツは身体を痛めつけることと引き換えに勝利を得るのが基本なので、運動によるからだ作りとは最終的に逆の行為
    • トレーニングや柔軟体操はそれを防ぐためのものでもある
  • 純粋な運動と比べて、スポーツで怪我をする可能性の方が高い
  • 好きでもないスポーツだと身が入らないことが多く、怪我につながりやすい

勝ち負けより重要なのは怪我をしないことだと思っています。「無事これ名馬」なんてことわざもあるくらいですし。

知人が居合わせたのだそうですが、2日徹夜後のヘロヘロ状態で日曜日の社会人サッカーに参加した人が試合中に急死したという話を何かにつけて思い出します。

私はこうやってます: その2

階段上りに加えて、朝目覚めたときにベッドの上で腹筋をやっています。さまざまなアレンジを加えているので、一般的な腹筋とは異なります。ジョギングなどのように天気に左右されることもなく、費用もかからないので、自宅警備の多い自分は気に入っています。

あくまで自分用のやり方であり、これを殊更おすすめしているわけではありませんのでご了承ください。自分の性に合う運動が一番です。

  • 足は固定せず、膝は曲げっぱなしで仰向けになる(固定するとむしろ腰を痛める可能性があります)
  • 手を頭の後ろに回すことはしていない(体の前で軽く曲げる程度)
  • 最初は2〜3回から始め、絶対に無理しないよう、長期間かけて少しずつ回数を増やす
    • 一週間ごとに数回ずつ増やすぐらいのペースで
    • 現在は50回が当たり前になってきたので、60回にしてみているところです
  • 必ずベッドの上など、固めのクッションのあるところでやる
    • 自分のベッドのスプリングは一番硬いものを選んでいます
    • 畳に敷いた布団の上や板敷きの場所では逆に腰を痛める可能性があります
    • 長年やっているとベッドのスプリングがへたるので、ときおり掃除を兼ねて裏返す
  • 腹筋運動は素早くを心がけている
    • よくある腹筋運動は、ゆっくり持ち上げてそこでホールドするみたいな方法ですが、これはボディビル向けの発想なので自分はやりません
    • 持ち上げるときにビュンと素早く、戻すときは重力にお任せする
    • その日の10回目以降は左右に身体をねじり、まんべんなく腹筋が使われるようにする
    • 背筋も使うよう意識しながら腹筋を行う

これがそこそこ近い感じです。30×4セットみたいにセットを組むのはよさそうですね。早速取り入れました。

そこに、さらにいくつかのアレンジを加えています。毎日まったく同じ内容にせず、そのときどきで工夫を凝らしています。

  • 耳を手のひらで揉むと目覚めもいい
  • 腕や足や足の裏や胸や背中を手のひらでペシペシ叩く
    • 骨に適度な打撃を加えることで骨電流を生じさせ、骨の衰えを防ぐ
    • (自転車のように身体にまったく打撃が加わらない運動ではこの辺が手薄になりがち)
  • 手のひらや足の指を開いたり閉じたりする
    • 普段使われない足の指を開いたり閉じたりすることで運動の回路が形成される
  • 片足の指を開いて反対側の手の指を突っ込み、足首をぐりぐり回す(反対側も同様に)

朝は他にもちょこちょこやっていますが、きりがないのでこの辺で。

私の場合の効能

8年近くこうしてきた結果、はっと気がつくと冬の寒さがこたえなくなり、寒さで身体がブルルッと震えることがなくなりました❄。おそらく腹筋を含む下半身の筋肉量が増えたためと思われます。夏はその分暑いです☀。今日も既に暑くてしょうがないくらいで、仕事中は年中扇風機をつけてます。

3年連続で参加しているRubyKaigiもおかげさまで全部乗り切れています。今年のアフターパーティはさすがに無理でしたが…。

自分はいわゆるボディビル系の筋肉は趣味の範疇だと思っているので、ケンシロウやラオウのような上半身は目指していません。下半身(肋骨より下全部)を鍛えるつもりでいます。

注意点: 頭に血が昇らないようにすること

どの運動でもそうですが、顔を真赤にして力むということは脳の毛細血管にも相当の高圧が加わります。これを癖にしてしまうと、今はよくても年齢が進んだときにプツッとやってしまうかもしれません。

話はそれますが、トイレで息を止めて力む癖も同じ理由でよくありません。腹式呼吸をベースに、息を吐きながら絞り出す練習をするとよいでしょう。最初は非常に違和感がありますが、一週間もすればできるようになります(なりました)。この習慣を促進する方法として、お経を唱えながら用を足すという荒業もあるそうですが、恥ずかしいのでやったことありません。

まとめ

  • 身体も脳も、適度な負荷を継続的に与えることで余裕が得られる
  • 身体も脳も、使わないと衰え、使うと鍛えられ、使いすぎると壊れる
  • 毎日続けられる、自分に合った運動方法を見つけよう
  • 怪我をしないのが一番: 無理はしないこと

関連記事

デスクワーカー向けストレッチのススメ

メガネに今の倍額以上を投資してマジ世界が変わった【BPS Advent Calendar 12/21】

Slackのシンタックスハイライト付き「スニペット機能」は使わないと損

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチのつっつき会で、このテクニックを今更知ったので自分のためにメモします。今まで最初と最後を```で囲む方法↓しか知らなかった(´・ω・`)。

スニペット機能を使うのと使わないのとでは、Slackでの質問に回答してもらえる率がきっと違うことでしょう。

Slackのコードスニペット

Slackのメッセージウィンドウ左の「+」ボタンをクリックすると、コードスニペットのメニューが表示されます。

ショートカットを使えばもっと楽に出せます。

  • Mac: + Shift + Enter
  • Win: Control + Shift + Enter


Slack のキーボードショートカット – Slackより

後はコードを貼り付けて言語を選び、タイトルやらコメントやら[wrap]チェックボックスやらを指定して保存すれば完了です。

これでコードが気持ちよくシンタックスハイライトされます。再編集もできます。編集中にもシンタックスハイライトされるのが最高です。

なお、Slackのヘルプによると、モバイル版アプリからはスニペット機能は使えないそうです。

現時点のシンタックスハイライト対応言語

Slackのことですから今後知らないうちに追加されるでしょう。よく見ると知らない言語もちらほら。

関連記事

Slackで日本語にはさまれた英語を検索するコツ

まだまだあった!Slackの便利なショートカット集

Slackの全チャンネルのメッセージを1箇所に表示できるアプリSlackStream

はじめてのSlack(初心者&非エンジニア向け)

Rails:「Pagy」gemでRailsアプリを高速ページネーション(翻訳)

$
0
0

概要

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

Rails:「Pagy」gemでRailsアプリを高速ページネーション(翻訳)

私はRuby on Railsがバージョン0.8の頃から開発者として使っていることもあり、その間に誇大広告気味のRubyGemを星の数ほど目にしてきました。

成熟したソフトウェア開発コミュニティと同様、特定の用途では事実標準のgemとして永らえているライブラリもありますが、他の多くのライブラリは誰もサポートしてくれないまま、採用に値しない瓦礫の山を築いています。

用途によっては1つや2つgemをあえて追加することもありますが、ほとんどどの機能にもそれぞれに相応しいチャンピオンが君臨しているものです

Railsの進化がたどった道

以下の図は、ここ10年の間に新しいライブラリやアイデアにコミュニティが貢献した様子を示しています。

年ごとのRubyGem数

当初のgem数は増加していますが、2015年をピークに以後は減少しています(成熟したとも言えるでしょうか)。私は、2018年のgem数は2009年とほぼ同レベルであろうと睨んでおり、この傾向が続くならば新しいgemの数は今後も減少し続け、安定期に入るでしょう

ここからさまざまな疑問が湧き起こります。「Railsの潜在能力は2015年がピークだったのか?」「Railsというプラットフォームの採用も減少しているのか?」「むしろこれは成熟の証で、Railsは大企業が採用してよいものになったのだろうか?」

私としては、このデータだけでは何とも言えません。むしろそうした視点とは違うアプローチによって、今日の私たちの仕事に大きなインパクトをもたらす部分に着目してみたいと思います

新しいgemの数が減少するに連れて、今の私たちの仕事に大きく影響するような画期的なものはなかなか出現しなくなるだろうと考えられます。しかしそんなことはありません。それを今から証明いたします。

Pagyとの出会い

Ruby on Railsで開発したことがある方なら、アプリのindexページをwill_paginateやKaminariでページネーションしたことがおそらくあるでしょう。Rails経験の浅い方なら、今後そうしたページネーションgemを探すことになるでしょう。

PagyはRails向けの新しいページネーションライブラリです。パフォーマンスを念頭に置いて開発されており、かつ新規または既存のRailsアプリの使いやすさを損ないません。

はい、今皆さんが思っていることを当ててみせましょう。

「お、今度のページネーションgemこそ俺たちが求めていたもの…なわけないだろ!」

そこについては私も同感です。オープンソースでソリューションが分散してしまうのはよくないと思います。しかも、このPagy gemによってもたらされる改良点のいくつかは、既にKaminariでも実装が提案されたのですが、リジェクトされました。おそらく、改善にはKaminariのコア部分で大規模な変更が避けられなかったからでしょう。

何かが華々しく登場したら、ときにはそれが本当にベストなのかどうか一歩身を引いて考える必要があると、私は固く信じています。そしてページネーションについて言うなら、このgemには疑いの余地はありません。理由を説明しましょう。

pagy-kaminari-will_paginate-memory-used-per-page-shown

この図がすべてを物語っていると私は信じています

アプリが数百〜数千ものユーザーを同時にさばいている状態では、ちっぽけな機能を1つ足すだけでもリソースに大きな影響を及ぼすことは容易に想像が付きます。20ページに対して実施した1つのテストの結果を見ると、will_paginateはうまくやっている一方、Kaminariのメモリ容量は著しく増大しています。しかしPagyはwill_paginateよりさらに優秀です。数千人ものユーザーをさばこうとするとき、この違いがもたらすインパクトを今一度考えてみてください

さらに詳しく見てみましょう。今度はそれぞれのgemのメモリフットプリントを比較します。

pagy-kaminari-will_paginate-total-memory-used

Kaminariと同じジョブを、Pagyは遥かに少ないメモリで実行できます。will_paginateもKaminariより省メモリではあるものの、Pagyと比べると7倍にものぼります。さらに皆さまに考慮いただきたいのは、will_paginateが最後にリリースされたのは1年も前のことで、リポジトリにはプルリクが何十個もたまったまま、最終コミットの日付は2017年7月のままになっている点です。

今仮に新規プロジェクトがスタートすることがあったとしても、will_paginateは選択肢には含めないでしょう。

はい、皆さんはここできっとこう思うでしょう。

「よっしゃ気に入った。ところでPagyが他のgemと比べてメモリフットプリントがここまで少ない理由が謎なんだけど、どうやってんの?」

よくぞ聞いてくださいました。ご説明いたしましょう。

pagy-kaminari-will_paginate-number-of-objects-created

実は私も、当初同じ疑問を抱いたのでした。

「Kaminariが生成するオブジェクト数が6,000個超えってどういうこと?」

もちろんwill_paginateの方が省メモリではあるものの、それでも20ページのページネーションでオブジェクト数が3,000個超えというのはあんまりです。一方Pagyのオブジェクト数は400個を下回っています。

Pagyの方が優秀な理由

pagyのどこがそんなに違うのでしょうか?ご説明いたします。

  • PagyはRubyオブジェクトではなくintegerで計算している
  • Pagyのコアコードは60行にも満たない
  • Pagyはアプリのモデルに癒着しておらず、HTMLやURLや複数形化(pluralization)や式展開を独自に生成する。
  • Pagyは、一般的なヘルパーではなく完全に特殊化したコードを用いている。
  • コードが専門化していることで、作者が1行ずつ丁寧にベンチマークを取っている(コードが100行もなければ十分可能です)。

上の理由のおかげで、pagy gemのコードはきわめて理解しやすいという実にうれしいおまけまでついています。

しかも拡張で機能を追加することも可能で、いくつかの拡張は組み込み済みなのですぐにでも使えます。

さらなる考察

私がRailsアプリを開発した当初はwill_paginateを使っていましたが、その理由は当時はその業務にベストだったからです。数か月後にKaminariがリリースされると、そちらに乗り移りました(なにぶんあまりにも昔のことなので、乗り換えた理由はよく思い出せません)。そしてつい数週間前にPagyがリリースされるまで、Kaminariを使い続けていました。

私が初めてPagyを知ったときに心底愕然としたのは、私がwill_paginateからKaminariに移行したときにパフォーマンスのことをまったく考慮していなかったことでした。後者は前者よりずっと遅かったにもかかわらず、です。今の私はパフォーマンスを考慮することを覚えました。

これまで私たちが設計・リリースしたアプリで、いったいいくつのgemを取り入れてはパフォーマンスを下げまくっていたことでしょう。

will_paginateやKaminariからPagyへの移行は実に簡単で、必要なコードはわずか数行で済みます。次回の私の記事では皆さまに関心を持っていただけるよう、具体的な移行方法を解説することをお約束いたします。

免責事項: 本記事の画像およびパフォーマンスはPagyの作者が行ったベンチマークテストを用いています。ベンチマークのソースコードはddnexus/pagination-comparisonでご覧いただけます。RubyGem採用の推移チャートには、こちらでご覧いただけるRubyGems.orgのデータを用いております。

訳注: Pagyリポジトリ

関連記事

Rails: パーシャルと`collection:`でN+1を回避してビューを高速化(翻訳)

kaminariでRubyの地雷を踏んだ


Rubyの明示的/暗黙的な型変換についてのメモ(翻訳)

$
0
0

概要

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

Rubyの明示的/暗黙的な型変換についてのメモ(翻訳)

自分用にメモ。

Rubyには型強制(type coercion)メソッドがいくつもペアになっています。

  • to_ito_int
  • to_sto_str
  • to_ato_ary
  • to_hto_hash

右と左はいったい何が違うのでしょうか?どういうときに使い、どんなときに実装するのでしょうか?

短い方は明示的な変換です。

  • (integerやstringやarrayといった)何らかの型に変換される可能性がある場合は、その型に明示的な変換を実装すべき
  • 何らかの意味のある型に変換されることを期待するのであれば、受け取るデータに対してこれらのメソッドを呼び出すべき

長い方は暗黙的な変換です。

  • 型が自分自身を「kind of」に該当する型(よりよい、または特殊なnumber、string、array、hash)に変換されるとみなす場合にのみ、暗黙的な変換を実装すべき
  • 変換後の型について厳密な要求がある場合は、受け取るデータに対してこれらのメソッドを呼び出すべきだが、クラスの比較ではなくダックタイピングによるRubyらしい方法でチェックしたいと思うのが普通
  • それ以外の場合にこのメソッドを実装したり呼んだりすることはまかりならぬ

Rubyの演算子やコアメソッドは、データを暗黙的に変換します。

  • "string" + other#to_str on otherを呼び、[1,2,3] + other#to_aryを呼ぶなど(オブジェクトがこのメソッドに応答しない場合はTypeError: no implicit conversionを返す)
  • "string" * other[1,2,3] * other#to_int on otherを呼び出す
  • a, b, c = *otherother#to_aryを呼ぶ(そして独自のコレクションオブジェクトを”unpack”するのに使われるが、otherto_aryを実装していると思わぬ振る舞いを見せることがある: しかしこれはコレクションではないと考えられる)
  • some_method(*other)は上と同様にother#to_aryを使う
  • some_method(**other)(Ruby 2で登場)はother#to_hashを使う

何度でも言います。自分のやっていることを把握できていないのであれば、暗黙の変換を決して実装してはなりません。たとえば#to_hashが実装されている(#to_hよりはイケてる名前だから?)ために奇妙極まる振る舞いが発生する事例をあちこちで目にします。

実用的な例をご覧いただきましょう。

class Dog
  attr_reader :name, :age
  def initialize(name, age)
    @name, @age = name, age
  end

  # シリアライズをto_hではない方法で定義するのは誤り
  def to_hash
    {species: 'Dog', name: name, age: age}
  end
end

def set_options(**some_options)
  p some_options
end

dog = Dog.new('Rex', 5)

set_options(foo: 'bar')
# {foo: 'bar'}が出力される、よしよし

set_options(1)
# この呼び方はできない: ありがたいArgumentErrorが発生

set_options(dog)
# なぬ?dogがオプションハッシュとして扱われて
# {species: 'Dog', name: name, age: age}が出力された

require 'awesome_print' # pretty print(pp)に使われる人気gem
ap dog
# のぉぉぉ!Dog#inspectじゃなくてハッシュのppメソッド呼んでるし

以下もご覧ください。

関連記事

Ruby: 自分のクラスに`#to_a`を実装すべきじゃない(翻訳)

Ruby 2.5の`yield_self`が想像以上に何だかスゴい件について(翻訳)

Ruby: 「マジック」と呼ぶのをやめよう(翻訳)

やさしいRubyメタプログラミング: RSpecを自分で作って学ぶ(翻訳)

$
0
0

概要

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

以下の記事を参考に、テスト対象コードとテスト用コードを1つのファイルにまとめて記述しておくと試しやすいでしょう。

Rails tips: コードとテストを同じファイルに書けるRSpec autorun(翻訳)

やさしいRubyメタプログラミング: RSpecを自分で作って学ぶ(翻訳)

RubyのDSLとメタプログラミングのやさしい入門記事です。

DSL(Domain Specific Language): 言語を新たに作ることで特定の問題を記述する、プログラミング技法の一種です。Ruby on Railsフレームワークのルーティングや、RSpecの構文はDSLのよい例です。

メタプログラミング: プログラミング技法の一種で、指定のコードに別のコードを生成する能力を与えます。メタプログラミングはDSLとの関連が深く、メタプログラミングが使えないとDSLを使えません。

RSpec: RubyコードをテストするDSLのテストツールであり、Rubyで記述されています。RSpecはgemとして利用できるので、Ruby on Railsアプリで簡単に使えます。

DSLとメタプログラミングは、どちらもそれ単体だけで大きなトピックになりますので、本記事では楽しくわかりやすい方法でざっくりご紹介するにとどめます。RSpecを使ったことのない方には、先にRSpecを少しでも使ってみてからお読みいただくことをおすすめします。

DSLを使うべき理由

独自のRSpecをこしらえる前に、DSLやメタプログラミングの使い所や使うべき理由がまだよくわからない方向けに、簡単なコード例を1つご覧いただきます。次の設定用クラスで考えてみましょう。

class Server
  attr_reader :env, :domain
end

class Config
  def server
    @server ||= Server.new
  end
end

これは設定用クラスの模造品です。環境やサーバーのドメインを設定するには、次のような呼び出しをかけなければなりません。

config = Config.new
config.server.env = 'production'
config.server.domain = 'domain.com'

この設定方法を実際に使うわけではないので、このままでは何だかつまらないですね。メタプログラミングをカッコよく使って、もう少し楽しくしてみましょう。ここではinstance_evalメソッドを使うことにします。

インスタンスをevalって?

はい、本記事で最も多用されるのがこのメソッドです。先ほどの新しい設定用クラスを題材に、意味のあるコード例を書いてご説明します。

class Server
  attr_accessor :env, :domain
end

class Config
  def initialize(&block)
    instance_eval &block
  end

  def server
    @server ||= Server.new
  end
end

Config.new do
  server.env = 'production'
  server.domain = 'domain.com'
end

オシャレ感増し増しの設定方法になりましたね。先ほどの実装から唯一変更された点は、initializeメソッドを追加して、ブロック引数を1つ取れるようにしたことです。このメソッドで、本日の主役であるinstance_evalを呼び出すわけです。instance_evalが何をするかというと、渡されたブロック内のあらゆるコードを「Configクラスインスタンスのコンテキストで実行」します。言い換えれば、渡すブロック内ではいちいちconfig.プレフィックスを付けなくても(そのインスタンス内の)どんなオブジェクトにもアクセスできるということです。

クラスの例

独自のRSpecをこしらえるからには、その独自RSpecでテストする対象が必要です。ここではTDD(テスト駆動開発)方式ではなく、最初にサンプルのクラスを作成して本記事での説明に役立てたいと思います。

class NumberService
  def number
    12
  end
end

張りぼてのクラスができたので、RSpecで以下のようなテストを書いてみましょう。

describe NumberService do
  describe '#number' do
    it '12を返す' do
      expect(NumberService.new.number).to eq(12)
    end

    it '10を返さない' do
      expect(NumberService.new.number).not_to eq(10)
    end
  end
end

既にinstance_evalについて学んだので、このようなコードの書き方について薄々見当がついているかと思います。このテストで使われているすべてのメソッドをどう自作するかを考えることにしましょう。

  1. describe: クラスまたは文字列を1つの引数として受け取ります。どちらを渡しても出力は文字列になります。このあたりは、--format documentationを追加してテストを実行してみればおわかりいただけると思います。
  2. it: 指定された実行パスのdescriptionを受け取ります。describeメソッドのブロック内に記述することで、テストの概要をドキュメントらしい形式で表示できます。
  3. expect: 単に値を1つ取ります。私もRSpecのソースコードを詳しく見たわけではありませんので、実際の実装と比べたところで相当かけ離れているかと思います。
  4. tonot_toは、それぞれ==演算子と!=演算子と同等です。
  5. eqは読みやすくするためのシンタックスシュガーです。

少なくともこの6つのメソッドを実装しなければなりません。まずはdescribeメソッドから。

class Describe
  attr_reader :context_name

  def initialize(context_name, &block)
    @context_name = context_name
    instance_eval &block
  end
end

このメソッドは引数を2つ取ります。1つ目はdescriptionを、2つ目はブロックをそれぞれ1つずつ取ります。これで例の張りぼてクラスのインスタンスを作成してみましょう。

Describe.new NumberService do
  # 何かする
end

ここでの問題は、Describe.newではなくdescribeと書きたいことです。修正のためにヘルパーメソッドを1つ作らなければなりません。

def describe(context_name, &block)
  Describe.new(context_name, &block)
end

describe NumberService do
  # 何かチェックする
end

だいぶいい感じになってきました。先の例では、2つのdescribeブロックがネストしているので、ネストをサポートしなければなりません。

class Describe
  attr_reader :context_name

  def initialize(context_name, &block)
    @context_name = context_name
    instance_eval &block
  end

  def describe(context_name, &block)
    Describe.new(context_name, &block)
  end
end

## ヘルパーメソッド

def describe(context_name, &block)
  Describe.new(context_name, &block)
end

## おれおれテスト

describe NumberService do
  describe '#number' do

  end
end

これでdescribeブロック内に別のdescribeブロックをネストできるようになりました。これらをrspec.rbファイルに保存してruby rspec.rbを実行できます。

今度は、実行パスを記述するitメソッドを実装します。

class Describe
  attr_reader :context_name

  def initialize(context_name, &block)
    @context_name = context_name
    instance_eval &block
  end

  def describe(context_name, &block)
    Describe.new(context_name, &block)
  end

  def it(context_name, &block)

  end
end

def describe(context_name, &block)
  Describe.new(context_name, &block)
end

describe NumberService do
  describe '#number' do
    it 'returns 12' do

    end
  end
end

次は、指定の結果のexpectationを得るコードを追加します。そのためには、expectメソッド、toメソッド、eqメソッドの実装が必要です。これらについては、新しくExampleクラスを作ってそこに追加します。

class Example
  attr_reader :context_name

  def initialize(context_name, &block)
    @context_name = context_name
    instance_eval &block
  end

  def expect(result)
    self
  end

  def to(expectation)
    self
  end

  def eq(expectation)

  end
end

describe NumberService do
  describe '#number' do
    it 'returns 12' do
      expect(NumberService.new.number).to eq(12)
    end
  end
end

このコード例を実行しても何も起きません。チェイン内でメソッドを呼び出せるようになっただけで、まだ何も実装されていないからです。

expectメソッドは何をすべきなのでしょうか?あるメソッド呼び出しの結果を代入して、toメソッドで取り出せるようになればよいのです。

class Example
  attr_reader :context_name

  def initialize(context_name, &block)
    @context_name = context_name
    instance_eval &block
  end

  def expect(result)
    @result = result
    self
  end

  def to(expectation)
    self
  end

  def eq(expectation)

  end

  private
  attr_reader :result
end

describe NumberService do
  describe '#number' do
    it 'returns 12' do
      expect(NumberService.new.number).to eq(12)
    end
  end
end

このeqメソッドは単なる==の置き換えですが、ここでの課題は、これをtoメソッドに渡して結果と比較することです。その答えはProcです。

class Example
  attr_reader :context_name

  def initialize(context_name, &block)
    @context_name = context_name
    instance_eval &block
  end

  def expect(result)
    @result = result
    self
  end

  def to(expectation)
    self
  end

  def eq(expectation)
    Proc.new { |n| n.eql?(expectation) }
  end

  private
  attr_reader :result
end

describe NumberService do
  describe '#number' do
    it 'returns 12' do
      expect(NumberService.new.number).to eq(12)
    end
  end
end

締めくくりは、toメソッドの実装です。toは、結果をexpectationと比較します。

class Example
  attr_reader :context_name

  def initialize(context_name, &block)
    @context_name = context_name
    instance_eval &block
  end

  def expect(result)
    @result = result
    self
  end

  def to(expectation)
    expectation.call(result)
  end

  def eq(expectation)
    Proc.new { |n| n.eql?(expectation) }
  end

  private
  attr_reader :result
end

describe NumberService do
  describe '#number' do
    it 'returns 12' do
      expect(NumberService.new.number).to eq(12)
    end
  end
end

 puts expect(NumberService.new.number).to eq(12)を実行してみると、trueと出力されました。やりましたね!

テスト結果を出力する

ロジックは実装できましたが、まだコンソールにテスト結果が何も表示されません。まずはExampleクラスにpublicなreaderを追加してテスト結果をそこに保存し、Describeクラスからアクセスできるようにしましょう。

class Example
  attr_reader :context_name, :test_result

  def initialize(context_name, &block)
    @context_name = context_name
    instance_eval &block
  end

  def expect(result)
    @result = result
    self
  end

  def to(expectation)
    @test_result = expectation.call(result)
  end

  def eq(expectation)
    Proc.new { |n| n.eql?(expectation) }
  end

  private
  attr_reader :result
end

describeブロックやitブロックがたくさん使われることはわかっているので、それらをDescribeクラスに集めてテスト出力を生成するときにアクセスできるようにします。

class Describe
  attr_reader :context_name, :examples

  def initialize(context_name, &block)
    @context_name = context_name
    @describes = []
    @examples = []
    instance_eval &block
  end

  def describe(context_name, &block)
    describes << Describe.new(context_name, &block)
  end

  def it(context_name, &block)
    examples << Example.new(context_name, &block)
  end

  private
  attr_accessor :describes
end

締めくくりは、出力用メソッドの実装です。メソッド名をtestにして、Describeクラスに追加します。

class NumberService
  def number
    12
  end
end

class Describe
  attr_reader :context_name, :examples

  def initialize(context_name, &block)
    @context_name = context_name
    @describes = []
    @examples = []
    instance_eval &block
  end

  def describe(context_name, &block)
    describes << Describe.new(context_name, &block)
  end

  def it(context_name, &block)
    examples << Example.new(context_name, &block)
  end

  def test
    puts context_name
    describes.each do |describe_node|
      puts "  " + describe_node.context_name
      describe_node.examples.each do |example_node|
        puts "    " + example_node.context_name
      end
    end
  end

  private
  attr_accessor :describes
end

def describe(context_name, &block)
  Describe.new(context_name, &block)
end

class Example
  attr_reader :context_name, :test_result

  def initialize(context_name, &block)
    @context_name = context_name
    instance_eval &block
  end

  def expect(result)
    @result = result
    self
  end

  def to(expectation)
    @test_result = expectation.call(result)
  end

  def eq(expectation)
    Proc.new { |n| n.eql?(expectation) }
  end

  private
  attr_reader :result
end

rspec = describe NumberService do
  describe '#number' do
    it 'returns 12' do
      expect(NumberService.new.number).to eq(12)
    end
  end
end

rspec.test

ruby rspec.rbを実行すると、以下が出力されます。

NumberService
  #number
    returns 12

ところでspecがパスしたか失敗したかはどうすればわかるのでしょうか?失敗の場合は赤で、成功の場合は緑を表示しましょう。そのためにはcolorize gemを追加しなければなりません。

gem install colorize

colorize gemの#colorizeメソッドを用いて、テキストの色を指定できます。このメソッドは色のシンボルを引数に取ります。ここでは:red:greenを指定します。example_node.test_resultを呼び出せば使うべき色を検出できます。最終的なコードは以下のようになります。

require 'colorize'

class NumberService
  def number
    12
  end
end

class Describe
  attr_reader :context_name, :examples

  def initialize(context_name, &block)
    @context_name = context_name
    @describes = []
    @examples = []
    instance_eval &block
  end

  def describe(context_name, &block)
    describes << Describe.new(context_name, &block)
  end

  def it(context_name, &block)
    examples << Example.new(context_name, &block)
  end

  def test
    puts context_name
    describes.each do |describe_node|
      puts "  " + describe_node.context_name
      describe_node.examples.each do |example_node|
        color = example_node.test_result ? :green : :red
        puts "    " + example_node.context_name.colorize(color)
      end
    end
  end

  private
  attr_accessor :describes
end

def describe(context_name, &block)
  Describe.new(context_name, &block)
end

class Example
  attr_reader :context_name, :test_result

  def initialize(context_name, &block)
    @context_name = context_name
    instance_eval &block
  end

  def expect(result)
    @result = result
    self
  end

  def to(expectation)
    @test_result = expectation.call(result)
  end

  def eq(expectation)
    Proc.new { |n| n.eql?(expectation) }
  end

  private
  attr_reader :result
end

rspec = describe NumberService do
  describe '#number' do
    it 'returns 12' do
      expect(NumberService.new.number).to eq(12)
    end
  end
end

rspec.test

さらに手を加えるには

非常にシンプルかつ限定的な実装ですが、皆さんがRSpecのしくみを知る手がかりになればと思います。クラスの宣言を別ファイルに切り出すなり、to_notメソッドをサポートするなり、好きに改造するとよいでしょう。

RSpecが既に存在しているおかげで、アプリをテストする独自RSpecを何もないところからこしらえなくて済むありがたいことですね。

お知らせ: RSpec & TDDの電子書籍を無料でダウンロード

もっと稼ぎたい方や会社をさらに発展させたい方へ: テスティングのスキルの重要性にお気づきでしょうか?テストを正しく書き始めることが、唯一のファーストステップです。無料でダウンロードいただける私の書籍『RSpec & Test Driven Developmentの無料ebook』をどうぞお役立てください。

関連記事

Ruby: 「マジック」と呼ぶのをやめよう(翻訳)

Ruby: メタプログラミングに役立つフック系メソッド(翻訳)

Ruby: 条件を切り出していい感じのメソッド名を付けよう(翻訳)

$
0
0

概要

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

Ruby: 条件を切り出していい感じのメソッド名を付けよう(翻訳)

シンプル(かつ効果の高い)リファクタリング手法のひとつといえば、メソッドへの切り出しがあります。切り出したメソッドに名前を付ける手法は、コードの背後に潜むアイデアを示すうえで有用なツールです。

次のようには書かないこと

条件にステートメントをごちゃごちゃ書く。

class BrightonCoffeeShop
  def initialize(name)
    @name = name
  end

  def good?
    if @name == 'Starbucks' || @name == 'Costa' ||
       @name == 'Barry's Caff'
      false
    elsif @name == 'Coffee@33' || @name == 'Small Batch'
      true
    else
      false
    end
  end
end

この実装が、nameが不明な場合にfalseを返すことはもう見え見えです。あなたがこの喫茶店で満足にくつろげるようにする唯一の方法は、わけのわからないルールを排除することです。

次のように書くこと

「メソッドの切り出し」リファクタリング手法を用いて、ロジックをprivateメソッドに移動し、コードのコンセプトを的確に表すメソッド名を付ける。

class BrightonCoffeeShop
  def initialize(name)
    @name = name
  end

  def good?
    clean? && local? && excellent_coffee?
  end

  private

  def clean?
    !filthy?
  end

  def excellent_coffee?
    ['Coffee@33', 'Small Batch'].include?(@name)
  end

  def filthy?
    @name == "Barry's Caff"
  end

  def local?
    !national_chain?
  end

  def national_chain?
    ['Starbucks', 'Costa'].include?(@name)
  end
end

そうすべき理由

メソッドを切り出して定義することで、メソッドに名前を付けます。上の#good?メソッドは、よい喫茶店かどうかを判断するための基準を示します。この新しいprivateメソッドは、クラスのドキュメントとしても機能します。

修正前のコードでは、「よい」喫茶店と「よくない」喫茶店のリストしかなく、このリストがどのように形成されたのかという知識も示されていませんでした。

このクラスに対する初歩的なリファクタリングとして、GOODBADという配列にそれぞれよい名前と悪い名前のリストを詰め込み、喫茶店のnameをそれでチェックするという方法もないわけではありません。しかしその方法では、その喫茶店がよい理由も悪い理由も見えなくなってしまうでしょう。しかもその理由こそが、このコードで最も重要な部分なのです。

修正後のコードでは、#clean?(清潔)や#local?(地元)といったポジティブな評価基準だけではなく、#filthy?(不潔)や#national_chain?(全国チェーン)といったネガティブな評価基準についても切り出しました。このおかげで、#good?メソッドのネガティブバージョンをprivateメソッドに用意しなくても済みます。少々やりすぎに思える部分かもしれませんが、このクラスでは満足の行く喫茶店に対する肯定的な評価の方を強く重視しているのですから、これによって有用なコンテキストが付け加えられます。

リファクタリングの手法が何であれ、リファクタリング前には最初の実装をカバーするよいテストをしっかり書いて、機能がうっかり変わらないようにしておきましょう。

そうするべきでない理由があるとすれば

上よりももっとシンプルな例であれば、このリファクタリングを行うまでもないかもしれません。しかしそれでもドキュメント作成の有用な手法でもあり、条件のロジックを明確化できます。

上よりもさらに複雑な場合(喫茶店に対して良否以外にも多くの要件を課すなど)は、個別の喫茶店をサブクラス化する形でリファクタリングするとよいかもしれません。

関連記事

Rubyのクラスメソッドがリファクタリングに抵抗する理由(翻訳)

Rails tips: テストから共通機能を切り出すリファクタリング(翻訳)

Rails tips: Railsアプリに1行書くだけでチャートを作成できるchartkick/chartable gem(翻訳)

$
0
0

概要

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

Chartableは著者自身が作成したgemです。

Rails tips: Railsアプリに1行書くだけでチャートを作成できるchartkick/chartable gem(翻訳)

Railsアプリでチャートが使えたらとても素敵ですが、正しいデータを準備してからJavaScriptでビューに表示するのはつらいことがあります。そこで今回はChartkick gemとChartable gemを用いるソリューションをご紹介いたします。

Chartkick gemは、コードを1行書くだけで美麗かつ有用なチャートを表示できます。Chartable gemは、任意のActive Recordクエリを分析可能なハッシュに変換します。この2つのgemを用いることで、チャート作成のための新しい武器が手に入ります。

設定方法

最初にGemfileに以下のgemを追加します。

gem 'chartkick'
gem 'chartable'

bundle installを実行したら、次はチャートのレンダリングに使うJavaScriptライブラリを読み込まなければなりません。これを行うには、application.jsに以下の行を追記します。

//= require Chart.bundle
//= require chartkick

分析データを取得する

Railsで作成したブログエンジンがあり、そこにArticleモデルがあるとします。そして毎月何件の記事が作成されているかを表示したいとしましょう。これを行うにはクエリをひとつ実行しなければなりません。

@articles = Article.analytics(:monthly)

4月に1件、5月に5件の記事が作成されていた場合は、次のようなハッシュが取れるはずです。

{"April 2018" => 1, "May 2018" => 5}

チャートに表示する

それではデータをビューに表示しましょう。最初に申し上げたとおり、必要なのはこの1行だけです。

<%= pie_chart(@articles) %>

これで、以下のチャートが表示されるはずです。

できました!他のチャートに変更したい場合や期間の異なるデータを取得したい場合は、以下のgemページをご覧ください。


最新記事をチェックしたい方はぜひhttps://twitter.com/pdabrowski_k1のフォローをお願いします。github.com/rubyheroに掲載される新しいRuby gemもどうぞお見逃しなく!

お知らせ: RSpec & TDDの電子書籍を無料でダウンロード

もっと稼ぎたい方や会社をさらに発展させたい方へ: テスティングのスキルの重要性にお気づきでしょうか?テストを正しく書き始めることが、唯一のファーストステップです。無料でダウンロードいただける私の書籍『RSpec & Test Driven Developmentの無料ebook』をどうぞお役立てください。

関連記事

Rails: ページタイトルはビューテンプレートの`content_for`で表示すること(翻訳)

週刊Railsウォッチ(20180608)特集「RubyKaigi 2018後の祭り」、`Enumerable#index_with`は優秀、コントローラから`@`を消し去るほか

$
0
0

こんにちは、hachi8833です。RubyKaigi 2018の余韻がまだ体内に響き渡っている気がします。よい〜ん。

各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ

梅雨入り間近のウォッチ、いってみましょう。今回のつっつきは開始が遅かったので短めです🙇。

特集: RubyKaigi 2018後の祭り

参加した皆さま、本当にお疲れさまでした。
会のサポートやドリンクアップなどのパーティイベントで多くのスポンサーの皆さまにお世話になりました。ありがとうございます。


rubykaigi.org/2018より

現時点ではまだのようですが、動画やスライドへのリンクはいずれ昨年同様公式のスケジュールにアップされるでしょう。


つっつきボイス: 「ドキュメントの専門家の端くれとして思うことなんですが、『愛』とか『感動をもらう』とか『絆』みたいな言葉って世の中でともすると無難なワーディングとして乱用されがちですよね: 状況次第では『こういっとけばいいんだろ?』みたいな意図が図らずも透けて見えてしまったりとか」「まあそれが一周回って元に戻ったりすることもありますけどね😎」

来年の会場が決定

早くも来年のRubyKaigi 2019@福岡の会場が決まりました。

参考: 【福岡国際会議場】FUKUOKA CONVENTION CENTER


つっつきボイス: 「今度の会場は遠いなー: さすがに飛行機かな」「地図でもわかるように、福岡空港って市街地に隣接したところにあるんで着陸のときにマジで住宅街が真下に見えてビビりますね」「こえー😳」「その分、地下鉄わずか数駅で繁華街の中心である天神に到着できます😊」

福岡は台湾や韓国からめちゃめちゃ近いので、そちらからの参加者も増えそうですね。あと沖縄も。

乗ったことはないのですが、福岡-釜山は通常の巡航高度に達しないうちに到着するそうです。

a_matsudaさんインタビュー

RubyKaigi 2018直前に公開された記事なので多くの方が既にお読みかと思いますが、Ruby/Railsどちらの方にも有用な話が多く、直接の関係はそれほどないにしてもRubyKaigiのナイスな副読本に思えました。インタビュアーはこれまたRubyKaigi 2018でも登壇したsotarouさん(SideCIのCTO)です。


つっつきボイス: 「お、この方がa_matsudaさんでしたか(今回初参加なのー☺️)」「ですです: a_matsudaさんはRubyとRails両方のコミッターを務めていて、RubyKaigiのオーガナイザーの一人です」「なるほど道理で司会いっぱいやってた」「Ruby/Rails両方のコミッターって、a_matsudaさん以外にはtenderloveことAaron Pattersonさんしか知らないんですが、他にいらっしゃるかな…?」

ご存知の方は@hachi8833までどうぞ🙇。

[インタビュー] Aaron Patterson(前編): GitHubとRails、日本語学習、バーベキュー(翻訳)

「インタビュー読んでて個人的に一番驚きだったのが、CRubyのソースコードに統一スタイルがないという点でした」「Kaigiでもどこかでそんな話出てましたね」「ご本人がリツィートしてたこれがわかりやすいです↓: 出元はKaigiでTRICKの司会を務めたmametterさん」「おっおー、タブ派こんなに多いし!😳」「C言語ですし歴史長いし、しょうがないのかな…」

まとめ情報

各種記事などを途中まで追っていたのですが、zundanさんが上記るびま特集号でガッツリまとめてくださっていることに気づいたので、るびまから辿るのが一番だと思います。今後の更新にも期待します🙇。

TechRachoでは、Kaigiのまとめ情報を急いで出すよりは、ウォッチなどで今後じわじわ追いかけることにしようかと考えています。

後はTwitterで以下のハッシュタグを眺めるのがいいかもです。

TRICK 2018のまとめはひとまず以下で。

同ハッシュタグのツイートを紹介していると本気できりがないので、泣く泣く1/10以下に絞り込みました。

こちらの↓Rubyアンケートも楽しいですね。

Rails: 今週の改修(Rails公式ニュースより)

今回はすべてRails公式情報からです。

(Rails 6)Enumerable#index_withが追加

# PRより
# before
POST_ATTRIBUTES.map { |attr_name| [ attr_name, public_send(attr_name) ] }.to_h

# after
POST_ATTRIBUTES.index_with { |attr_name| public_send(attr_name) }
# こんなこともできる: Enumerableをハッシュに変換するときとか
# before
WEEKDAYS.each_with_object(Hash.new) do |day, intervals|
  intervals[day] = [ Interval.all_day ]
end

# after
WEEKDAYS.index_with([ Interval.all_day ])
# https://github.com/rails/rails/pull/32523/files#diff-33d6b41213775779b35c3f31915e9f98R60
+  # Enumerableの項目をキーにし、ブロックで戻された値をハッシュに変換する
+  #
+  #   post = Post.new(title: "hey there", body: "what's up?")
+  #
+  #   %i( title body ).index_with { |attr_name| post.public_send(attr_name) }
+  #   # => { title: "hey there", body: "what's up?" }
+  def index_with(default = INDEX_WITH_DEFAULT)
+    if block_given?
+      result = {}
+      each { |elem| result[elem] = yield(elem) }
+      result
+    elsif default != INDEX_WITH_DEFAULT
+      result = {}
+      each { |elem| result[elem] = default }
+      result
+    else
+      to_enum(:index_with) { size if respond_to?(:size) }
+    end
+  end

つっつきボイス: 「おー、ハッシュが返ってくるということですか: beforeみたいにmapからto_hでつなげてハッシュを取るというのは定番で、ボクもよくやるけど、む、むむ、この新しいEnumerable#index_withメソッドは優秀ですね〜!😍」「やっぱりうれしいヤツだったんですね」「afterのこのシンプルさ☺️!: Rails 6から使えるですかー」「masterなんでそうなりますね: これRuby本家にあってもいいヤツかもー」「( ・∀・)イイ!!」

PRのコメントにも同じ意見がありました。

Wanna to have this method in ruby too! 👍
同PRより

(Rails 6)mail gemを自動でeager load

production環境で、mail gem内のファイル自動読み込み中にSidekiqのワーカースレッドが全部デッドロックするという問題が発生したことがあった。このmail gemは、config.eager_load = trueを設定したにもかかわらずActionMailerでrequireされていた。mail gemはActiveSupportのではなくRubyネイティブの自動読み込みを使っているので、ActiveSupportが自動でeager loadingできない。Mail.eager_autoload!というメソッドがあるしちゃんと動くのに、単にActionMailerがeager loading中にこれを呼んでなかった。この呼び出しをActionMailer Railtieのeager_load_namespacesに追加したことで、eager_load!イニシャライザの実行中にMail.eager_autoload!呼び出しされるようになった。
同PRより大意

# https://github.com/rails/rails/pull/32808/files#diff-d8d52836afe031f24a03fc96d5d6c8c4R56
+  def self.eager_load!
+    super
+
+    require "mail"
+    Mail.eager_autoload!
+  end
 end

つっつきボイス: 「短かったんで雑に訳してみました」「これはもろにバグ🕶」

「ところですっごく初歩的なこと聞いちゃいますけど、superってこうやってメソッド定義の冒頭に書くこともあるんですね」「えっ?自分はむしろsuperと言えば冒頭に書く方が普通かと思ってたけど」「あー、たまたまメソッド定義のラストでsuperを呼ぶコードを立て続けに見る機会があったんで思い込みしてたかも…失礼しましたー😅」「このコードの継承関係から言うと、まずsuperで元のを呼んで、それからゴニョゴニョすると: ちなみにJavaでは先頭にしか書けません🧐、確かコンストラクタでは、だったかな」「おー」

参考: 【Java】 継承とコンストラクタ super( )の意味 | 一番かんたんなJava入門

「逆にRubyではsuperは自由に置けるんで、今言ってたように先にゴニョゴニョしてからsuperを呼ぶこともできる(superに別の引数を渡したいときとかね🤓)」「そうでしたかー: super慣れしてなくって」「まーあんまり慣れたくはないですが😆」

参考: メソッド呼び出し(super・ブロック付き・yield) (Ruby 2.5.0)

(Rails 6)Range#===Range#cover?

ruby/ruby@989e07c(訳注: 原文リンク切れ)から、Range#===で従来のinclude?に変えてr_cover_p内部関数を使うようになった。このため、少なくとも8b67a02からActiveSupport::CoreExt::Rangeの挙動がドキュメントどおりにならなくなった。

このパッチはRange#cover?Range#===をオーバーライドして3つをすべてCompareWithRangeという1つのモジュールに配置した。
失敗時のログはhttps://travis-ci.org/rails/rails/jobs/380939901#L1224-L1247を参照。
同PRより大意

# https://github.com/rails/rails/pull/32938/files#diff-1b097291d613c82352e751d35befbea6R2903
+(1..10) === (3..7)  # => true
+(1..10) === (0..7)  # => false
+(1..10) === (3..11) # => false
+(1...9) === (3..9)  # => false
+
 (1..10).include?(3..7)  # => true
 (1..10).include?(0..7)  # => false
 (1..10).include?(3..11) # => false
 (1...9).include?(3..9)  # => false

-(1..10) === (3..7)  # => true
-(1..10) === (0..7)  # => false
-(1..10) === (3..11) # => false
-(1...9) === (3..9)  # => false
+(1..10).cover?(3..7)  # => true
+(1..10).cover?(0..7)  # => false
+(1..10).cover?(3..11) # => false
+(1...9).cover?(3..9)  # => false

つっつきボイス: 「これはテストの方が見やすいかなと思って」「ここが直感とズレると確かにつらいわー」

(Rails 6)xor_byte_stringsを70%高速化

# 同PRより
# Ruby 2.5.1の場合
Warming up --------------------------------------
             current     6.519k i/100ms
                 new    10.508k i/100ms
Calculating -------------------------------------
             current     84.723k (_ 0.4%) i/s -    423.735k in   5.001456s
                 new    145.871k (_ 0.3%) i/s -    735.560k in   5.042606s

Comparison:
                 new:   145870.6 i/s
             current:    84723.4 i/s - 1.72x  slower
# https://github.com/rails/rails/pull/32931/files#diff-c07f67f5a7946bbd9c38ba8283aea967L402
       def xor_byte_strings(s1, s2) # :doc:
-        s2_bytes = s2.bytes
-        s1.each_byte.with_index { |c1, i| s2_bytes[i] ^= c1 }
-        s2_bytes.pack("C*")
+        s2 = s2.dup
+        size = s1.bytesize
+        i = 0
+        while i < size
+          s2.setbyte(i, s1.getbyte(i) ^ s2.getbyte(i))
+          i += 1
+        end
+        s2
       end

つっつきボイス: 「例のpack()を使わない方向で書き直してるからそのおかげで速くなったのかな?、と思ったら、コメントを見るとループをwhileでやったことの効果が大きかったみたい: 元のコードだと内部のyieldが遅いとかありそうではある」

「PRのコメントでも『whileで書くのはパフォーマンス上は有利だけどRubyらしくない(C言語みたくカウンタ変数i使っちゃってるし)かなー: Rubyらしくブロックのループで書くと少し遅いは遅いけど』みたいな意見がありますね」「その後で『Rubyでミクロな最適化を追求するあまり可読性を損なうのはどうかとは思う: Rubyってたぶんそういう言語じゃないし』『いや、場合によっては可読性を下げてでもパフォーマンスを取る方がいいこともあるし』みたいなやりとりの末マージされました」「whileループが死ぬほどでかいなら止めた方がいいと思うけど、これならまあ読めるし、20%速くなるならやってみていいんじゃね?」

# 同PRより
def xor_byte_strings_new(s1, s2) # :doc:
  s2 = s2.dup
  s1.size.times { |i| s2.setbyte(i, s1.getbyte(i) ^ s2.getbyte(i)) }
  s2
end

参考: pack テンプレート文字列 (Ruby 2.5.0)

(Rails 6)子トランザクションで保存に失敗した場合に親トランザクションをロールバックするよう修正

# 同PRより
class Employee < ActiveRecord::Base
end

class Company < ActiveRecord::Base
  has_many(:employees)
end

company = Company.new
employee = company.employees.new
company.save # ここで失敗しても親がロールバックしなかった
# https://github.com/rails/rails/pull/32796/files#diff-829fd5510b886395117cc530518ef7f7R400
               if autosave != false && (@new_record_before_save || record.new_record?)
                 if autosave
                   saved = association.insert_record(record, false)
-                else
-                  association.insert_record(record) unless reflection.nested?
+                elsif !reflection.nested?
+                  association_saved = association.insert_record(record)
+                  if reflection.validate?
+                    saved = association_saved
+                  end
                 end
               elsif autosave
                 saved = record.save(validate: false)
               end

つっつきボイス: 「これもマジでバグじゃん😭!」「トランザクションの一貫性、大前提ですよね」

(Rails 6)実際の(=savepointにない)トランザクションの後にトランザクションのレコードをfinalize

# https://github.com/rails/rails/pull/32911/files#diff-52c8dd8e01c039c37b33b3fcbdfe406aR19
       def committed?
-        @state == :committed
+        @state == :committed || @state == :fully_committed
+      end
+
+      def fully_committed?
+        @state == :fully_committed
       end

       def rolledback?
-        @state == :rolledback
+        @state == :rolledback || @state == :fully_rolledback
+      end
+
+      def fully_rolledback?
+        @state == :fully_rolledback
       end
...
       def rollback
         connection.rollback_to_savepoint(savepoint_name)
-        super
+        @state.rollback!
       end

       def commit
         connection.release_savepoint(savepoint_name)
-        super
+        @state.commit!
       end
# https://github.com/rails/rails/pull/32911/files#diff-174733f2db65ef1bc53e3222d6ac0e61R473
       def update_attributes_from_transaction_state(transaction_state)
         if transaction_state && transaction_state.finalized?
-          restore_transaction_record_state if transaction_state.rolledback?
+          restore_transaction_record_state(transaction_state.fully_rolledback?) if transaction_state.rolledback?
+          force_clear_transaction_record_state if transaction_state.fully_committed?
           clear_transaction_record_state if transaction_state.fully_completed?
         end
       end

つっつきボイス: 「述語メソッドの方で|| @state == :fully_committedを追加して、下の方で@state.なんちゃらを明示的に呼ぶようになったと」

(Rails 6)SQLite3の最小バージョンが3.8に変更


つっつきボイス: 「SQLite3を通常業務で使うことはあまりないかなと思ったんですが、チュートリアルなどをやる人は押さえておくといいかなと思って」「| ゜Θ゜)<そうでもないよ: 雑にrails newして急いでモック作って渡したいときとかにSQLite3ありがたいし: この間も使ったし」「あ、そうか: 機動性いいですよね」「SQLite3のデータはファイルベースなんで、リポジトリ経由で渡すときにとっても楽☺️: 捨てるときも楽☺️」「git cloneすれば即動きますしね」「別にぽすぐれでもいいんだけどね、わざわざサーバー立ててテーブル作ってとか相手にやってもらうのは面倒だし」「SQLite3のビルド、ネイティブなんでたま~にコケますけど😎」「げ😫」

以下もSQLite3の改修です。


追いかけボイス:

参考: SQLite 3.24 Released With UPSERT Support - Phoronix

Rails

Railsのコントローラから@を消し去る(Hacklinesより)


つっつきボイス: 「お、@消し去るマン!こういうの大好きー😌」「まずは冗長な書き方: Rails wayから外れるけど」「ほー、コントローラでrenderでやるっすか!」

# 同記事より
def show
  user = User.find params[:id]
  render :show, locals: { user: user }
end

「でもって、コントローラは(概念上は)要するに以下をやればいいんだろ↓みたいな話」(以下延々)

# 同記事より
controller = UsersController.new request
response = controller.show

「いろいろあって、最終的にLettableというモジュールのletメソッドでやるんだそうです」

# 同記事より
module Lettable
  def let name, &blk
    iv = "@#{name}"

    define_method name do
      return instance_variable_get iv if instance_variable_defined? iv
      instance_variable_set iv, instance_eval(&blk)
    end
    helper_method name

    define_method :"#{name}=" do |value|
      instance_variable_set iv, value
    end
    private :"#{name}="
  end
end

「これいい記事じゃない!自分も以前同じような解に辿り着いたことあったし: この記事のやり方以外にヘルパーメソッドでやる手も考えられるナ」「😀」「Rails wayから外れるのは確かだからチーム内でコンセンサスは取らないといけないけど、考え方としては大好きなヤツ😍: ビューに@で渡すのはもう仕方ないとして、それ以外はこういうのでやりたい気持ち」「おー」「読み進んでみるとやっぱりちゃんとヘルパーメソッドでもやってみせてくれてるし、やるなー」

「記事の最後の方に、TechRachoにも何度か登場している↓decent_exposure gemから実は影響を受けたってありますね」「あーあれですか、コントローラにload_resourceを書くというナニな手法の中で軽く紹介されてた: でもあれとは違ってこのやり方にはコピペの匂いがしないし、筋がいいと思う」「letってRSpecみたいだけどこれなら紛らわしくはなりにくそう」「Lettableって凄い名前だけど😆」「@インスタンス変数でやる方法しか知らない方にも知っておいて欲しいです: Rails中級向けだけど初心者にもいい記事」「これ翻訳依頼かけますね😀」


hashrocket/decent_exposureより

Railsコードを改善する7つの素敵なGem(翻訳)

「話は逸れますが、記事にも出てくるboilerplateって単語聞いたことあります?」「うんにゃ」「これはローカライズ業界で知った用語なんですが、契約書的な硬い文書なんかによくある、いわゆる定型文を指すんですよ: ボイラーの板が何で定型文なのかはよくわかりませんが🤣」「2ちゃんで言う『テンプレ』ですな🤓」

ローカライズでは、boilerplateと指定されているパラグラフはフリーズされて、間違って翻訳されないように処置されたりします。辞書で調べると、boilerplateはどうやら印刷用の鉛版から来ているようです。

ついでにdecent exposureの意味も。

個人情報などの適度な露出。インターネット上のブログやツイッターなどで、公開していい情報と伏せるべき情報の区別をわきまえること。特定の個人がかかわる情報を書き込むとき、無断で個人名を書き込まないなど。
decent exposure | アメリカ新語 | 情報・知識&オピニオン imidas - イミダスより

Railsのルーティングを複雑にしない方法(RubyFlowより)


つっつきボイス: 「お、このトピック興味あるある👀」「割と短い記事ですね」「お、ルーティングのファイルが分かれてる↓😳?!」「ほんとだ: これ今まで見たことなかったんですけど」「これ思いつかなかったわー💦: ルーティングをきれいに切り分けられるんだったら、これはありかも: ただきれいに切り分けるのって割と難しいんですよ:『どれに書いたらいいんだ問題』とかフォルダ切らないといけないとかもあるし」「でしょうねー」「sidekiqとかdeviseとかの単位ならまあやってやれないことはないかも?」

squeel gemとbaby_squeel gem

以下の記事でsqueel gemのつらみが語られていて、最後にbaby_squeel gemが紹介されていました。

参考: さよならSqueel - Qiita


つっつきボイス: 「Squeel、知ったときにはもうサヨナラだったという」「ほほー、Arelをラップするコマンド的なやつが昔にあったんですかー」「で、上の記事にこんなこと書いてありました↓」

Squeelはたしかに便利なのですが、大きな問題点として「ActiveRecordを大胆にモンキーパッチしている」ということがあります。その結果、ActiveRecordがバージョンアップするたびに、大規模な対応作業が必要となってしまう、という構造上の欠点があります。
同記事より

「(ノ∀’)アチャー、Arelってちゃんと公開されてるAPIじゃないし、そうなるわなー: ActiveRecordで<とか>とか使いたい気持ちはとてもよくわかるけど(どうして使えないんだっ😤)」「😢」「Ransackならこういう比較演算子使えるんですけどね」「baby_squeelの方はパッチではなく別メソッドでやってるんですね: 赤い長靴はいた子豚ちゃんがかわいい🐷」

「こういうgemがあるとid.count > 5みたいな書き方ができるんですね↓」「そうそう: そういうのがないと文字列しか渡せないんで?でサニタイズして渡したりとか面倒」「セキュリティ作法👮‍♂️」

# baby_sqeel READMEより
Post.selecting { id.count }.grouping { author_id }.when_having { id.count > 5 }

AWS EC2とChefでRailsアプリを設定する(Hacklinesより)

かなり長いですがひたすら手順です。

RailsのjQueryをVueに完全置き換えする(RubyFlowより)


同記事より

// 同記事より
$(document).ready(function(){
   ...
   $('#some-radio-button1').on('click', function(){
     if ($(this).is(':checked')) {
       // removing "active" classes, hiding some blocks
       // showing related block
     } else {
       // opposite of above
     }
   });
});

つっつきボイス: 「jQueryをたっぷりトッピングしたRailsアプリをVueに移行したい人って多いんでしょうね」「この記事はVueも使えるようにした、って感じなのかな?」「そういえばそんなに長い記事ではないですね」「BootstrapがjQuery必須だから、Bootstrapがある間は当分切り離せないでしょうね…」「Bootstrapで使われるくらいのjQueryは別にあってもいいかなと: 乱用しないようにルール作りとかしておけば」

「jQueryで厄介なことがあるとすれば、ネットに落ちている野良コードを拾い食いしてあたるパターン😩、しかも他の人が拾ってコピペしたやつに」「それ、私も自分で踏んだ…」「何回やられたかもう覚えてないくらい😭」

後でこんな記事を見つけましたが、昨年夏の記事でした。今無理してjQueryを完全に殺すとどこかに無理が出そうな予感です。

参考: Drop jQuery From Your Bootstrap Project (and Replace it With Vue.js!) - DZone Web Dev

JavaScriptスタイルガイド 1〜8: 型、参照、オブジェクト、配列、関数ほか (翻訳)

gitignore.io: プロジェクトに最適な.gitignoreファイルを作成できるサイト

他にもこんなのがあるそうです。

TDD伝道師インタビュー

つっつきボイス: 「t-wadaさん!」「『設計だけでコードを書けないなら断る』って最初どの意味だろうって考えちゃいましたが、読み進めてみると『設計だけやれ/コードは書くな、なら断る』ってことだったんですね」「😆」「職業柄、文章を読むときに意味をいったん可能な限り広く取ってから絞り込む癖があるんです: 人によって解釈がブレてしまう可能性を考慮するんで、自分の文章ならそういう部分をリライトすることが多いですね🙃: 急いでるとなかなかやれないんですけど」

「まーでも設計だけやらざるを得ない状況はあるけどねー」「ソフトウェアだと設計と実装って分かれているようで不可分というか、建築家と大工さんのようには分かれてないですよね」「実装を経験せずに設計だけやるというのはソフトウェアでは無茶だし、設計で無駄な実装を減らすためにも実装を知ることは不可欠だし: 自分はプログラマーとはイコール設計する人だと思ってます」「😀」「対偶を取ったら、設計できない人はプログラマーではない🕶」

その他Rails

開発チームを苦しめるマイクロサービス(翻訳)


私も翻訳で関わってきたRailsチュートリアルRailsガイドでおなじみのYassLabさんが株式会社化したそうです。おめでとうございます🎉。


yasslab.jpより

Ruby trunkより

今回は主にRubyKaigi 2018から拾う形にしました。

参考: RubyKaigi 2018 2日目 - ENECHANGE Developer Blog

Guildがマージ

yield_selfのエイリアスとしてthenが追加

RubyKaigiの大喜利でも「もっといい名前にしたい」で盛り上がったネタですね。

File.read(filename).then(&JSON.method(:parse))
rand(10).then.detect(&:odd?)


つっつきボイス: 「Arelにthenならギリセーフかな: privateなものなんだし、みんなも使わないでしょ😆」「😆」

Ruby 2.5の`yield_self`が想像以上に何だかスゴい件について(翻訳)

提案: autoloadやめない?

↑大喜利で参照されてたのはこれでよかったのかな…(後でチェック)。


つっつきボイス: 「これは大喜利でRails勢がざわついてましたね☺️: autoloadをやめたい理由って何なんでしたっけ?」「autoloadをやめるとその分起動が重くなるかわりに、動的にバインディングされなくなってライブラリが確定するからなんですね」「あーそうだったかも!」「Matzもたしかそんなことを壇上で言ってましたよね」「autoloadでproduct環境がぐらついたら困るわけですよ: まったく同じ構成のアプリなのにデプロイ先でライブラリの読み込み順序やタイミングのせいで挙動が同じにならなかったりしたらコワイ」「確かにー」「その一方で、今回昔の知り合いにKaigiの場で久々にお会いしてこの話題になったときに『肥大化したコードではインフラに応じた分岐できないから、autoloadがあると使われないコードがロードされないのでautoloadはいいヤツだと思う』ということもおっしゃってましたね」「なるほどー」

提案: Refinementやめない?

(issueがうまく見つけられませんでした🙇)


つっつきボイス: 「大喜利でこの話になった途端ツイートでお二人が😀」「いやーほんとRefinement使ってる人こんなにいるとは」

RubyのRefinement(翻訳: 公式ドキュメントより)

Ruby

RubyKaigi 2018開催中ににRuby 2.6.0-preview2がリリース(Ruby公式ニュースより)

RubyKaigiの主に大喜利でだいたい触れられていましたね。


  • JITの改良
  • RubyVM::ASTの導入
  • Proc#callblock.call
  • $SAFEのステートがプロセスグローバルに
  • etc.

RuboCop 0.57リリース

RuboCop RSpecのリポジトリも移動したようです。

JSのコールバックやクロージャをRubyのyieldとlambdaでやってみる(Hacklinesより)

// 同記事より
Array.prototype.myMap = function(cb) {
  const mappedArr = [];
  for (let elem of this) {
    mappedArr.push(cb(elem));
  };
  return mappedArr;
};
[2,4,6].myMap((num) => { return num * 3 }); 
// [6, 12, 18]

Steep: Rubyの型チェック

# 同リポジトリより
class Person
  # `@dynamic` annotation is to tell steep that
  # the `name` and `contacts` methods are defined without def syntax.
  # (Steep can skip checking if the methods are implemented.)

  # @dynamic name, contacts
  attr_reader :name
  attr_reader :contacts

  def initialize(name:)
    @name = name
    @contacts = []
  end

  def guess_country()
    contacts.map do |contact|
      # With case expression, simple type-case is implemented.
      # `contact` has type of `Phone | Email` but in the `when` clause, contact has type of `Phone`.
      case contact
      when Phone
        contact.country
      end
    end.compact.first
  end
end


つっつきボイス: 「Kaigiでも発表されたSteepはMatzの考える型チェックに近そうですね」「そんな感じー: 型アノテーションはしたくないけど指定や推定はしたいと」「Rubyのシンタックスは変えたくないということなんでしょうね」「型アノテーションだとシンタックス拡張になりますし」

「あれ、私ちょっと勘違いしてたかも? アノテーションって#のコメントに型情報を書くことかと思ってました😓」「ここでMatzがRubyに入れたくないと言ってる型アノテーションはJavaで言っているヤツのことでしょうね: Javaには@で型アノテーションする拡張シンタックスがあるので」「そうでしたか~、アノテーションにもいろいろあるけど、その中にJavaの型アノテーションがあるということか」

参考: Java SE 8再入門、型アノテーションとは何か | 日経 xTECH(クロステック)

「そういえば誰かが『本当に欲しいのは型じゃなくって、優秀なIDEなんじゃ?』ってツイートしてました」「ホントそう思うですよ~🙂: ちゃんとは知らないんですけどPythonは型チェックのあたりが何やら成功したっぽいんで、Rubyでもやってよという形で押し寄せてるってことなんでしょうね」(以下延々)

追伸: sorbet.run: StripeによるRubyの型チェック(RubyFlowより)


sorbet.runより

Stripeと言えば決済代行サービスで有名ですね。Sorbet.runは3月にリリースされ、RubyKaigi 2018にも登壇してたのですが、今回見られなかった(´・ω・`)。


stripe.comより

参考: A practical type system for Ruby at Stripe. - RubyKaigi 2018

他にもType Systemといえば、Dmitry Petrashko, Paul Tarjan, Nelson Elhage A practical type system for Ruby at Stripe.があります。彼らはStripeの方ですが、なんとStripeにある数百万行ものRubyのコードには漸進的に型を適用するしくみがあるそうです。本当なのか。この発表はその取り組についての紹介になる予定です。
RubyKaigi 2018 タイムテーブル徹底解説より(強調は編集部)

Stripe決済を自社サービスに導入してわかった5つの利点と2つの惜しい点

EnvMem: メモリプロファイラ

Noah Gibbsさんのプレゼンにも登場した本人作のgemです。Rubyの環境変数でパフォーマンスチューニングするうえで便利なプロファイラです。自分でも動かしてみました。

↓このあたりの記事を翻訳していなかったらプレゼン追いきれなかったと思います。

Rubyのヒープをビジュアル表示する(翻訳)


つっつきボイス: 「Noah Gibbsさん、去年の広島でのRubyKaigiにもお見えだったのに自分がまったく気づいてなかったので😢、今回リベンジで記事翻訳許可のお礼を述べました: クマのぬいぐるみといつも一緒で、スライドの冒頭にもクマちゃん映ってました」

Ruby 2.5.0はどれだけ高速化したか(翻訳)

その他Ruby


つっつきボイス: 「pentomino?」「あー、何だったっけこれ…(画像でググる)」「これかー↓」「テトリスとは微妙に形が違う」「実はこのパズル苦手😓」「まさか!数学専攻だったのに?」「これ系のパズルは解けても何故かあまり快感を感じられなくて…(群とか集合とかが好きなのっ)」


  • 「Tell Don’t Askの原則」: 初めて知りましたが、Martin Fowlerさんのブログにありました。あえて言うなら「オブジェクトにデータがあるかどうかいちいち聞くな、指示だけ出せ」という感じでしょうか。(RubyFlowより)


クラウド&コンテナ

AWS CodeCommitとは

アレクサとAWSのAI


aws.amazon.comより


つっつきボイス: 「記事の内容云々より、技術系でない一般系のニュースサイトにこうやってAWSのチャートが掲載されたことが感慨深かったので🤓」「次はテレビですな📺」

みずほ銀行のプロジェクトがいよいよ

クラウドとはちょっと違いますが。

SQL

DB対決: Cassandra vs TimescaleDB(Postgres Weeklyより)


timescale.comより

TimescaleDBは前回のウォッチで取り上げましたが、Cassandraは初めて知りました。


https://ja.wikipedia.org/wiki/Apache_Cassandraより

参考: Apache Cassandra - Wikipedia

その名前から、ついこちらのいい話を思い出してしまいます。

アポローンに愛され、アポローンの恋人になる代わりに予言能力を授かった。しかし予言の力を授かった瞬間、アポローンの愛が冷めて自分を捨て去ってゆく未来が見えてしまったため、アポローンの愛を拒絶してしまう。憤慨したアポローンは、「カッサンドラーの予言を誰も信じないように」という呪いをかけてしまった。カッサンドラーは、パリスがヘレネーをさらってきたときも、トロイアの木馬をイリオス市民が市内に運び込もうとしたときも、これらが破滅につながることを予言して抗議したが、誰も信じなかった。
カッサンドラー - Wikipediaより(強調は編集部)


つっつきボイス: 「この話、お話づくりの雛形としてすっごくよくできてて好きなんです😁: アポローンも予言能力を与える前にそれに気づけとw」「😁」「ギリシャ神話で面白いのが、神には『一度言ったことを取り消す能力がない』という重大な設定上の縛りがあることなんですね: せっかく超能力があっても万能感台無し🤣」「ロールバック不可🥖」

神のこの制約を逆手に取ってチートをかけるしたたかな人間すら登場しています。

里中満智子は「マンガ・ギリシア神話7」でこの場面を、カッサンドラーとやりたい一心のアポローンがカッサンドラーの気を引こうとしてつい予言能力をプレゼントしてしまったと解釈していました。

そんなこんなでカッサンドラという名前は昔から不吉なニュアンスがあったのですが、今は割と普通に女性名に使われてるようです。

PostgreSQLのストレージをビット操作で効率アップ(Postgres Weeklyより)

odyssey: PostgreSQL向けマルチスレッドコネクションプール/リクエストルーター(Postgres Weeklyより)

JavaScript

Jest 23がリリーズ(JavaScript Weeklyより)

参考: Jest 23 リリース: 🔥 爆速で快適なテストを【日本語翻訳】 | maesblog

TypeScript 2.9がリリース(JavaScript Weeklyより)

Node.jsにおける設定ミスとは

deno: V8向けのセキュアなTypeScriptランタイム(JavaScript Weeklyより)

CSS/HTML/フロントエンド

Styled-componentsとは


styled-components.comより

// styled-components.comより
const Button = styled.a`
  /* This renders the buttons above... Edit me! */
  display: inline-block;
  border-radius: 3px;
  padding: 0.5rem 0;
  margin: 0.5rem 1rem;
  width: 11rem;
  background: transparent;
  color: white;
  border: 2px solid white;

  /* The GitHub button is a primary button
   * edit this to target it specifically! */
  ${props => props.primary && css`
    background: white;
    color: palevioletred;
  `}
`

GDPRで右往左往

無料で使えるアールデコ・アイコン

ただしAdobe XD専用でした(´・ω・`)。

参考: アール・デコ - Wikipedia

左がアール・ヌーヴォー、右がアール・デコ(対称が特徴)だそうです。

スライド: テスト自動化あるある

ITエンジニアに眼福を

15,000円のかき氷。

ITエンジニアをやっていて、IT以外の世界につい無関心になってしまうというのは割とありそうなので、料理に限らずいろんな分野のトップクラスの「いいもの」に一度は触れる機会を持てるとよい気がします。
本当においしい食べ物が、普段の食べ物とまったく次元を異にする別世界だった体験が忘れられません。

言語よろずの間

yew: RustのWebフレームワーク

// 同リポジトリより
use yew::services::console::ConsoleService;
use yew::services::timeout::TimeoutService;

struct Context {
    console: ConsoleService,
    timeout: TimeoutService<Msg>,
}

impl Component<Context> for Model {
    fn update(&mut self, msg: Self::Message, context: &mut Env<Context, Self>) -> ShouldRender {
        match msg {
            Msg::Fire => {
                let send_msg = context.send_back(|_| Msg::Timeout);
                context.timeout.spawn(Duration::from_secs(5), send_msg);
            }
            Msg::Timeout => {
                context.console.log("Timeout!");
            }
        }
    }
}

最近のGo言語 by JetBrains


jetbrains.comより

futureとtaskがRustの標準ライブラリ入り

その他言語




その他

マイクロソフトがGitHubを75億ドルで獲得

既にさんざん話題ですが一応。


blog.github.comより

↑以前はなかったディズニー成分がとっても気になります。

参考: これでわかる!マイクロソフトがGitHubを買収する理由 - M&A Online

インテリジェントな耳栓

普段の会話は聞こえ、大音量になると自動的に

だいぶ昔によくイベントでお付き合いしていたPA会社があったのですが、その会社が担当する野外ライブが年を追うごとに明らかにどんどんボリュームがアップしていて、「もしかして担当者さん、難聴始まってるのでは?」と慄いたことがあります。

何で読んだか思い出せませんが、この有毛細胞は大音量にさらされると抜けてしまい、一度抜けると二度と再生しないのだそうです。

参考: 音響外傷 - Wikipedia

英語の時制


科学論文における時制の使い分け | Editage Insightsより

見積もり


つっつきボイス: 「ガチ見積もりです」「ガチ」

「Dependency Injection」の訳語とは

誤訳と聞いて。


つっつきボイス: 「『の』を入れるかどうかって難しいですよね…」「『の』を使うとどうにでもつながっちゃいますし…」

「依存性(を必要なときだけ外部から)注入(する手法)」の略だと思えば、元の語がそもそも略しすぎな気もしました。

なお、業界が変わるとDIが指すものも変わります。

参考: DI(ダイレクトボックス)について|サウンドハウス — 音響関連
参考: Direct instruction - Wikipedia — 初等教育法の一種

教師の教え方を完全に型にはめるという意味で型破りな教育法。教師は最初から最後までシナリオに沿ってテンポよく進め、生徒(少人数が前提)にもテンポよく一斉に答えさせる。完全にマニュアル化され、教師に何の専門性も創造性も要求しないにもかかわらず、絶対計算による吟味の結果、ほかのどんな教育法よりも優れた結果を出していることが判明している。DIで育った生徒は単にすべての成績がよいだけでなく、高次の思考力にも優れ、自尊心も高められた。特に、成績の悪い子どもほどDIが効果的であるという。ご多分に漏れず現場教師および関係者から猛反発を食らい、当時のブッシュ大統領が後押ししたにもかかわらず、未だにほとんど普及していない。
その数学が戦略を決める』より抜粋・再構成

Aidemy.net: AIプログラミング学習サービス

未踏事業関連だそうです。

参考: AIプログラミング学習サービスAidemy「未踏ジュニア」に協賛 - 産経ニュース

番外

覚えておこう

盲亀の浮木

「盲亀の浮木」という言葉を知ったのは藤子・F・不二雄の『一千年後の再会』という短編漫画でした。

参考: 盲亀の浮木(モウキノフボク)とは - コトバンク

所変わればチートシート変わる

可視光って狭!

遠方にしてもの凄い精度


今週は以上です。土日は身体を休めましょう!

バックナンバー(2018年度)

週刊Railsウォッチ(20180525)特集: RubyKaigi 2018いよいよ来週、Rails vs Hanami対決、命名にはシソーラス使おうほか

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

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

Ruby 公式ニュース

Rails公式ニュース

RubyFlow

160928_1638_XvIP4h

Hacklines

Hacklines

Postgres Weekly

postgres_weekly_banner

JavaScript Weekly

javascriptweekly_logo_captured

Viewing all 1838 articles
Browse latest View live