Tech Racho エンジニアの「?」を「!」に。
  • 開発

週刊Railsウォッチ(20170616)railsdiff.orgはアップグレードに便利、RubyのDSLとかっこの省略、TerraformをRubyで制御ほか

こんにちは、hachi8833です。先週から歯痛で転げ回ってましたが、レントゲンにもCTにもそれっぽいものが映りませんでした。謎です。

6月第2週のRailsウォッチ、いってみましょう。

Rails 5.0.4.rc1リリース

バグ修正用です。リグレッションが見つからなければ早くも来週月曜にリリースされるそうです。

変更点はきわめてわずかで、ActiveRecordActiveModelの修正だけでした。Rails 5.0.x系をお使いの方はアップデートをおすすめします。

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

新機能: mattr_accessorでもデフォルト指定オプションを追加

DHHが先週class_attributeにデフォルト値を指定できるオプションを追加したからmattr_accessorでも追加しようよ、という流れです。今回も以下のような感じでガシガシ追加されています。

# actionmailer/lib/action_mailer/preview.rb
-      mattr_accessor :preview_interceptors, instance_writer: false
-      self.preview_interceptors = [ActionMailer::InlinePreviewInterceptor]
+      mattr_accessor :preview_interceptors, instance_writer: false, default: [ActionMailer::InlinePreviewInterceptor]

ついでにcattr_accessorでもガシガシやっています。

# actioncable/lib/action_cable/server/base.rb
-      cattr_accessor(:config, instance_accessor: true) { ActionCable::Server::Configuration.new }
+      cattr_accessor :config, instance_accessor: true, default: ActionCable::Server::Configuration.new

つっつきボイス: 「ところでcattr_accessormattr_accessorって何が違うんでしょうか」「なんだか動作同じなんですけど」

morimorihogeさんがRubyMineで追っかけてみたところ、以下のオチでした。一同「何じゃそりゃーw」

# activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
alias :cattr_accessor :mattr_accessor

新機能: 多数のキャッシュエントリを一括で書き込めるwrite_multi 💪

Rails.cache.write_multi foo: 'bar', baz: 'qux'
#write_multi_entriesを実装するストアに、より高速な#fetch_multiを追加。
見つからないキーは(個別ではなく)一括でキャッシュストアに書き込まれる

デフォルトの実装では単にエントリーごとに#write_entryを呼んでいる。
Redis MSETのように一括書き込み可能なストアはオーバーライドを行える。

# activesupport/lib/active_support/cache.rb
         options = names.extract_options!
         options = merged_options(options)
-        results = read_multi(*names, options)

-        names.each_with_object({}) do |name, memo|
-          memo[name] = results.fetch(name) do
-            value = yield name
-            write(name, value, options)
-            value
+        read_multi(*names, options).tap do |results|
+          writes = {}
+
+          (names - results.keys).each do |name|
+            results[name] = writes[name] = yield(name)
           end
+
+          write_multi writes, options
         end
       end

つっつきボイス: 「CacheバックエンドにRedisを使ってればRedisのMSET命令で一括書き込みできるということかな」

修正: チェックボックスやラジオボタンのラベルをクリックしても選択できるようになった

「チェックボックスやラジオボタンのラベルもクリッカブルにする」は最近のWeb UIエクスペリエンス周りではよく行われていますが、今回から対応しました。

# actionview/lib/action_view/helpers/tags/collection_check_boxes.rbの場合
           def check_box(extra_html_options = {})
             html_options = extra_html_options.merge(@input_html_options)
             html_options[:multiple] = true
+            html_options[:skip_default_ids] = false
              @template_object.check_box(@object_name, @method_name, html_options, @value, nil)
           end
         end

つっつきボイス: 「これ仕様だと思ってたけどついに直るのか」「自分でラベルを処理しようとするとすごく面倒くさい」

そういえばラベルをクリッカブルにするChrome拡張機能を以前使ったことがあるのですが、今探したところうまく見つけられませんでした。

修正: パーシャルのキャッシュログで誤ったパーシャルから属性を取得していた問題

# actionview/lib/action_view/helpers/cache_helper.rb
       def fragment_for(name = {}, options = nil, &block)
+        # Some tests might using this helper without initialize actionview object
+        @cache_hit ||= {}
         if content = read_fragment_for(name, options)
-          @cache_hit = true
+          @cache_hit[@virtual_path] = true
           content
         else
-          @cache_hit = false
+          @cache_hit[@virtual_path] = false
           write_fragment_for(name, options, &block)
         end
       end
# actionview/lib/action_view/renderer/partial_renderer.rb
           content = layout.render(view, locals) { content } if layout
-          payload[:cache_hit] = view.cache_hit
+          payload[:cache_hit] = !!view.cache_hit[@template.virtual_path]
           content
         end
       end

つっつきボイス: 「あー、パーシャルがネストしたときとかが問題だったのか」

余談: たった今気づいたのですが、これをコミットしたStan Lo氏は、この間Railsウォッチで紹介したGobyのメインメンテナでもあります。

RailsでAPMサービスをスクラッチ開発(RubyFlowより)

私が最初元記事を「AMP」と空目してしまい、ひととおりスマホのAMP談義があったあとで「あれ、これAPMじゃね?」と気づきました。失礼しました。


traceb.inより

つっつきボイス: 「なーんだ、Application Performance Monitoringのことか」「Advanced Power Managementではない(キリッ」「NewRelicみたいなことを自分たちでやろうとしてるのかー」「またしても車輪の再発明w?」

そこから、NewRelicはやっぱり凄いという話題になりました。morimorihogeさんがその場でNewRelicにログインしてみると、大昔に作ったトライアルのエントリでもしっかりチャートが生成されました。

つっつきボイス: 「NewRelic、やっぱりいろいろよくできてる」「ライブラリやメソッドの呼び出しレベルまでチェックしてくれるし」「高いけどねw」「トライアルだとどこまでできるんだったかな?」「NewRelicってアプリの相当深いところまで食い込むのか」「たしかそのためのエージェントをgemでインストールしたはず」


newrelic.comより

静的型付け言語だからバグが減るわけではない?

某所で見かけました。これは2016年に書かれた元記事が別サイトに再掲載されたものですが、忘れた頃にバズったようです。

原文でも断言は避けていますが「静的型付け言語ならバグが減る、とは言い切れないのでは」「むしろ言語仕様のシンプルさの方がバグ減らしに貢献しているのでは」という見解です。

グラフのx軸はバグの密度です。グラフ上ではRubyが健闘しています。


labs.ig.comより

つっつきボイス: 「GitHubのbugタグでかき集めたのか」「うーん、これは信頼に足る情報なんだろうか」「とりあえず集計方法は疑問だなー」「すごく巨大なC++プロジェクトとかあったらbug多いに決まってるんだから不利じゃん」「極端なデータは捨てないといけないのかな」「それもデータの種類や目的とかによりますね: 極端なデータに意味があることもあるし」

DockerとクラウドでHeroku的なデプロイソリューションを構築(Ruby Weeklyより)

タイトル通りです。長いです。


semaphoreci.comより

つっつきボイス:Google Container Engineとか使えばいいのに」「意識高めの車輪の再発明という感じ」

stdgems.org: Rubyバージョンごとのデフォルトgemとバンドルgemのリスト

サイト: stdgems.org

Rubyのバージョンごとにどんな標準のgemがあるかを一覧できます。標準のgemはデフォルトgem(取り外せない)またはバンドルgem(外部メンテ、取り外し可)に区別されており、Ruby 2.2以降が対象です。


stdgems.orgより

Rubyの標準ライブラリはgem化が進められており、Ruby 2.5の「たぶんこうなる」も見ることができます。

つっつきボイス: 「これ悪くないかも」「知ってれば見るかなー」「普通にrbenvでインストールして確認しちゃうかも」

同時実行制御を深掘り: イベントループの巻(Ruby Weeklyより)

シリーズものです。


http://blog.appsignal.com/より

つっつきボイス: 「このあたりを掘っていくとOSの話は避けられないね」「OSは一度みっちりやっておくのが大事」

そこから、concurrentとparallelの違いなどについて話題になりました。なお、上の記事にはparallelという言葉は一度も使われていません。

つっつきボイス: 「concurrentとparallelはITの世界でははっきり違う」「concurrentは「異なるタスクの手分け」、parallelは「同じ種類のタスクの手分け」」「concurrentをsimultaneousという言葉で形容することはない: simultaneousは「開始が同時」というだけ」

Rubyの#each_consは他の言語でどう書くの?(Ruby Weeklyより)

Rubyだと以下のように書けるコードを他の言語で書くとどうなるか、という記事です。F#、C#、Kotlin、Idrisのコード例などが集められています。

([0] + arr).each_cons(2).count {|x,y| x == 0 && y == 1 }

Rubyリファレンスマニュアル: each_consでは以下のようになっています。

要素を重複ありで n 要素ずつに区切り、 ブロックに渡して繰り返します。
ブロックを省略した場合は重複ありで n 要素ずつ繰り返す Enumerator を返します。

C#ではそれ用のコードをこしらえています↓。


[realfiction.netより

つっつきボイス: 「ちょC#スクショだしww」「しかも斜めってる」「#each_consって要素が重なるのか」「重ならないのが#each_slice、その名のとおり」

インタビュー: Aaron Patterson(Ruby Weeklyより)


blog.rubyroidlabs.comより

tenderloveの名前でおなじみのAaron Patterson氏インタビューで、Railsconf 2017でのインタビュー音声つきです。HTTP/2と「Rack 2」計画との関連など、技術面でも非常に読み応えのある良記事です。

ベーコン作りが趣味で、Rubyのハックに必要な日本語記事を読むために11年前から日本語を勉強し、今では日本語ブログも問題なく読めるようになったそうです。凄い!

インタビュー中で、kazuho氏の作ったH2OというWebサーバーを「HTTP/2サーバーとしては最高峰だ」と激賞しています。

つっつきボイス: 「これは日本語になってたら読みたい」「長いから英語で読むのはだるい」「H2O、数年前に話題になってました」「当時HTTP/2をまともに使えるWebサーバーがこれしかなかったんだったかな」「H2Oのh2o.examp1e.netってドメイン名、クラックサイトっぽいwww」「お、lが1になってるw」

参考: 東京 Ruby 会議 11 直前特集号 Aaron Patterson さんインタビュー

開発用ローカルWebサーバーをNginxからTræfikに置き換える(Ruby Weeklyより)

Træfikは文字化けではありません。Goで書かれたリバースプロキシ兼用Webサーバーで、GUIで設定できるのがウリのようです。


traefik.ioより

つっつきボイス: 「GUI(゚⊿゚)イラネww」「CLIでちゃんと設定できない人がGUIでできると思えない」「両方で設定を繰り返すと泣くことになりそうってkazzさんも言ってた、そういえば」

Railsでcookieをテストする(Ruby Weeklyより)


blog.arkency.comより

capybaraでfeature specを書くのは避けたかったらしく、rack-test gemの#get-cookieを使っています。

# http://blog.arkency.com/2017/06/testing-cookies-in-rails/ より
describe do
  specify do
    get "/"

    Timecop.travel(35.minutes.from_now) do
      get "/"

      cookie = get_cookie(cookies, "foo")
      expect(cookie.value).to eq("some value!")
      expect(cookie.expires).to be_present
    end
  end

  # rack-test > 0.6.3 に組み込まれる予定
  def get_cookie(cookies, name)
    cookies.send(:hash_for, nil).fetch(name, nil)
  end
end

つっつきボイス: 「cookieでここまでテストしないといけないのか、大変だなー」

たった2分でRubyプリミティブをカスタムドメインオブジェクトに書き換える方法(Ruby Weeklyより)

ここではNet::HTTPが返すStringのステータスコード'200'を、#success?でチェックするようリファクタリングしています。テストコードも一緒に書いてあるので助かります。

つっつきボイス: 「こういうの、たまにやるかなー」

GeoEngineering: TerraformのDSLをRubyで書けるgem(Ruby Weeklyより)

インフラをコードで管理するソフトウェアであるTerraformをRubyで制御するラッパーのようです。なおTerraformはGo言語で書かれています。


www.terraform.ioより

つっつきボイス: 「Terraformは超有名で実績も多数」「Terraformのリポジトリ、★8,600超えだ」「TerraformのDSLは割りと独特なので、それをRubyで書きたいってことか」「GeoEngineeringの方は★200個程度というのがちと不安」

# https://www.terraform.io/ のサンプルDSL
resource "aws_elb" "frontend" {
  name = "frontend-load-balancer"
  listener {
    instance_port     = 8000
    instance_protocol = "http"
    lb_port           = 80
    lb_protocol       = "http"
  }

  instances = ["${aws_instance.app.*.id}"]
}

resource "aws_instance" "app" {
  count = 5

  ami           = "ami-408c7f28"
  instance_type = "t1.micro"
}
# https://github.com/coinbase/geoengineer のサンプルコードより
class GeoEngineer::Resources::AwsSecurityGroup < GeoEngineer::Resource
  # ...
  def all_egress_everywhere
    egress {
        from_port        0
        to_port          0
        protocol         "-1"
        cidr_blocks      ["0.0.0.0/0"]
    }
  end
  # ...
end

project.resource('aws_security_group', 'all_egress') {
  all_egress_everywhere # use the method to add egress
}

ところで、GeoEngineeringという名前は明らかにTerraformを意識してますね。暁星記という未完のマンガを思い出しました。

n_plus_one_control: RSpec/miniTestの両方で使えるN+1問題検出マッチャー(Ruby Weeklyより)


github.com/palkan/n_plus_one_controlより

expect { subject }.to query(2).timesのように具体的な回数を書かずにN+1問題をテストできるそうです。クエリの数がO(N)ではなくO(1)として振る舞うかどうかをテストします。

bulletでもテスト書けるんだけど?」については、「bulletは銀の弾丸(silver bullet)じゃないので、偽陽性や偽陰性の可能性が残る」だそうです。誰がうまいこと言えと。

つっつきボイス: 「N+1が起きてないことをテストする?」「N+1警察はN+1でないことを確認するテストを書くんだろうなーw」「★まだ80個台...」

Rubyのコードを読む: DSL(RubyFlowより)

連載記事の4回目です。RubyのROMのDSLをとことん追求している濃厚な記事です。

# https://blog.mikecordell.com より
def schema(dataset = nil, infer: false, &block)
  if defined?(@schema)
    @schema
  elsif block || infer
    self.dataset(dataset) if dataset
    self.register_as(self.dataset) unless register_as

    name = Name[register_as, self.dataset]
    inferrer = infer ? schema_inferrer : nil
    dsl = schema_dsl.new(name, inferrer, &block)

    @schema = dsl.call
  end
end

つっつきボイス: 「ROMはRuby Object Mapperのことか」「RubyでDSL書きたい人にはよさそう」


rom-rb.orgより

morimorihoge「RubyのDSL入門ならCodeSchoolのRuby Bits part 2がシンプルでおすすめですよー

その後、Rubyの引数のかっこを省略できる仕様の理由を私は今頃になって初めて知りました。

つっつきボイス: 「Rubyでかっこを省略できるのは、RubyでDSLを書きやすくするためという明確な目的がある」「そうだったのか!」「たしかmatzはRubyの初期からそのつもりでやっているはず」

ネットの記事で「Rubyではかっこを省略できるので、DSLを簡単に書けます」という記述をときどき見かけていました。そこから「たまたまかっこを省略できるのでそれを利用して...」かなと思っていましたが、違いました。Rubyは「言語を書くための言語」を最初から志向していたんですね。

RailsでUIコントローラ(RubyFlowより)

ネストしたフォームを使う場合など、ビューが複雑になった場合にコントローラをどう扱うかを検討し、ここでは以下の3番目の方法を使っています。

  • 同じコントローラに新しいメソッド(アクション)を追加
  • 別のコントローラを書く
  • UIコントローラをネストする(Railsの密かな第3のオプション)

ここではコントローラでささやかなDSLを書く方法を使っています。さらに、ネストを重ねたり、ネストしたコントローラでもパーシャルを参照できるようにするなどし、ルーティング用のモジュールも書いています。

つっつきボイス: 「この方法、悪くないかも」「『RESTfulを崩さないかぎりコントローラは増やしてもいい』がDHH流ですよね」「ネストするフォームってつらくなりがち」「この落書きwww↓」「1分間に何回WTF(何やこれは!)って言われるかでコードの良し悪しがわかるw」


labs.kollegorna.seより

Railsアプリのアップグレードに役立つリソース集(RubyFlowより)

「アップグレードのときはこれ見ましょう」リンク集です。

つっつきボイス: 「Rails 4どまりかー」「世の中にはRails3っていうものもあるんだじぇー」

⭐ railsdiff.org ⭐

むしろ、その中で紹介されていたrailsdiff.orgの方が一同の目を引きました。

RailsのバージョンアップでRailsアプリのどこを変更しなければいけないかをざっと表示するもので、内部コードのdiffではありません。当然ながらバージョンが離れるほどどんどん量が増えます。こちらは何とRails 2.3.6以降に対応しており、Rails 4.xとRails 3.xといった設定の差も表示できます。


railsdiff.orgより

つっつきボイス: 「これ、いいんじゃん!?」


railsdiff.orgより

⭐ を進呈いたします。おめでとうございます。

Rubyで機械学習: 線形回帰を実装(RubyFlowより)

x_data = []
y_data = []
# CSVから配列に読み込む - 自由変数 X と従属変数 Y
# 各行には以下のようにプロパティと生活圏の広さ(平方フィート)を含む
# [ SQ FEET PROPERTY, SQ FEET HOUSE ]
CSV.foreach("./data/staten-island-single-family-home-sales-2015.csv", :headers => true) do |row|
x_data.push( [row[0].to_i, row[1].to_i] )
y_data.push( row[2].to_i )
end

つっつきボイス: 「Pythonでやってくれーw」

Quora: 有名サイトでRubyが使われてないのはどして?

最近のQuoraから届く更新メールはこれ系の記事が多く、こちらのクリックがトラック・分析されていることをひしひしと実感します。

回答は「そりゃRailsが登場する前からあるサイトばかりだからなんじゃ?」「Rails登場以前はRubyで大規模Webサイトを作るとかありえなかったし」などなど。

つっつきボイス: 「(トラック・分析は)記事表示の最適化がQuoraの売りだから」「煽ってくるなー、このタイトル」「その割にスレが伸びてないなw」「みんな不感症になっちゃった?」「初期のTwitterとかもRailsで構築されていたんだけどな」「表にもTwitterあるし」


今週は以上です。

関連記事

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

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

Rails公式ニュース

Ruby Weekly

RubyFlow

160928_1638_XvIP4h


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。