Quantcast
Channel: Ruby on Railsの記事一覧|TechRacho by BPS株式会社
Viewing all 1381 articles
Browse latest View live

週刊Railsウォッチ(20180730)Rubyの速い書き方集、最近登場のRailsアプリたち、RubyWorld Conference受付開始ほか

$
0
0

こんにちは、hachi8833です。やっとGobyにRipperライブラリをマージしました。

束の間の涼しい夏日のウォッチ、いってみましょう。

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

⚓【お知らせ】週刊Railsウォッチ「公開つっつき会 第1回」今週8/2(木)開催

今週木曜日です。引き続き参加をお待ちしております!🙇懇親会にて軽食&ビールもお待ちしています🍺

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

先週は公式の更新情報がなかったので、masterブランチなどから見繕いました。

⚓scopingメソッドの廃止(オープン中)

これまでのscopingは、元のスコープチェインを伝搬させる目的で、リレーション由来の名前付きスコープメソッドの委譲に使われてきた。しかしクラスメソッドへのクエリがすべて汚染されるという望ましくない振る舞いが生じていた(例↓)。

class Topic < ActiveRecord::Base
  scope :toplevel, -> { where(parent_id: nil) }
  scope :children, -> { where.not(parent_id: nil) }
  scope :has_children, -> { where(id: Topic.children.select(:parent_id)) }
end

# 期待どおり動作
Topic.toplevel.where(id: Topic.children.select(:parent_id))

# `toplevel`から`Topic.children`にお漏らししてるので動かない
Topic.toplevel.has_children

#29301で名前付きスコープ内のレシーバがモデルクラスから元のスコープチェインに変更されたので、クラスメソッドのクエリ汚染は必要でなくなった。
同PRより大意

# activerecord/lib/active_record/relation.rb#L310
    def scoping
-      previous, klass.current_scope = klass.current_scope(true), self
-      yield
+      previous, klass.current_scope = klass.current_scope(true), scope
+      delegate_to { yield }
     ensure
       klass.current_scope = previous
     end

kamipoさんによるPRです。まだマージされていません。


つっつきボイス: 「Railsdmのオーラスでkamipoさんが言及してたので(先週のウォッチ)」

⚓Rubocopのお導きでパフォーマンスを改善

# actionpack/lib/action_controller/metal/strong_parameters.rb#L793
     protected
       attr_reader :parameters

-      def permitted=(new_permitted)
-        @permitted = new_permitted
-      end
+      attr_writer :permitted
  • getter/setterメソッドよりattr_accessorを使うべし(メソッドは12%遅い)
  • map.flatten(1)よりflat_mapを使うべし(flattenは66%遅い)
    • (正確には「map.flatten(1)は66%遅い」ですね☺
  • 引数が1つならhash.merge!よりhash[]=を使うべし(merge!は166%遅い)

つっつきボイス: 「flat_mapって初めて見た😧」「TechRachoにあるRubycop式Rubyスタイルガイドにも載ってた気がします(↓あった:2-70)」「flattenは泥臭いデータ構造を処理するときに結構使うけど最近あまり使ってない😆flattenの動作は再帰的だと思われるので遅そう」「何となく英語圏にflatten好きが多い気も」「ちーっす」「お、ちょうどいいところへ: flat_mapって使います?」「存在は知ってるけど使ったことはちょっと…🤔」「やっぱりねー」「flattenしてからmapするってことかな?」「可読性とのバランスもあるし、速い書き方よりメジャーな書き方の方がいいときもあると思うんだけどなー☺

【保存版】Rubyスタイルガイド(日本語・解説付き)総もくじ

  • #flat_map: 配列の階層を1段階浅くする
  • #flatten: 配列の階層を完全にフラットにする(再帰)

参考: instance method Array#flatten (Ruby 2.5.0)
参考: instance method Enumerable#collect_concat (Ruby 2.5.0)flat_mapはこのエイリアスでした

「『merge!は166%遅い』というのもスゴイ😮」「これは確かにhash[]=の方がわかりやすいし速いのも納得だけど、例外が発生する可能性があるならmerge!がいいかもね: それにフレームワークのコード(strong parametersとか)はいくつ渡すか決められないものも多いからmerge!になるだろうし」

このPRで引用されていた#32337で、fast-rubyとfasterer gemを知りましたのでこの後の「Ruby」のコーナーで。

⚓ブロック渡しよりシンボル渡し

2014年のコミットなのでだいぶ古いですが、上の#32381で言及されていたので。

def finalize!
  route_sets.each do |routes|
    routes.finalize!
  end
end

# ↓

def finalize!
  route_sets.each(&:finalize!)
end

ざっと見ると、インスタンス変数の個数が少ない場合はブロック渡しのほうが速く(Procオブジェクトが生成されないので)、多くなるとシンボル渡しの方が速くなるとあります(16833#)。


つっつきボイス: 「&:finalize!みたいなProcにシンボルを渡す記法、今でこそ慣れたけど最初見たときは『へ?👁』って思いましたね: もうイディオムとして普及しているので、単純な呼び出しならProcシンボル渡しの方がいいのかも」

自分でもRuby 2.5.1でベンチ回してみました。

参考: class Proc (Ruby 2.2.0)

参考: &演算子と、procと、Object#method について理解しなおす - Qiita

⚓日付を2000-01-01で正規化してサマータイムのDBとの日付のズレを修正

# activerecord/lib/active_record/connection_adapters/abstract/quoting.rb#L132
       def quoted_time(value) # :nodoc:
+        value = value.change(year: 2000, month: 1, day: 1)
         quoted_date(value).sub(/\A\d\d\d\d-\d\d-\d\d /, "")
       end

つっつきボイス: 「今はもう2000-01-01で正規化できちゃうのね(遠い目」「UNIX時間だと1970年あたりから開始でしたっけ↓」「connection_adaptersのあたりとか触ったことない」「サマータイムなければいいのにって思うし」

参考: Ruby on Rails 5.2 / ActiveRecord::ConnectionAdapters::Quoting#quoted_date — DevDocs

参考: UNIX時間 - Wikipedia — 「協定世界時 (UTC) での1970年1月1日午前0時0分0秒から」

「そういえば昔『インターネット時間』ってものがありましたねー: 自分が覚えているのはPSOことファンタシースターオンライン(セガのネトゲ)で使われてたくらい😆

参考: ファンタシースターオンライン - Wikipedia

「あ、ちょうど今日タバコ吸いながらその話になったんですが、その昔腕時計で有名なSWATCHがインターネット時間を提唱したんだそうです↓」「へー?そっちが元だったのか」「何でも一日を1000で割るという豪快な案らしくて🤣」「何ちゅう🤣」「PSOもたぶん初期がそれだったし: 世界中で同時にサービスしてたからでしょうけど」「時差というか経度に影響されない時間だからですね」「細かい単位を小数点で表せるのは10進数ならでは」「結局インターネット時間はさっぱり定着してませんが😎: UNIX時間でいいけど別に」

参考: スウォッチ・インターネットタイム - Wikipedia


Wikipediaより: [CC BY 2.5](Creative Commons — Attribution 2.5 Generic — CC BY 2.5)

⚓Railsガイドへのリンクを遅まきながらHTTPS化

# README.md#L78
 5. Follow the guidelines to start developing your application. You may find
    the following resources handy:
-    * [Getting Started with Rails](http://guides.rubyonrails.org/getting_started.html)
-    * [Ruby on Rails Guides](http://guides.rubyonrails.org)
+    * [Getting Started with Rails](https://guides.rubyonrails.org/getting_started.html)
+    * [Ruby on Rails Guides](https://guides.rubyonrails.org)
     * [The API Documentation](http://api.rubyonrails.org)
     * [Ruby on Rails Tutorial](https://www.railstutorial.org/book)

つっつきボイス: 「ほら、Chromeが最近…」「API DocはまだHTTPS化されてない?」

なおAPI Documentationは既にHTTPSされてますが、執筆時点のmasterのリンクはまだ修正されてないようです。

⚓limit(1).firstlimit(1).lastで前者だけがデフォルトで主キーでソートされていたのを修正

# activerecord/lib/active_record/relation/finder_methods.rb#L552
       def ordered_relation
-        if order_values.empty? && primary_key
+        if order_values.empty? && primary_key && limit_value.blank?
           order(arel_attribute(primary_key).asc)
         else
           self
         end
       end

よくみると2016年にオープンされていたのをkamipoさんがつい最近マージしていました。breaking changeなのでバックポートはしないようです。類似の#27597はbreakingではないので早々にマージされたとのこと。


つっつきボイス: 「2年越しのマージ」「あーなるほど、遅延評価されるときにfirstlastの結果が違ってたということか: でもlimit(1).firstなんて異常な書き方は普通まずしないし😆」「でもスコープにlimit書いちゃうと踏むことがあるかも?」「#27597は以前のウォッチで見たことあるな」「#27597は明らかにバグだから全員困るけど、この#24131は修正することで逆に困る人が出てくるからバックポートしなかったんでしょうね」

⚓Active Recordコールバックの引数を厳密にチェック

# activemodel/lib/active_model/callbacks.rb#L127
     private

       def _define_before_model_callback(klass, callback)
-        klass.define_singleton_method("before_#{callback}") do |*args, &block|
-          set_callback(:"#{callback}", :before, *args, &block)
+        klass.define_singleton_method("before_#{callback}") do |*args, **options, &block|
+          options.assert_valid_keys :if, :unless, :prepend
+          set_callback(:"#{callback}", :before, *args, **options, &block)
         end
       end

       def _define_around_model_callback(klass, callback)
-        klass.define_singleton_method("around_#{callback}") do |*args, &block|
-          set_callback(:"#{callback}", :around, *args, &block)
+        klass.define_singleton_method("around_#{callback}") do |*args, **options, &block|
+          options.assert_valid_keys :if, :unless, :prepend
+          set_callback(:"#{callback}", :around, *args, **options, &block)
         end
       end

       def _define_after_model_callback(klass, callback)
-        klass.define_singleton_method("after_#{callback}") do |*args, &block|
-          options = args.extract_options!
+        klass.define_singleton_method("after_#{callback}") do |*args, **options, &block|
+          options.assert_valid_keys :if, :unless, :prepend
           options[:prepend] = true
           conditional = ActiveSupport::Callbacks::Conditionals::Value.new { |v|
             v != false
           }
           options[:if] = Array(options[:if]) << conditional
-          set_callback(:"#{callback}", :after, *(args << options), &block)
+          set_callback(:"#{callback}", :after, *args, **options, &block)
         end
       end

つっつきボイス: 「おー、今までの*argsの他に**optionsも追加されてる」「options.assert_valid_keys :if, :unless, :prependでアサーション追加してますね」「コールバックメソッドに余計なオプションを付けられないようにしてるようだ」

⚓番外: Railsガイドの囲みの背景色を変更

PRそのものより、これを投げたlanzhihengさんのアバターが喚起する極めて強い既視感が気になったので。


つっつきボイス: 「こういう修正は地味だけど重要ですね: 見やすいは正義」「とりあえずアバターをどぞ」「これは結城浩さんのスレッドおばけっぽいw: 結城さんのアバターはこういうクォータービューじゃないけど😆」「好きなのかな?」「間違える人いそう😆

⚓Rails

⚓⭐今週の人気Railsリポジトリ(2018/07/23)⭐

今回最も盛り上がりました。目ぼしいもののみピックアップしてみました。Railsアプリでないものもあったりします。こちらの記事に⭐を進呈いたします。おめでとうございます。


つっつきボイス: 「お、知らないものがいろいろあるなー」「どれどれ👀

⚓Zammad: ヘルプデスク/カスタマーサポートアプリ



同サイトより

「ヘルプデスク/カスタマーサポートのシステム」「Redmineと同じような感じで、Railsで書かれたアプリか」「errbitみたいな感じで導入できそう」「そんな感じで単体でも導入できるしRailsエンジニアがカスタマイズもできると: これなかなかよさそうじゃない❤」「😃

⚓flutie: content_forを改善

Factory_botでおなじみThoughtbotのgemです。


同リポジトリより


つっつきボイス: 「フルーティ?」「むむん?ははー、content_forをもっと使いやすくするgemか!」「地味といえば地味」「SEO的な改修をしたい人向けっぽいかも」「確かに今のcontent_forって生々しすぎてちょっと使いづらいし、デフォルト値書き忘れたりするし、lambda渡し始めるとつらくなってくるし」「content_forって、記述したものがyieldで呼び出されるのが違和感あるんですよ: 『え?yieldでやるの?』っていう気持ちになるし、そのことを知らないと直感的にわかりづらい」「その意味ではflutiecontent_forよりは読みやすいですね❤

#同リポジトリより
content_for(:site_page_title, 'My title of my page')
page_title(:app_name => 'My app name', :page_title_symbol => :site_page_title, :separator => " | ")
=> "My app name | My title of my page"

参考: Ruby on Rails 5.2 / ActionView::Helpers::CaptureHelper#content_for — DevDocs

⚓OpenProject: プロジェクト管理



同サイトより


つっつきボイス: 「これはプロジェクトマネージメントのアプリ」「おおー!?これなかなかよくできてるかも: ガントチャートもカンバンも標準でついてて必要な機能がひととおり揃ってそうな感じ」「Redmineにライバル出現?」「JIRAより好きかも」「UIがんばってるし」「Redmineのデータをインポートできたらスゴイな(追伸: 一応手順あり)」「OpenProject悪くなさそう: 機会があったら使ってみたい😋(自前でホスティングするのは面倒だけど)」

⚓bootstrap-rubygem: Bootstrap 4の公式gem


つっつきボイス: 「前からあるんでしょうね」「ですね: SCSS版のBootstrap 4が使えるヤツ」「そういえばBootstrap 4って今もjQuery必須なんですね🤔」「jQuery手放してないですね」「jQueryがないとBootstrap 4でJavaScriptが動かないから😅」「将来はjQueryなくすみたいなプランぐらいあってもよさそうだけど(追記: 今の所見当たりませんでした)」


getbootstrap.comより

⚓Skinny: RailsにインスパイアされたScala言語向けWebフレームワーク


同サイトより


つっつきボイス: 「サーブレット?」「Javaサーブレットですね」「ScalaでRailsが動く?」「いや、これはRailsにインスパイアされたとあるから、RailsっぽいWebフレームワーク」「コマンドもRailsっぽい↓」「PHPのSymfonyフレームワークなんかもRailsに影響受けてますしね」

# 同サイトより
./skinny db:migrate test
./skinny test

参考: Scala - Wikipedia
参考: Symfony - Wikipedia


Wikipediaより(パブリックドメイン)

⚓react-rails: React.jsの「公式」gem


つっつきボイス: 「お、Webpackerをちゃんとサポートしてるし😃」「Sprocketsもサポートされてる」「サポートされているWebpackerは3まで… 4はまだ入ってない(惜しい!)😢」「誰か人柱になる😆?(チラッチラッ」

Rails 5: Webpacker公式README — Webpack v4対応版(翻訳)

「そういえばreact_on_railsなんてgemもありませんでした?」「紛らわしい😅」「お、でもこのreact-railsはReact.jsの公式ですよ」「ほんとだ!😲」「待てよ、ということはReact.jsはWebpackerを公式に推しているってことなんじゃ?」「😃」「公式が推してくれているんなら、Webpackerを導入するときに宗教戦争にならないで済みそう?」「もしそうなら安心感が違う😋」「ま、いつまで公式に推してくれるかはわかりませんけどね😎

⚓Oj: Ruby製高速JSONパーサー


同リポジトリより

# 同リポジトリより
require 'oj'

h = { 'one' => 1, 'array' => [ true, false ] }
json = Oj.dump(h)

# json =
# {
#   "one":1,
#   "array":[
#     true,
#     false
#   ]
# }

h2 = Oj.load(json)
puts "Same? #{h == h2}"
# true

つっつきボイス: 「高速なJSONパーサー」「このロゴ見たことがある気がします」「こういう高速なgemはあるときRailsにしれっと入るかもしれませんからね🕶」「よく見るとC拡張だった…」「ほんとだ: 環境によってはインストール面倒かも」

後でohler.com/ojを見てみると、Oj 3.0.0でRuby標準のjson gemの代わりに使ったり、RailsモードにすることでActiveSupport 5.0のエンコード/デコードをすべて置き換えられると書かれています。

⚓その他

  • snibox/sniboxはスニペットマネージャ」「スニペットをリモートに置く理由って、ローカルに置いとくと何かのはずみで消えそうでコワイからだよねっ😨
  • refinery/refinerycmsはRailsのCMS、trestleは管理画面フレームワーク」「こういうのはどっちももの凄い数あるしなー🤓」「でもCMSでは誰もWordPressに勝てないという…」

  • activerecord-importは息の長いアプリ: いいやつ❤

  • ifmeorg/ifmeって何だろう…?」「メンタルヘルスのコミュニティみたいだけど」「そのサイトをRailsで作ってソースを公開しましたってことなのかな…🤔

  • spreadsheet_architect、すごい名前😆」「ActiveRecordからXLSXやODSやCSVを作成できるらしい」「ODSってたしかOpenOfficeのフォーマットですね」「使ってみないとわからないけどActiveRecordから楽に作れるならいいかも」

# 同リポジトリより
Post.to_xlsx(instances: posts, spreadsheet_columns: Proc.new{|instance|
  [
    ['Title', :title],
    ['Content', instance.content.strip],
    ['Author', (instance.author.name if instance.author)],
    ['Published?', (instance.published ? 'Yes' : 'No')],
    :published_at, # uses the method name as header title Einstance. 'Published At'
    ['# of Views', :number_of_views, :float],
    ['Rating', :rating],
    ['Category/Tags', "#{instance.category.name} - #{instance.tags.collect(&:name).join(', ')}"]
  ]
})
  • 「突然にRailsの#33079が: Rails 6でWebpackerがデフォルトのJSコンパイルになると」

  • acts-as-taggable-on! こんな古いのまだあったとは👴」「だいぶ前に自分で書く方が早いって言ってたヤツですね」

  • RRRSpecといえばクックパッドさんの有名な分散RSpecシステム」

参考: 分散テスト実行システムRRRSpecをリリースしました - クックパッド開発者ブログ

  • koudokuはStripe IDを設定すれば購読フローを楽に作れそう」「どうやらサインアップが必要っぽいかな?」

Stripe決済を自社サービスに導入してわかった5つの利点と2つの惜しい点


「しかし知らないgemやアプリが結構ありましたね」「しばらくサーベイしていないと意外にいろんなのが出てるなー」「GitHub Trending以外にも情報源が見つかってうれしい😋

なお、上の元記事は以下から抜粋・再構成したものだそうです。

参考: Weekly trending Ruby on Rails repositories. June, 11

⚓テストでTimecopとVCRを連携させる(Hacklinesより)

短い記事です。


つっつきボイス: 「VCRってブラウザの画面操作を記録再生するツールですね」「VCRでwebmockが動かなくなってハマってた人いましたよ😨」「VCRはHTTPリクエストを横からトラップするから」「うろ覚えですけど、テスト書いている途中にwebmockしたいリクエストを間違えたりすると、VCRが無いときは単にwebmockが『mockがない』ってエラーを出すだけなのに、VCRと組み合わさると実際にリクエスト飛ばしてwebmockのカセットが作成される、みたいなことがありました」「ありゃー」「この種の自動でよしなにやってくれるgemはさり気に注意が必要🧐: ロード順序が変わると動かなくなったりとか」「VCRはこれはこれでいいgemだけどしっかり理解して使わないといけないっすね💦

参考: VCRを使って開発中にあほみたいにリクエストを飛ばさないようにする - かずおの開発ブログ(主にRuby)

⚓rails_event_store: ActiveRecord向けEvent Store

記事を先に出してましたが。

Rails: Event Storeの新しいAPIを解説する(翻訳)


つっつきボイス: 「rails_event_store、一度使ったことある気がする」

⚓Avdi Grimm氏のオンライン講座「Master the Object-Oriented Mindset」(Hacklinesより)

略してMOOMだそうです。


つっつきボイス: 「Avdi Grimmさんなのでたぶんちゃんとしてそう」「現実のオブジェクト指向設計って、単に勉強しただけでは意外に習得できなくて、自分で実際に何度もやってみて失敗を重ねていかないとなかなか腑に落ちないところがありますね🧐」「それあるかも😲

⚓Dockerイメージビルドを10分から5分に短縮(Hacklinesより)


同記事より


つっつきボイス: 「この手のDockerビルドの速度改善はいろいろやり方がありますね」「意識的にキャッシュするように書いたりとか」

⚓Rails 5.1の機能を使ってメール送信をリファクタリング(Ruby Weeklyより)

# 同記事より
# app/mailers/application_mailer.rb

class ApplicationMailer < ActionMailer::Base

  default from: "\"mkdev\" <#{ENV['SUPPORT_EMAIL']}>",
          subject: -> { default_i18n_subject },
          to: -> { @recipient.email }

  before_action :load_recipient

  private

  def load_recipient
    @recipient = params[:recipient]

    I18n.locale = if @recipient.is_a?(User)
                    @recipient.locale
                  elsif @recipient.user
                    @recipient.user.locale
                  else
                    :en
                  end
  end

end

つっつきボイス: 「Rails 5.1の#27825でメイラーがパラメタライズされたのでそれを活用したと記事の冒頭にありました」「その機能追加されてたなー: 上のsubject: -> { default_i18n_subject }みたいにパラメータにlambda使えたり」「書きやすくなったしこれはいいんじゃないかな😃

⚓その他Rails



つっつきボイス: 「社内Slackで貼っていただいたやつです」「これはついウケた: Rails本体ですらRubocopに怒られてしまうのかと😅」「DHHは見た感じRubocopに何言われても気にしてなさそうですけどね🤔」「気にしてなさそう😆

⚓Ruby trunkより

⚓ruby -wcを取れるAPIが欲しい


つっつきボイス: 「-wcって何だったかな」「コンパイルのみってことらしいです」「ははぁいわゆるシンタックスチェックか」「Dry-run的な」「Ruby Language Serverで使いたいんだそうです」「分かりみある」「Ruby Language Serverってまだそんなに普及してないのかな?」「でもたとえばGitLabにRuby Language Serverが入ったらめちゃくちゃうれしい」「今ならGitLabのWebIDEも使えますしね」「そうなったらもうGitLabだけで開発しちゃう❤: リポジトリをチェックアウトしてIDEで開かなくてよくなるし😋」「そういえば2017年のRubyKaigiの発表でもRuby Language Serverをエディタのシンタックスハイライトに使ってましたね」

⚓doendブロックと{}ブロックの挙動が違う->仕様どおり

# 同issueより
puts [1,2,3].map do |i|
  puts i
end
#<Enumerator:0x007ffe930b76d0>

puts [1, 2, 3].map { |i| puts i }
1
2
3

演算子結合の優先順位の違いのようです。


つっつきボイス: 「へー、挙動違うのか: でも普段こんなコードをそもそも書かないし」「書かないですねー」「こういうのを見つける人たちがスゴイ💪

⚓ハングルのUnicode正規化が仕様と異なる(調査中->closed)

-if length>2 and 0 <= (trail=string[2].ord-TBASE) and trail < TCOUNT
+if length>2 and 0 < (trail=string[2].ord-TBASE) and trail < TCOUNT 

ハングルはわかりませんが、Rubyのunicode_normalizeがNFC(デフォルト)、NFD、NFKC、NFKDのすべてに対応していることを今頃知りました。


つっつきボイス: 「RubyというよりはUnicodeの仕様の話が中心ですね: 主に私の興味で選びました」「Unicodeは言語側で面倒見るのは大変だし、Unicodeのテストコードを書くだけでもつらそう…」「unicode_normalizeにはハングルにだけ特殊な正規化コードがあることをついでに知りました」

参考: instance method String#unicode_normalize (Ruby 2.5.0)
参考: Unicode正規化 - Wikipedia
参考: Unicode正規化 — ハングルの正規化について説明しています。

なお#14934はつっつきの時点では調査中でしたが、その後Martin先生が「パッチは技術的には正しいが現実には問題ない」として今後に備えてテストをコミットしてクローズされました。

⚓Ruby

⚓臨時ニュース: RubyWorld Conference 2018参加受付開始

⚓fast-ruby: ベンチマークを元にした「Rubyが速くなる書き方集」

# 同リポジトリより
$ ruby -v code/general/attr-accessor-vs-getter-and-setter.rb
ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
Calculating -------------------------------------
   getter_and_setter    61.240k i/100ms
       attr_accessor    66.535k i/100ms
-------------------------------------------------
   getter_and_setter      1.660M (± 9.7%) i/s -      8.267M
       attr_accessor      1.865M (± 9.2%) i/s -      9.248M

Comparison:
       attr_accessor:  1865408.4 i/s
   getter_and_setter:  1660021.9 i/s - 1.12x slower

つっつきボイス: 「おー、なるほど: 現実にこれがコメントで付いた場合にどこまでそれに従うかというのはあるけどなっ」「速さが問題にならない箇所なら見やすさを優先したいときもあるし」「そういえばJavaの文字列にもこんなのがありましたね」「というと?」「Javaだと+じゃなくてStringBuilderで結合するみたいな定番中の定番の書き方があるんですよ」「Rubyで言うと+じゃなくて式展開で文字列を組み立てるみたいなヤツです」

参考: StringBuilder (Java Platform SE 8 )

Rubyでの文字列出力に「#+」ではなく式展開「#{}」を使うべき理由

⚓fasterer: 「fast-ruby」にインスパイアされたRuby高速化サジェスチョンツール

# 同リポジトリより
app/models/post.rb
Array#select.first is slower than Array#detect. Occurred at lines: 57, 61.

db/seeds/cities.rb
Hash#keys.each is slower than Hash#each_key. Occurred at lines: 15, 33.

test/options_test.rb
Hash#merge! with one argument is slower than Hash#[]. Occurred at lines: 84.

test/module_test.rb
Don't rescue NoMethodError, rather check with respond_to?. Occurred at lines: 272.

spec/cache/mem_cache_store_spec.rb
Use tr instead of gsub when grepping plain strings. Occurred at lines: 161.

つっつきボイス: 「でこっちのfastererがサジェスチョンしてくれるツールですね」「まあこういうツールがあってもいいけどね🕶、これがCIに入ったらますます怒られることが増えそうだなー」「サジェスチョンが賢ければいいんですけど、たとえば#merge!を使うと必ず怒られたりしたらつらい😅」「『こっちの方が速い』と言われればもっともなんだけど、本質的な指摘とも限らないし」「サジェスチョンの言い回し次第かも」「でもどんな感じなのか一度使ってみたいですね😃

「上のArray#select.firstよりArray#detectの方が速いというのは確かにもっとも: #selectは全部をまとめたものを出すんですけど、#detectは最初に見つけたものを出すんで、結果は同じでも見つけた瞬間に終わる#detectの方が速いし、たぶん可読性もこっちの方がいいし」「たいていの言語でも、全部をスキャンするメソッドと見つけた時点で終わるメソッドがそれぞれありますからね」「後者はany?的なメソッドですね」

「fastererがrubocop -aみたいに自動書き換えまでやってくれるならいいかなという気はする」「rubocop -aも万能じゃないですけどね😅」「でも-aがあるからrubocop使う気になれるというのはある: 重箱の隅的な指摘はこれでだいたい消えるし」

⚓pragmatic_segmenter: 文章を文ごとに区切るgem(Ruby Weeklyより)

# 同リポジトリより
text = "Hello world. My name is Mr. Smith. I work for the U.S. Government and I live in the U.S. I live in New York."
ps = PragmaticSegmenter::Segmenter.new(text: text)
ps.segment
# => ["Hello world.", "My name is Mr. Smith.", "I work for the U.S. Government and I live in the U.S.", "I live in New York."]

# Specify a language
text = "Այսօր երկուշաբթի է: Ես գնում եմ աշխատանքի:"
ps = PragmaticSegmenter::Segmenter.new(text: text, language: 'hy')
ps.segment
# => ["Այսօր երկուշաբթի է:", "Ես գնում եմ աշխատանքի:"]

# Specify a PDF document type
text = "This is a sentence\ncut off in the middle because pdf."
ps = PragmaticSegmenter::Segmenter.new(text: text, language: 'en', doc_type: 'pdf')
ps.segment
# => ["This is a sentence cut off in the middle because pdf."]

# Turn off text cleaning and preprocessing
text = "This is a sentence\ncut off in the middle because pdf."
ps = PragmaticSegmenter::Segmenter.new(text: text, language: 'en', doc_type: 'pdf', clean: false)
ps.segment
# => ["This is a sentence cut", "off in the middle because pdf."]

# Text cleaning and preprocessing only
text = "This is a sentence\ncut off in the middle because pdf."
ps = PragmaticSegmenter::Cleaner.new(text: text, doc_type: 'pdf')
ps.clean
# => "This is a sentence cut off in the middle because pdf."

つっつきボイス: 「どこからどこまでが1つの文かをマルチリンガルで検出するやつですね: READMEに区切りのルールがびっしり書いてあります」「言われてみれば?!も文の終わりを表すのか: ラノベをこれで処理したら相当戸惑いそうではある😆

「そういえばタイ語は文末にストップマーク(。や.など)がなくてスペースを置く、でも行末だとスペースは略すという凄い言語なのでパーサー泣かせで有名です😢」「単語と単語の間はスペースあるんでしょうか?」「えっとどっちだったかな…(追記: 単語間のスペースはありませんでした)」「まあそもそも文の中に単語があるみたいな構成の言語ばかりじゃないですしね: ヒエログリフとか😆」「そういえばヒエログリフは顔が右を向いてるか左を向いてるかで文の進行方向が変わりますね😅

参考: TRADOS - Wikipedia — 翻訳支援ツール(文の区切りを事細かに設定可能)

⚓どこまで継承でやるか

継承は悪か
(中略)
「継承はカプセル化の概念を破壊する」件については、また別にもう少し考えなければなりません。 なぜ継承がカプセル化の概念を破壊するかといえば、 サブクラスはインスタンスの構造に直接触ることができるからです。
インスタンス変数を直接参照すれば、スーパークラスの実装に強く依存してしまいますし、 インスタンス変数を直接更新すれば、インスタンスの不整合な状態を作り出すことができます。 確かにこれはカプセル化の原則に対立します。
しかし、スーパークラスで定義されたインスタンス変数に直接触ることができることは、 オブジェクト指向言語にとって必須ではありません。たとえばC++はprivateなメンバには サブクラスからもアクセスすることができません。そしてさいわいなことにprivateがデフォルトです。 これが私の考えるC++の唯一優れた点なのですが。
(中略)
結局問題なのはRubyのような言語がスーパークラスのインスタンス変数に簡単に触れてしまうという 「言語上の欠陥」なのであって継承そのものの問題ではないのですよね。 Rubyの次の世代の言語はぜひ「インスタンス変数はクラスローカル」が常識になってもらいたいものです。 事実、Perl6ではそうなるってDamian Conwayが言ってました。
同記事より

2003年のMatzの日記です。


つっつきボイス: 「何でも継承で書くもんじゃないというのはそのとおりですね🧐」「どちらかというと、オブジェクト指向の教科書がおしなべて最初にextends(Javaで言う継承)がオブジェクト指向』的なことを書いてしまっているから、それに釣られてみんな何でも継承でやろうとしちゃっている面があるんじゃないかな」「Matzも『is-aの関係かどうかが継承を使うときの唯一の基準』と書いてますしね」「言い換えれば継承は機能を拡張するためのものじゃないってこと」

「『スーパークラスのインスタンス変数に簡単に触れてしまう』はどうでしょう?」「Rubyのprivateはprivateじゃないから😆」「Rubyのprotectedが一番難しい: 毎回ググってるし」「そういえばC++はvirtualを書かないとメソッドをオーバーライドできないので、他の言語をやってからC++をやると結構戸惑う💦: virtualだと同名の別メソッドができるし」

参考: JavaやC#の常識が通用しないRubyのprivateメソッド - give IT a try
参考: 仮想関数 - C++入門

⚓書籍『Working with TCP Sockets』

TCPソケットをRubyで解説しています。有料です。

参考: 「Working with TCP Sockets」を読んだ - Fire Engine


つっつきボイス: 「TCPソケットのようなネイティブなものを勉強するなら個人的にはC言語でやるのをおすすめしたい🧐

⚓Rake::DSLを使おう(Ruby Weeklyより)

# 同記事より
# lib/tasks/bicycle.rake

class BicycleTasks
  include Rake::DSL

  def initialize
    namespace :bicycle do
      task :assemble do
        bicycle = Bicycle.new

        # Assemble the bicycle:
        attach_wheels(bicycle)
        attach_handlebars(bicycle)
        attach_brakes(bicycle)
      end
    end
  end

  private

  def attach_wheels(bicycle)
    # ...
  end

  def attach_handlebars(bicycle)
    # ...
  end

  def attach_brakes(bicycle)
    # ...
  end
end

# Instantiate the class to define the tasks:
BicycleTasks.new

しかしなぜか最近のRuby公式ドキュメントにはRake::DSLのドキュメントが見当たらないようです。

参考: Module: Rake::DSL (Ruby 2.2.1)
参考: library rake (Ruby 2.5.0)

Ruby 2.5.1のmasterにはRake::DSLモジュールはあり、読み込めました。

⚓splatとdouble splatの挙動


つっつきボイス: 「上みたいに示されると引数の"a" => 1, a: 1のハッシュが別物というのは何となくわかる気がする」「3つ以上引数が渡されるとどうなるのかな?↓あー[[{"a"=>1}], {:a=>1, :b=>2, :c=>3, :d=>4}]だから1つ目以外は全部**kに吸い込まれるのか😲」「1つ目のsplat *aと2つ目のdouble splat **kが連続しているとどっちに吸い込まれるのかがわかりにくいってのが問題なんでしょうね🤔

irb(main):004:0> p m("a" => 1, a: 1)
[[{"a"=>1}], {:a=>1}]
=> [[{"a"=>1}], {:a=>1}]
irb(main):005:0> p m("a" => 1, a: 1, b: 2)
[[{"a"=>1}], {:a=>1, :b=>2}]
=> [[{"a"=>1}], {:a=>1, :b=>2}]
irb(main):006:0> p m("a" => 1, a: 1, :b => 2)
[[{"a"=>1}], {:a=>1, :b=>2}]
=> [[{"a"=>1}], {:a=>1, :b=>2}]
irb(main):007:0> p m("a" => 1, a: 1, :b => 2, c: 3, :d => 4)
[[{"a"=>1}], {:a=>1, :b=>2, :c=>3, :d=>4}]
=> [[{"a"=>1}], {:a=>1, :b=>2, :c=>3, :d=>4}]

Rubyのパラメータと引数の対応付けを理解する(前編)

⚓クラウド/コンテナ/インフラ/Linux/Serverless

⚓Googleの「Serverless containers」と「Knative」


つっつきボイス: 「ああこれね: Serveless containersといいつつサーバーあるじゃんって思ったり😆」「流行り系」「これはもうリソースすら意識しなくてよくなるってことですかね?」「今日のBPS勉強会で出てきたDockerの話で言うとそうでしょうね: AWSのFargateに近い感じで、インスタンスを意識しなくてよくなる」
GKEというかKubernetesのPodって、AWS ECSでいうサービス(タスク定義の実体)に相当するんだと思うんだけど、とにかくKubernetesのPodだとまだインスタンスを意識しないといけない」


github.com/knative/docsより

「Knativeは何でしょうね?Kubernetesのnative?」「まだ詳しく見てないけど、KnativeはKubernetesが最終的に目指したい夢なんでしょうね」「ただこうやって自動化すると今度はlocalityの問題が出てくるから大変: 現在の管理ツールでどのインスタンスで動作するのかまで設定しないといけない理由のひとつが、データベースとアプリのコンテナみたいなものは互いにうんと近くないとパフォーマンスがめちゃくちゃ落ちるから」「あー😲」「だからそういうクリティカルなもの同士はlocalityつまりできる限り同じ物理サーバ上で動くことを担保したい: AWSのDedicated Hostsもそのためのオプション」「ふむぅ」「だから単に自動化されると、いつの間にかlocalityが失われてパフォーマンスが落ちたなんてときにトラブルシューティングしようがなくなってしまうので、そういう部分をうまく設定できるかが重要: まだちゃんと見てないけど、やるからにはそういう部分を扱えるだろうと予想してる」「😀

「ちなみにlocalityはインフラ業者側にとっても常に切実な問題で、同じ物理サーバーに乗っていればネットワークトラフィックがその中で収まるのに、遠くに配置されてしまえばその分トラフィックが増えてしまう: だからインフラ業者もlocalityを損なわないようにいろいろやっているはず」「なるほどー」

参考: [速報]Google、コンテナ実行環境をサーバレスで提供する「Serverless containers」発表。Google Cloud Next ’18 - Publickey

参考: 最強のServerlessプラットフォーム? Knative登場 - Cloud Penguins

⚓RailsをDockerに置くな(Hacklinesより)

短い記事です。


つっつきボイス: 「気持ちは一応わかる: 記事にもあるprybundle openbundle installとかはDockerに置いても一応同じことはできなくはないんだけど、やり方が面倒になるのは確か」「確かRailsdmでもこのあたりについて話していた発表があったと思う↓」「Docker化するとコマンドが増えますし」「本来の設計からかけ離れてしまったり」「複数のサーバークラスタに分散しているときにどのコンテナでrails consoleを実行すべきか?、とかDockerだと何かとややこしくなるわけですよ」「ふーむ」

⚓Twitterのアプリ連携仕様が変更


つっつきボイス: 「めちゃめちゃ不評を買ってたやつですね😆」「😆」「開発者を切り捨てる方向に向かってるとしか思えない…」

⚓Go Cloud: クラウド全般向けライブラリ

// 同記事より
// setupBucket opens an AWS bucket.
func setupBucket(ctx context.Context) (*blob.Bucket, error) {
    // Obtain AWS credentials.
    sess, err := session.NewSession(&aws.Config{
        Region: aws.String("us-east-2"),
    })
    if err != nil {
        return nil, err
    }
    // Open a handle to s3://go-cloud-bucket.
    return s3blob.OpenBucket(ctx, sess, "go-cloud-bucket")
}

つっつきボイス: 「マルチクラウドプラットフォームでやれるみたいなので、Terraformをちょっと思い出す」


terraform.ioより

「ちなみにTerraformは理想は高いけど、AWS向けに書いたTerraformのコードが無修正でGCPで使えるかというと現実にはそんなことはないので😆」「😆」「Terraform自体はどんなものでしたっけ?」「Terraformはクラウドの設定をTerraformの書式で書けるもので、普通だとGCPにはGCPの設定ファイルを書かないといけないしAWSだとCloudFormationというのがあってそれで書かないといけないので互換性がないんですけど、Terraformの中間コードで設定を書けばGCPにもAWSにも変換できるというものです」「😃」「おー、もっと壮大なものを想像してたんですが、設定を生成するマクロというか中間言語的なものだったんですね😲

「Terraformが流行った理由のひとつが、かつてのAWS CloudFormationがJSONにしか対応していなくて設定がとてもつらかったからなんですが、ここ一年ぐらいの間にCloudFormationがYAMLでも書けるようになったりGUIエディタが使えるようになったりと充実してきたせいか、前ほどTerraformの話題を見かけなくなったような気がする」「ふーむ」

⚓モバイル/Android/iOS

⚓Flutter Weekly


flutterweekly.netより

早くも#29だそうです。


つっつきボイス: 「社内アプリチームのFlutter勢からです」「iOSアプリもAndroidアプリもこれで書けるとしたらスゴイかも😳

参考: FlutterでAndroid/iOS両対応アプリを作ってみる

⚓Firebase SDK 2.0がリリース


つっつきボイス: 「社内でもアプリチームが使ってるみたいですね」「Firebaseはフロント系の人には大人気です: サーバーサイドのコードが要らないのでフロントエンジニアだけでアプリが作れるし」「お?!」「Firebaseの裏にデータベースを置いてそこにアクセスするAPIを自由に書けるみたいな感じ?」「それもできますが、Firebase自体はもっともっと壮大ですよ: Firebaseがあればマジでサーバーレスで何でもできる💪」「管理コンソールも充実してるし」「通知やレポートを投げたりとかもできるし」「データベースを置くとかもできるし、アプリのリリース管理もできるし、ほんと何でもできる」「アプリに更新をプッシュするだけじゃなかったんですね😆」「そういえばFirebaseは何年か前にGoogleが買収したんでしたね」「(最初からGoogleの製品だと思ってた…😅)」「フロントエンジニアにとってはとてもいいプラットフォーム❤

参考: Cloud Functions for Firebase  |  Firebase

⚓SQL

⚓PostgreSQLのSQL方言は先進的(Postgres Weeklyより)


同PDFより


つっつきボイス: 「ぽすぐれは機能が異常に多いからもうしょうがない😆」「使ったことないSQLとかいっぱいあるし」「(スライドを見ながら)ちょうどXMLTABLEが目についたので: 自分が学生だった頃は、データベースで一番権威のある国際会議のプロポーザルが2000ページぐらいあったんですけど、その半分ぐらいがXMLデータベースだった」「😲

⚓私の愛するPostgreSQLクエリたち(Postgres Weeklyより)


つっつきボイス: 「バックスラッシュ\使うクエリとかあったねー」「もう思い出せない…💦」「\?とかすれば表示されますよ😎」「PostgreSQLはコマンドもバックスラッシュだらけですし」「しかもロングバージョンがないし」「そんなにたくさんはないから覚えられますけどね😉

⚓PostgreSQLのJSONドキュメントAPI拡張「dox」を作った(Postgres Weeklyより)

# 同リポジトリより
select * from dox.search(collection => 'customers', term => 'jill'); -- full text search on a single term
select * from dox.find_one(collection => 'customers', term => '{"name": "Jill"}'); -- simple query
select * from dox.find(collection => 'customers', term => '{"company": "Red:4"}'); -- find all Red:4 people

つっつきボイス: 「生のJSONBの検索はいろいろつらいからもう少しマシな方法でやれるようにしたってことか」「クエリとして書けるようにしたんですね」「JSONBの比較演算子はもう宇宙語並にわけわからないし」「こうやって見やすく書けるならいいかもね😋

参考: JSON関数と演算子

⚓JavaScript

⚓Note.muがフロントエンドをAngular.jsからNuxt.jsに移行予定


ja.nuxtjs.orgより


つっつきボイス: 「Nuxt.jsはVue.jsのベストプラクティス的なものらしいですね」「移行することのビジネス上のメリットがどのぐらいあるのか知りたいところではある🤔

⚓WebAssemblyの未来を探る(JavaScript Weeklyより)

プロポーザルなどが紹介されています。


つっつきボイス: 「最近のWebAssemblyではLinuxカーネルも動きますからね😎」「マジで😲?!」「シェルはまだ動かないみたいですが」「LLVMも動いたと思う」「mrubyも動きますしね」「それだけ動けばもうたいていのものは動きそう」


スレチですがこんな記事も見つけました。

参考: サクッと Go → WebAssembly を試す - Qiita

Go 1.11beta1で動きました😋

⚓hypernova: JSのビューをサーバーサイドレンダリング(JavaScript Weeklyより)

airbnb製です。gemもあり、Railsでも使えると言ってます。

# 同記事より
class SampleController < ApplicationController
  around_filter :hypernova_render_support
end
<%= render_react_component('MyComponent.js', :name => 'Hypernova The Renderer') %>

hypernova {名} : 極超新星


つっつきボイス: 「似たようなSSR(サーバーサイドレンダリング)用のものをどこかで見た気が」「これを入れるだけでちゃんと動くならいいけど、どうせデバッグしないといけなくなるだろうし😆SSRは基本やりたくない」

「SSRは、速度向上と、SEO向けの対応という2つの側面がありますね」「後者は、SPAアプリとかでJavaScriptで画面を作っていると、動的なコンテンツを検索エンジンのクローラーが評価できなくなってSEO的に不利だから、その対応としてSSRを行うというヤツ」「もともとそれがSSRの目的でしたね」

「今はクローラーがJavaScriptを実行してからクロールするらしいから大丈夫になったのかな?」「Googleはそうだと言ってるけど、どこまで大丈夫かはまだ読みきれないところがある🤔

参考: SPAを導入する際に必要だったSEO対策についての話 -SSR? Prerendering?- - Qiita

⚓CSS/HTML/フロントエンド/テスト

⚓Chrome 68リリースでついにHTTPサイトに警告表示開始

たまたま気づきましたが、Windows版(上)とMac版(下)でメッセージの訳文が違ってますね。


つっつきボイス: 「違ってる😆」「そんなに強い言葉でもないからまあ大丈夫かな…」「背景が赤だったらインパクト大きい」

参考: 「Chrome 68」公開、HTTPサイトに「保護されていません」の警告を開始 - CNET Japan

⚓重要なHTTPセキュリティヘッダーとデプロイ方法(Hacklinesより)


つっつきボイス: 「X-Content-Type-Optionsは、最近これを付けないと通らないことある」「このヘッダーって、確かすごく古いブラウザで問題が起きないようにするやつでしたね: 古いIEでPDFの中身を見ると落ちちゃう場合にnosniffを指定する」「X-Frame-Optionsは、iframeを開いていいかどうかを判断するヤツで、SAMEORIGINにするとoriginが同じなら開ける」「任意のJSをぶち込まれてもこれが入ってれば防げることになっている」「面倒だけどこの手のヘッダーはまだまだ必要なので、こういう記事は読んでおくとよいです❤

参考: X-Content-Type-Options: nosniff つかわないやつは死ねばいいのに! - 葉っぱ日記
参考: X-Frame-Options - HTTP | MDN

⚓言語よろず部屋

⚓臨時ニュース: Python作者Guido van Rossum氏が意思決定から手を引くと表明

BDFL(Benevolent Dictator For Life: 優しい終身の独裁者)という略語を初めて知りました。

参考: 優しい終身の独裁者 - Wikipedia
参考: どうなるPython--生みの親「優しい終身の独裁者から引退」表明で衝撃 - ZDNet Japan

⚓その他

⚓セキュリティ関連

PDFは無料で読めます。

⚓四分位数とは

平成29年度の中学校の指導要領に含まれているそうです。


同記事より


つっつきボイス: 「四分位数…?」「なるほど箱ひげ図ね: 大学に入って必要に迫られて使ったけど習った覚えがない😆」「箱ひげ図ってたいてい縦向きですよね」「箱ひげ図は、最大値と最小値がひげの端で、端から1/4のところに箱を置いて、さらに中央値(メジアン)を置くことで分散を見やすくする」「箱ひげ図という表し方があることを知っておくとデータを見せるときに便利❤

参考: 箱ひげ図 - Wikipedia

⚓誰しも最初は


つっつきボイス: 「あのhikaliumさんでもそうだったとは😲: 小学生のときにOS自作本を読んでその道に進んだスゴイひとで、例のTuring Complete FMによく登場している人」

⚓番外

⚓火星の話題2つ

参考: 火星に巨大地下湖、欧州探査機が発見 水の存在「疑いない」 写真3枚 国際ニュース:AFPBB News

ついでながら、明日7/31は火星が大接近します。

参考: 火星大接近2018 | 国立天文台(NAOJ)


つっつきボイス: 「この間から真夜中に火星ばかり異様に目立っててキモチワルイと思ってました」「天文好きな人はぜひ✨🌗」「そういえば天文出身の人ってまだBPSにいないな: ちなみに上の世代でコンピューターに詳しい人って数学とか天文とか音楽の人が多い」

そういえばパズル作者としても有名なこの方↓も天文出身でした。

参考: 岸田孝一 - Wikipedia


今回は以上です。公開つっつき会でお会いしましょう!

バックナンバー(2018年度後半)

週刊Railsウォッチ(20180723)Railsdm Day 3 Extremeを後追い、PSDにはZeplin.io、好みの分かれるJSX、負荷テストツール比較ほか

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

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

Rails公式ニュース

Ruby Weekly

RubyFlow

160928_1638_XvIP4h

Hacklines

Hacklines

Postgres Weekly

frontendweekly_banner_captured

JavaScript Weekly

javascriptweekly_logo_captured


Rails: `before_validation`コールバックで複雑なステートを正規化する(翻訳)

$
0
0

概要

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

Rails: before_validationコールバックで複雑なステートを正規化する(翻訳)

数か月前に投稿した別記事で、ActiveRecordコールバックbefore_validationがいかに誤った理由で乱用されているか、そしてほとんどの場合before_validationは常用すべきでないことについて説明しましたが、before_validationが適切なユースケースがひとつあることについて書き漏らしていました。before_validationが適切なユースケースは、おそらく多くのRailsアプリに該当するでしょう。そこで再びbefore_validationを取り上げ、before_validationの別の姿をご覧いただきたいと思います。

問題の分析

Paymentモデルがあり、そこにamountcurrencyを保存する必要があるとしましょう。ただし、支払いの作成時点の為替レートを用いて正規化したUSドル表記の総額も保存しておいて、統計で使えるようにしたいとします。ここは私たちの(ビジネス)ドメインの重要な部分なので、amount_in_usd属性のバリデーションも追加したいと思います。この時点のPaymentモデルは次のような感じになります。

class Payment < ApplicationRecord
  validates :amount, :currency, :amount_in_usd, presence :true
end

ここで問題になるのは、「amount_in_usdをどこから取得するか」と「amount_in_usdをどのように代入するか」です。

解決方法

解決方法のひとつは、すべての属性を代入するタイミングで直接代入するというものです。この場合次のような感じになるでしょう。

Payment.new(currency: currency, amount: amount, amount_in_usd: CurrencyExchanger.exchange(amount, from: currency, to: "USD"))

この方法の問題は、支払いを初期化するたびにこのロジックを繰り返す必要がある点です。どのシナリオでも再利用できるファクトリークラスを1つ実装してDRYにする方法もあるといえばありますが、少々オーバーヘッドを伴うためRails界隈では人気がありません。しかも内部ステートを管理するので、これはPaymentモデル自身の責務であるように思われます。

以前の記事でも説明したように、amount_in_usdcurrencyamountという2つの属性に依存しているため、ライターをオーバライドする方法では解決できません。しかも、2つの属性が代入される順序も予測できません。

before_validationはまさにこのような場合にうってつけなのです。before_validationは、複数の属性が絡んでいる複雑なステートをかなりエレガントかつシンプルに正規化できます。

class Payment < ApplicationRecord
  validates :amount, :currency, :amount_in_usd, presence :true

  before_validation :assign_amount_in_usd

  private

  def assign_amount_in_usd
    if currency && amount
      self.amount_in_usd = CurrencyExchanger.exchange(amount, from: currency, to: "USD")
    end
  end
end

別の解決方法

本記事の最初のパラグラフで、この解決方法は特にRailsアプリでうまくいくと書きました。つまり、HTTP params由来の「プリミティブな」属性は通常モデルにマスアサインされるという事実があるということです。言うまでもなくRubyではあらゆるものがオブジェクトですが、シンプルに解決するのであれば数値型と文字列型をプリミティブとして扱いましょう。

しかしプリミティブでない値の場合はどうすればよいのでしょうか?上の事例の場合、広く使われているValue Object的なものを使う手もあります。つまりamountcurrencyを持つMoneyオブジェクトです。代入前の属性が他のドメイン指向オブジェクトにも対応付けられる場合は、次のようにさらにシンプルに解決できるでしょう。

money = Money.new(amount, currency)
Payment.new(money: money)

この場合Paymentモデルは次のようになるでしょう。

class Payment < ApplicationRecord
  validates :amount, :currency, :amount_in_usd, presence :true

  def money=(money_object)
    self.amount = money_object.amount
    self.currency = money_object.currency
    self.amount_in_usd = CurrencyExchanger.exchange_money(money_object, to: "USD")
  end
end

この方法は不要なオーバーヘッドを含むようにも見えますが、Value Objectは多くの場合コードをシンプルかつDRYにする傾向があります。さらに複雑なアプリでは、少々のオーバーヘッドを伴ってもValue Objectを使う価値があります。

最後に

before_validationは有用な場合もあります。しかし、複雑なアプリならValue Objectを検討する価値もあるでしょう。

関連記事

Rails: :before_validationコールバックの逸脱した用法を改善する(翻訳)

Rails: Active Recordのコールバックを避けて「Domain Event」を使おう(翻訳)

$
0
0

概要

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

参考: Domain Event
参考: ドメイン イベント: 設計と実装 | Microsoft Docs

Rails: Active Recordのコールバックを避けて「Domain Event」を使おう(翻訳)

最近Marcinの書いた「Railsアプリの最大のコードの臭いはActiveRecordのコールバックである」記事にあるように、コールバックはあっという間に制御不能になってしまう可能性があります。この記事がRedditに投稿された後、実に興味深いやりとりをここで見かけました。

重要なモデルがひとつあり、そこにはビジネス上重大なデータがあってめったに変更されることはないが、さまざまなコントローラに散らばった自動化ビジネスロジックによって変更されることがあるとしよう。

このデータが変更されたときに何らかのアラートや通知を送信したいとする(メールとかチャットとか別テーブルにエントリを足すとか)。理由は、非常に重要かつ変更のまれなデータであり、多くの人が変更を知っておくべきだから。

こんなとき次のAとBのどちらを選ぶ?

A: 変更のたびにそのモデルからのメール送信を許可し、「変更時に通知」というコア機能をカプセル化して、変更が発生する実際の場所に配置する。

B: コントローラ内で指定のモデルファイルが変更されるあらゆる箇所に個別に呼び出しを挿入する。

私ならAを選ぶだろう。こちらの方が将来の変化に強く、しかも的確だから。さらに今後プログラマーがエラーを埋めるリスクも軽減でき、「変更時に外部に通知する」という単体の責務を僅かなコストでそのモデルファイルに追加できる。

コメ主の指摘は非常に興味深く、かつ非常に有益です。私自身、数か月前に以下のようなコールバックを使っていました。

class Order < ActiveRecord::Base
  after_commit do |order|
    Resque.enqueue(IndexOrderJob,
      order.id,
      order.shop_id,
      order.buyer_name,
      order.buyer_email,
      order.state,
      order.created_at.utc.iso8601
    )
  end
end

Elasticsearchデータベースのインデックス作成をスケジューリングするために、問題を手っ取り早く解決したのですが、私たちのコードベースにそれ以上の改善がもたらされないことは承知の上でした。しかしこのときは、今後このコードを取り除くのに役立つ可能性のある作業を同時に並行してやっていたことも承知していました。

こうしたコールバックには否定し難いメリットがあると同時に、いくつもの問題を抱え込んでしまいます。それについて書きたいと思います。

コールバックを正しくするのは簡単ではない

上ととてもよく似た以下のコードがあるとします。

class Order < ActiveRecord::Base
  after_save do |order|
    Elasticsearch::Model.client.index(
      id: id,
      body: {
        id:              id.to_s,
        shop_id:         shop_id,
        buyer_name:      buyer_name,
        email:           buyer_email,
        state:           state,
        created_at:      created_at
    })
  end
end

パッと見には何の問題もなさそうですが、もしこのトランザクションがロールバックされると、(手動でオープンした巨大なトランザクションの中でOrderがsaveされて)2番目のデータベースのインデックス化されたステートに誤りが生じるかもしれません。そのまま進めるか、after_commitに切り替わることになります。

さらに、Elasticsearchで例外が発生すればただちにDBトランザクションもロールバックされるでしょう。それをよしとする考え方(DB不整合が発生せず、ElasticsearchにもSQL DBにも何も残らない)もあれば、よくないとする考え方(重要性の低いDBエラーのせいでユーザーの発注が止まってしまい利益が損なわれる)もあります。

そこで今度はafter_commitに乗り換えてみましょう。この特定のニーズにはこちらの方が合っていそうです。そしてドキュメントには次のように書かれています。

コールバックが他のシステムとのやりとりに有用なのは、データベースのステートが不変な場合にのみ実行されることが保証されるからです。たとえばafter_commitがキャッシュをクリアするフックの置き場所として手頃なのは、キャッシュをトランザクション内でクリアすると、データベース更新が完了する前にキャッシュの再生成がトリガされてしまう可能性があるからです。

言い換えると、そうしたフックをサードパーティのシステムやAPIやDBと統合するのであればafter_commitの方がより安全性の高い選択肢となります。副作用がSQL DBにも保存されるのであれば、after_saveafter_updateでも十分です。

class Order < ActiveRecord::Base
  after_commit do |order|
    Elasticsearch::Model.client.index(
      id: id,
      body: {
        id:              id.to_s,
        shop_id:         shop_id,
        buyer_name:      buyer_name,
        email:           buyer_email,
        state:           state,
        created_at:      created_at
    })
  end
end

そういうわけで私たちはafter_commitを使うことを覚えました。さて、おそらくテストの大半はトランザクションで占められているので、テストはDBトランザクション内で実行するのが最も高速です(そのテストではフックが発火しないため)。現在関心のあるテストがほんの数件しかない場合、これは嬉しい点です。嬉しくないのは、Elasticsearchに保存されているデータが多くのユースケースで必要になる場合です。こうなったら、トランザクションを用いない方式でテストを実行するか、test_after_commit gemを使うか、Rails 5にアップグレードしなければなりません。

歴史的には(レガシRailsアプリから読み取った範囲では)after_commitコールバックで発生した例外は握りつぶされて単にロガーに出力されます。すべてがコミットされてしまった後では何もできないからです。これはRails 4.2以降で修正されましたが、スタックトレースは以前ほど良好ではないかもしれません。

ほとんどの技術的問題への対応方法はいくつもあるので、それらについてひととおり知っておく必要があります。これらの例外は最も困った問題であり、何らかの形で取り扱う必要があります。

コールバックは癒着を増やす

Railsの多くの問題は癒着から来ていると私は直感します。Railsの技術的な層はデフォルトでは不十分です。私たちにあるのはビュー(ビューは本記事と何の関係もありませんが)とコントローラとモデルです。すなわち、操作で副作用をトリガしたいと思った場合、そのコードの置き場所はコントローラかモデルが唯一のデフォルト選択肢となります。コードをどちらに置いてもそれなりに問題があります。

副作用(API呼び出し、キャッシュ、セカンダリDBの統合、メール送信)をコントローラに配置すると、テストを正しく行おうとするときに問題が生じるかもしれません。理由は2つあります。コントローラはHTTPインターフェイスと密結合しているので、副作用をトリガするにはテストでHTTP層を用いて副作用とやりとりする必要があります。テストでコントローラをインスタンス化してメソッドを直接呼び出すのは簡単ではありません。そこはフレームワークが管理している領域だからです。

かといって副作用をモデルに配置すると、今度は別の問題が生じます。副作用がハードコードされてしまうので、別途結合テストを(明示的に)用いない限りこのドメインモデルのテストは困難です。つまり「遅いテストで我慢する」か「テストを毎回モックやスタブで塞ぐ」かのどちらかしかありません。

RailsコミュニティでService Objectに関するおびただしい記事が見つかる理由はこれです。アプリが複雑になり始めると、開発者はメール送信やサードパーティAPIへの通知などの関心事を「after save」的な副作用のところに置きたがります。別のコミュニティやアーキテクチャではこの種のコード部品をTransaction Script(訳注: Martin Fawler氏の用語)と呼んだりAppplication/Domain/Infrastructure Service(訳注: ドメイン駆動開発(DDD)の用語)と呼んだりすることがあります。しかしどちらもデフォルトのRailsにはありません。開発者がこぞってブログ記事やgem(1つや2つではありません)あるいはこの層をしっかり備えている新しいフレームワーク(hanamitrailblazer)を頼りにサービスの「車輪の再発明」に走る理由はここにあります。新しいフレームワークに移行せずにこの層をアプリのコードに導入する方法については弊社のFearless Refactoring bookをぜひご覧ください。本書は、システムに高度な概念を導入する前の重要なステップです。

コールバックはデータ変更の意図がわからなくなる

コールバックが呼び出されればデータが変更されたことはわかりますが、変更された理由はわからずじまいです。ユーザーがかけた発注のせいなのか、別の処理でPOSを操作した人のせいなのか、それとも支払いが原因か、返金が原因か、キャンセルが原因なのか、知りようがありません。あえてstate属性を元に変更をかけるのは、多くの場合アンチパターンです。変更された理由がわからなくても(コールバックで何らかのデータを送信しているという理由で)問題にならないこともありますが、それ以外の場合には問題になる可能性があります。

モバイルからのAPI呼び出しや、Webブラウザから別のエンドポイントを介してUserが登録されたときに、ユーザーに「ようこそメール」を送信したいとしましょう。さらにユーザーがFacebookから登録した場合にも送信したいとします。ただしユーザーをシステムにインポートするときには送信したくない(新しいパートナー企業が顧客を連れてこちらのプラットフォームに移籍することを決めたという理由で)とします。つまりこの4つの状況のうち3つに対して(メール送信という)副作用が欲しいのですが、残りの1つでは副作用が欲しくありません。発生したイベントに対して何を行いたいかという意図がわかる方がよいでしょう。after_createはその目的には向いていません。

Domain Eventとは

Active Recordのコールバックの代わりに私が推奨しているのは、UserRegisteredViaEmailUserJoinedFromFacebookUserImportedOrderPaidといった「Domain Event」をパブリッシュして、イベントに対して応答するハンドラをそこにサブスクライブするという方法です。この方法では、さまざまなpub/sub gem(whisperなど)を使うことも、rails_event_store gemを用いてデータベースへの保存や将来インスペクション/デバッグ/ログ出力などを使えるようにすることもできます。

このアプローチについて詳しくお知りになりたい方は、2 years after the first domain event – the Saga patternのスライドや動画をご覧いただけます。Domain Eventのパブリッシュ方法や、それを用いた副作用のトリガ方法について解説しています。このアプローチはActive Recordコールバックの代わりに用いることができます。

今後アプリで何か変更が生じれば必ずイベントがパブリッシュされるようになります。変更が、指定のモデルのどこで生じるのかを探し回ることもありません。変更が生じる場所はすべて明らかだからです。

追伸: Rails 5では更に深刻です

関連記事

Ruby: gemが生成するコードを無名モジュールとprependで動かす(翻訳)

Rails: beforeバリデーションをやめてセッターメソッドにしよう(翻訳)

RailsアプリでConcurrent Rubyを使う(翻訳)

$
0
0

概要

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

RailsアプリでConcurrent Rubyを使う(翻訳)

Concurrent Rubyは、関数型言語の興味深いアイデアや古典的なコンカレンシーパターンを多数取り入れて、それを基礎として構築されたコンカレンシーツールキットです。Concurrent RubyはActive Support経由で既にRailsに導入されているので、Railsアプリでスレッド化コードを書くなら他のものを探す必要はありません。

Concurrent::Futureを使う

私たちのアプリのひとつで、パフォーマンス改善の目的でConcurrent::Futureを用いてスレッド化コードを追加しました。結果は実に上々でしたが、ある日突如として動かなくなったのです。

「なぜスレッドが必要になったのか?」と思われるかも知れません。このときに問題になったコードは、教科書にもあるスレッディングのユースケースでした。そのコードはAPI呼び出しやDBリクエストをいくつか含み、最終的にかき集められた全データに対して操作を行うというものでした。

とりあえずコードを見てみることにしましょう。

スレッド化されていないコード

selected_shipping_companies.each do | carrier |
  # api呼び出し
  distance_in_miles = find_distance_from_origin_to_destination
  historical_average_rate = historical_average_for_this_particular_carrier

  # 操作の実行
  build_price_details_for_this_carrier(distance_in_miles,
                                       historical_average_rate)
end

上のコードをConcurrent::Futureでカバーするのは簡単です

futures = selected_shipping_companies.map do |carrier|
  Concurrent::Future.execute do
    # api呼び出し
    distance_in_miles = find_distance_from_origin_to_destination
    historical_average_rate = historical_average_for_this_particular_carrier

    # 操作の実行
    build_price_details_for_this_carrier(distance_in_miles,
                                         historical_average_rate)
  end
end

futures.map(&:value)

Concurrent::Futureについての補足

スレッドの利用については二の足を踏むことがよくあります。スレッドによってコードが複雑になりますし、スレッド安全性を確保できていないと思わぬ振る舞いを示す可能性もあるからです。Rubyは参照がミュータブルな言語なので、100%のスレッド安全性の確保は何かと困難を伴います。

Concurrent::Futureはスレッド安全性を保証するシンプルなしくみで、Clojure言語のFuture関数にヒントを得ています。Concurrent::Futureは実行したいブロックを1つ取り、Concurrent Rubyのグローバルスレッドプールを用いて非同期実行します。Concurrent Rubyはこのfutureを扱えるようにし、#value(または#deref)が呼び出されるとブロックの値を返します。

問題のバグ

通常であれば、メインスレッドで例外が発生するとRubyインタプリタが停止して例外データを収集します。RubyのThreadの場合、Thread#joinが呼び出された場合にのみunhandled exceptionが発生します。この場合Thread#abort_on_exceptiontrueに設定する方が、実行中のどのスレッドで例外が発生してもすべてのスレッドが終了するので良好な結果になります。このあたりについては最近公開したBigBinaryブログで詳しく解説されています。

Concurrent Rubyで例外を扱う方法

future = Concurrent::Future.execute {
            raise StandardError.new("Boom!")
          }

sleep(0.1) # futureを実行する時間を任意に設定

future.value     #=> nil

例外はどこに行ってしまうのでしょうか。実はこのコードは例外を発生せずに落ちてしまいます。コードの実行に成功したかどうかをどうやって検出すればよいでしょうか?

future = Concurrent::Future.execute {
              raise StandardError.new("Boom!")
          }

sleep(0.1) # futureを実行する時間を任意に設定

future.value     #=> nil

future.rejected? #=> true
future.reason    #=> "#<StandardError: Boom!>"

問題の修正方法

訳注: このラッパークラスについては末尾の「追伸」もご覧ください。

私たちは、アプリでConcurrent::Futureが使われている場所を見つけました。例外はそこで飲み込まれているようです。さらに、例外を明示的に手動で出力する必要性を多くの人が見落としてしまう可能性も残されています。この懸念点を解消するため、以下のラッパークラスを用いました。

module ConcurrentExecutor
  class Error < StandardError
    def initialize(exceptions)
      @exceptions = exceptions
      super
    end

    def message
      @exceptions.map { | e | e.message }.join "\n"
    end

    def backtrace
      traces = @exceptions.map { |e| e.backtrace }
      ["ConcurrentExecutor::Error START", traces, "END"].flatten
    end
  end

  class Future
    def initialize(pool: nil)
      @pool = pool || Concurrent::FixedThreadPool.new(20)
      @exceptions = Concurrent::Array.new
    end

    # Sampleの使い方
    # executor = ConcurrentExecutor::Future.new(pool: pool)
    # executor.execute(carriers) do | carrier |
    #   ...
    # end
    #
    # values = executor.resolve

    def execute array, &block
      @futures = array.map do | element |
        Concurrent::Future.execute({ executor: @pool }) do
          yield(element)
        end.rescue do | exception |
          @exceptions << exception
        end
      end

      self
    end

    def resolve
      values = @futures.map(&:value)

      if @exceptions.length > 0
        raise ConcurrentExecutor::Error.new(@exceptions)
      end

      values
    end
  end
end

ここで注意が必要なのは、Concurrent RubyのFutureを使うとCircle CIでspecを実行中にsegmentation faultが発生することです。私たちはしばらくの間Circle CIでFutureをやめて通常のループに切り替え、その間にsegfaultの原因を切り分けて修正しました。

追伸

Concurrent::FutureのAPIには、ブロックの値を返すだけでなく、メインスレッドで発生した例外をraiseする機能もあります。

thread_pool = Concurrent::FixedThreadPool.new(20)
executors = [1, 2, 3, 4].map do |random_number|
  Concurrent::Future.execute({ executor: thread_pool }) do
    random_number / (random_number.even? ? 0 : 1)
  end
end

executors.map(&:value)
=> [1, nil, 3, nil]

executors.map(&:value!)

> ZeroDivisionError: divided by 0
> from (pry):4:in `/'

ドキュメントに記載されていないAPIについてreddit書き込みで指摘を下さったJonathan Rochkindに感謝いたします。

訳注: jrochkind氏はredditで、#value!を用いることで上述のラッパークラスが不要になると指摘しています。

関連記事

Ruby/Railsのプロ開発者としての5年間を振り返る(翻訳)

Railsのフラグメントキャッシュを分解調査する(翻訳)

Rails 5.2: Redisキャッシュに複数エントリを一括保存する`write_multi`メソッド(翻訳)

$
0
0

概要

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

タイトルは内容に即したものにしました。

Rails 5.2: Redisキャッシュに複数エントリを一括保存するwrite_multiメソッド(翻訳)

本記事はRails 5.2シリーズの記事です

Rails 5.2より前は、複数のエントリをキャッシュストアに書き込むことは不可能でした。RedisなどのキャッシュストアにMSETがあって1回のアトミックな操作で複数のキーを設定できるにもかかわらず、です。Railsのキャッシュ実装方法が原因で、Redisのこの機能を利用できませんでした。

RailsにActiveSupport::Cache::Storeという抽象クラスを用いたキャッシュが実装されました。これは、すべてのキャッシュストアが実装すべきインターフェイスを定義します。また、Railsはすべてのキャッシュストアクラスで必要な共通機能も提供します。

Rails 5.2より前のActiveSupport::Cache::Storeには、複数のエントリを一括で書き込むメソッドがありませんでした。

Rails 5.2からはwrite_multiメソッドが追加されました(#29366)。各キャッシュストアがこのメソッドを実装することで、複数エントリを一括で追加する機能を提供できます。デフォルトの実装は各key/valueペアをループしてwrite_entityメソッドで個別に設定します。

複数エントリの設定方法は次のとおりです。

Rails.cache.write_multi name: 'Alan Turning', country: 'England'

redis-railsは、Redisをキャッシュストアとして提供しますが、write_multiメソッドを実装していません。

しかしRails 5.2を使っていれば、write_multiメソッドを実装するRedisキャッシュストアをRailsが組み込みでサポートするのでredis-rails gemが不要になります(#31134で追加)。

以下の変更が必要です。

# before
config.cache_store = :redis_store

# after
config.cache_store = :redis_cache_store

redis-railsのリポジトリにあるissue #81には、このgemの開発が終了するというお知らせが掲載されていますので、今後はRails 5.2のRedisキャッシュストアを使うのがよいでしょう。

関連記事

RailsアプリでConcurrent Rubyを使う(翻訳)

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

RailsのJSON生成をPostgreSQLのJSON関数で高速化(翻訳)

$
0
0

概要

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

参考: JSON関数と演算子 — 本記事で扱われている関数が掲載されています

RailsのJSON生成をPostgreSQLのJSON関数で高速化(翻訳)

RailsでのJSON生成方法は多種多様です。#to_jsonメソッドはRailsに組み込まれていますし、jbuildergemやactive_model_serializers gemも同じ目的に使えます。

データベースのレコード数が増加するに連れて、Railsのレスポンス生成時間が大きく増大します。このボトルネックを追うと一般にJSON生成部分にたどり着きます。

最近私たちのアプリで、ページ読み込みに異常に時間がかかる問題が発生しました。そのページはサイトで最も訪問数が多いので、この読み込み時間は重大です。そのページは、あるレースとその出場レーサーや詳細なラップタイムを読み込みます。

レーサーが10〜15人でラップが30〜50程度の短距離レースなら問題ありませんでしたが、レーサーが50〜80人でラップが700〜800の耐久レースともなると、読み込み時間のボトルネックが顕在化しました。

JSON生成のベンチマークを取ったところ、バックエンドが容疑者であることが判明しました。

この問題の修正方法を調べているうちに、PostgreSQLのJSON関数を見つけました。

JSON生成はPostgreSQL 9.2以降で組み込みとしてサポートされており、row_to_json関数やarray_to_json関数を用います。この2つをもう少し詳しく見てみましょう。

row_to_json

row_to_jsonは、各行をJSONオブジェクトとして返します。

select row_to_json(laps) from laps;
{"id":1,
 "number":1,
 "position":4,
 "time":"628.744",
 "flag_type":"Green"
}
.
.
.

サブクエリを用いて、必要な属性やカラムだけをフェッチすることもできます。

select row_to_json(lap)
from (
  select id, number, position, time, flag_type from laps
) lap;
{"id":1,"number":1,"position":4,"time":"628.744","flag_type":"Green"}
{"id":2,"number":2,"position":4,"time":"614.424","flag_type":"Green"}
.
.
.

array_to_json

array_to_jsonを理解するには、最初にarray_aggという集約関数を調べなければなりません。集約関数は、入力値のセットから単一の結果を算出するもので、summinmaxも集約関数です。array_aggはすべての入力値を結合してPostgreSQLのarrayにまとめます。

select array_agg(lap)
from (
  select id, number, position, time, flag_type from laps
) lap;
{"(1,1,4,\"628.744\",\"Green\")","(2,2,4,\"614.424\",\"Green\")", ... }

PostgreSQLのarrayをISONに変換するには、array_to_json関数を次のように用います。

select array_to_json(array_agg(lap))
from (
  select id, number, position, time, flag_type from laps
) lap;
[{"id":1,
  "number":1,
  "position":4,
  "time":"628.744",
  "flag_type":"Green"},
  ...]

より複雑な例

上の2つの関数を組み合わせてカスタムJSONレスポンスを生成できます。

select row_to_json(u)
from (
  select first_name, last_name,
    (
      select array_to_json(array_agg(b))
      from (
        select number, position, time, flag_type
        from laps
        inner join racer_laps
        on laps.id = racer_laps.lap_id
        where racer_laps.racer_id = racers.id
      ) b
    ) as laps
  from racers
  where first_name = 'Jack'
) u;
{
  "first_name": "Jack",
  "last_name": "Altenwerth",
  "laps": [
    {
      "number": 1,
      "position": 4,
      "time": "628.744",
      "flag_type": "Green"
    },
    {
      "number": 2,
      "position": 4,
      "time": "614.424",
      "flag_type": "Green"
    },
    ...
  ]
}

関数をRailsで使う

上述の関数をRailsで使うには以下のようにします。

query = <<~EOQ
select row_to_json(u)
from (
  select first_name, last_name,
    (
      select array_to_json(array_agg(b))
      from (
        select number, position, time, flag_type
        from laps
        inner join racer_laps
        on laps.id = racer_laps.lap_id
        where racer_laps.racer_id = racers.id
      ) b
    ) as laps
  from racers
  where first_name = 'Jack'
) u;
EOQ

generated_json = ActiveRecord::Base.connection.execute(query).values;
puts generated_json
{
  "first_name": "Jack",
  "last_name": "Altenwerth",
  "laps": [
    {
      "number": 1,
      "position": 4,
      "time": "628.744",
      "flag_type": "Green"
    },
    {
      "number": 2,
      "position": 4,
      "time": "614.424",
      "flag_type": "Green"
    },
    ...
  ]
}

上の方法でJSONを生成すると、Railsでの通常のJSON方法に比べて冗長で読みづらくなりますが、その代り高速です。

導入の結果

レーサーのページでPostgreSQL関数を用いてJSONを生成したところ、以下の改善が見られました。

短距離レース(レーサーが10〜15人でラップが30〜50程度)の場合、APIの平均レスポンスタイムは40msから15msに短縮されました。

耐久レース(レーサーが50〜80人でラップが700〜800)の場合、APIの平均レスポンスタイムは1200msから20msと大きく短縮されました。

まとめ

問題がなければ、Rails wayでJSONを生成しましょう。パフォーマンスが問題視されるようになったら、データベースで使えるこの機能をためらわずに使いましょう。この場合パフォーマンスと複雑さのトレードオフになりますが、時にはトレードオフの価値があるものです。

関連記事

PostgreSQL 10の使って嬉しい5つの機能(翻訳)

Webアプリの基礎とさまざまな実行環境を理解する#1(社内勉強会)

$
0
0

BPS社内勉強会のmorimorihogeさんのスライド「Webアプリの基礎とさまざまな実行環境を理解する」を2回3回に分けてお送りします。

追記: 3分割に変更しました🙇

はじめに

本記事はWebアプリの開発初心者、または書いたことはあるけどサーバーやインフラについてはよく分かっていない人向けです。
細かい話をしていくと色々とありますが、あくまで代表的なWebアプリの動作環境の構成について説明していきます。
なお、発表者(morimorihoge)のWeb開発経歴はPHP 7年程度 -> Rails開発8年程度となっており、いわゆるエンタープライズJavaアプリケーションやASP.NETのような世界は遠巻きには見ていましたが真面目に触ったことはありません。

1. Webのしくみ

Webアプリの今と昔

どんなWebサービスでも、Webアプリケーションは常にWebサーバーとクライアント間の通信が基本です。

morimorihoge注)

昨今サーバレスアーキテクチャが流行っていますが、あれも本質的にはWebサーバーが存在しています

「サーバレス」という言葉はあくまで「自前でWebサーバーを立てなくて良い」という意味で使われているので、言葉尻だけ捉えて間違った理解をしないよう気をつけましょう。

Webアプリケーションの特徴:

  • ブラウザさえ動けばあらゆるデバイス・プラットフォームで動作する
  • 通信プロトコルにHTTP/HTTPSを使っていれば良いので、Webサーバー側の実装自由度が高い(言語・構成を問わない)
  • 仕様が公開されている(RFCやW3C・WHATWG等の標準化団体)
    • ブラウザを選ぶ自由がクライアント側にある(特定のベンダーに縛られない)

一方、自由度が高い分、Webアプリケーション開発者はそれらの自由度を理解した上で開発しなければいけません。

昨今はFireBaseをはじめとするmBaaSや、kintoneなどのPaaSクラウド環境を前提としたアーキテクチャで作られたWebアプリケーションも増えているため、色々なものが複雑化している印象を受けます。

Webサーバーとは何か

WebサーバーとWebブラウザは、HTTPまたはHTTPSというプロトコルで通信を行います。

なお現在は、経路暗号化されないHTTPは推奨されない流れになっており、新しくサービスを立ち上げる場合には常にSSL/TLSを使ったHTTPS環境を使うと思って良いでしょう(参考)。

HTTP/HTTPS通信の元々の用途はハイパーテキスト(HTMLやSGMLといったWebページ)の転送であり、ステートレスプロトコルである点が非常に重要です(ステートレスについては次回で解説します)。

HTTP 1.0であればGET/POST/PUT/DELETEという限定された手段(メソッド)のみが使えます。HTTP 1.1ではTRACEOPTIONSなどのメソッドが追加されています。

現在はHTTP 1.1が主流なので、RFCを読むならHTTP 1.1をおすすめします(参考)。

morimorihoge注) HTTP 1.0はリクエストヘッダにおけるHostヘッダが必須でないため、いわゆるバーチャルホスト機能が使える保証がありません。しかし今の世の中では一般的にバーチャルホスト機能が使われているため、実質HTTP 1.0は滅びたと言って良いと思います。

また、これから勉強するならHTTP/2だろという方もいるかも知れませんが、HTTP/2はHTTP 1.1の理解が前提となります。したがって、HTTP 1.1の仕様も読んでいない初心者にいきなりHTTP/2の仕様から読ませるのはお勧めしません。

プロトコルとは

通信のプロトコル(protocol)とは、作者も実装も異なるさまざまなソフトウェア同士が正しく通信するための決めごと・約束事です。

逆に言えば「異なる作者・実装のソフトウェア同士であっても所定のプロトコルに準拠していれば相互に通信できる」ということです。たとえばWebであればブラウザとWebサーバがHTTPを使うという点で合意しているので、お互いの通信が可能になっています。

ネットワーク絡みのソフトウェアを開発または拡張するときには、「既存のプロトコルがあるか」「やろうとしていることがプロトコルに含まれているか」といった具合に、前提として既存のプロトコルを意識することが大事です。

morimorihoge注) 勉強のためにオレオレ通信仕様のプログラムを書いても良いですが、そのプログラムが他者の作ったプログラムと連携できる保証はありません。

最近のWebの話題

HTTP/2の普及

HTTP/2は、Googleが手がけたSPDYを元にした次世代のHTTPプロトコルで、PUSH通知などの新しい機能を備え、性能面で優れています。通信相手がHTTP/2に対応していなければ従来のHTTP 1.xに自動で切り替わります。
※こうした挙動はフォールバックと呼ばれ、システムではよく後方互換性のために実装される機能です

HTTPが非推奨、HTTPSが標準になりつつある

HTTPS(SSL/TLS越しのHTTP)が標準になってきた」ことも重要です。これは主にLet’s Encryptなどによる無料SSL証明書作成サービスや、HSTS(HTTP Strict Transport Security)の普及が要因となっています。

今やHTTPを選択する理由はありません。よほど特殊な理由がない限りサイトすべてをフルHTTPSにすべきです。

原則としてSSLではなくTLSを用語として使いましょう。SSLはTLS(Transport Layer Security)にその座を譲ったので、一部のレガシーブラウザ対応以外ではまず使われていません。SSLは3.0までのすべてのバージョンで脆弱性が見つかっています。

参考: URIとURLについて

URI(Uniform Resource Identifier)はリソースを一意に指定するもので、HTTP/HTTPSの仕様とは独立しています

ネットワーク上でのリソース(HTMLや画像など)を一意に指し示すための識別子としてURL(Uniform Resource Locator)がありますが、昨今ではWebサーバーが実際には分散した場所に置かれていたり、URL自体にアプリケーションのパラメータなどが含まれているケースなどもあり「Locator」という言葉は適切ではないということで、現代では一般に「URI」という言葉が使われます

※プログラマであれば、昨今のネットワークライブラリでは”URL”ではなく”URI”という名称が使われていることに気付けると思います

URIにはネットワーク上のあるリソースに対する

  • アクセス方法: HTTP、HTTPS、FTPなどの接続プロトコル
  • ホスト名: 接続先ホストを表すIPアドレスやホスト名
  • リソースパス: そのホスト内のリソースの場所を表すパス
  • パラメータ: アプリケーションサーバーなどの場合に渡すパラメータリスト

などが含まれているため、インターネット上には数え切れない程のサービスやデータがありますが、URIさえ分かれば誰もが確実に同じデータを参照することができるわけです。

ここまでのまとめ

  • ブラウザはHTTP/HTTPSでWebサーバーと通信する
  • HTTPにもバージョンがある
    • 新しいバージョンのプロトコルで通信するにはクライアントとサーバーの両方でのサポートが必要
    • 一方が対応できない場合は共通で使えるバージョンリストのうち最も新しいバージョンにフォールバックする

※次回「#2. Webサーバーの役割とWebサービスの代表的な形態I〜III」に続きます。

関連記事

【社内勉強会】バージョン管理の重要性とGitの運用について

週刊Railsウォッチ(20180813)Rails 5.2.1リリース、sanitize_sql_arrayは5.2からpublicだった、Dev.toがRailsアプリのソースを公開ほか

$
0
0

こんにちは、hachi8833です。自宅のエアコンの室内機からジャージャー水漏れしたのでこいつ↓でドレーンパイプを吸ったらコガネムシの破片が転がり出てきました🐞

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを社内有志でつっついたときの会話です👄

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

Rails 5.2.1が8/7に正式にリリースされました🎉🎉

早速記念写真。

その後のコミットを見ると、Railsガイドの修正が目立ちます。

なお、References to changes in Rails 5.2という変更点まとめ記事をHacklinesで見つけました。網羅しているかどうかはわかりませんが、トリビアな変更を避けているっぽいです。


つっつきボイス: 「お、これで5.2系もマイナーバージョンが1つ上がって安定度が増すかな😋」「😆書籍も初版は誤植が付き物ですしね」「実際gem install railsでインストールされるようになってから本格的にバグが見つかるし😎

⚓ActiveRecordのsanitize_sql_*系メソッドが5.2で既にpublicメソッドに

先週のではなく2017年のy-yagiさんによる改修でした。

# activerecord/test/cases/sanitize_test.rb#L12
  def test_sanitize_sql_array_handles_string_interpolation
    quoted_bambi = ActiveRecord::Base.connection.quote_string("Bambi")
-    assert_equal "name='#{quoted_bambi}'", Binary.send(:sanitize_sql_array, ["name='%s'", "Bambi"])
-    assert_equal "name='#{quoted_bambi}'", Binary.send(:sanitize_sql_array, ["name='%s'", "Bambi".mb_chars])
+    assert_equal "name='#{quoted_bambi}'", Binary.sanitize_sql_array(["name='%s'", "Bambi"])
+    assert_equal "name='#{quoted_bambi}'", Binary.sanitize_sql_array(["name='%s'", "Bambi".mb_chars])
    quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote_string("Bambi\nand\nThumper")
-    assert_equal "name='#{quoted_bambi_and_thumper}'", Binary.send(:sanitize_sql_array, ["name='%s'", "Bambi\nand\nThumper"])
-    assert_equal "name='#{quoted_bambi_and_thumper}'", Binary.send(:sanitize_sql_array, ["name='%s'", "Bambi\nand\nThumper".mb_chars])
+    assert_equal "name='#{quoted_bambi_and_thumper}'", Binary.sanitize_sql_array(["name='%s'", "Bambi\nand\nThumper"])
+    assert_equal "name='#{quoted_bambi_and_thumper}'", Binary.sanitize_sql_array(["name='%s'", "Bambi\nand\nThumper".mb_chars])
  end

もうsendで叩かなくて済むということですね。


つっつきボイス: 「おおこれは前から何かと話しに上がってたヤツ: 自分もsendで叩いてたし」「やっぱり使ってたんですね?」「sanitize_sql_arrayとかは生SQLを書く人には絶対に必要です😤」「これはSQLインジェクション防止のために超大事なヤツっす」「なぜ今までこれがprivateだったのかホント不思議なくらいで、ツイートにもあるけど誰もが同じこと思ってた」「sendすれば使えますけどねっ🕶書き方としてよくないというだけで」

「生SQLで変数のプレースホルダーが複数出てきたりすると結局sanitize_sql_arrayとかでやる以外に方法はないですね: 当時自力で追いかけましたもん🤓」「2017年に入ってたのにウォッチで見逃してたとは…😅これだけのために5.2入れる甲斐がありそう」「あ、今回の5.2.1からじゃなくて5.2で既に入ってたのか😲」「そうそう、もう大手を振って使えます😋ある程度以上大きなプロジェクトならたいてい生SQL書くことになるので」

確かに5.2で入ってました↓。5.1ではClassMethodsが丸ごとprivateになっていました。

# https://github.com/rails/rails/blob/5-2-0/activerecord/lib/active_record/sanitization.rb#L123
      def sanitize_sql_array(ary)
        statement, *values = ary
        if values.first.is_a?(Hash) && /:\w+/.match?(statement)
          replace_named_bind_variables(statement, values.first)
        elsif statement.include?("?")
          replace_bind_variables(statement, values)
        elsif statement.blank?
          statement
        else
          statement % values.collect { |value| connection.quote_string(value.to_s) }
        end
      end

      private

参考: Ruby on Rails 5.2 / ActiveRecord::Sanitization::ClassMethods — DevDocs
参考: SQLインジェクション - Wikipedia

「今までprivateだったのは、もしかすると『生SQL書くな』ってことなのかな?なんて思ったりもするけど」

「話逸れますけど、@__gfx__さんのTwitter IDの前後にアンスコが2つずつついてるので、この人のツイートはTechRachoの記事に直接埋め込めないんです😭」「Markdownと誤認識されちゃうのね: ワカルワカル😆」「RailsdmでElasticsearchの話してた方でしたね↓」

「SQL向けのpublicなsanitizeメソッドって他にあった気がしないでもないけど気のせいだったかな🤔…?」

ActiveRecord::Base.sanitizeが以前あったのが削除されたそうです↓。placeholder展開もしないようです。
* ActiveRecord::Base.sanitize removed in 5.1 · Issue #28947 · rails/rails

追いかけボイス: 「(後日)sanitize_sql_arrayあまり使わないと聞きましたが」「Arelは基本使わないし、生SQLも本当に必要になるまで使わないマンなので🤓」「Rails wayというかActiveRecord wayでやってるんですね☺」「生SQLは最近少し使ったのでそのときはさすがにsanitize_sql_arrayしましたが😎

⚓Relation#updateのスコープ追加回避」を取り消し

5.2.1rc1から5.2.1の唯一の変更点でした。

# activerecord/lib/active_record/persistence.rb#L100
-      def update(id, attributes)
+      def update(id = :all, attributes)
        if id.is_a?(Array)
          id.map { |one_id| find(one_id) }.each_with_index { |object, idx|
            object.update(attributes[idx])
          }
+        elsif id == :all
+          all.each { |record| record.update(attributes) }
        else
          if ActiveRecord::Base === id
            raise ArgumentError,
               "You are passing an instance of ActiveRecord::Base to `update`. " \
               "Please pass the id of the object by calling `.id`."
           end
           object = find(id)
           object.update(attributes)
           object
         end
       end

つっつきボイス: 「all.each { |record| record.update(attributes) }のところeachで回してるとは😲あ、でもeachでやらないとフックが実行されないのか: でもパフォーマンスが問題になりそうな気もするけど🤔」「breaking changesになるのでいったん取り消したみたいですね」「確かにこれはbreaking changesになりますね: ARの#updateのフックをざっと見てみると、before_validationとかbefore_saveなんかも呼ばれてるので、breaking changesだけどこの取り消し前の修正が正しいんだな💡」「😃

参考: Railsのcallbackについて調べた - Qiita

「元の修正は0b29a42か: それにしてもIDにarrayを渡せるとは知らなかったし」「まあ自分はRelation#updateはそんなに頻繁には使ってませんけどね😋

⚓DBのパラレルテストを高速化


同PRより

# activerecord/lib/active_record/tasks/database_tasks.rb#L248
      def load_schema(configuration, format = ActiveRecord::Base.schema_format, file = nil, environment = env, spec_name = "primary") # :nodoc:
        file ||= dump_filename(spec_name, format)
+         verbose_was, Migration.verbose = Migration.verbose, verbose? && ENV["VERBOSE"]
        check_schema_file(file)
        ActiveRecord::Base.establish_connection(configuration)
          case format
         when :ruby
           load(file)
         when :sql
           structure_load(configuration, file)
         else
           raise ArgumentError, "unknown format #{format.inspect}"
        end
        ActiveRecord::InternalMetadata.create_table
        ActiveRecord::InternalMetadata[:environment] = environment
+      ensure
+        Migration.verbose = verbose_was
      end

つっつきボイス: 「ピンポイントで修正してますね」「これは見てのとおりマイグレーションを呼ばずにスキーマを読み込むように変わってるし: これなら確かに速くなる🏎

⚓ネストした複数のrespond_to同士に互換性がない場合にのみ例外を発生するようになった

# actionpack/lib/action_controller/metal/exceptions.rb#L64
+  class RespondToMismatchError < ActionControllerError
+    DEFAULT_MESSAGE = "respond_to was called multiple times and matched with conflicting formats in this action. Please note that you may only call respond_to and match on a single format per action."
+     def initialize(message = nil)
+      super(message || DEFAULT_MESSAGE)
+    end
+  end

つっつきボイス: 「今までのだとouterとinnerが違っても通っちゃってたのか↓: jsなのに中にHTMLがあったらそりゃマズイ」

# 同PRより
    respond_to do |outer_type|
      outer_type.js do
        respond_to do |inner_type|
          inner_type.html { render body: "HTML" }
        end
      end
    end

「でRespondToMismatchErrorクラスを新しく定義してrespond_toでraiseするようになったと↓: respond_toってネストできるのね💡

# actionpack/lib/action_controller/metal/mime_responds.rb#L193
     def respond_to(*mimes)
       raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given?

       collector = Collector.new(mimes, request.variant)
      yield collector if block_given?
       if format = collector.negotiate_format(request)
+        if content_type && content_type != format
+          raise ActionController::RespondToMismatchError
+        end
        _process_format(format)
        _set_rendered_content_type format
        response = collector.response
         response.call if response
       else
         raise ActionController::UnknownFormat
       end
     end

⚓HEADのデフォルトのContent-Typetext/html

# actionpack/lib/action_controller/metal/head.rb#L38
      if include_content?(response_code)
-        self.content_type = content_type || (Mime[formats.first] if formats)
+        self.content_type = content_type || (Mime[formats.first] if formats) || Mime[:html]
        response.charset = false
      end

つっつきボイス: 「今までHEADが返してたのはMime::NullType😲」「HEADはHTTPのメソッドですね」「あーなるほど、HEAD 200↓のときにMime::NullTypeを返すのは確かにどこかヘンだ: Webサーバーならtext/htmlを返すのが普通だと思うし」

# actionpack/test/controller/render_test.rb#L230
+  def head_default_content_type
+    # simulating path like "/1.foobar"
+    request.formats = []
+     respond_to do |format|
+      format.any { head 200 }
+    end
+  end

「たぶんですけど、修正前だとヘルスチェックで引っかかるんじゃないかな」「HEADって本文がないレスポンスでしたっけ」「お、RFC2616の14.17によるとContent-TypeGETリクエストと同じものを返すとある↓から、Mime::NullTypeを返すのは仕様違反だったっぽい」「おー😲」「もちろん常にtext/htmlを返していいものではないので適切なものを返さないといけないはずですが、少なくともデフォルト値としてはtext/htmlの方が適切でしょうね🧐」「なるほどっ😃

The Content-Type entity-header field indicates the media type of the entity-body sent to the recipient or, in the case of the HEAD method, the media type that would have been sent had the request been a GET.
HTTP/1.1: Header Field Definitions 14.17より

⚓ログ出力されるIPアドレスをプロキシのIPからリモートIPに修正

# railties/lib/rails/rack/logger.rb#L48
         # Started GET "/session/new" for 127.0.0.1 at 2012-09-26 14:51:42 -0700
         def started_request_message(request) # :doc:
          'Started %s "%s" for %s at %s' % [
            request.request_method,
            request.filtered_path,
-            request.ip,
+            request.remote_ip,
            Time.now.to_default_s ]
        end

つっつきボイス: 「おおー!この修正はかなりエライ!💪そしてbreaking changeなんだけど、これがbreaking changeになるような設定はよくないという意味でこの修正は正しい」「というと?」「修正前だとロードバランサーを間に挟んだときにロードバランサーのIPが取れてしまうから」「これってbreaking changeになるんでしょうか?ログの項目が変わっただけなのかなと思ったり」「いえいえ、修正前にこれでロードバランサーのIPが取れると思ってそれを当てにして実装していた人たちにとってはbreaking changeになるということです: 修正後が正しいのは確かですが🧐

参考: remote_addrとかx-forwarded-forとかx-real-ipとか - Carpe Diem

⚓ソート順変更をいったん取り消し

例のlimit(1).first問題の修正(#24131)の影響が大きいので、deprecationサイクルをはさんで行うことにしたそうです。

# activerecord/lib/active_record/relation/finder_methods.rb#L552
      def ordered_relation
-        if order_values.empty? && primary_key && limit_value.blank?
+        if order_values.empty? && primary_key
          order(arel_attribute(primary_key).asc)
        else
          self
         end
       end

つっつきボイス: 「ああこの間limit(1).firstですね: deprecationを挟むはもっともだし、こういうPRを見るとRailsのdeprecation warningにはちゃんと対応しないといけないなって改めて思いますね😎

⚓Rails

⚓Rails 5.2のcredentialチートシート(Ruby Weeklyより)

# 同記事より
Rails.application.credentials.secret_key_base

Rails.application.credentials.fetch(:secret_key_base) { raise "it seems you didn't configure credentials" }

Rails.application.credentials[:secret_key_base] || "someDefaultValue"

ENV["SECRET_KEY_BASE"] ||  Rails.application.credentials[:secret_key_base]

Rails.application.credentials.dig :secret_key_base

つっつきボイス: 「例のRAILS_MASTER_KEYを使うヤツっすね: 前も話しましたけど個人的にはあまり好きでない方式」「そうでしたね☺」「リポジトリよりはKMSとかに保存したいし、この方式だとマスターキーを持っている人じゃないとviで開けないし、そもそもエディタで開いて編集するのもちょっとねー😅」「確かにー」「ま、でも個人がHerokuで動かす小規模なアプリならこの方法で十分だと思いますけどね」「小規模チームにもよさそう」

「ところで記事タイトルのCheat Cheatってスペルミスなのかわざとなのかちょっと謎でした🤔」「😆

↓Urban Dictionaryに一応ありました。ダジャレのようです。

参考: Urban Dictionary: Cheat Cheat

⚓Hanami v1.3.0.beta1リリース

変更点:

  • デフォルトのフレームワークがRSpecに
  • Hanami::Utils::StringHanami::Utils::HashHanami::Utils::Inflectorが非推奨に: 一部はdry-inflectorに移行
  • body_parsersがミドルウェアに移行
  • HTTPSがRackミドルウェアに移行
  • アセットプリコンパイル時にディレクトリ構造を保持
  • ネストしたモジュール/クラス定義でactions/views/mailersを生成
  • CLIの機能追加

以下で試せます。

gem install hanami --pre
hanami new bookshelf

Hanamiは以前はLotusという名前だったことをついでに知りました。


つっつきボイス: 「RSpecに変わるのね☺ユーザー数多いし」「HTTPSをRackミドルウェアに任せるんだそうです」「アプリ側ではSSLしないよなーやっぱり」

後で検索してみると、記事中で触れられていたrack-sslミドルウェアはアーカイブ化されてたので、今だとrack-ssl-enforcerミドルウェアを使うんでしょうね。

⚓Dev.toがRailsアプリのソースコードを公開

フロントエンドはPreactjs、単体テストはJestだそうです。


preactjs.comより


つっつきボイス: 「社内Slackで教わりました」「Dev.toって本当にRailsだったのね😳」「爆速で有名になりましたね: CDNも使いまくってるんでしょうね」「CDNは当然使ったうえで他にもいろんな最適化やってるはず: CDNだけであんなに速くなるはずないので🧐」「コミット数500そこそこは意外に少ないかも」「お、コントローラでset_surrogate_key_header "now_page"みたいにサロゲートキー使ってる↓👀」「何でしょう?」「ちゃんと見ないと何とも言えないけど、ハッシュ的なものに格納してるっぽいので、おそらくこの辺は固定ページなのかな」「最適化されまくったソースを参考にRailsアプリを書くのはちょっと大変そうだし、Railsで超高速を目指すのは果たしてどうなのかとは思いつつも、これは面白いですね😋今度中身見てみよっと🛠

# 同リポジトリより
class PagesController < ApplicationController
  # No authorization required for entirely public controller
  before_action :set_cache_control_headers, only: %i[rlyweb now events membership survey]

  def now
    set_surrogate_key_header "now_page"
  end

  def survey
    set_surrogate_key_header "survey_page"
  end

  def about
    set_surrogate_key_header "about_page"
  end


dev.toより

⚓Inspec: インフラのテスト/監査フレームワーク(Ruby Weeklyより)


inspec.ioより


つっつきボイス: 「RSpecのインフラ版みたいなやつが前からありましたけど…えっと何でしたっけ」「serverspec」「それそれ、それのライバル的なものみたいです」


serverspec.orgより

「↓この辺はserverspecっぽい」

# inspec.ioより
describe file('/etc/myapp.conf') do
  it { should exist }
  its('mode') { should cmp 0644 }
end

describe apache_conf do
  its('Listen') { should cmp 8080 }
end

describe port(8080) do
  it { should be_listening }
end

「↓これはAWSのリソースチェックか: だいたいやってることは同じかな😎

# inspec.ioより
describe aws_s3_bucket(bucket_name: 'my_secret_files') do
  it { should exist }
  it { should_not be_public }
end

describe aws_iam_user(username: 'test_user') do
  it { should have_mfa_enabled }
  it { should_not have_console_password }
end

参考: InSpecではじめるテスト駆動インフラ - tkak’s tech blog

「そういえばInspecのロゴの下にちっちゃーくChefって書いてありますね↑」「うんChefっぽい」


chef.ioより

「最近Chefってあまり聞かなくなった気がしますけどどうなんでしょう?」「Chefは何年か前に流行りましたが結局オーバースペックでしたね: Chefに精通したインフラエンジニアがいないと結局つらくなる」「😲」「そういえばBPS社内でもAnsibleに移り変わってますね」「chef-soloしか使わないならAnsibleでいいんじゃね?って思うし: Ansibleはシンプルだし実行順序が保証されるので」「ということは…」「Chefは実行順序が保証されないところが大変: イミュータブルなコンフィグレーションだと思って書かないとつらい」「あー」「実行順序を保証するときにはChefからシェルスクリプト呼んだりとか」「それって本末転倒感💦


ansible.comより

Itamaeは?」「クックパッドさんの作ったヤツで、こちらは使いやすいです❤


itamae.kitchenより

「あとChefにいいrecipeがあまりなかったのが残念」「うーむ」「今はchef.ioだけど確か元々違う会社名でChefのホスティングサービスをやってたはず…何だっけ…(しばらく探す)↓Opscodeだっっっ!🎯」「しかも2013年に社名変わってたんですね」「というぐらい最近Chefを使ってなかった😅

参考: Chef開発元のOpscode、社名をChefに変更。「検索が難しくなる」とあちこちで悲鳴が - Publickey

chefからansibleに乗り換えた5つの理由

⚓番外: gist-it: GitHubリポジトリをGist的に埋め込めるサービス


つっつきボイス: 「Railsとは関係ないんですが、GistでなくてもブログにGitHub上のコードを埋め込めるというので後で試してみます」「😃

できました↓。?slice=12:18のようにパラメータを追加すれば行数を指定できます。

diffは無理みたい(´・ω・`)。

⚓Ruby trunkより

⚓提案: Hash#===Array#===Ruby Weeklyより)

他にFeature #14973: Proposal of percent literal to expand Hash - Ruby trunk - Ruby Issue Tracking Systemもありました。

珍しくRuby Weeklyがtrunkのissueを紹介していたのですが、いずれも日本語で書かれています。

Hash#===
レシーバのキーの要素と引数のハッシュのキーの要素を #=== で比較して、全てが真なら true を返し、そうでないなら false を返す。
また、レシーバが空のハッシュの場合、引数が空のハッシュなら true を返し、そうでないなら false を返す。

user = { id: 1, name: "homu", age: 14 }

# name 要素が data にあるので true
p ({ name: "homu" } === user)
# => true

# 複数の要素があっても OK
p ({ id: 1, name: "homu", age: 14 } === user)
# => true

Array#===
配列の各要素をそれぞれ順に === で比較し、全要素が true の場合に true を返す。そうでない場合は false を返す。

# 配列の各要素を #=== を使用して比較する
[ String, /\w/ ]          === [ "a", "c", 7 ]   # => false
[ String, /\w/, (1..10) ] === [ "a", "c", 7 ]   # => true
[ String, /\w/, (1..10) ] === [ "a", "!", 42 ]  # => false

つっつきボイス: 「さっきbugs.ruby-lang.orgが落ちてたんですが今は動いてるのでやっと読めました💦」「issueは日本語だけど英語のレスもついているという😆」「トリプルイコールかー😲」「ディープな場合はどうなるんだろう?」「これは要素を順に比較するだけだからディープな動作ではないですね😎」「こういうのを見ると、メソッドの戻り値が複数の場合に===でパターンマッチ的に比較したくなりますね」

「今は===ってどこにあるんだっけ?(コードを掘り始める)」「ownerで取れますね確か」「あった: Kernel#===なのね↓」「普段めったに使わない操作w」「methodsでやるとドバっとメソッドが表示されるから大変ですよね」

[].method(:'===').owner #=> Kernel

RubyのIRBやpryでメソッドの定義元をすっと調べる方法

Hash#===Array#===、あったらうれしいという気持ちはわからなくもないけど」「===だからcase文で使えるってことなんですね」「これかー↓」「これは相当しっかり理解しとかないとハマりそう😅」「このcase文はヤバイ😆」「Rubyのcase文はもともとかなり自由だし」

#14916より
def plus *args
  case args
  # 数値の場合
  when [Integer, Integer]
    args[0] + args[1]
  # 数字の場合
  when [/^\d+$/, /^\d+$/]
    args[0].to_i + args[1].to_i
  # それ以外はエラー
  else
    raise "Error"
  end
end

p plus 1, 2
# => 3
p plus "3", "4"
# => 7
p plus "homu", "mado"
# Error (RuntimeError)

⚓Solarisでコケるテストがある


つっつきボイス: 「Solarisという単語につい惹かれてしまいました」「Ruby、Solarisサポートしてるのか😲」「そういえばSolaris使ったことあります?」「大学にあった気がする」「前職でちょっぴり」「薄紫色のピザボックス型でした」「筐体はいろいろありますからね: Fireには冷蔵庫みたいなデカイのもあったし」「マシン自体は見たことなかった…」「自分Ultra SPARC IIIのマシン自宅に持ってました💪」「おほー!」「DECのAlpha 21264マシンも持ってたし: どっちも捨てちゃったかな…」「それもスゲー」「64ビットマシンだったし: 今は珍しくも何ともないという🤣」「🤣」「あっ、でもAlpha 21264はRISCだったし: RISCは珍しーだろー🤓

参考: Solaris - Wikipedia
参考: Alpha 21264 - Wikipedia
参考: RISC - Wikipedia

⚓提案: Any

# issueより
class Any
  class << self
    def ===(b)
      true
    end

    def ==(b)
      true
    end

    def to_proc
      proc { true }
    end
  end
end

# ------

case ['Foo', 25]
when [/^F/, Any] then true
else false
end
# => true

case {id: 1, name: 'foo', age: 42}
when {id: Any, name: /^f/, age: Any} then true
else false
end
# => true

case {id: 1, name: 'foo'}
when {id: Any, name: /^f/, age: Any} then true
else false
end
# => false

先のArray#===Hash#===と組み合わせるといい感じになるという提案でした。


つっつきボイス: 「お、Any型かー: こういう番兵的なものが言語レベルであると実は便利なんですよね❤」「確かにー」「Scala言語にAnyってのがあるのね」「番兵って…? 英語だとsentinelって言うみたいですが…」「よくC言語なんかで文字列終端を表すヌル文字\0が番兵って呼ばれてますね: ただヌルって他でも使うから、たまたま値がヌルになるとそこで止まっちゃうなんてことがよくあるという」「なるほどー!😃」「だからこのAnyみたいに他では絶対使われない値があれば、終端が事前にわからないストリームなんかを扱う場合の番兵として使いやすいですよね☺

参考: Scala - Wikipedia
参考: 番兵 - Wikipedia

「提案しているのはRubyと関数型の記事でお馴染みのBrandon Weaverさんでした」

Rubyで関数型プログラミング#1: ステート(翻訳)

⚓Ruby

⚓Rubyの高速化は単純には見積もれない(Ruby Weeklyより)

Noah Gibbsさんの記事です。


つっつきボイス: 「パフォーマンスの見積もり方というか、個別の高速化を足しても100%の高速化にはならないよ的な話のようです」「ぼやきというか」「高速化は単純に足し算できませんからね🕶」「Noah Gibbsさんがチューニングしたものをさらに高速化するのはすげー大変そう」「自分ならマシンを速くして高速化するな🤣」「札束で殴る🤣

⚓undercover: 賢いカバレッジ(Ruby Weeklyより)


同リポジトリより


つっつきボイス: 「カバレッジに対するRuboCopみたいなものだそうです」「おー、lambdaとかの中までチェックしているみたい」「全部のクラスにはかけたくはないけど、決済系のコードみたいなすごく重要な部分に絞ってこういうツールをかけるのはありかも😋」「ありそうでなかったツールなんですね😃」「機会があったらピンポイントで使ってみようかな」

⚓STDOUT/STDERRデバッグメッセージの出処を突き止める

# 同記事より
class << STDERR
  alias_method :orig_write, :write
  def write(x)
    orig_write(caller[0..3].join("\n"))
    orig_write(x)
  end
end

つっつきボイス: 「Sam Saffronさんの記事です」「ははぁこうやってSTDERRを拡張するのね↑」「おほー😃」「STDERRってクラスじゃないですよね?」「RubyではIOオブジェクトだったはず: Rubyはオブジェクトを直接拡張できるので、そこにalias_methodwriteをフックすると」「やるな~」「これは実にRubyらしい発想❤

参考: constant Object::STDOUT (Ruby 2.5.0)
参考: class IO (Ruby 2.5.0)

「他の言語だとどんな感じになるんでしょうか?」「他の言語だと、普通はSTDOUTやSTDERRのファイルディスクリプタが取れるので、ファイルディスクリプタをウォッチするOSのメソッド(macOSだとFSEvents、Linuxだとinotifyあたりだったかな)を使って、特定のディスクリプタに対して特定のAPI呼び出しをハンドルする感じですかね」「なるほどー😃」「例のguard gemみたいなファイル更新をチェックするソフトウェアは、たぶんそのあたりを使ってると思いますよ」「他の言語だとOS側でイベントを監視してもらわないといけないんですね」「ですです: 普通はOSにどこかでイベントをディスパッチしてもらわないといけない: でもRubyだと上のようにSTDOUTやSTDERRといったオブジェクトに直接フックをかけられるのが面白いですね😋

参考: ファイル記述子 - Wikipedia

そういえばGobyでたまたまSTDOUT.to_sしたらそのまんま<File: /dev/stdout>が出てきたのを思い出しました😅。もろにファイルディスクリプタ。

⚓Rubyの整数の最適化

参考: 第2章 オブジェクト — Rubyソースコード完全解説


つっつきボイス: 「RubyのRVALUEの最適化ってほんと半端ない」「RVALUEは記事を翻訳して知りました↓」「ツイートで『ポインタを, 何らかの構造体へのポインタとして使ったり, 整数そのものとして扱ったりすることを褒めたつもりだった』ってありますね」「最適化では割と使われる手法だけど、自分はちょっとビビる😅

Rubyのメモリ割り当て方法とcopy-on-writeの限界(翻訳)

/* http://i.loveruby.net/ja/rhg/book/object.html より*/
 123  #define INT2FIX(i) ((VALUE)(((long)(i))<<1 | FIXNUM_FLAG))
 122  #define FIXNUM_FLAG 0x01

(ruby.h)

「このFIXNUM_FLAG↑っていう発想が面白い: 下位1ビットをフラグとして使うことでFixnumかBignumかを判定するという😆」「それ完全にハックやん…🛠」「4の倍数アドレス云々の部分は確かPOSIXで規定されていたはずだから、mallocを使っていればポインタが4の倍数アドレスであることが保証されるはず」「仮に1ビット単位で最適化するソフトウェアがあったとしても、mallocを使えば4の倍数にアラインされる」「その隙間をフラグに使うという😲」「爪に火を灯すような最適化🔥

参考: POSIX - Wikipedia

⚓Julia Evansさんがプロファイラを語る


同PDFより


つっつきボイス: 「このPodcastの凄い点は、スポンサーを募って文字を全部書き起こしているところかも」「おータイムスタンプまできっちり起こしてるし↑😲この起こしは大変そう」「PDFですけどね😆」「他のPodcastなんかで、あちこち<UNREAD>みたいになってる文字起こしをちょくちょく見かけますけど、あれはきっと自動で起こしてるんだろうなーって🤣」「🤣」「🤣」「技術トークの起こしって音声認識泣かせですねホント」「実際に目の前で聞いてもよくわからないことあるし🤣

Ruby: eBPFでメモリアロケーションをプロファイリングする(翻訳)

⚓debug_helper: デバッグ情報をyaml風に出力するgem(Hacklinesより)

# 同記事より
require 'debug_helper'

ary = [0, 'one', :two]
DebugHelper.show(ary, 'My mixed array')
Array (message='My mixed array'):
  size: 3
  Element 0: Fixnum 0
  Element 1:
    String:
      to_s: one
      size: 3
      encoding: !ruby/encoding UTF-8
      ascii_only?: true
      bytesize: 3
  Element 2:
    Symbol:
      to_s: two
      size: 3
      encoding: !ruby/encoding US-ASCII

つっつきボイス: 「ほほーyamlに出力とな」「出力に改行が混じったりするとyamlもそんなに読みやすくないけど😅」「😆」「↑このぐらいならキレイでも現実のデータだとなかなかこうならなかったりしそうだけど、とりあえずこうやって作ってみたのはいいですね😋

⚓Rubyのオブジェクト生成方法を変えるには(Hacklinesより)

割と短い記事です。


つっつきボイス: 「モジュールでsuperしてるし↓」「なぬ?😅」「その後object.singleton_class.include(Logging)してるから動くけど、なかなかキモい👻

# 同記事より
module Logging
  def make_noise
    puts "Started making noise"
    super
    puts "Finished making noise"
  end
end

class Bird
  def make_noise
    puts "Chirp, chirp!"
  end
end

object = Bird.new
object.singleton_class.include(Logging)
object.make_noise
# Started making noise
# Chirp, chirp!
# Finished making noise

「最終的にself.newしてるし↓」「なぬなぬー?😅」「ははぁ、allocateをオーバーライドできないからこういう遠回りな方法でやってるのか」「おー😲」「確かにコアクラスの方でオーバーライドされると手も足も出なくなっちゃうからこういう手段に出たんだろうけど、基本的にはこういうことはしない方がいい気はするなー🧐」「STIなんかで下の方に埋め込まれてコントロールできなくなっちゃうとか、たまにありそうですね」「newを無理やりオーバーライドするか、さもなければリフレクションしまくるか、どっちもやりたくないなー😭」「この記事のやり方はモジュールをincludeで入れているあたりがDIっぽいからまだいい方かも😋

# 同記事より
class Bird
  def self.new(*arguments, &block)
    instance = allocate
    instance.singleton_class.include(Logging)
    instance.send(:initialize, *arguments, &block)
    instance
  end
end

参考: 依存性の注入 - Wikipedia

⚓mini_i18n: 小規模なi18n化ライブラリ

# 同リポジトリより
>> MiniI18n.t(:hello)
=> "Hello"
>> MiniI18n.t(:hello, locale: :fr)
=> "Bonjour"
>> MiniI18n.locale = :fr
=> :fr
>> MiniI18n.t(:hello)
=> "Bonjour"
>> MiniI18n.t(:hellooo)
=> nil

つっつきボイス: 「文字どおりミニなi18n☺
「ところでRailsのi18nって基本的にRubyのI18nだから別に大きくはないですよね?」「お、でも#l#localizeのエイリアス)とか割と複雑なことやってた気がしたけど: DateTimeとかを渡すとその国の形式に変換してくれるヤツ」「あれってRubyのI18nに丸投げしてませんでしたっけ?」「あーrequire i18nしてるからやっぱりRubyのI18nですね」「でもエイリアスはRailsの方で定義されてるみたい…」「ややこしい😅

# File actionview/lib/action_view/helpers/translation_helper.rb, line 117
def localize(*args)
  I18n.localize(*args)
end

参考: Ruby on Rails 5.2 / ActionView::Helpers::TranslationHelper#localize — DevDocs

「そういえばRailsの#lメソッドってnil渡すとエラーになるんですよね😅」「そうそうw 誰もが一度はハマるヤツ😆」「でpresent?するとその後if文がだんだん増えていくという🤣


「ちょっと話逸れるんですけど、10年くらい前に英語/日本語を同じぐらい使える大先輩に『ローカライズの仕事してます』って話したら大爆笑されたことがあって、どうやらlocalizeっていう単語は技術から離れると『土着化』とか『現地住民と同化する』的に響くところがあるみたいで」「😆」「もともとそういうニュアンスで使われてた時代があったからかも🤔」「そのせいかどうかわかりませんが、最近はlocalizeという言葉をglobalizeと言い換えてるところもあるようです☺

localizeの基本的な意味は「局所化」ですね。

参考: ローカライゼーション - Wikipedia

⚓その他Ruby



↑シンガポールでの講演だそうです。

⚓クラウド/コンテナ/インフラ/Linux/Serverless

⚓Google Cloud MemorystoreのRedisサービス

最初の比較記事を書いたときはCloud Memorystoreはまだなかったそうです。

⚓GLB: GitHubのロードバランサー


同記事より


つっつきボイス: 「GLBはまだざっとしか見てなくて正体わかってないんですが、この図↓などを見た感じではNGINX Unitにちょっと似ている印象でした」「とりあえずロゴ↑は秀逸ですね😋」「カワイイ❤

新しいRubyアプリサーバー「NGINX Unit」を調べてみた(翻訳)

⚓Statsd: 統計収集ツール

Node.jsベースで書かれているようです。★13,000超えです。


つっつきボイス: 「Sam Saffronさんの記事に登場してたので」「めっちゃ★付いてる」「これは使ったことなかった」

参考: netdata と statsd によるリアルタイムモニタリング - biaxident’s blog

⚓標準電波JJYの夏時間対応


つっつきボイス: 「JJY、夏時間は対応してるけど2時間ずらしは対応してないという😆

⚓サーバーレスアプリのアーキテクチャとパターン(Serverless Statusより)


つっつきボイス: 「Microsoftのドキュメント、例によって最近まとまりがいいですね😋」「日本語版もあります↓」

参考: サーバーレス アプリ: アーキテクチャ、パターン、および Azure の実装 | Microsoft Docs

⚓Google App Engineの次世代ランタイムとPython 3.7

Introducing App Engine Second Generation runtimes and Python 3.7 | Google Cloud Blog

よし、これでpython2を捨てれる

2018/08/09 12:23


つっつきボイス: 「個人的におおっと思ったので: 以前のApp EngineライブラリはPython 2系で、機械学習系ライブラリは3系が多かったので、自分のMacbookでpyenvで切り替えないといけなくて何かと面倒でした」「今までPython 3系使えなかったとは😲」「GoogleってPythonの偉い人がいるはずなんだけどなー: でももしかするとその人が2系を守ってたとか、2系に寄せたチューニングをやりすぎてたとかだったりして🤣」「🤣

⚓モバイル/Android/iOS

⚓Flutter対React Native(Mobile Dev Weeklyより)


同記事より


つっつきボイス: 「Flutterの方が前からあったというのが意外でした」「まあ検索ボリュームって意外と当てになりませんけどねっ😎: みんなが当たり前に使うようになるとかえって検索数減ったりとか」「😆

⚓SQL

⚓サイボウズさんのMySQLのパフォーマンスチューニング


つっつきボイス: 「お、はてブでも上がってましたねこの記事: JOIN禁止とか書いてないところがエライ😆」「😆」「JOIN禁止ってソシャゲ界だとありがちなんですよね」「そんなレベルでパフォーマンスを気にしてるんですかね?」「ソシャゲだとシャーディングしてたりするから」「あー😲

ハンズオン: PostgreSQLシャーディング(翻訳)

「ちなみに昔のMySQLはJOINが遅かったし、今でもサブクエリは遅いという😆」「それでもMySQLのサブクエリは以前の異常な遅さよりはずいぶんマシになったというか順当な遅さ(😆)にとどまるようになったみたいですね」「以前があまりに遅すぎた🐢」「そんなに!?」「サブクエリをWHEREで書き直すだけで1000倍以上速くなったりしてましたし🏁: おかげでサブクエリをWHEREで書き直す方法を覚えたけど、今度はPostgreSQLでサブクエリ使うのを最初ためらっちゃいましたね🤣」「サブクエリの方が読みやすいのは確か」「ほんに」

参考: 漢(オトコ)のコンピュータ道: なぜMySQLのサブクエリは遅いのか。 — 2009年の記事です

⚓AWS AuroraとAWS RDSの使い分け(DB Weeklyより)


percona.comより


つっつきボイス: 「これはPerconaさんの記事ですね」「Perconaは元々MySQLのチューニングツールを出してた会社ですが、その後MariaDBのカスタマイズ版のPerconaDBを出したりしてます」

参考: MariaDB - Wikipedia
参考: What is Percona DB | InMotion Hosting

「AuroraがPostgreSQLでしたっけ?」「いや、Auroraは元々MySQL互換で、その後for PostgreSQLが出た: まだベータだったかな?と思ったらもうベータは取れてるのか」「でRDSが…」「MySQL、PostgreSQL、Microsoft SQL Server、MariaDB、Oracle、それとAuroraですね」「そういう構成だったんですね😃

「ただAuroraはノード数がある程度必要なので結構お金かかります💰」「スケーラビリティとか凄そうですもんね」「あとAuroraが面白いのがストレージを使えば使うほど速くなるところ😆」「😆」「どういう仕組みなんでしょう?」「分散がデータブロック単位になっていて、データ量が増えるとデータブロックも分散して並列化が進むので速くなる」「おほー😲」「なのでデータ量が増えても指数関数的に遅くならないという」「すげー」

「Auroraのコアって実は分散ファイルシステムになっていて、他にも書き込みの非同期ネゴシエーションで『n個以上のノードからACKが返ってきたら書き込み成功と見なして進む』(Asynchronous group commits)ようになってるとかすごく頑張ってる、という解説を2、3年前のAWSサミットで聞いてしみじみ感心しました」「😃」「それをMySQLとかPostgreSQLとかと別のところでここまで追い込んでいるのが凄すぎ」「昔は研究レベルだったことが今こうやって使えますからねー☺

参考: Amazon Aurora(MySQL、PostgreSQL 互換のリレーショナルデータベース)|AWS
参考: 無料でデータベースを構築する

⚓TimescaleDBとInfluxDBを比較(Postgres Weeklyより)


同記事より


つっつきボイス: 「InfluxDBはNoSQL系だそうです」「Fluxってどこかで聞いた…あ、時系列データベースか」「Timescale社のブログなので最後はSQLに軍配を上げつつ自社のTimescaleDBを推してます☺


influxdata.comより

参考: Flux | Application Architecture for Building User Interfaces
参考: Influx Query Language (InfluxQL) reference | InfluxData Documentation


timescale.comより

⚓JavaScript

⚓Reduxの「Reselect」ライブラリでメモ化

JavaScript: Reduxが必要なとき/不要なとき(翻訳)

⚓React/Preact/VueでサーバーサイドレンダリングのXSSを修正(JSer.infoより)


つっつきボイス: 「Preactって名前だけどこかで見たんですけど何でしたっけ?」「リポジトリにFast 3kB React alternativeって書いてあるし」「速いReact」「もしかしたらFacebookのライセンスを気にしなくていいとか?」

後で「Facebook、ReactをMITライセンスに変更」という記事を見つけました。


github.com/developit/preactより

参考: Using Preact as a React Alternative — SitePoint

⚓CSS/HTML/フロントエンド/テスト

⚓EV証明書

Firefoxを自分でビルドしてオレオレEV証明書を使えるようにしてみたそうです。「再定義できない世界になりつつあるネットの新秩序 - 雑種路線でいこう」という記事で知りました。

⚓JavaScriptの「Reactivity」を理解する(Frontend Weeklyより)


同記事より


つっつきボイス: 「Reactivity: また新しいものが」「DOM構造的にreactできるかどうかという話かな?」「時間ないので次へー」

⚓言語よろずの間

⚓Inko: RubyでコンパイルしてRust VMが走る言語

GobyのメンバーにもInkoに興味津々の人がいました。

⚓chart: 標準出力からいきなりグラフを生成(Golang Weeklyより)

Go言語で書かれています。早速インストールしました

# 同リポジトリより
history | awk '{print $2}' | chart


同リポジトリより


つっつきボイス: 「みんな一度はこういうの作る☺」「☺

⚓その他

⚓G.ワインバーグ氏死去

↓だいぶ前にこの本で知りました。

参考: ジェラルド・ワインバーグ - Wikipedia — ワインバーグの法則がどっちゃり載っていてちょっとびっくりしました。

⚓名著復活の兆し?

少し違いますがこんな記事も↓。


つっつきボイス: 「売れない理工学書はそもそも売れないから運営資金が続かないという問題がつきまといますね…」「😭

参考: 科学論文の海賊版「Sci-Hub」を違法と訴える巨大出版社に対して根本的法改正の必要性を訴える科学者という構図 - GIGAZINE

⚓【書評】SOFT SKILLS


つっつきボイス: 「SOFT SKILLSはどうやら評価が両極端に分かれてますね」「身体を鍛える話とか載ってるみたい」「このあたりは人に言われてやるのとセルフモチベーテッドでは相当違ってきそう🤔

⚓技術書オンリーイベント第5回が10/08(月)開催決定


つっつきボイス: 「今度こそ行きたいと思って😤

⚓番外

⚓Dimensions: 数理を動画で学ぶ

フランスで制作された、割と前からあるCreative Commonsベースのコンテンツで、個人的に好きです。最初に見たのはニコ動でした。


つっつきボイス: 「上の日本語版動画は東大のサイトにあります」「おwフォーマットがRealVideoとかWMVとか😆」「かなり歴史を感じるフォーマット📜

参考: RealVideo - Wikipedia
参考: Windows Media Video - Wikipedia


今回は以上です。

バックナンバー(2018年度後半)

週刊Railsウォッチ(20180806)Rails 5.2.1.rc1リリース、Railsガイド日本語版が5.1に対応、Regexp#match?ほか

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

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

Rails公式ニュース

Ruby Weekly

Hacklines

Hacklines

Postgres Weekly

postgres_weekly_banner

DB Weekly

db_weekly_banner

Serverless Status

serverless_status_banner

Mobile Dev Weekly

mobile_dev_weekly_banner

Frontend Weekly

frontendweekly_banner_captured

JSer.info

jser.info_logo_captured

Golang Weekly

golangweekly_logo_captured


RailsのパフォーマンスをPrometheusで測定する(翻訳)

$
0
0

概要

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

RailsのパフォーマンスをPrometheusで測定する(翻訳)

Discourseで行っているRailsのinstrumentation(測定)方法を皆さまにご紹介します。


私をフォローいただいている方は以下のようなグラフが投稿されているのをたまに目にします。

rails_performance_graph_captured

この種のinstrumentationとグラフ化をNewRelicSkylightに任せていることがよくありますが、私たちほどの規模になると、instrumentationやグラフ化や監視をローカルで行うことには絶大なメリットがあります。私たちはホスティングをビジネスとして行っており、ホスティングは私たちの業務の中心を占める重要なものだからです。

測定値の収集や警告の通知の分野では、ここ数年Prometheusが主要な選択肢として台頭しています。その一方、Railsを使っている人々はみな大変な苦労をして測定値を抽出しています。

公式のRuby向けPrometheusクライアントのIssue #9は3年前にオープンされましたが、当分「solved」になる見込みはなさそうです。

このissueの根本的な問題は、GraphiteStatsdが測定値のプルを概念の中心に置いているのは逆に、Prometheusが測定値のプッシュを中心に置いていることです。

これはつまり、公開したいすべての測定値を集める単一のHTTPエンドポイントを自分で提供しなければならないということです。このため、UnicornやPumaやPassengerのように複数のプロセスにforkして使うことの多いWebサーバーでは特に複雑になってしまいます。セキュアな/metricsエンドポイントを単純にアプリに実装すると、forkしたプロセスのどれがリクエストを受け付けるのかをまったく保証できなくなってしまいます。「cross fork」で集約しなければ、単一プロセスの測定値をランダムに得ることしかできません。これはいくらなんでも不便です。

しかもPrometheusでは、どの測定値をどのようにコレクションすべきかを調べるのが難しく、自分が欲しい設定が判明するまで軽く数週間はかかることもあります。

この重大な問題をDiscourseで解決できたので、私はしばらく時間を使ってこれをパターンとして取り出しました。

prometheus_exporterを導入する

必要な機能は、Discourseのprometheus_exporter gemですべて手に入ります。

  1. 拡張可能な「コレクタ」を用いて、1台のコンピュータ上のマルチプロセスについても測定値をシングルプロセスで集約できる。
  2. ゲージ、カウンタ、サマリーの測定を実装している。
  3. デフォルトのinstrumentationをアプリに簡単に追加できる。
  4. forkしたプロセスとマスターコレクタの間のトランスポートチャンネルが非常に高効率かつ頑健。マスターコレクタがHTTP経由で測定値を収集するが、チャンクトエンコーディングでオーバーヘッドを低減したことで単一セッションで極めて膨大な測定値を収集できる。
  5. 測定値は専用ポート経由でPrometheusに公開されるので、HTTPエンドポイントを節約できる。
  6. 完全に拡張可能であり、すべての機能を使うことも、用途に応じてごく一部の機能だけを使うこともできる。

Railsアプリでの最小限の測定の実装例

Gemfileに以下を追加します。

gem 'prometheus_exporter'
# config/initializers/prometheus.rb
if Rails.env != "test"
  require 'prometheus_exporter/middleware'

  # HTTPステータスやタイミングなどをリクエストごとに統計をレポートする
  Rails.application.middleware.unshift PrometheusExporter::Middleware
end

この時点でWebのinstrumentationが行われ、あらゆるリクエストでSQL/Redis/Total timeがトラッキングされます(PostgreSQLを使うことが前提)。

rails_99th_percentile_captured

プロセスごとの統計も、たとえば次のように見ることができます。

rails_object_allocation_rate_captured

rails_ruby_heap_stats_captured

# config/initializers/prometheus.rb
if Rails.env != "test"
  require 'prometheus_exporter/instrumentation'

  # RSSやGC情報などの基本的なプロセス統計をレポートする
  # type: "master"はマスタープロセスを測定していることを示す
  PrometheusExporter::Instrumentation::Process.start(type: "master")
end
# Unicorn/Puma/Passengerでは、fork後に必ず新しいプロセスinstrumenterを実行すること
after_fork do
  require 'prometheus_exporter/instrumentation'
  PrometheusExporter::Instrumentation::Process.start(type:"web")
end

Sidekiqの統計を見たければ次のように表示できます。

image

Sidekiq.configure_server do |config|
   config.server_middleware do |chain|
      require 'prometheus_exporter/instrumentation'
      chain.add PrometheusExporter::Instrumentation::Sidekiq
   end
end

最後に、全プロセスに渡るグローバル統計もコレクションできます。

image

そのためには「type collector」を導入します。

# lib/global_type_collector.rb
unless defined? Rails
  require File.expand_path("../../config/environment", __FILE__)
end

require 'raindrops'

class GlobalPrometheusCollector < PrometheusExporter::Server::TypeCollector
  include PrometheusExporter::Metric

  def initialize
    @web_queued = Gauge.new("web_queued", "Number of queued web requests")
    @web_active = Gauge.new("web_active", "Number of active web requests")
  end

  def type
    "app_global"
  end

  def observe(obj)
    # 何もしない(測定値がアプリから転送されているかどうかのチェックにのみ利用)
  end

  def metrics
    path = "/var/www/my_app/tmp/sockets/unicorn.sock"
    info = Raindrops::Linux.unix_listener_stats([path])[path]
    @web_active.observe(info.active)
    @web_queued.observe(info.queued)

    [
      @web_queued,
      @web_active
    ]
  end
end

以上の準備が整ったら、(productionで監視するプロセス内で)コレクタを実行する必要があります。runitsupervisordsystemdなどどれでも使えます。ちなみに私のはrunitです。

bundle exec prometheus_exporter -t /var/www/my_app/lib/global_app_collector.rb

続いてさまざまなオンラインガイドを参考にPrometheusと、かの有能なGrafanaをセットアップすれば、素敵なグラフを表示できます。

参考までに本記事では、とある社内アプリで私が測定した(Gist)生測定値のフィードの様子の一部をサンプルとして使いました。

本記事がお役に立てば幸いです。皆さまのinstrumentationが成功しますように。

関連記事

PrometheusでDockerホスト + コンテナを監視してみた

Webアプリの基礎とさまざまな実行環境を理解する#2(社内勉強会)

$
0
0

BPS社内勉強会のmorimorihogeさんのスライド「Webアプリの基礎とさまざまな実行環境を理解する」の第2回です。当初2分割の予定を3分割に変更しました。

2. Webサーバの役割

Webサーバの役割を一言で言うと「HTTPリクエストにHTTPレスポンスを返すソフトウェア」です。

一般には以下のWebサーバが知られています。

サーバ 説明
Nginx Apacheに比べて軽く、大量のリクエストを捌けるということでここ数年でシェアを伸ばしているWebサーバ
Apache 長い歴史を持つ世の中のWebサーバの実質的なスタンダード
IIS Windows Serverに付属のWebサーバ。ASP.NET等のMicrosoftプロダクトと多分相性がいいはず

この他にも特定機能に特化したさまざまなWebサーバがあります。なおNginxは「エンジンエックス」と読みます(公式FAQ参照)。

「特定機能に特化したWebサーバ」の例としてはリバースプロキシがあります。Nginxはリバースプロキシとしても使えますし、逆にプロキシサーバとして有名なSquidも実はWebサーバの一種ということになります。

WebサーバでサポートするHTTPバージョンはソフトウェアとバージョンによって異なりますが、当該ソフトでサポートするHTTPバージョンの仕様上「必須(MUST)」とされている機能は必ずそのWebサーバに実装されています。これができていないWebサーバはそのプロトコルバージョンに対応していると謳ってはいけないことになっているからです。

3. Webサービスの代表的な形態

Webサービスにはさまざまな形態があり、以下の最初の4つの基本構成が代表的です。複雑なWebサービスも、何らかの形でこれらの基本構成を元にしています。

morimorihoge注)

なお、ここで話す「Webサーバー」が意味する機能的な役割は、アプリケーションサーバーとしてのプログラム実行機能は含まず、一般的なファイル配信やリバースプロキシ機能程度までを処理する範囲までとしています。

※細かい定義をすると色々あれ?と思うところがあると思いますが、説明のための簡略化だということで目を瞑って下さい>詳しい人

主要な4つの構成について以下で詳しく説明します。「Webサーバ単独系」「Webサーバ+CGI系」「アプリケーションサーバ系」の3つ(I〜III)と、合わせ技としてRailsに代表される「Webサーバ+アプリケーションサーバ」(IV: 次回)の順になります。

I. Webサーバ単独系

静的なコンテンツを配信するだけの最も単純な形式のサービスですが、パフォーマンス面とセキュリティ面で有利なので、要件に合えば現在でも非常に有用な形態です

コーポレートサイトや静的な情報系サイト、あるいはコンテンツをFTPやSCPなど手動だけで更新しているサイトもこれに該当します。

メリット

  • サーバ処理が軽い: サーバーの役割はファイルを返すだけで良いので、CPU/メモリ負荷は小さい(ネットワーク帯域は必要)
  • キャッシュが効く: 同じコンテンツへのリクエストであれば、どのクライアントにも毎回同じレスポンスを返せるのでキャッシュヒットしやすい
  • セキュリティ上のリスクが非常に低い: サーバ側でアプリケーションロジックを動かす必要がないため、XSSOSコマンドインジェクションなどの代表的なWebサーバーに対する攻撃が無効(リスクがあるとすれば古いファイルの消し忘れぐらいか)

デメリット

  • 動的な要素を自前で用意できない: Google Formなどの外部サービスを埋め込めば申込みフォームぐらいは作れるが、自由度は低め
  • 毎回FTPなどで人力更新が必要: ちょっとしたコンテンツ更新の度に手間がかかってしまう(ブログシステムのようなやり方はできない)。※ビルド・アップロード自動化などを組み合わせればいちおう自動化は可能だが、システム運用は複雑になる

実際のサービス例

使いどころ

  • 静的なファイルを不特定多数に配信したいとき
  • とにかく安く配信したいとき
  • サーバサイドセキュリティ対策そのものから解放されたいとき
morimorihoge注)

なお、昨今サーバーサイドエンジニア不要説を唱える過激派フロントエンジニア界隈では、動的処理は全て外部サービスを利用して実装し、自前でホスティングするWebサーバーは完全に静的なコンテンツを配信するWebサーバーとする構成も出てきています。
ブラウザ側でできることが高機能化してきた昨今ではまだまだ増えていく界隈だと思いますので、コンテンツを静的配信する=完全に静的なサービスとは限らない(JavaScript+外部サービスを使って動的に見える)ことは頭に置いておくと良いでしょう。

II. Webサーバ+CGI系

CGI(Common Gateway Inteerface)という言葉そのものはあまり見かけなくなりましたが、CGI系Webサーバは実は今も昔も動的なWebサービスの主流です。というのも、世界のWebサイトの1/4を占めるとまで言われるWordPressもこれに該当するからです。

CGIはWebサーバがプログラムを実行した結果を返すしくみで、ざっくり「PHPやPerlのスクリプトファイルを置いたら動くやつ」ぐらいに思っていればだいたい間違いではありません。ただし、本来の意味でのCGIを使っているものは少数で、今現在の多くのWebサーバでは言語別のサーバモジュールを使っています(パフォーマンス上有利なため)。

この手のWebサーバには上述のApacheやNginxが一般に使われますが、たまにIISなども見ないことはないです。

CGI方式は動的Webページの基本であり主流なので、Web開発者はまずはここから触ることが多いのではないでしょうか。

メリット

  • 導入が簡単: 既に動かしているWebサーバにモジュールを組み込むだけで良いため、設定ファイルの更新やパッケージの追加だけで導入できる
  • 実績もノウハウも多い: 歴史が長く、使われているサービスも多い
  • プログラムの更新が簡単: ファイルを置くだけで更新できる(さくらの共有サーバ等の共有型Webサーバーでも運用できる)

デメリット

  • セキュリティ上注意が必要: 呼び出されたプログラムがWebサーバの権限で実行されるため、セキュリティ・権限管理が甘いと見られてはいけないファイルが漏洩する可能性がある
  • プログラムが常駐しない: Webサーバからのプログラム呼び出しはあくまでステートレスであり、リクエストのたびにアプリケーションプログラムが終了する(※モジュール型の場合には実際には準備されたWorkerを使い回すケースがありますが、実行するプログラムの内部コンテキスト上はリクエストの都度破棄されると考えた方が良い)

実際のサービス例

  • レンタルサーバ(さくらのレンタルサーバなど多数あります)

こうしたレンタルサーバでは、1台のWebサーバを複数ユーザーで共有する「共有型サービス」が一般的です。共有型サービスでは特定の操作だけが許可されているのが普通なので、sshで接続はできてもroot権限の必要な操作は行なえません。
また、サーバー側や利用者側のファイルなどの権限設定が適切に行われていないと、同居している他のユーザーにデータを抜かれる可能性もゼロではありません(適当に権限を777(誰でも読み書き可能)とかにしていると今でもたまに見る)。

使いどころ

  • PHPやPerlなどのスクリプト言語アプリを手軽に使いたいとき
  • インフラエンジニアの手を借りれるアテがないとき
  • OSやソフトウェアのアップグレードをやりたくないとき

レンタルサーバはWebサーバを専有できないので、その分さまざまな制限が生じます。一般的な使い方ならほぼ問題ありませんが、特殊な使い方はできないこともあるので注意しましょう。

morimorihoge注)

世の中の多くのサービスはこの形式で動作しています。WordPressその他のメジャーな「サーバーに置いたら動く」系のソフトウェアは大抵この形式で動作するので、Webサイトの実数上のシェアもかなりの割合を占めていると思います。
また、もちろん自前でサーバーを立てる場合もPHPやPerlのプログラムではこの形式を選ぶことがほとんどですので、業務アプリケーションでは使えないということも全くないです。

一方、レンタルサーバーなどを利用する場合には「インフラはお任せ」と思いがちですが、実はレンタルサーバー業者によって細かい設定(例えばphp.iniなど)はかなり異なりますので、レンタルサーバー間でプログラムを移設する場合なんかにはそれまで「おまかせ」だと思っていたものにも気を配らないといけないことがあります。
例えば、さくらのレンタルサーバーではSNIを使ってマルチサイトTLS対応した場合にX-Sakura-Forwarded-Forというさくらのレンタルサーバー独自ヘッダに本来のホスト名が入ります。こういった落とし穴は探せば解決策は見つかりますが、落とし穴がある可能性は考慮しておかないとトラブル対処でスケジュールに影響が出たりするので注意ですね。

参考: X-Sakura-Forwarded-Forの検索結果

III. アプリケーションサーバ系

汎用的なWebサーバソフトを使わず、Webアプリケーション専用のサーバプログラムが直接リクエストに応答します。このタイプはアプリケーションサーバが何もかも引き受けるので、ApacheやNginxなどのWebサーバを持ちません

Java serverで構築されるサービスはたいていこれに該当します。Tomcatなどのサービスが有名です。

今なら、Railsのミドルウェアとして使われているRackを単体で使ったWebアプリもこれに相当します(Rackは単体でもWebサービスを構築できます)。ちなみにHerokuではRackアプリもホスティングできます。
※が、一般的には次回IVで紹介するようにRailsはNginxと組み合わせて使われることが多いです。

メリット

  • TCPレベルで通信を細かく制御できる: クライアントからのHTTPリクエストをアプリケーションサーバが直接受信して処理できる
    • 例: 動画配信でレート制御するといったことも可能(Webサーバがあると邪魔になる)
  • サーバプロセスがステートを持てる: HTTPはステートレスだが、アプリケーションサーバプロセスは起動後常駐するため、プロセスのメモリ状態を複数のリクエスト間で共有できる
    • ステートを持つことで、メモリ上のキャッシュなどのリソースを有効利用できる
  • 高速: アプリケーションで必要なデータがメモリに載った状態でリクエストを受けられる
    • CGIは呼ばれるまで起動しないし、終わったらメモリを解放してしまうので遅い

デメリット

  • アプリケーションサーバが肥大化する: あらゆるHTTP通信を処理するため、アプリケーションサーバがWebサーバと同じ機能を持たないといけない
    • SSL/TLSに対応するにはその機能をアプリケーションサーバが実装しなければならない
    • Apacheなどから移行する場合に設定ファイルを再利用できず、アプリケーションに合わせてやり直さなければならない
  • メモリ効率が悪い: 常駐するサーバはリクエストがなくてもメモリを消費してしまう
  • 静的ファイルの効率が悪い: 本来プログラム処理が不要な静的ファイルもアプリケーションサーバが扱わなければならないので、高速でないとサイト全体が重くなる
    • サーバが遅くなると静的な利用規約まで読めなくなったりとか
morimorihoge注)

現代では、ほとんどのアプリケーションサーバー型のシステムは次回説明するIVの形式を利用していると思います。ただ、例えばRailsアプリケーションをローカル環境でrails sしてlocalhost:3000にアクセスしている時なんかはこの形式になります。

また、メリットにも書いたように細かいレート制御を行いたい場合(例えばTCPの生SocketのAPIを叩きたい)にはこの形式を用いる必要があります。WebSocketやUDPを用いた高い応答性やレート制御が求められるアプリケーション開発の場合には、間にWebサーバが入ってしまうとリクエストがバッファリングされて困るというケースはあるので、アーキテクチャの一つとして理解はしておくと良いと思います。

(次回: #3 Webサービスの代表的な形態IV(Rails)、ステートレスとステートフルに続く)

関連記事

Rails: Puma/Unicorn/Passengerの効率を最大化する設定(翻訳)

【社内勉強会】バージョン管理の重要性とGitの運用について

Webアプリの基礎とさまざまな実行環境を理解する#3(社内勉強会)

$
0
0

BPS社内勉強会のmorimorihogeさんのスライド「Webアプリの基礎とさまざまな実行環境を理解する」の第3回です。

IV. Webサーバ+アプリケーションサーバ

いよいよRailsにつながる話になります。

これは前回のI.とIII.のハイブリッドと言えます。ハイブリッドすなわち「いいとこ取り」で、静的なファイルやHTTPの処理はWebサーバに任せ、プログラム処理が必要なリクエストだけをアプリケーションサーバに送ることでI.とIII.のメリットを同時に得ることができます。

現在最も一般的なRails環境はこれに該当します。Java serverでもこの形式が使われることはありました(Apache + Tomcatなど)が、Railsの登場で一気に普及した感があります。

現在のBPSでは「Nginx + Puma」か「Apache + Puma」が標準のRails実行環境です。Webサーバは以前はUnicornでしたが今はPumaが標準になりました。

メリット

  • Webサーバの軽さアプリケーションサーバの柔軟性を同時に得られる
  • アプリケーションの実装を軽減できる: WebサーバでできることはWebサーバに任せる
  • レスポンスが速い: III.のアプリケーションサーバ単体構成と比べ、静的ファイルはアプリケーションサーバに問い合わせずすぐに返せるため、一般に高速になります

デメリット

  • 設定が増える: 運用するサーバプログラムが2つになるため、2つの設定を管理する必要がある
  • 監視ポイントが増える: 障害発生のポイントが増えるため

Webサーバ+アプリケーションサーバの実際のサービス例(専門サービスの場合)

以下はRailsを前提とした話です。

  • Heroku: IIIの形式でも公開できますが、Nginxを組み合わせて運用することも可能です
  • 他にもいくつか

HerokuはRailsアプリケーションのホスティングサービスの典型です。Heroku独自の設定やルールがある点に注意しましょう。

以前はニフティクラウドC4SAもRailsアプリのホスティングサービスを行っていましたが、現在はサービス終了しています。

使いどころ

  • Railsサーバを立てたいけどインフラエンジニアがいないとき
  • とにかく(エンジニア人件費的に)安く(リリース準備的に)早く立てたいとき
  • Linuxサーバー側のセキュリティを考えずに済ませたいとき

ただし、Rails本体や組み合わせるミドルウェアのセキュリティ問題については当然考慮が必要です。

Webサーバ+アプリケーションサーバの実際のサービス例(自前で立てる場合)

マネージドなサービスではなく自前でサーバーを構築することで、サーバー要件に対する自由度が高くなります
「普通のWebアプリケーションを」「普通の構成で」「普通の作り方で」実装する場合にはマネージドなサービスを組み合わせることで事足りることが多いのですが、業務アプリケーションなどで高可用性、高性能、特別なライブラリを使いたいなどの事情がある場合には自前でサーバーを構築するという選択肢が結果として簡単になるケースが多いです。

morimorihoge注)

例えば以下の様な要件がある(または将来的に要求される可能性がある)場合、PaaS型のマネージドサービスよりも自前でLinuxサーバーを管理できる環境の方が作りやすいことがあります。

  • 特定のソフトウェアを用意して使いたい場合
    • ImageMagickでの画像変換やffmpegでの動画変換などで新し目の機能・データフォーマットに対応したい場合、PaaSで用意されたものではかゆいところに手が届かないため、自前でコンパイルして使いたいということがあります
    • プロプライエタリなソフトウェアやライブラリを利用したい場合、任意のバイナリを置けないPaaSでは利用できないことがあります
  • 長時間のバッチ処理や大きな一時データを必要とする処理がある場合
    • PaaS型サービスではサービス側でプログラムの実行時間に制限が付けられているケースがあり、超時間かかるバッチ処理などを知らずに作るとタイムアウトして中断されてしまうことがあります。バッチを小分けにしてQueueに入れて実行するなどで対策は可能ですが、実装時に考慮する一手間がかかりますし、何より根本的に大きなトランザクションのバッチは分割できません(そして業務アプリケーションではこのような要件がままあります)
    • PaaS型サービスはアプリケーションにあまり大きなファイル作成を許していないケースが多いです(大きなファイルを格納する場合にはAWS S3等のオブジェクトストレージを使うことが推奨される)。しかし、例えば動画をアップロードしてオーサリングする様なアプリケーションなどではアプリケーションサーバ側に大きな一時ファイルを置きたいというニーズがありえます
  • 特定のクラウドサービスにロックインされるリスクを嫌う場合
    • マネージドなサービスを提供するPaaSを利用する場合、当該サービスの運営状況やアップグレードに振り回される可能性があります。採用当時は割安だったサービスが突然値上げしたり、API仕様が変更になりプログラム側の修正が必要になったりするリスクは常にあります。自前でLinuxサーバーを運用する想定の環境であれば、そうしたリスクは最小限にすることが可能です。自前で管理するコストは増えますが、予測&コントロール可能であるということは大事なポイントです。
    • PaaSを「きちんと」使いたい場合にはそのPaaSの仕様や運用に詳しいエンジニアが必要になります。PaaSは正常系の利用についてはとても便利で誰でも使えるように作られていますが、異常系については当該PaaSに対するある程度深い知識がないとそもそも原因箇所自体が思い当たらなかったり、原因特定しても対策方法が分からなかったりします。ベストエフォート型のアプリケーションであれば問題になることは少ないのですが、重要なデータを取り扱うアプリケーションなどでは異常系の洗い出しや対策が必要になるため自前サーバーの方が全てを掌握できる分安心、というケースはあります。

この辺りの感覚はエンジニアそれぞれによってさじ加減が変わってくる部分ですので、一つの正解があるというよりも、様々な視点でそれぞれ最適な戦略があり、その中でどの戦略がより良いのかを考えていくことになりますね。

  • AWS EC2
  • さくらのVPS
  • その他「VPS」「専有」などの名がつくVPS型ホスティングサービス
morimorihoge注)

VPSはVirtual Private Serverの略で、「物理ホストは多重化されている」が、VM技術等を利用して「論理ホストとしては完全に管理権限の取れる」サーバーが提供される環境のことです。

BPSでは現在はお客様側からの指定が無い限り、AWS EC2を利用しています。

Webサービスで押さえておくべきその他の要素

こうした要素が加わると考慮すべき点が変わってきます。詳しくは今後の勉強会で。

まとめ

基本的に、どのようなWebサービスでもここで紹介した構成(またはそのサブセット)で構成されています。これらの基本構成を理解しておくことで、より複雑なWebサービス構成の把握に役立ちます。

Web環境構築は現在は主にインフラエンジニアの仕事ではありますが、自分の書いたプログラムがどのような環境で動作しているかを知ることは、トラブルシューティングはもちろん、設計にも役立ちます。

補講: ステートレスとステートフル

複数回の連続した通信を行う場合において「ステートレス」「ステートフル」という二種類の戦略があります。
ステート(state)は日本語では「状態」を意味し、状態を保存するか、という話題です。

ステートレス(stateless)

最大の特徴は「1つ1つの往復通信が独立・完結している」ことです。たとえばリクエストA => リクエストB => リクエストCの順にHTTPでの通信を行ったとすると、AとBとCの通信の間には何のつながりもありません。これを指して「状態がない」すなわちステートレスと呼びます。以下はステートレスなプロトコルの例です。

  • IPプロトコル
  • HTTP
  • memcachedプロトコル
morimorihoge注)

「いやWebサイトはログイン状態をリクエスト間で保持したりするじゃないか」という人はスルドイです。

HTTPにはプロトコル上ステートを維持するための機能はありません。多くのサイトではCookieに保存したセッションIDを使ってログイン状態を維持しますが、あれはHTTPに用意されたCookie機能を使うことでステートレスプロトコル上でステートフルな通信をアプリケーション層で実現しているのです。

ステートフル(stateful)

複数回行われる通信を1つのまとまりとして扱う」のが特徴です。たとえばA => B => Cの順に通信を行ったとすると、AとBとCの通信に関連や順序・状態をもたせることができます。
例えば「初期化 -> データ転送 -> 終了確認」という3回のメッセージに分けて順番に通信を行うステートフルプロトコルでは「初期化」せずに「データ転送」を行おうとすると「データ転送」の処理に失敗します。
これは、サーバー側で「初期化」したかどうかという状態を持っており、初期化されていないデータ通信は拒否するという状態に応じた処理が行われているわけです。

以下はステートフルなプロトコルの例です。

  • TCPプロトコル
  • 決済APIなど(「トランザクション開始」「与信」「売上」などを順番に呼び出す必要がある)

ステートレスとステートフルの使い分け

実用上はステートフルプロトコルの方が以前の状態を前提とした通信ができるため、通信上は効率が良くなります。しかし、ステートフルプロトコルは一般に「状態」を保持するためにサーバー側のリソースを消費します
サーバー側のリソースを消費するということはサーバー1台辺りで処理できるリクエスト数が減るということになるので、たくさんのリクエストを捌くという観点から見ると都合が良くありません。
そのため、不特定多数のリクエストを受け付けるWebで使うHTTPはステートレスプロトコルが選ばれています。

例えば、仮に100万人のユーザーがいるWebサーバへの通信がステートフルだとすると、Webサーバは100万人分のステートを保持し続けないといけなくなり、スケールしなくなってしまいます。ステートレスであれば、サーバは通信が終わったらその通信のことを忘れてもよいので、サーバの処理がずっと軽くなります。

なお、ステートフルプロトコルにおいてステートをどのように管理するのかは、プログラムの設計においても重要なポイントのひとつになります。

参考: TCPの状態遷移図(PDF)


TCPの状態遷移図(PDF)より

関連記事

Rails: Puma/Unicorn/Passengerの効率を最大化する設定(翻訳)

【社内勉強会】バージョン管理の重要性とGitの運用について

週刊Railsウォッチ(20180820)Railsで構築されたサイト40選、Deviseはつらいよ、ARのスコープとクラスメソッドの使い分けほか

$
0
0

こんにちは、hachi8833です。Matzにっきを最初から読みふけってたら完璧に寝不足になってしまいました。


つっつきボイス:「お、例のMatzにっき、この間404になってなかったっけ?」「その後復旧してました: ここのtDiaryのナビゲーションがつらくて、最も最初の月にたどり着くのに何回もクリックしないといけなかったり😭」「😆」「最初が2003年なのでまだRailsも世に登場してなかった頃でした」

スクレイピングしてローカルで読もうかとも思いましたが負荷かけるのがコワイので、ブラウザで月ごとに気長にSave asし始めてます🐢

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを社内有志でつっついたときの会話です👄

⚓お知らせ: 第2回「週刊Railsウォッチ 公開つっつき会」開催

初回の8/2に続き、「週刊Railsウォッチ 公開つっつき会 第2回」を9月6日(木)19:30〜より開催いたします。詳しくは上のリンクでご覧ください。皆さまのご参加をお待ちしております🙇🙇

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

⚓ActiveStorageのエラーハンドリングを改善

issue #33157の方がわかりやすいかも?

わかりにくかったエラー
NameError: Cannot load Rails.config.active_storage.service: wrong constant name diskService
#33157より

# activestorage/lib/active_storage/service/configurator.rb#L27
      def resolve(class_name)
-        require "active_storage/service/#{class_name.to_s.underscore}_service"
+        ActiveStorage::Service.const_get(:"#{class_name}Service")
+        ActiveStorage::Service.const_get(:"#{class_name.classify}Service")
+      rescue LoadError
+        raise "Missing service adapter for #{class_name.inspect}"
      end

つっつきボイス:「"#{class_name}Service""#{class_name.classify}Service"に…?🤔あー大文字小文字をクラス名のスタイルにするメソッドか」「言葉どおりの『分類』ではなかったんですね💦

参考: Ruby on Rails 5.2 / ActiveSupport::Inflector#classify — DevDocs

# devdocs.ioより
classify('ham_and_eggs') # => "HamAndEgg"
classify('posts')        # => "Post"

⚓キャッシュのフェッチでnilをスキップするようになった

# activesupport/lib/active_support/cache.rb#L701
         def save_block_result_to_cache(name, options)
           result = instrument(:generate, name, options) do
            yield(name)
          end
-           write(name, result, options)
+          write(name, result, options) unless result.nil? && options[:skip_nil]
          result
        end

つっつきボイス:「今までは結果がnilの場合もキャッシュに保存されて、キャッシュがexpireするまで残っちゃってたのか💡」「で、今後はskip_nil: trueというオプションを指定すれば、結果がnilの場合はキャッシュしないようになると👍: わかりみ」

追記:

従来のコードでは、例えば「最新データがキャッシュにあればキャッシュから拾い、なければDBを参照する」みたいな動作を期待している時に一度nilをキャッシュしてしまうと、DBに新しいデータが入ってもキャッシュがexpireするまでnilを返してしまう仕様でした。

ただ、これはキャッシュエンジンの仕様としてはあながち間違いではありません。DB側を更新する時に明示的に対応キーのキャッシュを飛ばす実装にすればそれでも正しく動くからです。

今回の変更の:skip_nil`オプションは、その辺のキャッシュ自動管理を多少サボれるようになります。一度作られたら変わらないデータみたいなものをキャッシュに載せる場合には手軽に使えて便利という感じです。

ただし楽になるのは新規追加の時だけで、updateの場合には結局手動でキャッシュ飛ばすコードが必要です。この設定が手動で必要なことを見落とすと、古いデータが更新されないというIssueが上がってくる所までがワンセットになりそうな予感がします。

⚓ネストした属性でextendしたときにbuildが呼ばれるように修正

これもissue #33389の方がわかりやすいかも。

# activerecord/lib/active_record/nested_attributes.rb#L503
            unless reject_new_record?(association_name, attributes)
-              association.build(attributes.except(*UNASSIGNABLE_KEYS))
+              association.reader.build(attributes.except(*UNASSIGNABLE_KEYS))
            end

つっつきボイス:「NestedAttributesは普段使わないなー: そこにも載っているaccepts_nested_attributes_forはいつ消えるんだろうって思ったり」「それ何でしたっけ?」「あ、これ実は要らないんじゃないかっていう説が前からあってですねー、典型的なパターンから外れてカスタマイズし始めると途端に破綻するヤツ」「へー!」「一応Railsの標準機能ではあるし、使われているのも見かけるんですが、たとえば順序を変えようとしたりするとすぐフォームが泥臭くなっちゃうんで自分はあまり好きでないです😩」「ストンとはまるところだけにしとけってことですね」「カスタマイズなしで作れる管理画面とか、後はSPAなんかでフォームを自分でビルドするときとかでしょうね😎

参考: #method-i-accepts_nested_attributes_for

# api.rubyonrails.orgより
class Member < ActiveRecord::Base
  has_one :avatar
  accepts_nested_attributes_for :avatar

  def avatar
    super || build_avatar(width: 200)
  end
end

member = Member.new
member.avatar_attributes = {icon: 'sad'}
member.avatar.width # => 200

参考: 複数の子レコードを作成・更新する. accepts_nested_attributes_for - Qiita

⚓Timeカラムの精度を改善

以下はReferences to changes in Rails 5.2から見繕いました。

# activemodel/lib/active_model/type/time.rb#L28
      private
         def cast_value(value)
-          return value unless value.is_a?(::String)
+          return apply_seconds_precision(value) unless value.is_a?(::String)
          return if value.empty?
-           if value.start_with?("2000-01-01")
-            dummy_time_value = value
-          else
-            dummy_time_value = "2000-01-01 #{value}"
-          end
+          dummy_time_value = value.sub(/\A(\d\d\d\d-\d\d-\d\d |)/, "2000-01-01 ")
           fast_string_to_time(dummy_time_value) || begin
            time_hash = ::Date._parse(dummy_time_value)
             return if time_hash[:hour].nil?
             new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
           end
         end

つっつきボイス:「precisionが指定できるようになったと」「quoted_なんちゃらのquotedって?」「そりゃ引用符""で囲む↓ってことでしょうね😆」「深読みしなくてよかったのか💦」「それにしてもActiveModelの中に2000-01-01みたいな日付がハードコードされてるのって何なんだろう?🤔テストコードならまだしも」

# activerecord/test/cases/adapters/sqlite3/quoting_test.rb#L58
+  def test_quoted_time_normalizes_date_qualified_time
+    value = ::Time.utc(2018, 3, 11, 12, 30, 0, 999999)
+    type = ActiveRecord::Type::Time.new
+     assert_equal "'2000-01-01 12:30:00.999999'", @conn.quote(type.serialize(value))
+  end

quoted company: 上場会社

⚓セッションやcookieでのto_hto_hashの挙動を揃えた

# actionpack/lib/action_dispatch/middleware/cookies.rb#L341
+      # Returns the cookies as Hash.
+      alias :to_hash :to_h
+

つっつきボイス:「to_hto_hashの挙動が違ってた?」「ActionDispatchのSession::Requestにはto_hashがあったけどto_hがなくて、Request:: CookieJarにはto_hがあったけどto_hashがなかった、ってことみたい」「ははぁ食い違ってたのか」「この記事↓ではto_hto_hashの違いを説明してたけど、それとは別の話だったか」「確かにセッションとcookieって近いところにあるからこれが違ってたらイヤだなー😤

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

⚓リレーションをmergeしたときにINNER JOINが変わらないようにした

# activerecord/lib/active_record/associations/join_dependency.rb#L107
-      def join_constraints(outer_joins, join_type)
+      def join_constraints(joins_to_add, join_type)
        joins = join_root.children.flat_map { |child|
          make_join_constraints(join_root, child, join_type)
        }
-         joins.concat outer_joins.flat_map { |oj|
+        joins.concat joins_to_add.flat_map { |oj|
          if join_root.match? oj.join_root
            walk join_root, oj.join_root
          else
            oj.join_root.children.flat_map { |child|
-              make_outer_joins oj.join_root, child
+              make_join_constraints(oj.join_root, child, join_type)
            }
          end
        }
       end

2016年11月のPRですが、マージされるまで半年以上かかってました。「INNER JOINが変わるのは仕様かも?」みたいな議論を経ています。

# 同PRより
Author.joins(:posts).merge(Post.joins(:comments))

# 期待
#=> SELECT ... INNER JOIN posts ON... LEFT OUTER JOIN comments ON...

実際
#=>SELECT ... INNER JOIN posts ON... INNER JOIN comments ON....

つっつきボイス:「確かに上の書き方ならINNER JOINにならないとおかしいのでわかるけど、元のが正規の挙動だと思ってやってた人いそう」「😅」「こういうのってto_sqlで確かめながら使うし、『こうやったらLEFT JOINできるのか!やったね💪』と思って使ってたらbreaking changeになったり」「これは完全にbreaking changesですね: マージに時間かかるのも無理ないし」

⚓Rails

⚓simple_calendar: Rails向けカレンダー


同サイトより


つっつきボイス:「最近kazzさんがカレンダーの設計と実装で頭抱えてたので」「ああーそういえば😆😆」「まーカレンダーは自力で実装するもんじゃないよなー😆」「表示はともかくモデルの設計で手こずってたようです」「カレンダーの中身をどこまで自由にさせるかで全然変わりますしね」「項目が増えたら...にするとかね😆

「このカレンダーはそこそこ使いやすそうっすね: このdoブロックをeach的に回してその中にやりたいことを書く↓みたいな」

<!-- 同記事より -->
<%= month_calendar do |date| %>
  <%= date %>
<% end %>

「それにしても、こういうのを効率よく書こうとすると現実にはたいていうまくいかないですよね🤣:カレンダーを全部ループで書くのはよくないかなってつい思っちゃうじゃないですか: 3重ループの中で同じメソッドを何回も呼ぶぐらいならプリレンダリングしようなんて思ってやると、後でハマるハマる🤣」「🤣」「なまじ効率よく書いちゃうと、月表示の他に週表示もやりたいとか、週始まりを月曜から日曜に変えたいみたいな要件が来たときにつぶしが利かなくなる🔨

「他にもBootstrapの縦分割が12だから、カレンダーを7日表示や5日表示にするときにつらいって言ってました」「それBootstrap 4ならできますよ: Bootstrap 3だとできないけどネ」「Bootstrap 3はFlexbox↓が使えないから🤣」「そういえばIE9縛りがあるとFlexboxが使えないからBootstrap 4が使えないんですよね😭

See the Pen Demo Flexbox 1 by Hugo Giraudel (@HugoGiraudel) on CodePen.

「Bootstrap 4だとどうやるんでしたっけ🤔」「こんなふうに数値を付けないcol-*↓を使えばFlexboxで均等割になってくれますね😋」「おほー😃」「カレンダーの7分割もこれでイケます💪

参考: Grid system · Bootstrap

<!-- getbootstrap.comより -->
<div class="container">
  <div class="row">
    <div class="col-sm">
      One of three columns
    </div>
    <div class="col-sm">
      One of three columns
    </div>
    <div class="col-sm">
      One of three columns
    </div>
  </div>
</div>

参考: Rails で簡単にカレンダーを扱える simple_calendar で週表示カレンダーのフォーム作成を試す - Qiita

⚓スコープよりクラスメソッドの方がよい場合

2015年の記事ですが。

# 同記事より
# app/models/review.rb にスコープで書いた場合
scope :created_since, ->(time) { where("reviews.created_at > ?", time) if time.present? }

# クラスメソッドで書いた場合
def self.created_since(time)
  if time.present?
    where("reviews.created_at > ?", time)
  else
    all
  end
end

つっつきボイス:「どのスコープの話かなと思ったらActiveRecordね: スコープかクラスメソッドかというのはよく言われる話で、たとえばこんな別記事もあるし↓」「これも2013年の記事だけど詳しそうですね」

参考: Active Record scopes vs class methods « Plataformatec Blog

「スコープにするならchainableでextensibleなものにすべきとあるし、かのRailsガイド様でも引数を付けるときにはクラスメソッドが推奨となってる↓」「ホントだ😳」「スコープに引数を付けるのはおすすめされてないっぽい: 実際複雑になるし」

Using a class method is the preferred way to accept arguments for scopes.
Active Record Query Interface — Ruby on Rails Guidesより

「自分はめっちゃ長くならない限り引数もあんまり気にしないでスコープで書いちゃいますけどね」「結局長くなってきたときが問題」「ただ、少なくともチェインの順序を変えると結果が変わるものはスコープにしない方がいい」「確かに」「『always chainable』っていうのはそういうことだと思う: たとえばWHEREしか使ってなければ大丈夫だけど、GROUPとかが使われているとおかしな結果になったり」「うんうん」「あとORDERが使われているスコープも、条件が複雑になったときにカオスになりやすいし」「それからスコープとクラスメソッドがどちらも使われている場合に、どういうのをどっちでやるかという方針はあったほうがいいですね🧐

⚓Google CalendarとRailsの連携

# developers.google.comより
def authorize
  client_id = Google::Auth::ClientId.from_file(CREDENTIALS_PATH)
  token_store = Google::Auth::Stores::FileTokenStore.new(file: TOKEN_PATH)
  authorizer = Google::Auth::UserAuthorizer.new(client_id, SCOPE, token_store)
  user_id = 'default'
  credentials = authorizer.get_credentials(user_id)
  if credentials.nil?
    url = authorizer.get_authorization_url(base_url: OOB_URI)
    puts 'Open the following URL in the browser and enter the ' \
         "resulting code after authorization:\n" + url
    code = gets
    credentials = authorizer.get_and_store_credentials_from_code(
      user_id: user_id, code: code, base_url: OOB_URI
    )
  end
  credentials
end

つっつきボイス:「これもカレンダー絡みで: だいたいGoogle公式に揃ってるみたいですね」「GoogleのAPIにはたいていRubyのインターフェイスもあるので、Spreadsheetとかも含めてこういうのはたいていできます、ただし」「お?」「Googleに限りませんが、APIで一番よくハマるのは上限値の制約に引っかかったとき⛔」「あー😲」「たとえば1回のリクエストで取れる件数に上限があったりとかですね」

「カレンダーなんか場合、データを1リクエストでガバっと取ってきてから処理するなら全然楽勝なんですが、ちゃんとやるなら100件単位とかでループで回してデータを取ってきたりしないといけない: それ自体はそんなに大変でもないんですけど、それが面倒でガバっと取ってやろうとするとこういう制約に引っかかったりすると」「はー😞」「そういえばAWS S3にもそういう上限ありますね: 1000件ぐらいだったかな?」「まあ外部APIを使おうとするとだいたいこういう制約がありますし」

「ループもeachでさくっと書けそうに見えたりしますが、この手の外部APIはループをeachで単純に回してデータを取ると失敗することがあります」「お?😳」「たとえばカレンダーなら、ある瞬間のorder by updated_at的なデータを取ってきてから次のデータをリクエストする間にデータが変更される可能性があるので、そのままだと一意の結果が保証されないんですね」「おぉー😲」「正しくやるためには、そのデータを取るときにその時点のスナップショットを表すクエリIDも返してもらって、そのクエリIDと一緒にページングやorderingを扱わないといけない」「そうそう😤」「でないとorder by IDでループを回している間にデータが挿入されて、レコードが重複してコケるとかね」

「GoogleのはクエリID取れるんでしょうか?」「どっちだったかな…🤔?」「S3だと上限に達したときにnextなんとかを返してくれた覚えが」「あ、Googleにもそういうnextなんちゃらがあったと思う: でもどれでやるにしてもこの処理は面倒😢」「面倒ですよねー😞」「上限に達するぐらいデータを入れておかないとその部分をデバッグできないし💦

⚓DataTablesをRailsで使う


datatables.netより


つっつきボイス:「jQuery向けということで同じようなのは既にいっぱいありますが、割と頑張ってるかなと思って」「この時代にjQueryで攻める勇者」「でもこのぐらいのテーブルならjQueryで十分じゃね?と思う: ↓こういう動作もfunctionを書けばちゃんと実装できるし、自分はこういうのキライじゃない❤


同サイトより

⚓Railsパフォーマンス最適化手法まとめ


同記事より


つっつきボイス:「割と基本というか常識的な話かな?」「プロバイダ選び、N+1、gem選び、バックグラウンドジョブ、CDN、インデックス…」「これほとんどRailsと関係ないし😆」「Webアプリ全般の最適化話やん🤣」「タイトル変えなきゃ🤓」「その分一般的に通用しそう」「はじめてWebアプリを作るのがRailsの人なら読んでおきたいヤツですね」

⚓Deviseのcustom mailerが動かなかった話

# 同Wikiより
class MyMailer < Devise::Mailer   
  helper :application # gives access to all helpers defined within `application_helper`.
  include Devise::Controllers::UrlHelpers # Optional. eg. `confirmation_url`
  default template_path: 'devise/mailer' # to make sure that your mailer uses the devise views
end

つっつきボイス:「DeviseのWikiに書いてある通りやったのにcustom mailerが動かなくてハマった人が今ちょうどここにいらっしゃるので😇」「ああ例のw」「😤😤」「お、issue立ってるのか↓: でもcloseしてるけど?」「needs debuggingとかつぶやきながら閉じられてるんですよ😭修正されてないのに」「ひどいw」「そっ閉じw」

参考: #4842 Unable to use custom views for emails

「問題だったのは以下のdefault template_path: 'devise_mailers'のところなんですけど」

# 同issueより
class DeviseMailer < Devise::Mailer
  prepend_view_path Rails.root.join('app', 'ui')
  include Devise::Controllers::UrlHelpers
  layout 'devise_mailer' # is in app/ui/layouts/devise_mailer.html.haml
  default template_path: 'devise_mailers' # is in app/ui/devise_mailers/...
end

「(怒涛のトーク)…まとめると、上で記述したdefault template_path:が効いてなくて、app/views/devise_mailersを参照して欲しいのにapp/views/devise/mailerが参照されてしまうんですね」

# これは動く => app/views/mailer_templateを参照する
class MyMailer < ApplicationMailer
  default template_path: 'mailer_template'
end
# これだと期待どおりに動かない => 相変わらず app/views/devise/mailerをみる
class MyMailer < Devise::Mailer
  default template_path: 'mailer_template'
end

「あーもしかしてDeviseMailerがActionMailerを継承しているから?」「かなっとも思ったんですが、Deviseのヘルパーにあるheaders_forというメソッド↓がtemplate_pathを書き換えていて、それによってこちらが書いたtemplate_pathが上書きされたので、メール文言がDeviseのデフォルト文言になってしまった😇」「はは~ん、わかりみ〜☺

# DeviseのHelperより
      def headers_for(action, opts)
        headers = {
          subject: subject_for(action),
          to: resource.email,
          from: mailer_sender(devise_mapping),
          reply_to: mailer_reply_to(devise_mapping),
          template_path: template_paths,
          template_name: action
        }.merge(opts)

「一応回避方法もissueに載ってます↓: この#headers_forメソッドがDeviseの汎用ヘルパー的なものらしいんですけど、それをこうやってオーバーライドしろってことでした」「privateメソッド臭ぷんぷん」「でもどんな影響が生じるか読みきれないんでneeds debuggingって言ってます」「DeviseのWikiは相当読み込んだけど、この手の『privateメソッドをオーバーライドせよ』的な驚きの拡張方法をちょくちょく強いられるんですよね」「『これ本当にオーバーライドしていいの?!』みたいなのが続出するし」「『え?、configじゃなくてメソッドをオーバーライド?!』とか」「オーバーライドするしかないとかキツイわ〜」「Deviseはこういうところがキラワれがち」

# 同issueより
class DeviseMailer < Devise::Mailer
  prepend_view_path Rails.root.join('app', 'ui')
  include Devise::Controllers::UrlHelpers
  layout 'devise_mailer' # is in app/ui/layouts/devise_mailer.html.haml

  # this below has no effect
  default template_path: 'devise_mailers' # is in app/ui/devise_mailers/...

  # this below enables effectively the custom views path
  def headers_for(action, opts)
      super.merge!(template_path: 'devise_mailers')
  end
end

「こういうことが起きるから、機能を拡張するときは『継承よりコンポジションしろ』って口を酸っぱくして言われるんですよね😆」「うんうん」

「今回特にムカつくのは、Wikiのコードが間違ってるのに動いちゃうところ: devise_mailerって間違ってるけど実在するから💦」「いっそ止まってくれればよかったのに😞

「DeviseでたとえばRegistrationコントローラを自前で作るときとか、DeviseのRegistrationコントローラをコピペするところから始めるなんてのもざらにやりますしね🤪」「そしてそのコピペをRubocopに怒られるところまでがテンプレww👮」「😭😭」「ところがところが、必要なメソッドだけ上書きするよりも全部コピペする方がまだ安全なんですねこれが」「そうかもしれない」「日暮れて道遠し感」「まさにイッツ・ア・Deviseワールド」

[Rails] Devise Wiki日本語もくじ1「ワークフローのカスタマイズ」(概要・用途付き)

⚓PHPからRubyへの移行ガイド


同サイトより

かなりがっつりと書かれています。「Railsの方が稼げる」というセリフも。


つっつきボイス:「自分もPHPからRailsにやってきた人だったしそういえば」「Rubyなら===とか使わなくても比較できるよみたいな?😆

⚓外部キーなしのActiveRecord関連付け

# 同記事より
class Order < ApplicationRecord
  has_one :custom_logo,
    ->(order) {
      unscope(:where).where(
        origin_value: order.origin,
        client_id:    order.client_id)
    }
end

つっつきボイス:「↑そうそう、lambda使えば外部キーなしでもできますね」「実用的ではないんでしょうか?」「一応使えるといえば使えそう: unscope(:where)とかやっていいのかなー?と思わなくもないけど🤔

そういえば以前著者のTom Copelandさんの記事を翻訳しました↓。

Rails: マイグレーションを実行せずにマイグレーションのSQLを表示する(翻訳)

⚓Railsで構築されたサイト40選: 2018年度版(RubyFlowより)


同記事より


つっつきボイス:「よくあるリストといえばそれまでですけど」「あ、でもこういうリストは『Railsってどの辺がいいの?』って聞かれたときなんかに便利ですよ😋」「AirbnbとかShopifyとか有名どころもあるし」「DribbleもRailsか」「何のサイトでしたっけ」「デザイナー向けSNSみたいな感じでそこそこ有名」


dribbble.comより

SlideShareHuluもRailsとは😲」「Huluは有料動画サイトでしたっけ」「kickstarterがRailsというのは何となくわかる」「意外にいろいろあるなー🎂


hulu.comより


kickstarter.comより

Urban Dictionaryは翻訳で愛用してます💚


urbandictionary.comより

Basecampが16番目とは😆」「DHHのお膝元なのに」

「カスタマーサポートサービスのZendeskも!へー😲


zendesk.comより

BloombergがRailsなのは有名」「知らなかったー」「確かgemも出してたはず(GitHub)」


bloomberg.comより

Indiegogoもか」「これは?」「有名なクラウドファンディングサイト」


indiegogo.comより

元記事のIndiegogoのリンクはなぜか間違ってました。

「えTwitchもRails?!😲」「何すかこれ?」「BPS社内Slackの格ゲー板では欠かせないヤツ💪」「最大のゲーム配信プラットフォームっす🎮

↓開くといきなり動画が再生されます。


twitch.tvより

CodeacademyがRailsというのも何かわかる」


codecademy.comより

「知らないサイトもあるけど、意外なサイトがRails使ってるんだなー☺」「こういうリストのエロサイト版があったら面白いかも🤪」「?」「エロサイトってそこらのメジャーなサイトとか目じゃないぐらい普段からものすごい量のアクセスをさばいているし、いろんな意味で(笑)常に最先端を行ってるから内部にはものすごいノウハウが蓄積されてるはずなんですよね: しかもたいてい門外不出という🤣

⚓Railsガイド日本語版に「Active Record と PostgreSQL」が追加

今日の昼に公開されました。本家英語版にもまだないエントリなので、当然ながらWIP状態です。


Railsガイド日本語版より

⚓Ruby trunkより

⚓2003年のRubyConferenceでのMatzのスライド

いつもとちょっと毛色が違いますが、Matzにっきで見かけた古きを訪ねてみました。


同スライドより


つっつきボイス:「2003年です」「Ruby 1.9が始まろうとしてた頃かー😲当時学生だったし」「たとえば以下のようなローカル変数スコープの変更が提案されてました↓」

# 同スライドより加工
# Ruby 1.9でのローカル変数の扱いの変更の構想
def foo
  a = nil
  ary.each do |b|
    # bはブロックに対してローカル
    c = b
    a = b
    # aとcはメソッドに対してローカル
  end
  # aとcはブロックの外でアクセスできる
  puts a
  puts c
end

2.5.1で試すとNameError (undefined local variable or methodc’ for main:Object)`になりました

「この頃からRubyConferenceってあったんだなー: 明らかにBefore Rails時代だし🏛」「今やお馴染みのキーワード引数やハッシュリテラル↓も提案されているし」「マルチアサインとか」

def foo(a, b: 42, **keys)
  p [a, b, keys]
end
foo(2, b: 1)       #=> [2, 1, {}]
foo(2, b: 5)       #=> [2, 5, {}]
foo(3, b: 4, c: 6) #=> [3, 4, {:c=>6}]
# 1.8まで
{ :a => 45, :b => 55, :c => 65 }

# 1.9向け提案
{ a: 45, b: 55, c: 65 }

「このメソッド結合(method combination)って何だろうと思って」「これは明らかに今のRubyにはない」「ボツになったんでしょうね」「スゴイ書き方😅」「フックをかける書式のようだ🤔

class Foo
  def foo:pre(*args)
    p "pre"
  end
  def foo:post(*args)
    p "post"
  end
  def foo:wrap(*args)
    p "wrap pre"
    super
    p "wrap post"
  end
  def foo(*args)
    p "foo"
  end
end

Foo.new.foo
# wrap pre
# pre
# foo
# post
# wrap post

個人的にはインスタンス変数にprivateスコープを作る提案↓が気になりました。本当はデフォルトでprivateにしたかったようですが、日記の方で「評判よくなかった」とあり、いずれも採用されなかったようです。

@foo  = 42  # サブクラスからアクセスできる
@_foo = 55  # そのクラス/モジュールからのみアクセス

⚓提案: 名前空間の改善

# 同issueより
class Foo
end

# Kernel#namespaceの導入
namespace :Hello do
  # 名前空間のカオスを回避
  # Foo -> NameError, can't access TOPLEVEL_BINDING directly

  # Kernel#importはFooという名前をOPLEVEL_BINDINGから導入する
  import :Foo

  # 名前空間内では、インポートした名前にしかアクセスできない
  Foo

  # 定数は別のエイリアス名にインポート
  # ネストしたモジュール名/クラス名を書くことを回避できる
  import :"A::B::C::D", as: :E

  # requireしてインポートするショートハンド
  import :"A::B::C::D", as: :E, from: 'some_rb_file'

  # 2つのgemから同じ名前をインポート
  import :"Foo", as: :Foo_A, from: 'foo_a'
  import :"Foo", as: :Foo_B, from: 'foo_b'

  # 名前をバッチでインポート
  import %i{"A::B::C::D", "AnotherClass"}, from: 'some_rb_file'

  # インポートとエイリアス化をバッチで
  import {:"A::B::C::D" => :E, :Foo => Foo2}, from: 'some_rb_file'

  class Bar
    def xxx
      # 名前空間のスコープ内にあるすべての名前にアクセスできる
      [Foo, Foo_A, Foo_B]
    end
  end
end

その後namespaceisolateに変えて提案しています。


つっつきボイス:「Rubyに名前空間とは」「お、確かRailsにそれっぽいものがいくつかあったと思う: Rubyの機能じゃないんですよね」「そうそう」「Railsじゃない普通のRubyコードでnamespaceって書いたらundefinedって言われた覚えはある」「(RubyMineで探す)お、たとえばこれですね↓(GitHub)」「おー」

# rails/railties/lib/rails/command/base.rb
        # Convenience method to get the namespace from the class name. It's the
        # same as Thor default except that the Command at the end of the class
        # is removed.
        def namespace(name = nil)
          if name
            super
          else
            @namespace ||= super.chomp("_command").sub(/:command:/, ":")
          end
        end

ルーティングにもnamespaceがありますね。

「Rubyだとモジュールでも名前空間を切ることはできるけど、明示的に名前空間を切るという意味ではnamespaceっていうキーワードはRubyにもあっていいんじゃないかなとは思いますね」「むしろRubyにデフォルトでnamespaceがないのが少々驚き😮

⚓RubyソースのインデントはTabから8スペースに

#14984を追ってて見つけました。

RubyのCファイル238個を調べたところ
* スペースインデントのみ: 10件
* タブインデントのみ: 66件
* インデントなし(!): 61件

つまり残り101ファイルはタブ/スペースインデントが混じっているということ。
同issueより大意


つっつきボイス:「少し前にUrabeさんが提案してました」「なるほど☺これは揃えないとねー」「↑ちゃんと調べてるしスゴイなー😲」「Cのソースレベルだと他にもいろいろ揃ってないものがありまくりそうだけど、せめてインデントだけでもってことなんでしょうね」

⚓Ruby

⚓ngx_mruby: Nginxにmrubyを

RubyKaigi 2018でも話されていました

参考: ngx_mruby v2のHTTPクライアントをv1よりも最大90倍高速にした - 人間とウェブの未来
参考: Rubyエンジニアはsleep 1で殺せる、をngx_mrubyのAsync.sleepで乗り越える - The paradigm shift


つっつきボイス:「『sleep 1で殺せる』記事で見かけたので」「そうそう、sleep 1はマジで死にますよ: コントローラとかに絶対書いちゃダメなやつ」「ギャー!それはダメー!😩」「やったらどうなるんでしょう?」「ワーカーが詰まるw」

「Rubyでなくたってsleepはあかんでしょう…😅」「でもまあ、慣れてないとAPIアクセスなんかで間を空けたいときについsleepを入れたくなっちゃうんでしょうねー: そんなにやりたかったら非同期でやれと😆

「そういえばブラウザのJavaScriptでsleep使いたくて探し回ったことありました」「setTimeout()」「ブラウザのJavaScriptでsleepする分には何の問題もないんですけど、サーバーサイドのRubyコードでsleepするとその間ワーカーをがっちり掴んで離さない: とっても簡単に死ねる😇」「😆

参考: JavaScriptでループ中にスリープしたい。それも読みやすいコードで - Qiita

⚓DIとハードコード定数のいいとこ取り(RubyFlowより)

class RegisterUser
  attr_reader :validator, :repo

  def self.build
    new(
      validator: UserValidator.new,
      repo: UserRepository.new,
    )
  end

  def initialize(validator:, repo:)
    @validator = validator
    @repo = repo
  end

  def call(params)
    if validator.validate(params)
      repo.save(params)
    else
      nil
    end
  end
end

つっつきボイス:「DIするとスタブやモックを入れやすいよ、みたいな話なんでしょうね: test doubleとか」「で上のアプローチ↑では、実装はDIだけど、validator: UserValidator.newのようにハードコードするってことか」「おー、これはキレイキレイ❤」「この書き方は割とよく使いますね: 書いた時点では1種類しかないからこういう形にしておくけど、後でほぼ確実に増えることが予測できる場合なんかに拡張性を担保できるのがいいですよね😋」「ま、オーバーキルにも見えるので普通にハードコードしちゃうこともありますが😆

「あたしもinitializeのデフォルト引数でこうするのが最近お気に入り😍」「それも同じコンセプトですね: 実装をDIにできるし、引数もキーワード引数にしておけばなおいいし」

⚓dry-rbでサービスを設計する(Awesome Rubyより)

# 同記事より
class NotifySlack 
  extend Dry::Initializer
  option :message
  option :notifier, default: -> { Slack::Notifier.new(URL) }

  def self.call(**args)
    new(**args).call
  end

  def call
    notifier.ping(message)
  end
end

つっつきボイス:「ここでも依存性の注入が登場していました」「そういえばDependency Injectionを『依存性の注入』と言うのってどうかな~と思ったり😉」「あーそういえばそうだったか💦

「まあ多くの日本語書籍で『依存性の注入』と訳されちゃっている現実があるんですが、この記事がひと頃流行りましたよね↓」「これ見たことあったそういえば👁」「それもあって自分はDIと言うようにしてる😎

参考: やはりあなた方のDependency Injectionはまちがっている。 — A Day in Serenity (Reloaded) — PHP, FuelPHP, Linux or something

「注入って言葉が何かよくないっすよね」「そうそう、注入というよりイメージとしては差し込む感じ」「ジェネリクスというか」「しかも注入するものって依存性じゃないんですよね」「そうそう🤣」「どっちかというと『差し替え可能にすることで依存しないようにする』なんじゃ?」「dependencyとinjectionをそのまま訳しちゃったからこういうことになったんでしょうね〜」「元の英語もそれなりに残念な気がしますね」「たぶんinjectionを『注入』としちゃったのが敗因じゃないかな〜?」「dependencyも何か問題ありそう」「もう慣れちゃったから依存性の注入でもいいかなっと😉」「日本語にしないのが確実かも」「実際に日本語で言う人見たことないなー」「本で覚えちゃった人は言うかもしれない」「読むときも心の中でDIに差し替えてるし🧐


確かに「依存性」も「注入」も薬物依存を強く連想してしまいがちですね。プログラミング言語の仕様を後からドラスティックに変えるのが困難なように、翻訳もいったん定着してしまった用語を覆すのは大変です。自分ひとりではできない作業なので💦

そういえば「差し込み印刷」がmerge printの訳語だったのを思い出しました。これはいい訳だと思います💪

参考: 差し込み印刷とは - IT用語辞典 Weblio辞書

⚓次に学ぶ言語にRubyを選ぶことの知られざるメリット(RubyFlowより)


つっつきボイス:「『Rubyistはフレンドリー』😆」「😆」「『Rubyistは美しいコードを心がける』ホントかー?🤣美しいとは言い難いコードも山ほど見たけどなっ」「🤣」「オープンソースのコードを書いている人ならRubyに限らずだいたいそうだと思う」「他のコミュニティ向けの記事みたいです」「確かにRubyコミュニティは不毛な論争になりにくい感じはありますね😋

⚓Ruby関連カンファレンス情報


同エントリより

大江戸Ruby会議に値段が2種類あるのが謎でした。


つっつきボイス:「5,656円ってもしやゴロゴロ😆」「語呂合わせ」「雷門にかけてるのか」「倍以上違うし🤣」「前回は御茶ノ水で今回はお膝元の浅草ですが、自分は御茶ノ水の方が行きやすいなーと思ったり」「花やしきの裏か」


こちらも↓。

⚓JSONを安全に「mung」する(RubyFlowより)

# 同記事より
def munge(hash)
  instructions.each do |inst|
    inst.call(hash)
  end
  hash
end

つっつきボイス:「このmungって言葉が割と謎で」「mail munglingとかじゃなくて?」「mungleじゃなくてmungeなんですがさっき手元の串刺し辞書検索で見つからなくて💦」「(しばらく探す)お、Macの英英辞典にあった↓🎯」「へー!」「しかも例文がIT系でピンポイントの内容」「『操作する』」「mungまたはmungeなのね」「しかもmash until no goodの略語という説が: すげー😳」「時間ないので次へー」

⚓Ransack gemのyaml翻訳を増やしたい(RubyFlowより)


つっつきボイス:「珍しくRubyFlowのエントリをそのまま貼ってみました: Ransackのローカライズ済みyamlをもっと増やしたいんだそうです」「『みんな!オラにyamlをくれ!』みたいな😆」「日本語yamlは3年前のがもうあるのね」「今のところyamlは18言語分、と」

⚓クラウド/コンテナ/インフラ/Linux/Serverless

⚓Aurora Serverless MySQLがリリース


aws.amazon.comより


つっつきボイス:「AuroraのこのサービスをServerlessって呼んでいいものかどうか微妙〜😆」「😆」「どんなサービスでしたっけ」「単にMySQL connectしたタイミングでRDSインスタンスが起動するヤツです」「?」「つまりインスタンスは普段は止まっていて、MySQL Connectすると勝手に起動してくれて、接続が終わったらしばらくしてまた自動的に止まるということ」

「よくバッチ処理なんかで一時的に激しくアクセスしたいなんてときに、これまでだとRDSの起動や終了を手動でやらないといけなかったんですが、そういうのをお任せでやってくれるというイメージ」「ほほ〜」「だからこのサービスはバッチ処理に向いてると思うんですよね」「確かに使いみちはありそうっすね: しかしServerlessかと言うとw」「Aurora On-Demandとかそういう名前の方がよくね?」「流行り言葉だからServerlessって言ってみるテスト?」「インスタンスを起動するのに1分とかかかるから、即時性を要求するものよりは、やっぱりバッチ処理に向いてるんじゃないかなー🤔

「Lambdaと相性がいいとかあるんですかね?」「いや特に😆」「Lambdaも即時応答を必要としないバッチ的なものならいけるでしょうし☺

「そういえばAWS Batchってのもありますね?」「あれはただのバッチで🤣、cron的にEC2インスタンスを起動してコマンドを実行して終わったらインスタンスを落とすとかに使う」「なんだ🤣

⚓Cookieの次はSec-HTTP-Stateヘッダ

Sec-HTTP-State: token=J6BRKagRIECKdpbDLxtlNzmjKo8MXTjyMomIwMFMonM

Googleの中の人です。

draft-cdn-loop-prevention-00 - CDN Loop Preventionというのもありました。

参考: Cookieにかわる Sec-HTTP-State ヘッダの提案 - ASnoKaze blog


つっつきボイス:「これはてブで見ましたネ」「先週出した勉強会記事↓の最後のステートレスとステートフルの話にも書きましたけど、CookieってステートレスなHTTPプロトコルの上でステートフルな振る舞いを実現するために使われまくっているわけで、WebアプリがCookie使ってステートフルなのはもう当たり前になってしまってますよね」「ですねー」「それほどまでに使われているのに、Cookieって未だに不便なところが多すぎ😭」「記事にあるけど『secure属性やhttp-only属性の利用率は10%に達してない』のが悲しすぎる😢」「かーなーしぃー😞

Webアプリの基礎とさまざまな実行環境を理解する#3(社内勉強会)

「ブラウザを替えると動かなくなるアプリだって多すぎるんだし、それなら新しいものを定義した方がいいっていう発想は自然」「↓こういうの見るとCookieも限界だなって思いますね」

Cookie request header is 409 bytes, while the 90th percentile is 1,589 bytes, the 95th 2,549 bytes, and the 99th 4,601 bytes
同記事より

「問題なのはCookieの仕様がレガシーすぎることで、ブラウザのCookie APIを生で触るとかホント地獄ですよ💀やったことあります?」「ありますw」「あそこにはいわゆるPOSTパラメータがそのまんま入ってるんで、JSON的にオブジェクトをCookieに格納しようと思ったら自分でシリアライズしないといけないという🤮」「一応js-cookie↓というのを使うとキーに保存できますけどね」「つかそもそもCookieだけではキーにセットすることすらできないんだからぁああ😤」「CSSですらやれることもできないCookieって…😅

「その点SessionStorageとかlocalStorageはキーバリューストアとして使えることが保証されているんで、断然使いやすいですね😍」「GoogleスプレッドシートのデータとかはlocalStorageに保存されてるんでしょうね…」「バカでかいJSONデータみたいなものはよくlocalStorageにそのまんま放り込まれてたりしますね🗻」「indexedDBなんかは確かSQLライクにちゃんと扱えるんですよね?」「そうそう😋あんまり実装されてないけど」

参考: HTML5 SessionStorageの使い方 - WebStorageを使ってみよう!
参考: Window.localStorage - Web API インターフェイス | MDN

⚓REST APIはデータベースではない(Awesome Rubyより)


同記事より


つっつきボイス:「RESTはー、データベースじゃー、ないよー😆」「😆」「この絵好き↑」

⚓go-health: ヘルスチェッカー


同記事より


同リポジトリより


つっつきボイス:「このGopherくんみたいな絵ってみんなどこから仕入れてるんだろか?😆」「😆」「眼球よく見たら完全な球体w」「ヘルスチェックっていかに軽くするかが至上命題ですね🧐

ついでにこんなのを見つけちゃいました。


同リポジトリより

⚓モバイル/Android/iOS

⚓Lottie: Adobe After Effectsアニメーションを直接動かせるライブラリ


つっつきボイス:「BPSアプリチームとデザインチームがこれにちょっとざわついてました」「After Effectsを直接かけられるとはねー😲よく頑張ったなってマジで思う」

参考: Adobe Creative Cloud | プロのクリエイター向けソフトウェアとサービス

⚓Flipper: Facebookのモバイルアプリデバッガ(JSer.infoより)



同サイトより


つっつきボイス:「うん、こういうのはいろいろあるけどやっぱり便利😋」「SafariのDeveloper Toolsとかでもある程度できるけど時々扱えない項目とかあったりしますね」

⚓SQL

⚓JOINのコスト(Postgres Weeklyより)

-- 同記事より
EXPLAIN ANALYZE
SELECT
    count(*)
FROM
    table_1 AS t1 INNER JOIN
    table_2 AS t2 ON
        t1.id = t2.table_1_id INNER JOIN
    table_3 AS t3 ON
        t2.id = t3.table_2_id INNER JOIN
    table_4 AS t4 ON
        t3.id = t4.table_3_id INNER JOIN
    table_5 AS t5 ON
        t4.id = t5.table_4_id
WHERE
    t1.id <= 10;

                                                                            QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=88.52..88.53 rows=1 width=8) (actual time=0.411..0.411 rows=1 loops=1)
   ->  Nested Loop  (cost=1.43..88.50 rows=9 width=0) (actual time=0.067..0.399 rows=10 loops=1)
         ->  Nested Loop  (cost=1.14..85.42 rows=9 width=4) (actual time=0.054..0.304 rows=10 loops=1)
               ->  Nested Loop  (cost=0.86..82.34 rows=9 width=4) (actual time=0.043..0.214 rows=10 loops=1)
                     ->  Nested Loop  (cost=0.57..79.25 rows=9 width=4) (actual time=0.032..0.113 rows=10 loops=1)
                           ->  Index Only Scan using table_1_pkey on table_1 t1  (cost=0.29..8.44 rows=9 width=4) (actual time=0.015..0.023 rows=10 loops=1)
                                 Index Cond: (id <= 10)
                                 Heap Fetches: 10
                           ->  Index Scan using table_2_table_1_id_idx on table_2 t2  (cost=0.29..7.86 rows=1 width=8) (actual time=0.007..0.007 rows=1 loops=10)
                                 Index Cond: (table_1_id = t1.id)
                     ->  Index Scan using table_3_table_2_id_idx on table_3 t3  (cost=0.29..0.33 rows=1 width=8) (actual time=0.008..0.008 rows=1 loops=10)
                           Index Cond: (table_2_id = t2.id)
               ->  Index Scan using table_4_table_3_id_idx on table_4 t4  (cost=0.29..0.33 rows=1 width=8) (actual time=0.007..0.008 rows=1 loops=10)
                     Index Cond: (table_3_id = t3.id)
         ->  Index Only Scan using table_5_table_4_id_idx on table_5 t5  (cost=0.29..0.33 rows=1 width=4) (actual time=0.007..0.008 rows=1 loops=10)
               Index Cond: (table_4_id = t4.id)
               Heap Fetches: 10
 Planning time: 2.287 ms
 Execution time: 0.546 ms
(19 rows)

しょっぱなで「場合によりけり」とあります。


つっつきボイス:「おータイトルがかっこいいなー😎」「↑ぽすぐれはこうやってクエリプランがとっても読みやすいのがホントいい❤」「MySQLのクエリプランは、読めるけど読めないw」「フォーマットしてハイライトしてもあかん感じでしょうか?」「MySQLのはテーブルで出力されるますしねー💦」「クエリがどういう戦略で組み立てられたのかが結局よくわかんない😭だからforce indexしたりwhereの順序を変えたりとかして試さないと」「最後はログからMySQL様のお気持ちを読み取ることに🤣」「そうそう🤣

「ポスグレの出力はどこを評価したらどうなったかというのが逐一分かるのがエライ」「しかも数値付きだし」


同記事より

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

⚓時間のかかるクエリ


つっつきボイス:「これMySQLだし」「およ、Using temporaryしてるし↓これはヤバいやつ」「というと?」「これをやると中間テーブルを一度ファイルに出すんですけど、そうすると何やかんやでディスクがボトルネックになっていく」「あぁ〜😲」「それがサブクエリになってたりするともうあかん🤣」「🤣

+------+-------------+-----------------+--------+---------------+---------+
| id   | select_type | table           | type   | possible_keys | key     |
+------+-------------+-----------------+--------+---------------+---------+
|    1 | SIMPLE      | project_commits | ALL    | NULL          | NULL    |
|    1 | SIMPLE      | commits         | eq_ref | PRIMARY       | PRIMARY |
+------+-------------+-----------------+--------+---------------+---------+

+---------+-------------------------------------+------------+-----------------+
| key_len | ref                                 | rows       | Extra           |
+---------+-------------------------------------+------------+-----------------+
| NULL    | NULL                                | 5417294109 | Using temporary |
| 4       | ghtorrent.project_commits.commit_id |          1 |                 |
+---------+-------------------------------------+------------+-----------------+

追いかけボイス: もう少し具体的にはこんな流れ:

  • JOINやサブクエリで必要な一時テーブルのサイズがtmp_table_sizeとかmax_heap_table_size(今は名前違うかも?)とかから算出されるサイズを超えるとtemporary tableとしてファイルに書き出し始める
  • ↑の時点で既に激遅いのに、さらにファイル書き出し&READが発生するため、OS側で空きメモリを使って実施しているファイルキャッシュがゴリゴリ破棄されていく
  • 他のプロセスのキャッシュもガンガンページアウトされ、次回参照時にまたディスクから読みに行く
  • 実質メモリ足りない時のスラッシングに近い状況が発生する

「『5 billion rows and commits 847 million rows』というボリュームでやってたらそうなりそう: よく10日も待ってたって思う😢」「クエリがなかなか戻ってこない時の判断って迷うよね: productionでどれぐらい時間かかるクエリを回したことあります?」「productionじゃないけど、移行の時にproduction相当のセッティングにして3時間経っても戻ってこないからこれは絶対何かおかしいと思って、速攻アプリを閉じてOracleを再起動したら10分かからずに返ってきましたね🤣」「🤣」「チューニング無しで12時間かかるクエリをチューニングして6時間まで短縮して、それをproductionで回したことならある😆」「夜が明けるまでが勝負のヤツですね」「SQLで1000行ぐらいあったかな」「ぶほっ」「SQLのありがたいところは、途中でエラーがraiseされたりしないところですね☺

⚓JavaScript

⚓Vue.jsのAsyncコンポーネント

// static import
import utils from "./utils";

// dynamic import
import("./utils").then(utils => {
  // utils module is available here...
});

つっつきボイス:「thenで書けるようになったってことみたい」「thenってES2015あたりで入ってきたんだったかな?」「Promiseのthenですね」

参考: Promise - JavaScript | MDN

「そういえばRubyでもthen入ってきましたね」「あーyield_selfの名前がRubyKaigiで変わったヤツ」「へー知らなかった」

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

⚓WebpackのHMR(Hot Module Replacement)とは


つっつきボイス:「HMRは今日の社内の勉強会の発表で出てきたので: Webpackerのつらさがよくわかるスライドでした☺

参考: モジュールをHMRに対応するための実装について - Qiita

⚓Electron Fiddle(JavaScript Weeklyより)


同記事より

Fiddleで書いたものをElectron Forgeを使ってElectronアプリにできるようです。


electronforge.ioより

⚓CSS/HTML/フロントエンド/テスト

⚓配色アイデア見本2018年保存版


つっつきボイス:「BPSデザインチームが感動してました: とってもキレイですね」「こういうのは自分で直接カラーパレットいじるとろくなことにならないという」「カラーパレットはやっぱりツールを使って選ぶべきですねー」「そういうサイトもいろいろあるし」「Photoshopにもありますし」

⚓WordPressの編集画面が5.0で変わる

WordPressの更新情報で気が付きました。

参考: Before Gutenberg - WordPress 5.0になるまでに準備すべきこと - Capital P


つっつきボイス:「どうよくなるのか次第ですけどねー」「今までのTinyMCEがあまりに歴史古いし: 相当前からある」「TinyMCEで編集中にdivから抜けたつもりでEnter押してたら抜けてなかったなんてことあった😢」「TinyMCEの出た頃ってtableレイアウト全盛でしたね☺」「Gutenbergになったらどうなるのかちょっとだけ心配っす: Markdownしか使わないけど」「Markdownは大丈夫でしょうきっと」


tiny.cloudより

参考: TinyMCE - Wikipedia

⚓言語よろずの間

⚓lem: Common LISPベースのEmacsエディタ


同リポジトリより


つっつきボイス:「まあエディタは好きなものを使えばいいんじゃね?☺」「これCLIでも動きそう」「Emacsはめちゃ重いからな〜(と起動してみる)お、一回起動すると速いのかな?: 久しぶりにEmacs起動したし😆

追いかけボイス:「どうやら以前入れていたspaceemacsが重すぎてMac更新時に捨てていたので速かったようです: 今は.emacsファイルすらないという」

⚓コンパイラは新しいフレームワークだ

「Webの人も今こそコンパイラの仕組みを学ぼう」という結論でした。


つっつきボイス:「コンパイラのことは知ってて損はないと思いますね: 『こういうコードは最適化が効きやすい』みたいのを押さえておけばスクリプトエンジンの最適化を活用できるし」

⚓日本語や中国語でルビをマークアップする(Awesome Rubyより)


同記事より


つっつきボイス:「Rubyじゃなくてルビということで」「これ見てると例の文字渦↓を思い出しちゃう」「それそれ」「やっぱスゲー」「タイトルは中島敦の『文字禍』のもじりなんでしょうね」「このルビはまったく別の文章?」「二重化されてるっぽいですね」「他のページはもっとスゴイのかも?」

参考: 文字禍 - Wikipedia

⚓その他言語

⚓その他

⚓安価なMacbook欲しい

⚓Intel CPUの「Foreshadow」脆弱性の解説ムービー

投機的実行の脆弱性、Turing Complete FMでも話されていたような覚えがあります。


つっつきボイス:「これはMeltdownとかとまた別の脆弱性?」「そうみたいです」「L1キャッシュをクリアすると安全にはなるけど遅くなっちゃうんですよね」

参考: IntelのCPUで新たに発見された脆弱性「Foreshadow」の解説ムービーをRedHatが公開中 - GIGAZINE

⚓世界最悪のログイン処理コードを鑑賞


つっつきボイス:「これはホントすごいよなー: 首を傾げ度がヤバい😆」「"true" === "true"とか」「apiService.sql()とか」「腹いてー🤣」「ツッコミどころしかない🔫」「コワイよ〜」

「C言語だったら"true" === "true"みたいなのを書くことはたまにあるけど」「お?」「パイプラインを強制的にフォールトさせるときとか: コンパイラの最適化を全部オフにしておいてブランチ命令を実行することでパイプラインを無理やり落とすという」「ま、そういうのは普通インラインアセンブラでやるでしょうけど😆

追いかけボイス: 「組み込み向けのマイコン開発などではこういうことが割とあります: CPU初期化系のレジスタ更新がスペックシート上はすぐに反映されそうに書いてあっても、実際には数クロック待たないと反映されないケースではNOP NOP NOP NOPしたりするなどです」

⚓その他のその他


⚓番外

⚓回路で遊ぶ

⚓木星並の漂流惑星が発見される

記事の方ではこの惑星をNibiruと呼んでました。

参考: ニビル (仮説上の惑星) - Wikipedia
参考: どの星系にも属さず宇宙を漂流する巨大惑星を発見 - NRAOなど | マイナビニュース


今回は以上です。

バックナンバー(2018年度後半)

週刊Railsウォッチ(20180813)Rails 5.2.1リリース、sanitize_sql_arrayは5.2からpublicだった、Dev.toがRailsアプリのソースを公開ほか

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

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

Rails公式ニュース

Ruby Weekly

Awesome Ruby

RubyFlow

160928_1638_XvIP4h

Hacklines

Hacklines

WebOps Weekly

webops_weekly_banner

Postgres Weekly

postgres_weekly_banner

JSer.info

jser.info_logo_captured

JavaScript Weekly

javascriptweekly_logo_captured

週刊Railsウォッチ(20180827)Ruby Prize 2018募集開始、Interactor gemとReader Object、書籍『Real World HTTP』、Basecampのヒルチャート機能ほか

$
0
0

こんにちは、hachi8833です。今度はコードの未来で寝不足になってしまいました。

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを社内有志でつっついたときの会話です👄

今回のウォッチは社内つっつき会当日の台風接近が危ぶまれたので件数を抑えてかつ駆け足気味です。

⚓お知らせ: 第2回「週刊Railsウォッチ 公開つっつき会」開催

現時点ではあと1名様の空きがあります。引き続き募集していますのでお気軽にご応募ください。

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

⚓Rails 6.0でWebpackerをデフォルト化する作業が進行中

カバレッジを手伝ってくれる人募集中だそうです。

#33079は少し前のウォッチでもどさくさに紛れて取り上げていました。

Rails 6ではデフォルトのJavaScriptコンパイラがWebpackerになり、以下の変更が生じる:

  • Webpacker gemがデフォルトでインストールされ、Railsのアプリジェネレータでwebpacker:installが実行されるようになる
  • Action Cableチャンネルのジェネレータは、CoffeeScriptのスタブではなくES6のスタブを生成する
  • Active Storage/Action Cable/Turbolinks/Rails-UJSは、デフォルトでapp/javascriptにあるapplication.jsパックによって読み込まれる(自分でオプトアウトしない限り)
  • Active Storage/Action Cable/Turbolinks/Rails-UJSのnpmモジュールはデフォルトのpackage.jsonの依存リストに自動で含まれる
  • Sprocketsで使われるすべてのJavaScript関連の追加機能(圧縮や難読化など)はデフォルトでは設定もインクルードもされなくなる
  • scaffoldジェネレータは今後デフォルトでJavaScriptのスタブを生成しなくなる


つっつきボイス:「WebpackerになればなったでJSエンジンが必要になってくるので、今度はインストール要件が増えるんですよね〜😅」「ファイル数も増えますね😢」「さしあたってRailsを初めてインストールする人の手順が増えるという😆」「yarnはパッケージの管理に使ってるんでしたっけ?」「ですね」

⚓Cookieにpurposeメタデータが追加

まとめ
このPRはcookieにpurposeメタデータを追加することで、あるcookieの値を別のcookieにコピーして使えないようにする。

その他の情報
config.action_dispatch.use_cookies_with_metadataをオンにすると、expirypurposeメタデータが署名/暗号化済みcookieの内部に埋め込まれ、かつその署名/暗号化済みcookieの値がコピペされたかどうかを検証する。
従来のcookie設定(purposeexpiryメタデータを使わないもの)も引き続き使える。
同PRより大意

# actionpack/lib/action_dispatch/middleware/cookies.rb#L
+        def cookie_metadata(name, options)
+          if request.use_cookies_with_metadata
+            metadata = expiry_options(options)
+            metadata[:purpose] = "cookie.#{name}"
+             metadata
+          else
+            {}
+          end
+        end

つっつきボイス:「このpurposeメタデータの資料がググっても見当たらなくって」「見た感じRFCのものではなくてRails独自っぽい?🤔

⚓Array#extract!メソッドを追加

# activesupport/lib/active_support/core_ext/array/extract.rb#L3
+ class Array
+   # Removes and returns the elements for which the block returns a true value.
+   # If no block is given, an Enumerator is returned instead.
+   #
+   #   numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+   #   odd_numbers = numbers.extract! { |number| number.odd? } # => [1, 3, 5, 7, 9]
+   #   numbers # => [0, 2, 4, 6, 8]
+   def extract!
+     return to_enum(:extract!) { size } unless block_given?
+      extracted_elements = []
+      reject! do |element|
+       extracted_elements << element if yield(element)
+     end
+      extracted_elements
+   end
+ end
# 同PRより
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
odd_numbers = numbers.extract! { |number| number.odd? } # => [1, 3, 5, 7, 9]
numbers # => [0, 2, 4, 6, 8]

つっつきボイス:「RubyのArray#select!とどう違うのかなと思って」「探してみたけど!なしのArray#extractはないみたい」「ちなみに破壊的メソッドはだいたいメモリ消費が少なめではありますね」

実際に動かしてみました。どちらも破壊的メソッドですが、変更時にselect!はselfを改変してそれを返すのに対し、extract!は抽出した配列を返し、selfにはその残りが置かれるということですね。

## select!の場合
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
#=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
odd_numbers = numbers.select! { |number| number.odd? }
#=> [1, 3, 5, 7, 9]
numbers
#=> [1, 3, 5, 7, 9]

## extract!の場合
class Array
  def extract!
    return to_enum(:extract!) { size } unless block_given?
    extracted_elements = []
    reject! do |element|
      extracted_elements << element if yield(element)
    end
    extracted_elements
  end
end

numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
odd_numbers = numbers.extract! { |number| number.odd? }
#=> [1, 3, 5, 7, 9]
numbers
#=> [0, 2, 4, 6, 8]

参考: instance method Array#select! (Ruby 2.5.0)

ブロックが false を返した要素を自身から削除します。 変更があった場合は self を、 変更がなかった場合には nil を返します。

a = %w{ a b c d e f }
a.select! {|v| v =~ /[a-z]/ }   # => nil
a # => ["a", "b", "c", "d", "e", "f"]

ブロックが与えられなかった場合は、自身と select! から生成した Enumerator オブジェクトを返します。
docs.ruby-lang.orgより

⚓rake dev:cacherails dev:cacheに移動

# railties/lib/rails/commands/dev/dev_command.rb#L3
+ require "rails/dev_caching"
+  module Rails
+   module Command
+     class DevCommand < Base # :nodoc:
+       desc "Toggle development mode caching on/off"
+       def cache
+         Rails::DevCaching.enable_by_file
+       end
+     end
+   end
+ end

つっつきボイス:「またひとつrakeコマンドが非推奨になりました」「dev:cacheって割と最近入った機能だった気がする」「そうだったかも」

↓これでした。

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

⚓numericalityバリデータがカスタムgetterから影響されないよう修正

ここからはコミットリストから見繕いました。

# activemodel/lib/active_model/validations/numericality.rb#L21
       def validate_each(record, attr_name, value)
         came_from_user = :"#{attr_name}_came_from_user?"
         if record.respond_to?(came_from_user) && record.public_send(came_from_user)
          raw_value = record.read_attribute_before_type_cast(attr_name)
+         elsif record.respond_to?(:read_attribute)
+           raw_value = record.read_attribute(attr_name)
        end
        raw_value ||= value
          if record_attribute_changed_in_place?(record, attr_name)
           raw_value = value
         end

つっつきボイス:「確かにgetterを自前で書いていればこれ必要そう: getterがattributeに既にあるならそっちを使うべきだろうし」「numericalityっていかにも造語っぽい」「これはバリデーションのオプションですね↓」

参考: numericality - リファレンス - - Railsドキュメント

⚓Active Storageフォームでsubmitボタンを複数サポート

# activestorage/app/assets/javascripts/activestorage.js#L857
  var processingAttribute = "data-direct-uploads-processing";
+   var submitButtonsByForm = new WeakMap;
  var started = false;
  function start() {
    if (!started) {
      started = true;
+       document.addEventListener("click", didClick, true);
      document.addEventListener("submit", didSubmitForm);
      document.addEventListener("ajax:before", didSubmitRemoteElement);
    }
  }
+   function didClick(event) {
+     if (event.target.tagName == "INPUT" && event.target.type == "submit" && event.target.form) {
+       submitButtonsByForm.set(event.target.form, event.target);
+     }
+   }

つっつきボイス:「これはJavaScriptの修正、と」「複数のsubmitボタンというと、縦長のページのトップとボトムに同じボタンがあったりとか」「GitLabのこういうボタン↓なんかもそうですね」

⚓Rails

⚓ActionCableも臭うのか?(Ruby Weeklyより)

Noah Gibbsさんの記事です。


つっつきボイス:「FayeJuggernautよりはいいけれどと認めつつ『ActionCableって必要?』というそもそも論っぽい」「まあよく言われる話ではある: サブスクライバをサーバーでやると複雑になりがちだし、pub/subをアプリケーションサーバーでやるより切り離しちゃえば?というのももっともだけど、Railsでまとめてやりたいということなんだろうし、モデルで扱いたいというニーズもあるだろうし☺

⚓Punditのベストプラクティス(Ruby Weeklyより)

# 同記事より
class PostPolicy < ApplicationPolicy
  # ジェネレータで生成されたアプリケーションポリシースコープを継承する
  class Scope < Scope
    def resolve
      if user.admin?
        scope.all
      else
        scope.where(published: true)
      end
    end
  end

  def update?
    user.admin? or not record.published?
  end
end

つっつきボイス:「Punditは個人的に肌に合わないけど」「cancancanが好きって言ってましたね」「割と普通のことを書いてるかな: スコープ↑を理解する話とか、コントローラでrescue_from使って認証エラーを拾う↓話とかもあるけど」「Scope < Scopeってナニソレ?😳」「こんな書き方ができるのね😆

# 同記事より
class ApplicationController < ActionController::Base
  protect_from_forgery
  include Pundit

  rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized

  private

  def user_not_authorized
    policy_name = exception.policy.class.to_s.underscore

    flash[:error] = t "#{policy_name}.#{exception.query}", scope: "pundit", default: :default
    redirect_to root_path
  end
end

⚓Fakerbot: Fakerが手放せない人に(Ruby Weeklyより)

# 同リポジトリより
$ gem install fakerbot
$ fakerbot list
# Faker::BackToTheFuture
# ├── quote
# ├── date
# └── character
# Faker::Finance
# └── credit_card
# ....

つっつきボイス:「Fakerと言えばテストなんかで使う架空の名前を取れるヤツですね」「Fakerのどこに何があるのかをコマンドラインでさっと調べられるのか: しょっちゅう使うならこういう探し方したいのはワカル」「Fakerって日本語データもあった気が」「日本語ロケールあるし、こういう既存のデータで拡張もできますね↓😎

ランダムな日本語のデータを生成するGemまとめ - Qiita


stympy/fakerより

⚓go-on-rails: RailsアプリをGoコードに変換


同リポジトリより


つっつきボイス:「まだアルファ版っぽい感じで、よほど単純なRailsアプリじゃないと変換できなさそうな雰囲気でした: とりあえずオレオレRailsアプリを変換して生成されたgo_app↓を動かしてみると、普通のデフォルトRailsページが表示されました」

⚓「さようならImageMagick」

そういえばRailsはlibvipsを使う流れになってますね(ウォッチ)。


つっつきボイス:「ImageMagick万能感はヤバいですからね: PDF生成もできるし、動画からの画像切り出しもできるし、GIFアニメーションも生成できるし、画像関係は本当にありとあらゆることができてしまうから💪」「守備範囲そんなに広かったのか😳」「この記事みたいに画像サムネイル作るだけなら明らかにImageMagickはオーバーキルだろうし、もっと軽いソリューションはあってもいいでしょうね」

「ImageMagickというと、gemでインストールしたときにC拡張のコンパイル時間かかるとか面倒という印象でしたが」「それはRMagicでインストールした場合でしょうね、たしか静的コンパイルしてたんじゃなかったかな: MiniMagickならコンパイル走らない(別途ImageMagickライブラリのインストールは必要ですが)」「そっか💦

ffmpegなんかもそうだけど、ImageMagickってコマンドラインであらゆることをできるようにした結果ものすごいものになったソフトウェアの象徴みたいなところがありますね😆」「😆

[Rails] MiniMagickでPDFのページ数を取得するときはフォントエラーに注意!


ImageMagickより

⚓InteractorとReader Object(Hacklinesより)

Railsのパターン記事2本です。

# 記事1より
class ReplyToSurvey
  include Interactor::Organizer

  organize CreateResponse, AddRewardPoints, SendNotifications
end

class CreateResponse
  include Interactor

  def call
    responder = context.responder

    survey_response = responder.survey_responses.build(
      response_text: context.answers[:text],
      rating: answers[:rating]
      survey: context.survey
    )

    if survey_response.save
      context.survey_response = survey_response
    else
      context.fail!(errors: survey_response.errors)
    end
  end
end

つっつきボイス:「InteractorってService Object的なもの?」「これもクリーンアーキテクチャ系の設計パターンのひとつかも: ビジネスロジックでService Objectに相当する口の部分をInteractorと呼ぶ人たちもいたと思う」「😃

そういえば以前の翻訳記事↓にもInteractor gemが登場してたのを後から思い出しました💦。チェインできることと失敗時にロールバックできる点がポイントでした。

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

# 元記事2より
# app/readers/user_reader.rb
class UserReader
    include Enumerable

    def initialize(params = {})
        @params = params
    end

    def each(&block)
        User.all.each(&block)
    end

    private

    attr_reader :params
end

つっつきボイス:「こちらはReader Objectというやつだそうです」「パターン名として見たことはないけど、どうやら参照専用のコレクションマッパーでしょうねこれは」「それも知らなかった💦」「Readerという名前を付けて渡すことで参照のみであることを強調する: ActiveRecordだと更新削除もできてしまうので」「ふむぅ」「Query Objectとはまた違う感じかな…参照のみの検索フォームで使うForm Objectにやや近い位置づけのようにも見える」

参考: データマッパー

元記事2の後半ではpagy gem↓をReader Objectと組み合わせています。

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

⚓その他Rails


⚓Ruby trunkより

⚓利用頻度の低い関数にcold属性を指定してパフォーマンスを改善


つっつきボイス:「gccのcold属性!」「じわっと改善されたみたいですね😋

参考: Function Attributes - Using the GNU Compiler Collection (GCC)coldについて記載あり

⚓引数のarray生成を減らしてパフォーマンスを向上

# 同issueより
def rest_method(*args) #-> arrayが2つ生成される
end

def post_method(*args,last) #-> arrayが3つ生成される
end

つっつきボイス:「このarrayってどのarrayなんでしょう?」「Rubyの内部処理でしょうね」「やっぱりC言語のarrayでしたか」

⚓Ruby

⚓bloomfilter-rb: ブルームフィルタのRuby実装

# 同リポジトリより
require 'bloomfilter-rb'

bf = BloomFilter::Native.new(:size => 100, :hashes => 2, :seed => 1, :bucket => 3, :raise => false)
bf.insert("test")
bf.include?("test")     # => true
bf.include?("blah")     # => false

bf.delete("test")
bf.include?("test")     # => false

# Hash with a bloom filter!
bf["test2"] = "bar"
bf["test2"]             # => true
bf["test3"]             # => false

bf.stats
# => Number of filter bits (m): 10
# => Number of filter elements (n): 2
# => Number of filter hashes (k) : 2
# => Predicted false positive rate = 10.87%

Matzのコードの未来に載っていて今頃知りました。データ量が少ないのにO(1)というハッシュ並みの速度でデータの存在確認ができるアルゴリズムだそうです。

参考: ブルームフィルタ - Wikipedia

今はScalable Bloom Filterという強化版アルゴリズムもあるそうです。

参考: Scalable Bloom Filtersとは一体....? - Qiita


つっつきボイス:「ブルームフィルタをRubyで実装したと」「Rubyの実装はだいぶ前のものみたい」「ブルームフィルタみたいな、特定条件において速いアルゴリズムは探すと結構ありますね」「業務で使えそうでしょうか?」「通常の業務ロジックでここまでカリカリにチューニングすることはまずなさそうかなー😎」「スペルチェックを実装する時なんかには便利らしいです」「そういうふうに大量のデータの中に存在するかどうかだけをチェックするにはいいでしょうね」

⚓wavefile: Rubyで音声ファイルを扱う

# 同リポジトリより
require 'wavefile'
include WaveFile

FILES_TO_APPEND = ["file1.wav", "file2.wav", "file3.wav"]

Writer.new("append.wav", Format.new(:stereo, :pcm_16, 44100)) do |writer|
  FILES_TO_APPEND.each do |file_name|
    Reader.new(file_name).each_buffer do |buffer|
      writer.write(buffer)
    end
  end
end

つっつきボイス:「音声ファイルをこんなふうにコードで結合できるのが面白いですね」「.wavファイルのデータ形式はとっても単純だから、こうやって雑にappendしても動く😉」「ファイルのどこから読んでも大丈夫なんでしょうね」「出だしとか末尾が少々壊れてても読み取れるし」

参考: WAV - Wikipedia

⚓コードの振る舞いをlambdaでスッキリさせる(Hacklinesより)

# 同記事より
def qname(qname)
  ->(message, _protocol = nil) do
    eq_case_insensitive(message.qname, qname)
  end
end

つっつきボイス:「Rubyでは普通によく使われる書き方ですね☺: JSとかでもできますし」「😃

⚓「Ruby Prize 2018」募集開始

未受賞の個人が対象で、自薦もありだそうです。

⚓tty-pie_chart: キャラクタベースの円グラフ

# 同リポジトリより
require 'tty-pie_chart'
data = [
  { name: 'BTC', value: 5977, color: :bright_yellow, fill: '*' },
  { name: 'BCH', value: 3045, color: :bright_green, fill: 'x' },
  { name: 'LTC', value: 2030, color: :bright_magenta, fill: '@' },
  { name: 'ETH', value: 2350, color: :bright_cyan, fill: '+' }
]
pie_chart = TTY::PieChart.new(data: data, radius: 5)
print pie_chart


同リポジトリより


つっつきボイス:「前回取り上げたchartと打って変わったオールドスタイルがちょっとかわいかったので❤」「ザ・デコレーターという感じ☺

⚓その他Ruby


⚓クラウド/コンテナ/インフラ/Linux/Serverless

⚓AWSのT3とNitro


つっつきボイス:「結局NitroだとAMIがそのままでは動かない: ドライバが必要になる」「今更ですみませんがNitroってそもそも何でしたっけ?🙇」「NitroはAWSが独自開発したハードウェアですね: Nitro版システムではストレージ周りでこれが使われていて、Hypervisorのドライバが違います」「なるほど😃」「なので単にインスタンスタイプを変更しただけではドライバが認識されないので起動しない😆」「つまりAMIを作り直す必要がある?」「ですね: それか頑張って何とかモジュールをロードするとか、Hypervisor部分だからそんなに深刻ではないけど面倒w」「新しくAMIを作るならNitroにしてもよさそう」「テストはされてるはずだし大丈夫じゃないかな☺: Nitroの登場は割と最近で、割安かつスペックが高いのがメリット💰: クラウドは世代が新しいほどコスト対性能比がよくなっていくものなので」

参考: AWSの「Nitro System」(ナイトロシステム)とは? ベアメタルサーバ実現の裏側 |ビジネス+IT
参考: ハイパーバイザ - Wikipedia

⚓FunctionShield: サーバーレスアプリをポリシーで保護(Serverless Statusより)

// 同サイトより: JSの場合
var AWS = require('aws-sdk');
const FunctionShield = require('@puresec/function-shield');
FunctionShield.configure(
{
    policy: {
        // 'block' mode => active blocking
        // 'alert' mode => log only
        // 'allow' mode => allowed, implicitly occurs if key does not exist
        outbound_connectivity: "block",
        read_write_tmp: "block", 
        create_child_process: "block"
    },
    token: process.env.FUNCTION_SHIELD_TOKEN
 });

exports.hello = async (event) => {
    // ... // your code
};

つっつきボイス:「ほー、AWS Lambdaで使えるアクセス保護か」「ラッパーではないと書かれてますね」「Lambdaで実装するときに思わぬところにリクエストが飛ばないようにポリシーで制限をかけるということのようだ: 『/tmp/へのアクセスを無効にできる』とかもそうで、これはLambdaで一応できるけど確かにあまりよろしくないヤツ」「お?」「Lambdaへの個別のリクエストは同じハードウェアで動く可能性があって、そのときに/tmpもリクエスト間でかぶる可能性があるので、特に固定値を置くとアブナイ」「ははー😲」「子プロセスを実行できないようにするポリシーもある: チームでLambdaコードを書くならとりあえずこのソフトウェアをインストールしておくのは悪くなさそう❤」「オープンソースですしね😃

⚓H2Oサーバーの作者Kazuhoさんのスライド


つっつきボイス:「最近H2Oってどうなってるかなと思って探してたら見つけました」「おーTCPでパケットロスしたときの影響はHTTP/2の方が大きいと: 確かにー」「HTTP1.1はマルチスレッドだけどHTTP/2はシングルスレッド(正確にはTCPのフローとかセッションですが)というのがよくわかる絵↓😆」「😆」「HTTP/2の仕組みを理解していれば納得の内容😋


同スライドより

「ところでこの『パケロス2%』↓は相当ひどいw」「2%は悪いんでしょうか?」「(カンファレンス会場の無線LANとかを別にすれば)普通ありえないですね: そんなネットワークがあったらヤバい💀


同スライドより

「そして結論は『場合による』☺

参考: HTTP/2 サーバープッシュ : H2OとRuby on Rails 5.2beta、HTTP 103 EarlyHintsでページ高速化 - Qiita
参考: HTTP の新しいステータスコード 103 Early Hints | blog.jxck.io

⚓その他


つっつきボイス:「内容はともかく、『個人的には、ほとんどの案件においてRed Hat Enterprise LinuxかCentOSしか使わないので』という記述があって、普段Red Hat Enterpriseをめったに見かけないのでちょっと気になりました」「Red Hat Enterprise Linuxは何度か使ってますけど、高いからなー😆」「どういうところで使うんでしょうか?」「主に有料サポートの付いたOSでないと導入が認められない組織ですね: SIer向け」「やはり」

「ちなみにCentOSの中身はRed Hat Enterprise Linuxと基本一緒で、有料ソフトウェアの違いぐらい」「そういえばそうでしたね」「ただセキュリティパッチなんかはRed Hatが投稿しているのでその分早く使えたりします🕶: CentOSはあくまでオープンソースソフトウェアなので」

参考: CentOS - Wikipedia
参考: Red Hat Enterprise Linux - Wikipedia

⚓SQL

⚓RedisGraph: Redisで動くグラフデータベース


oss.redislabs.comより

↓Dockerでデモを動かせます。

docker run -p 6379:6379 -it --rm redislabs/redisgraph
$ redis-cli
127.0.0.1:6379> GRAPH.QUERY MotoGP "CREATE (:Rider {name:'Valentino Rossi'})-[:rides]->(:Team {name:'Yamaha'}), (:Rider {name:'Dani Pedrosa'})-[:rides]->(:Team {name:'Honda'}), (:Rider {name:'Andrea Dovizioso'})-[:rides]->(:Team {name:'Ducati'})"
1) (empty list or set)
2) 1) Labels added: 2
   2) Nodes created: 6
   3) Properties set: 6
   4) Relationships created: 3
   5) "Query internal execution time: 0.399000 milliseconds"


同記事より


つっつきボイス:「グラフ理論のグラフ↑ですね」「Redisってモジュール入れられるのかー😳: しかも他にもいろいろあるし↓」「Redisってもう単なるKeyValueストアじゃないですね」「Redisは最早SQLではない何か🧞‍♂️」「Redis、恐ろしい子…!」


redis.ioより

ブルームフィルタのモジュールもあるし」「cthulhuっていうモジュールもいい名前❤」「クトゥルー😆」「RedisからJSを呼べるみたい: 何でこの名前にしたのか知らんけど😆

参考: クトゥルフ - Wikipedia

⚓JavaScript

⚓Vue CLI 3ではVueをTypeScriptで書けるようなった


cli.vuejs.orgより


つっつきボイス:「Vue CLIそのものは開発ツールなんですね」「みなさんやっぱりTypeScriptに向かってるらしき☺」「社内にもTypeScript使いたい勢がいますね」「自分も生JSはヤダ😆

yarn global add @vue/cliしてプロジェクトを作ってみました↓。

参考: Vue CLI 3.0 で TypeScript な Vue.js プロジェクトをつくってみる - Qiita

⚓たった1行のJSコードでパフォーマンスが激悪化(Frontend Weeklyより)


同記事より


つっつきボイス:「頑張って診断してる」「以下が犯人だったそうです↓」「JSON.stringifyねー: deep cloneしてたら確かにそれは遅くなる🧐

return JSON.parse(JSON.stringify(this._data));

⚓CSS/HTML/フロントエンド/テスト

⚓待てる応答時間

これも本日のBPS社内勉強会で登場した定番記事です。

  • 0.1sec: 遅延なしで操作できると感じられる上限
  • 1.0sec: ユーザーの思考を中断しない上限
  • 10sec: ユーザーが注意を維持できる上限

つっつきボイス:「1993年の記事なので、コマンドラインベースの端末しかなかったような時代の話ですけどね😆: Webになるともっと厳し目になるし」

上の続編としてWebサイトの応答時間についての考察記事もありました。

⚓HTTPSは僻地でのネットアクセスに不利(Frontend Weeklyより)


つっつきボイス:「HTTPSにしたことで少々遅くなるのは珍しくないだろうけど?」「お、『geosynchronous-satellite internet access』での話か」「衛星経由だったかー」「それだけでめちゃめちゃ不利」

著者はウガンダの農村部でソフトウェア開発を教えていて、そこでは衛星インターネット以外のアクセス手段がほとんどなく、Wikipediaを開くだけでも往生するそうです。遅さを緩和するためにローカルキャッシュサーバーを立てたのに、そこの環境だとほぼまったくHTTPSページのキャッシュが効かず困り果てています。

↓記事と直接関係ありませんが、ウガンダのスーダン難民キャンプでのインターネット接続の改善を訴える動画です。

⚓⭐書籍『Real World HTTP』⭐

  • HTTP/1.0 のシンタックス:基本となる 4 つの要素
  • HTTP/1.0 のセマンティクス:ブラウザの基本機能の裏側
  • Go 言語による HTTP/1.0 クライアントの実装
  • HTTP/1.1 のシンタックス:高速化と安全性を求めた拡張
  • HTTP/1.1 のセマンティクス:広がる HTTP の用途
  • Go 言語による HTTP1.1 クライアントの実装
  • HTTP/2 のシンタックス:プロトコルの再定義
  • HTTP/2 のセマンティクス:新しいユースケース
  • Go 言語による HTTP/2、HTML5のプロトコルの実装
  • セキュリティ:ブラウザを守るHTTPの機能
  • クライアント視点で見るRESTful API
    同記事より

つっつきボイス:「実はGo 1.11 Release Party in Tokyoに行ってきまして、そこで最後にお話しされていた渋川さんの本です」「おー、これ結構よさそうな本じゃないですか❤今こういう本を出すことに意義がありますね」「😃」「HTTPの本って古いのしかなかったし、HTTP 1.1は拡張に次ぐ拡張を繰り返してきたから、今どうなっているのかがわかる本は貴重💎」「目的を絞り込んでいるのもよさそうですね」「それもあるし、HTTP/2が当たり前に使われるようになった後の時代にマッチしていますね😋

「HTTPのRFCを読むというのは?」「現実にはすべて読みこんで理解するのは不可能ですね😤」「ありゃ」

遅ればせながら今週の⭐を進呈いたします。おめでとうございます。

⚓言語よろずの間

⚓Goのバイナリサイズを小さくするテクニック

Go 1.11 Release Party in Tokyo - connpassで発表された資料です。

なおGo 1.11は昨日リリースされました🎊


つっつきボイス:「こちらが渋川さんのスライドです: Goって中身がほぼ空っぽのソースでもバイナリサイズが1MBぐらいになってしまうので、WebAssemblyだとサイズのデカさが不利になりがちです😢」「GCとかいろいろ機能を内包しているからしょうがないですね: shared libraryにリンクってできます?」「一応できます」

「スライドにあった『GoはリポジトリのURLとパッケージが密結合してる』これねー: Goの仕様を最初に見たときにこれ大丈夫か?って思ったけど、意外とみんな使ってるから大丈夫なのかな?」「いやー大丈夫じゃないですね💦: パッケージの依存関係管理はdepというツールに収束しそうではあるものの今も大変で、RubyのBundlerって本当に偉いなと思います」「そこでGoが見せてくれる夢はわかるけど、スライドにもあるようにローカルのプライベートパッケージまでやり始めると大変だろうなーとは思う☺」「そうなんですよ~😢


bundler.ioより

参考: 依存関係管理ツールdep(golang) - Qiita

⚓その他

⚓GoogleのCloud AutoMLで自分のテキスト学習モデルを育成できるようになった


つっつきボイス:「自分が持っている原文訳文を大量に食わせることで、機械翻訳なんかの学習モデルを自分で育成できるんだそうです: 週末遊んでみようかなと」「データを大量に持ってる人ならいいかもねー☺

参考: Google、Cloud AutoMLでテキスト分析と翻訳に対応、認知系AIサービスも強化:Google Cloud Next ’18 - @IT

⚓Basecamp「ヒルチャート」機能でプロジェクトの問題を直感的に理解する



同記事より


つっつきボイス:「ヒルチャートって初めて知ったんですけどちょっとよさそうだなと思って」「ふむぅ、進捗管理とかでヒルを超えてないタスクがどのぐらいあるかをわかりやすく見せる感じ?」「ひと目でわかるのがいいなと思いました😋」「プロジェクト管理向きかも」

追いかけボイス:

参考: New in Basecamp: See where projects really stand with the Hill Chart


m.signalvnoise.comより

⚓番外

⚓世界の中心はオレ


つっつきボイス:「これも渋川さんのリツィートからです」「今日は宇宙じゃなくて古代にw」「お、早めに終わったので台風が来る前に撤収〜」


今回は以上です。

バックナンバー(2018年度後半)

週刊Railsウォッチ(20180820)Railsで構築されたサイト40選、Deviseはつらいよ、ARのスコープとクラスメソッドの使い分けほか

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

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

Rails公式ニュース

Ruby Weekly

Hacklines

Hacklines

Serverless Status

serverless_status_banner

Frontend Weekly

frontendweekly_banner_captured

週刊Railsウォッチ(20180903)次世代アップローダーgem「Shrine」、RSpecをどこまでDRYに書くか、Rubyのmainオブジェクトの秘密、GitLabのCookie利用許諾機能はエライほか

$
0
0

こんにちは、hachi8833です。少し涼しくなったらなぜかGobyのコミット頻度が上がってきました。

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを社内有志でつっついたときの会話です👄

⚓第2回「週刊Railsウォッチ 公開つっつき会」いよいよ今週木曜開催

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

⚓rake initializersタスクをrailsコマンドに移動

# railties/lib/rails/commands/initializers/initializers_command.rb#3
+module Rails
+  module Command
+    class InitializersCommand < Base # :nodoc:
+      desc "Print out all defined initializers in the order they are invoked by Rails."
+      def perform
+        require_application_and_environment!
+         Rails.application.initializers.tsort_each do |initializer|
+          puts "#{initializer.context_class}.#{initializer.name}"
+        end
+      end
+    end
+  end
+end

つっつきボイス:「またrakeからrailsコマンドに引っ越しですね」「rails initializersというコマンドがそもそもあったのかー😲: 使ったことないな」「私もです」「Railsディレクトリでbundle exec rake -vTしてみるとたしかに↓こう出てくる」

rake initializers # Print out all defined initializers in the order they are invoked by Rails

「でbundle exec rails initializersしてみると…」「お~出た出た🌙

「なるほど、イニシャライザをパスとともに起動順に表示してくれるのね😋: rake middlewareというミドルウェアを表示してくれるコマンド↓があるけど、それのinitializer版みたいなものかな」「起動順を追いたいときによさそう😃

⚓コントローラ名が複合語のときにrails routes -cできない問題を修正

rails routes -c UserPermissionsControllerができるようになりました。

# actionpack/lib/action_dispatch/routing/inspector.rb#L83
      private
        def normalize_filter(filter)
          if filter[:controller]
-           { controller: /#{filter[:controller].downcase.sub(/_?controller\z/, '').sub('::', '/')}/ }
+           { controller: /#{filter[:controller].underscore.sub(/_?controller\z/, "")}/ }
          elsif filter[:grep]
            { controller: /#{filter[:grep]}/, action: /#{filter[:grep]}/,
              verb: /#{filter[:grep]}/, name: /#{filter[:grep]}/, path: /#{filter[:grep]}/ }
           end
         end

つっつきボイス:「rails routes -cがそもそもかなり新しそう」「Rails 6.0向けかなと思ったら5.2にもバックポートされてる」

⚓ActionCableのテストに便利なアダプタを追加

#33635もこれと関連するPRのようです。

+# actioncable/lib/action_cable/subscription_adapter/test.rb
+# frozen_string_literal: true
+require_relative "async"
+module ActionCable
+  module SubscriptionAdapter
+    # == Test adapter for Action Cable
+    #
+    # このテストアダプタはテスト以外では使わないこと
+    # ActionCable::TestHelperと併用することでRailsアプリのテストに有用
+    #
+    # このテストアダプタを使うにはcable.ymlのアダプタ値に「test」を設定する
+    #
+    # メモ: テストアダプタはActionCable::SubscriptionsAdapter::Asyncをextendするので
+    # システムテストでも使える
+    class Test < Async
+      def broadcast(channel, payload)
+        broadcasts(channel) << payload
+        super
+      end
+       def broadcasts(channel)
+         channels_data[channel] ||= []
+      end
+       def clear_messages(channel)
+        channels_data[channel] = []
+      end
+       def clear
+        @channels_data = nil
+      end
+      private
+        def channels_data
+          @channels_data ||= {}
+        end
+    end
+  end
+end

つっつきボイス:「broadcastとかclear_messagesとか、こういうテストアダプタが欲しいのわかる」「😀」「ActionCableのテストは確かに難しいよな〜: pub/subのテストってマルチスレッドにしないとちゃんとできないし」

⚓ActiveStorageでファイルが見つからない場合に500ではなく404エラーを出すように修正

これによって#33647がcloseされました。

# activestorage/app/controllers/active_storage/disk_controller.rb#L7
 class ActiveStorage::DiskController < ActiveStorage::BaseController
   skip_forgery_protection

   def show
     if key = decode_verified_key
       serve_file disk_service.path_for(key), content_type: params[:content_type], disposition: params[:disposition]
    else
      head :not_found
    end
+   rescue Errno::ENOENT
+     head :not_found
  end

つっつきボイス:「うん、これは404エラーが正しいっすね💪

⚓空のトランザクションからBEGINCOMMITを除外

トランザクションでクエリが実行されずにopen/closeされる場合はBEGINやCOMMITを安全に除外できる。このPRは変更なしのレコードをsaveするときのオーバーヘッドを取り除くことでsave if changed?を不要にできる。
同PRより大意


つっつきボイス:「言われてみればRailsって、トランザクションで何もしていないのにBEGINやCOMMITを吐くことがあったし: そのあたりが修正されるということかな」「広範囲にがっつり更新されてますね: save if changed?しなくてよくなるみたい」「このあたり↓が改修の中心のようだけど複雑なことしてる感: マテリアライズというとPostgreSQLなんかのマテリアライズドビューみたいで中々紛らわしい…」「ですね💦」「やりたいことはだいたいわかる: シングルのトランザクションならそもそもBEGIN TRANSACTIONで囲まなくていいとか、そういうところもやってるんだろうし」「そのうちRailsガイドあたりで解説が付きそうな予感🙏

# activerecord/lib/active_record/connection_adapters/abstract/transaction.rb#L110
+     def materialize_transactions
+       return if @materializing_transactions
+       return unless @has_unmaterialized_transactions
+        @connection.lock.synchronize do
+         begin
+           @materializing_transactions = true
+           @stack.each { |t| t.materialize! unless t.materialized? }
+         ensure
+           @materializing_transactions = false
+         end
+         @has_unmaterialized_transactions = false
+       end
+     end

参考: マテリアライズドビュー - Wikipedia

⚓tryを20%高速化

Twitterで拾ったsgrifさんのPRです。

Warming up --------------------------------------
                 old   179.987k i/100ms
                 new   199.201k i/100ms
Calculating -------------------------------------
                 old      3.029M (± 1.6%) i/s -     15.299M in   5.052417s
                 new      3.657M (± 1.2%) i/s -     18.326M in   5.012648s

Comparison:
                 new:  3656620.7 i/s
                 old:  3028848.3 i/s - 1.21x  slower
# activesupport/lib/active_support/core_ext/object/try.rb#L5
module ActiveSupport
  module Tryable #:nodoc:
-   def try(*a, &b)
-     return unless a.empty? || respond_to?(a.first)
-     if a.empty? && block_given?
+   def try(method_name = nil, *args, &b)
+     if method_name.nil? && block_given?
        if b.arity == 0
          instance_eval(&b)
        else
          yield self
        end
-     else
-       public_send(*a, &b)
+     elsif respond_to?(method_name)
+       public_send(method_name, *args, &b)
      end
    end
-    def try!(*a, &b)
-     if a.empty? && block_given?
+   def try!(method_name = nil, *args, &b)
+     if method_name.nil? && block_given?
        if b.arity == 0
          instance_eval(&b)
        else
          yield self
        end
      else
-       public_send(*a, &b)
+       public_send(method_name, *args, &b)
      end
    end
  end
end

つっつきボイス:「respond_to?を後回しにしたりして最初の条件チェックを軽くしてるっぽいですね」「empty?よりnil?の方が速いとかそういう感じかな?: 微細な最適化だけどこういうのが効いたりしますね😋」「引数名もaとかじゃなくてmethod_nameに変えてるし」「tryは結構使われるし、ループで使ってたら効果ありそう: いい修正」「tryというとこの記事思い出しちゃいます↓」「何でもtryしたらそもそも遅くなるし😎

Railsの`Object#try`がダメな理由と効果的な代替手段(翻訳)

⚓ActiveModelで時間の乗算を20%高速化

これもTweetで見つけました。

# activemodel/lib/active_model/type/helpers/time_value.rb#L70
          # Doesn't handle time zones.
          def fast_string_to_time(string)
            if string =~ ISO_DATETIME
-             microsec = ($7.to_r * 1_000_000).to_i
+             microsec_part = $7
+             if microsec_part && microsec_part.start_with?(".") && microsec_part.length == 7
+               microsec_part[0] = ""
+               microsec = microsec_part.to_i
+             else
+               microsec = (microsec_part.to_r * 1_000_000).to_i
+             end
              new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
            end
          end
       end

つっつきボイス:「これも高速化」「microsec_part.start_with?(".")…?ははぁ、小数の場合は文字列として分解することで計算しないようにしてるのか!そりゃ速くなるな」「大胆だな〜🤯

⚓Rails

5.0->5.1で地味に名前が変わっていたActiveModel::Dirty系メソッド

2017年の記事です。場所もActiveRecord::AttributeMethods::Dirtyに変わったものがあるようです。

Rails 5.0 Rails 5.1〜
<attribute>_changed? saved_change_to_<attribute>?
<attribute>_was <attribute>_before_last_save
changed? saved_changes?
changed_attributes saved_changes.transform_values(&:first)

つっつきボイス:「今日kazzさんが<attribute>_wasという以前からあるメソッドを使ってみようかなと言ってたので、私もちょっと調べてみたら早くも名前が変わってました」「wasが消えた話、そういえばどっかで聞いたな〜: とにかく名前がわかりにくいし、そもそもDirtyあまりに複雑すぎるから、メソッド名を長くしてでもわかりやすくしたいというのはわからんでもない😎」「wasはあんまりですね〜😢」「ちょろいトランザクションならともかく、それ以上はどうも不安が残るんで自分もあんまり使わないし😆

参考: Ruby on Rails 5.2 / ActiveRecord::AttributeMethods::Dirty — DevDocs
参考: Ruby on Rails 5.2 / ActiveModel::Dirty — DevDocs

ActiveRecordの便利機能previous_changes


追いかけボイス:「wasが名前変わった話知ることができてちょうど良かった〜」「タイムリーでしたね😋

参考: Rails 5.1 で attribute_was, attribute_change, attribute_changed?, changed?, changed 等が DEPRECATION WARNING - Qiita

上のPRの下の方でこの記事↓がよくまとまってると紹介されてました。

参考: Cleaning Up: ActiveRecord::Dirty 5.2 API Changes - The Lean Software Boutique

⚓Railsは2018年の今も現役か?

「何がむかつくって、グローバル変数を普段ほいほい改変しておきながら『AIが世界を支配する』とかぬかす開発者だよ」

同記事より


つっつきボイス:「めちゃ長い記事なんですが、既に翻訳許可を取って今半分ぐらい翻訳しているところです: 結論だけ言うと『私は今後もRubyとRailsを使う』でした☺

⚓DHHのインタビューPodcast


つっつきボイス:「例のYouTubeチャンネルじゃなくて?」「音声だけのインタビューで文字起こしもありません😅」「じゃあ頑張って聞くしかない😆」「やっぱりダルいのでapp.voicebase.comというサービス↓に試しに流し込んでみたら思ったよりよくできてました: 機械起こしの文字をクリックするとその位置にジャンプ再生できるので少々の聞き取りミスがあっても気にならないし」「こういうのいいっすねー😋: ディクテーションの練習になりそうだし」「これ日本語でできたら最高😃


app.voicebase.comより

⚓torque-postgresql: RailsでPostgreSQLの高度な機能を使うgem(Ruby Weeklyより)

# 同リポジトリより
create_table :users do |t|
  t.string :name
  t.role :role                            # Uses the type of the column, since enum is a type
  t.enum :status                          # Figures the type name from the column name
  t.enum :last_status, subtype: :status   # Explicit tells the type to be used
end

つっつきボイス:「ぽすぐれのEnumとArrayは確かRails本家でも最近サポートされてたような気がするけどRails 6からだったかな…?」

↓EdgeguidesにはArrayが載っていました。Enumなどの複合型はサポートしていないようです。

参考: Active Record and PostgreSQL — Ruby on Rails Guides

「どのバージョンのRailsで動くのかをチェックするか(torque_postgresql.gemspec)↓」「5.0〜5.1ですね」「せま〜い😆このあたりの機能はRailsが今後サポートに含める可能性があるので、今gemで対応すると後で少々面倒になるかもね😉」「それもそうか〜」「どうしても必要なら別ですけどね☺

s.add_dependency ‘rails’, ‘>= 5.0’, ‘< 5.2’

⚓DCIアーキテクチャとは

1本目の記事はすごく長い論文で、10年ぐらい前に翻訳されたものです。


元記事1より


つっつきボイス:「上述のIs Rails still relevant in 2018 ?の中で言及されていて知りました」「DCIって何の略?名前ぐらいは聞いたことあるけど」「えーと『データコンテキストインタラクション』: MVCアーキテクチャに対するものとして説明されている感じ」「ははぁ、こういう図ね↑コントローラとモデルをコンテキストとして扱う、でモデルの本体がオブジェクトなのか: でコンテキストはシナリオレベルで作ると↓」

  • データ:これはドメインオブジェクトの中にあり、ドメインクラスに由来している。
  • コンテキスト:要求に応じて、アクティブなオブジェクトをシナリオにおけるポジションに位置づける。
  • インタラクション:ロールの観点からエンドユーザのアルゴリズムを記述するもの。ロールもアルゴリズムもエンドユーザの頭の中に存在する。
    元記事1より

「腰を据えて読まないと評価しようがないけど、大規模で複雑なアプリをユースケースからブレイクダウンして実装する場合によさそうに見える🧐


おまけ: 同じ記事OODDという初めて見る略語から以下のコワモテ動画にリンクされてました。英BBCの番組というのがまた不思議。

参考: Object Oriented Versus Functional

「なぜかググってもOODDの略語の意味がなかなか見つからなくて、こちら↑でやっと『Object-Oriented Decomposition and Design』だとわかりました: それに賛同しているっぽいElegant Objectsというサイト↓はnull禁止/ゲッターセッターコンストラクタ禁止とか主張が強めな感じでちょっとビビりました💦」「こういうのは同じようなものにいろんな人がいろんな名前を付けたりしますからね〜☺


elegantobjects.orgより

⚓Railsコンソールの便利ワザ(Hacklinesより)


つっつきボイス:「1.の--sandbox、へぇぇこんなのあったんだ!😳」「使ったことあったかも」「ロールバックが複雑になったときにどこまでできるかな?😆

「2.の_で前の値が取れるのってRailsコンソールの機能じゃなくてIRBとかPryでもできたと思うし」「そっか😲」「うん、irbでもできた💪」「3もまあ普通かな」

「4.や5.の.method(:inquiry).source_location.method(:inquiry).source.display↓はこれもRubyの機能かな」「6.のhelperは普通にRailsコンソールでコントローラから取れます」

「7のappオブジェクト↓もありますね: app.getとかはテスト書くときに使いますし」「そうだった💦

# 同記事より
>> app.get('/')
Started GET "/" for 127.0.0.1 at 2018-08-25 22:46:52 +0000
   (0.5ms)  SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
Processing by HomeController#show as HTML
  Rendering home/show.html.erb within layouts/application
  Rendered home/show.html.erb within layouts/application (11417.2ms)
  Rendered shared/_menu.html.erb (3.6ms)
  Rendered shared/header/_autocomplete.html.erb (292.2ms)
  Rendered shared/_header.html.erb (312.9ms)
  Rendered shared/_footer.html.erb (3.7ms)
Completed 200 OK in 11957ms (Views: 11945.5ms | ActiveRecord: 0.0ms)
=> 200

「これはルーティングのパスを取れる↓」「6.と7.はRails的、他はRuby寄りの機能かな」「😃

# 同記事より
>> app.methods.grep(/_path/).grep(/game/)
=> [:search_games_path, :game_ownlist_placements_path, :game_ownlist_placement_path, :game_wishlist_placements_path, :game_wishlist_placement_path, :game_path]

⚓対決!Node.js vs Rails(Hacklinesより)


つっつきボイス:「Railsの強みというとやっぱり生産性ですかね」「Nodeの『growing fast』ってメリットなのかと😆」「『変化が激しすぎる』🤣

⚓RSpecをどこまでDRYに書くのが正しいかを探る(Hacklinesより)

# 同記事より: bad
describe 'token verification' do
  shared_examples 'invalid token' do |expected_error|
    it 'responds with the expected error' do
      response_json = JSON.parse(response.body)

      expect(response).to be_forbidden
      expect(response_json.fetch("error").to eq expected_error
    end
  end

  subject { get :test }

  context 'missing header' do
    include_examples 'invalid token', 'Authorization header not found'
  end

  context 'bad header' do
    before { request.headers.add('Authorization', 'badvalue') }
    include_examples 'invalid token', 'Incorrect authorization token'
  end
end


同記事より


つっつきボイス:「どこまでDRYに…😅😅😅」「テストのduplication、これ判断が難しいよね〜: 自分ならテストコードの重複をそこまで気にするよりも、少々重複してもいいから書けばよくね?と思うし」「上の図↑みたいにコントローラのテストとサービスのテストが同じところをテストすると重くなるのは確かだし、重複を気にするのももっともなんだけど、それよりテストのカバレッジを上げる方が有用なのではと思うし😎」「この記事ももちろん有用❤

⚓⭐Rails向け画像アップローダShrine⭐


同記事より


つっつきボイス:「Revisited!」「前にもそういう記事書いたんでしょうね」「結論はActiveStorageかShrineだそうです」「ActiveStorageは言わずもがなだけど、Shrineって?」「初めて見た」「★1800超えてるから使っている人はけっこういそう」


shrinerb.comより

「↓こういう感じか: プラグインを使えたりフォームを拡張したりできる」

# 同リポジトリより
require "shrine"
require "shrine/storage/file_system"

Shrine.storages = {
  cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"), # temporary
  store: Shrine::Storage::FileSystem.new("public", prefix: "uploads"),       # permanent
}

Shrine.plugin :sequel # :activerecordでもよい
Shrine.plugin :cached_attachment_data # 複数のフォーム再表示用にキャッシュファイルを保持
Shrine.plugin :restore_cached_data # キャッシュされたファイルのアタッチ時にメタデータを再抽出
Shrine.plugin :rack_file # Rails以外のアプリ向け

「Uploaderのクラスを作るところはCareerWaveっぽいかな」

# 同リポジトリより
class ImageUploader < Shrine
  # 画像添付のロジックをここに書く
end
uploader = ImageUploader.new(:store)
uploader #=> `:store`で登録されたストレージ向けのアップローダー

「お、ストリームを直接扱えるのか😋: これは結構よさそう」

# 同リポジトリより
uploaded_file.url                 #=> "https://my-bucket.s3.amazonaws.com/949sdjg834.jpg"
uploaded_file.open                # アップロードされたファイルを開く
uploaded_file.download            #=> #<File:/var/folders/.../20180302-33119-1h1vjbq.jpg>
uploaded_file.stream(destination) # アップロードされたコンテンツを書込み可能な相手にストリーミングする
uploaded_file.exists?             #=> true
uploaded_file.delete              # ストレージのファイルを削除

# アップロードされたファイルをブロックの中でオープン/ダウンロード
uploaded_file.open     { |io| io.read }
uploaded_file.download { |tempfile| tempfile.read }

「AWS S3もこうやって扱えるみたい」

# 同リポジトリより
# Gemfile
gem "aws-sdk-s3", "~> 1.2" # for AWS S3 storage
require "shrine/storage/s3"

s3_options = {
  bucket:            "my-bucket", # required
  access_key_id:     "abc",
  secret_access_key: "xyz",
  region:            "my-region",
}

Shrine.storages = {
  cache: Shrine::Storage::S3.new(prefix: "cache", **s3_options),
  store: Shrine::Storage::S3.new(**s3_options),
}

「しかもrewindを明示的に行える↓し: いいじゃないの~❤」「rewindできるアップローダーってあんまりないかも?」「ファイルのIOを取るには本来ファイルが手元にないとできないはずなんだけど、Shrineは手元になくてもできそうな雰囲気」「そういえばHTTP 1.1のchunkedを使うと指定のバイトから取り出せたりしますけど、Shrineがそれとうまく連携できていればレジュームもできるのかな?なんて期待しちゃいますね😋」「動画を途中から再生するとか」

参考: HTTP/1.1 の Transfer-Encoding: chunked をビジュアライズするツール書いてみた - blog.nomadscafe.jp

# 同リポジトリより
uploaded_file.read   # アップロードされたファイルのコンテンツを返す
uploaded_file.eof?   # IOをすべて読み終わったらtrueを返す
uploaded_file.rewind # IOを巻き戻す
uploaded_file.close  # IOをクローズする

「Rackでダイレクトアップロードもできるのかっ😃」「Railsのワーカーにファイルを流さずに済むと」

# 同リポジトリより
# config.ru (Rack)
map "/images/upload" do
  run ImageUploader.upload_endpoint(:cache)
end

# OR

# config/routes.rb (Rails)
Rails.application.routes.draw do
  mount ImageUploader.upload_endpoint(:cache) => "/images/upload"
end

「Shrine、CarrierWaveよりも痒いところに手が届いてる感じ😋: ものすごく大きなファイルにアクセスするときはストリームで引き回せるといいなと思ったりするし」「気が利いてますね👍」「このとおりにちゃんと動くならいいヤツ!」「インターフェイスもほぼCarrierWaveっぽいし」「実績もシェアもあるCarrierWaveで足りるならわざわざShrineを使うこともないかもしれないけど、CarrierWaveだと機能不足ならShrine悪くなさそう😍

「Shrine(神社仏閣)って名前も面白いっすね」「ところで元記事を見ると『CarrierWaveRefileDragonflyは使ったことないのでわかりましぇん』とあるし🤣」「そこだけ投げやり🤣」「まあShrineから先に覚えたらCarrierWave欲しくないかもね☺」「Dragonflyってまだあったとは: 一応更新されてるけどそんなにメンテされてないかな〜」

Shrineに今週の⭐を進呈したいと思います。おめでとうございます。

⚓GitLabのCookie利用許諾機能はエライ


つっつきボイス:「あーGitLabのこれね↓: 例のGDPR対応のときからやってる」「Cookieをこれとこれになら使っていいよ、とユーザーが選択できる: これはほんとエライ」「さすが丁寧なご挨拶」「GitLabとしてはライバルのGitHubにこういうところで差をつけたいだろうし🤣」「🤣」「『うちはあそこより正しい』を強調しすぎたら逆効果でしょうけど🤣」「これが当然という感じで黙々とやっていくでしょうね」


gitlab.comより

「Cookieをこう使っていいですか宣言って今後普及しそうですね」「いやマジで、こういうのがgemとかJSのライブラリとして提供されてもいいぐらい: Cookieでやりたいことなんてどこのサイトも基本同じなんだし」「フォーマットも同じでいいんだし」「これは共通化すべき」

Railsアプリと技術ブログにGDPRコンプライアンスを追加する(翻訳)

⚓Ruby trunkより

何だかbugs.ruby-lang.orgにスパム書き込みが繰り返されてたっぽいです。

⚓ENVのエンコーディングがUTF-8にならないことがある?

$ irb
2.5.1 :001 > 'secret'.encoding
 => #<Encoding:UTF-8>
2.5.1 :002 > ENV['PASS'] = 'secret'; ENV['PASS'].encoding
 => #<Encoding:US-ASCII>
2.5.1 :009 > ENV['PASS'] = 'Ł'
 => "\u0141"
2.5.1 :010 > ENV['PASS'].encoding
 => #<Encoding:ASCII-8BIT>

つっつきボイス:「私のmacOS環境では普通にUTF-8になりました」「もしかするとターミナルの言語設定に影響されてるのかな?」

⚓Ruby

⚓Rubyの変数の「シャドウイング」(Ruby Weeklyより)

割と長い記事です。

# 同記事より
x = 42
3.times { |x| puts "x is #{x}" }
$ ruby variable_shadowing.rb
x is 0
x is 1
x is 2

つっつきボイス:「こういうの↑をシャドウイングって言うというのを聞いたことはある: ブロックの外のxとブロックのxが別物になるヤツ」「正直、Rubyのシャドウイングは別につらくない: つらいのはやっぱりJavaScript😆」「やっぱり〜😆

参考: シャドーイング – rilakkuma3xjapan’s blog

「JavaScriptのつらさといえばもうthisに集約されますよね😭」「あれもワカラン😭

参考: 【JavaScript基礎】thisとは何か・シーン別参照先のまとめ - KDE BLOG

「Rubyで気をつけたいものといえば、mixinしたときの変数の重複: これ踏むとけっこうビビる」「includeするモジュール同士でかぶることもあるし」「解決するにはモジュールのancestorsを調べたりとか面倒になりがち」「だからconcernsを書くときなんかはできるだけ変数名がかぶらないように結構気遣いますよ」「モジュールでインスタンス変数を使う場合も相当気をつけないとヤバい」「そうそう、かなりヤバみある😅

⚓Rubyのモジュールをクリーンにinjectする(Ruby Weeklyより)

# 同記事より
2.5.1 :001 > module PrintIncludedClass
2.5.1 :002?>   def self.included(base)
2.5.1 :003?>     puts base.class
2.5.1 :004?>   end
2.5.1 :005?> end
 => :included 
2.5.1 :006 > Object.include(PrintIncludedClass)
Class
 => Object

つっつきボイス:「もじゅーるくりーんいんじぇくしょん!: Railsならincludedでフックかければできるっていう話かな」「includedってRailsの機能?」「Rubyの機能ですね」「RailsではActiveSupport::Concern#includedか: Railsではこっちを使うのが推奨されてる」「これがないとconcernsの意味がなくなるヤツ」「このあたりのメソッドって、クラスを拡張するのかインスタンスを拡張するのかとか毎回調べてる💦

参考: Ruby 2.5 / Module#included — DevDocs
参考: Ruby on Rails 5.2 / ActiveSupport::Concern#included — DevDocs

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

⚓「Roseメモ化」でモジュールの一部だけをincludeする(Hacklinesより)

# 元記事1より
module MyModule
  def the_method_i_want
    "Hello!"
  end

  def method_i_do_not_want
    "I don't think so."
  end
end

class MyClass
  def run_demo
    # このヘルパーオブジェクトを明示的に呼べる
    helpers.the_method_i_want

    # 委譲もできる:
    the_method_i_want

    # これはできない
    method_i_do_not_want
  end

  private

  def helpers
    @helpers ||= Object.new.extend(MyModule)
  end

  extend Forwardable
  def_delegator :helpers, :the_method_i_want

  # Railsなら以下で委譲することもできる
  delegate :the_method_i_want, to: :helpers
end

MyClass.new.run_demo

この方の造語だと思いますが、元記事2で@ ||=を「ASCII Rose」と呼んでいます。


つっつきボイス:「モジュールのパーシャルインクルードとかやめて〜🤣普通にモジュール分けようよ」「🤣」「MyModule.extend(MyModule)とかトリック感ヤバい」「まあでもライブラリなんかだと、こうでもしないと実現できないことってありそう」

「この記事の人、@インスタンス名 ||=をRose memoizationって名付けてますね」「@ ||=って、バラの花を横に倒したものに見立ててるのか〜」「||=は大好き😋」「まともにメモ化しようと思ったらインスタンス変数かクラス変数使うだろうから必ずローズになりそうなもんですけどね😆

「ところで||=は『たてたてイコール』っていうかわいい呼び方が好きなんですが、これも他では言わないだろうな…」「英語圏も含めて普通『orイコール』じゃないかな〜」「今更だけど、||=って単独の演算子なんだな: 間にスペース入ると意味変わるし」「公式だと『自己代入演算子』↓」「自己代入だと何だかちょっと意味が違うような…?🤔」「内部的に自己代入してるとか?😆」「+=とか-=と同じくくりなのかも」「まあまあ謎の名前は家庭内新聞ぐらいにしときましょう☺

参考: 演算子式 (Ruby 2.5.0)

⚓Rubyの”main”オブジェクトの謎(Hacklinesより)


つっつきボイス:「お馴染みNoah Gibbsさんの記事です」「IRBのトップレベルのmainオブジェクトをどうやって取るか」「mainってselfで取れるのか↓😲」「あー、mainって欲しくなるときあるな: プロセスコンテキストを取りたいときとか」

# 同記事より
2.5.0 :001 > self
 => main
2.5.0 :002 > self.class
 => Object
2.5.0 :003 > self.singleton_class
 => #<Class:#<Object:0x00007f9ea78ba2e8>>

「mainってObjectクラスだけど自力で拡張しているみたい: こういうのって楽しい〜😍

# 同記事より
2.5.0 :005 > self.methods.sort - Object.methods
 => [:bindings, :cb, :chws, :conf, :context, :cws, :cwws, :exit, :fg,
     :help, :install_alias_method, :irb, :irb_bindings, :irb_cb,
     :irb_change_binding, :irb_change_workspace, :irb_chws,
     :irb_context, :irb_current_working_binding,
     :irb_current_working_workspace, :irb_cwb, :irb_cws, :irb_cwws,
     :irb_exit, :irb_fg, :irb_help, :irb_jobs, :irb_kill, :irb_load,
     :irb_pop_binding, :irb_pop_workspace, :irb_popb, :irb_popws,
     :irb_print_working_binding, :irb_print_working_workspace,
     :irb_push_binding, :irb_push_workspace, :irb_pushb, :irb_pushws,
     :irb_pwb, :irb_pwws, :irb_quit, :irb_require, :irb_source,
     :irb_workspaces, :jobs, :kill, :popb, :popws, :pushb, :pushws,
     :pwws, :quit, :source, :workspaces]

「こういうのはRubyの中でC言語で書くしかないだろうなー」

/* top self */

static VALUE
main_to_s(VALUE obj)
{
    return rb_str_new2("main");
}

VALUE
rb_vm_top_self(void)
{
    return GET_VM()->top_self;
}

「これは相当楽しい: 仕事の役には立たないけど🤣」「🤣」「RubyでRubyじゃないものを動かすみたいなやんちゃしたいときとかに使えそう❤

Ruby 2.5.0はどれだけ高速化したか(翻訳)

⚓transducers-ruby: Rubyでトランスデューサー

こちらの記事↓を見て見つけました。JavaScript版のトランスデューサー解説記事をRubyで書き直したいというリクエストです。


つっつきボイス:「上の記事のそのまたリンク先の記事↓にあったトランスデューサーの解説図↓がちょっと面白いかも」「わかったようなわからないような🤣」「たぶん上はシーケンシャルな処理で、下は並列化可能という感じなんでしょうね」



同記事より

「トランスデューサーって、どこかApacheのHadoopを思わせるものがある: map/filter/reduceってまさにHadoopっぽいし、reducerって言ったりするし」「おー」「Hadoopもそのぐらいしか知りませんけど😆

参考: Apache Hadoop - Wikipedia


Wikipediaより

「そういえばRubyだとinjectreduceって同じですよね」「たぶん他の言語屋さんならreduceを使いたいんじゃないかな〜?: Matzがinjectという言葉を使ったのは何かの言語の影響だった気がする」「そういえばRubyスタイルガイドではSmalltalkの影響ってあった」

Rubyスタイルガイドを読む: 文法(8)配列や論理値など

「話ちょっと逸れますけど、Brandon Weaverさんのこの翻訳記事↓のタイトル、元は『Reducing Enumerable』なんですが日本語タイトルすっごく悩みました😅: 本文でもreduceが掛けことば的に使われまくってるし」「reduceで繰り返しを減らそう、みたいな😆」「英語でうまいこと言ってるタイトルを訳すのは大変そう😆

Ruby: Enumerableを`reduce`で徹底理解する#1 基本編(翻訳)

「あとBrandon Weaverさんは関数型言語ラブの人なので、絶対にメソッドと言わずに関数と言う」「メソッドはあくまでクラス型オブジェクト指向の用語ですね🧐」「あ〜なるほど」

⚓問題「Rubyのクラス名とファイル名は合わせるのか」(Hacklinesより)


つっつきボイス:「答えは『Railsでは合わせるけどRubyは別にそうではない』という」「まあRailsもオートローダーを使わなければクラス名とファイル名はいくら違っててもいいんですけどね😎」「そうそう: オートローダーが認識するためだけなので☺」「Javaはそこが厳しいんですよね?」「Javaはクラス名とファイル名の一致が必須: 一致してないと読み込んでくれない」「そのぐらいは強制でもいいんじゃね?とは思うけどねっ☺

⚓その他Ruby

⚓クラウド/コンテナ/インフラ/Linux/Serverless

⚓ヒッチハイカーのためのAWS Step Functionsガイド(Serverless Statusより)


同記事より


つっつきボイス:「AWSのStep Functionって何だろうと思って」「これは自分も知らなかったー: AWSって前からワークフロー系のサービスをやってたけどそれなのかな🤔

公式: AWS Step Functions (分散アプリケーションとマイクロサービスの構築) | AWS

「あれ?公式サイトの料金表クリックしたら404 not foundだし?🤔」「れれ、ほんとだ」「URLのpricing/pricingが間違ってるっぽい」「1つ取ったら見えた🤣」「公式がリンクエラーとは🤣

参考: AWS Step Functions で作る Serverless バッチシステム - Qiita — 状態遷移1000回ごとに課金だそうです

⚓SQL

⚓Railsで複数PostgreSQLデータベースの分散やった(Ruby Weeklyより)


同記事より


つっつきボイス:「こういうのはどのレベルで分散させるかでアレゲ感が違ってくる」「個人的にはアプリケーションレベルで分散頑張るよりはデータベースのドライバで頑張りたい気がするけど🕶

現実のSSDはこうやって死ぬ(Postgres Weeklyより)


同記事より


つっつきボイス:「サーバーの用途によってSSDがどう劣化していくかという記事です」「SSDって実際にデータが消えるから👻」「消える消える」「そういえばSSDの耐久テスト記事ってひと頃よくあった」

「SSDは素子ごとに読み書きできる上限(10万回とか)があるんですけど、そのブロック管理をSSDのコントローラが行っているので、SMARTのインターフェイスとかを経由しないと知りようがない」「隠蔽されてるんですね😮」「実際コントローラがやるべきだと思いますし: そして正常な空きブロックがなくなるとSSDは死ぬ」

参考: 【レビュー】SSD/HDDの“S.M.A.R.T.”情報を取得して表示する「DiskSmartView」 - 窓の杜

「私のMacbook Pro、もう5年も使ってるしSSDそろそろヤバいかな…?😅」「いやーそのぐらいなら全然問題ないっすよ💪: ましてやサーバー用SSDならスペックにもっと余裕あるものが使われてるし」「仮に自分のマシンでハイバネーションを毎日やったりしてたらあっという間に底をついちゃいますけどね」「よかったー、ハイバネーションはしてない😂」「後は毎日ディスクを動画でぎっしり埋めるとかそういうことしてなければ😎

参考: ハイバネーション - Wikipedia

⚓JavaScript

⚓lazyestload.js: viewportまで読み込みを遅延するライブラリ(Frontend Focusより)


同リポジトリより

// 同リポジトリより
img {
    transition: filter 0.3s;
}

img.lazyestload {
    width: 100%;
    filter: blur(8px);
}

つっつきボイス:「最上級😆」「あんまりlazyにすると今度は遅くなりますけどね🕶

⚓CSS/HTML/フロントエンド/テスト

⚓Let’s Encryptの快挙


つっつきボイス:「もうHTTPSが70%近い😍

続編: Let’s Encrypt・激安SSL・AWS Certificate Managerの比較と注意点

⚓「結合テストと呼ぶのをやめた話」

フロントエンドのパフォーマンスチェックリスト(Frontend Focusより)


同リポジトリより

その他CSS/HTML/フロントエンド/テスト

⚓言語よろずの間

⚓Go 2でGenerics導入の検討始まる


つっつきボイス:「Go言語のバージョン2はやっぱりGo 2と呼ばれるみたい: このダジャレが言いたかったに違いないと睨んでます☺

go-to
{形-1} : 頼りになる
{形-2} : 〔チーム・組織などの〕主力の、大黒柱の

⚓その他言語よろず

⚓その他

⚓コードには癖が残る


つっつきボイス:「元記事はマルウェア作成の犯人を捜すためみたいですけど、言われてみればコードでも普通の文章でも人物特定は十分可能だなって」「やーこれは全然あるある: 自分のコードも特徴ありありだし☺」「ボクもー😜」「git blameする前にだいたいわかっちゃうとか」「さすがに1行だけ見せられてもわからないけどねー😆

参考: プログラムのコードには、個人を識別できる“指紋”が残されている:研究結果|WIRED.jp

⚓RISC-Vを一晩で実装

まだまだやる気満々のロードマップもあります。


同リポジトリより

参考: RISC-V - Wikipedia

⚓番外

⚓眼球を3Dプリンタで

参考: 3Dプリンターで完璧に機能する目「バイオニックアイ」を作り出す研究 - GIGAZINE


つっつきボイス:「今や航空機のパーツでも3Dプリンターで作れますね✈

⚓いいこと聞いた


つっつきボイス:「最後は珍しく実用的な情報で」「そうそう、これチャ~ンス!😋」「ざわっ😋」「きら〜ん✨」「『供出』って言葉が戦時中に釣り鐘とか徴用しまくったときみたいで🤣

参考: 金属類回収令 - Wikipedia
参考PDF: 小型家電リサイクル対象品目(28品目)


今回は以上です。今週の公開つっつきでお会いできるのを楽しみにしております。

おたより発掘

バックナンバー(2018年度後半)

週刊Railsウォッチ(20180827)Ruby Prize 2018募集開始、Interactor gemとReader Object、書籍『Real World HTTP』、Basecampのヒルチャート機能ほか

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

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

Rails公式ニュース

Ruby Weekly

Hacklines

Hacklines

Postgres Weekly

postgres_weekly_banner

Serverless Status

serverless_status_banner

Frontend Focus

frontendfocus_banner_captured

JSer.info

jser.info_logo_captured

Rubyのオブジェクト作成方法を改変する(翻訳)

$
0
0

概要

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

Rubyのオブジェクト作成方法を改変する(翻訳)

Rubyの素晴らしさのひとつに、必要に応じてほぼ何でもカスタマイズできるという点があげられます。カスタマイズは便利であると同時に危険も伴うので、うっかりすると簡単に自分の足を撃ち抜いてしまいますが、十分気をつければ相当強力なソリューションを生み出すこともできます。

「Ruby Magic」では、便利さと危険は名コンビであると考えます。それではRubyのオブジェクト初期化方法を調べ、デフォルトの振る舞いを改変してみましょう。

クラスからの新規オブジェクト作成の基本

最初に、Rubyがオブジェクトを作成する方法を見てみましょう。新しいオブジェクト(インスタンス)を作成するには、そのクラスでnewを呼び出します。他の言語と異なり、Rubyのnewは言語のキーワードではなくメソッドであり、次のように他のメソッドとまったく同じように呼び出されます。

class Dog
end

object = Dog.new

このnewメソッドに引数を渡すことで、新しく作成されたオブジェクトをカスタマイズできます。引数として渡したものは、種類を問わずイニシャライザに渡されます。

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

object = Dog.new('Good boy')

繰り返しますが、Rubyのイニシャライザは他の言語のような特殊な構文やキーワードではなく、単なるメソッドです。

ということは、Rubyの他のメソッドと同様に、こうしたイニシャライザメソッドにもちょっかいを出せるのではないでしょうか?もちろん可能です!

単独のオブジェクトの振る舞いを改変する

特定のクラスから派生するどのオブジェクトからも、常にログを出力したいとしましょう(メソッドがサブクラスでオーバーライドされたときにもです)。これを実現する方法のひとつは、そのオブジェクトのシングルトンクラスにモジュールを1つ追加することです。

module Logging
  def make_noise
    puts "Started making noise"
    super
    puts "Finished making noise"
  end
end

class Bird
  def make_noise
    puts "Chirp, chirp!"
  end
end

object = Bird.new
object.singleton_class.include(Logging)
object.make_noise
# Started making noise
# Chirp, chirp!
# Finished making noise

上の例ではBird.newBirdオブジェクトが1つ作成され、シングルトンクラスを使って、そのオブジェクトにLoggingモジュールがincludeされます。

シングルトンクラスとは

Rubyでは特定のオブジェクトだけで使える固有のメソッドを利用できます。Rubyはこの機能をサポートするために、そのオブジェクトと実際のクラスの間に無名クラスを1つ追加します。メソッドが呼び出されると、実際のクラスにあるメソッドよりも、このシングルトンクラスで定義されているメソッドが優先されます。このシングルトンクラスは他のどのオブジェクトとも異なる固有のものなので、そこにメソッドを追加しても、実際のクラスから派生するオブジェクトには何の影響も生じません。詳しくはProgramming Ruby guideをご覧ください。

オブジェクトが作成されるたびにそのシングルトンクラスをいちいち変更するのはちょっとイケてません。そこでLoggingクラスのincludeをイニシャライザに移して、作成されるすべてのオブジェクトに追加されるようにしましょう。

module Logging
  def make_noise
    puts "Started making noise"
    super
    puts "Finished making noise"
  end
end

class Bird
  def initialize
    singleton_class.include(Logging)
  end

  def make_noise
    puts "Chirp, chirp!"
  end
end

object = Bird.new
object.make_noise
# Started making noise
# Chirp, chirp!
# Finished making noise

この方法はうまくいきますが、Birdのサブクラス(Duckなど)を作成する場合は、イニシャライザでsuperを呼んでLoggingの振る舞いを維持する必要があります。メソッドをオーバーライドする場合は常にsuperを正しく呼び出すのがよいとする考えもあるのですが、それなしでできる方法がないかどうか探してみましょう。

サブクラスでsuperを呼ばないと、Loggerクラスはincludeされません。

class Duck < Bird
  def initialize(name)
    @name = name
  end

  def make_noise
    puts "#{@name}: Quack, quack!"
  end
end

object = Duck.new('Felix')
object.make_noise
# Felix: Quack, quack!

では代わりにBird.newをオーバーライドしましょう。前述のとおりnewはクラスに実装されたメソッドのひとつに過ぎないので、これをオーバーライドしてsuperを呼べば、新しく作成されるオブジェクトを望みのままに改変できるのです。

class Bird
  def self.new(*arguments, &block)
    instance = super
    instance.singleton_class.include(Logging)
    instance
  end
end

object = Duck.new('Felix')
object.make_noise
# Started making noise
# Felix: Quack, quack!
# Finished making noise

しかしイニシャライザでmake_noiseを呼ぶとどうなるのでしょうか?残念ながらLoggingモジュールはシングルトンクラスにincludeされないので、結果は期待どおりになりません。

ありがたいことに解決方法がひとつあります。allocateを呼べば、デフォルトの.newの振る舞いをゼロから作り出せます。

class Bird
  def self.new(*arguments, &block)
    instance = allocate
    instance.singleton_class.include(Logging)
    instance.send(:initialize, *arguments, &block)
    instance
  end
end

allocateを呼んでクラスから新規作成されたオブジェクトは初期化されていないので、そこに追加の振る舞いをincludeしてその後でオブジェクトのinitializeメソッドを呼べばよいのです。initializeはデフォルトではprivateメソッドなので、sendを最後の手段として使わなければなりません。

Class#allocateの真実

allocateは他のメソッドとは異なり、オーバーライドは不可能です。Ruby内部ではallocateのメソッドディスパッチで通常の方法を使っていないので、newをオーバーライドせずにallocateをオーバーライドするだけでは動作しません。しかしallocateを直接呼び出してしまえば、再定義されたメソッドが呼び出されます。RubyのClass#newClass#allocateについて詳しくはRubyのドキュメントをご覧ください。

今回改変した理由

Rubyがクラスからオブジェクトを作成する方法を改変することはさまざまな危険を伴いますので、思わぬ部分に影響が生じる可能性があります。

とは言うものの、オブジェクト生成方法の改変に意味のあるユースケースもあります。実際、ActiveRecordでは別のinit_from_dbというメソッドでallocateを用いて初期化プロセスを変更し、保存されてないオブジェクトをビルドするのではなくデータベースからオブジェクトを作成しています。ActiveRecordでは、種類の異なるSTI(Single Table Instance)同士のレコードをbecomesで変換するときにもallocateを使っています。

オブジェクト作成の挙動を変えて遊ぶときに最も重要なのは、Rubyがオブジェクトを作成するときの仕組みを深く理解し、別のソリューションを受け入れることです。本記事を皆さまが楽しんでいただければ幸いです。

皆さまがRubyのデフォルトのオブジェクト作成方法を変更してどんなものを実装したかをお知らせいただけるとうれしく思います。どうぞお気軽に@AppSignalまでお知らせください。

関連記事

Railsのワナ: モデルでbooleanメソッドをオーバーライドするな(翻訳)


週刊Railsウォッチ(20180910)公開つっつき会#2、RSpecは何を参考にするか、イベントソーシング、marginalia gem、負荷テストツールvegetaほか

$
0
0

こんにちは、hachi8833です。今回の「公開つっつき会 第2回」にお集まりいただいた皆さま、ありがとうございました!おかげさまで盛況のうちに終わり、懇親会も盛り上がりました🍻

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを社内有志でつっついたときの会話です👄
  • お知らせ: 来週9/17と9/24は、週刊Railsウォッチはお休みをいただきます🙇

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

⚓ActiveJobにretryやdiscardなどのフックを追加

# activejob/lib/active_job/logging.rb#L91
+       def enqueue_retry(event)
+         job = event.payload[:job]
+         ex = event.payload[:error]
+         wait = event.payload[:wait]
+          error do
+           "Retrying #{job.class} in #{wait} seconds, due to a #{ex.class}. The original exception was #{ex.cause.inspect}."
+         end
+       end
+        def retry_stopped(event)
+         job = event.payload[:job]
+         ex = event.payload[:error]
+          error do
+           "Stopped retrying #{job.class} due to a #{ex.class}, which reoccurred on #{job.executions} attempts. The original exception was #{ex.cause.inspect}."
+         end
+       end
+        def discard(event)
+         job = event.payload[:job]
+         ex = event.payload[:error]
+          error do
+           "Discarded #{job.class} due to a #{ex.class}. The original exception was #{ex.cause.inspect}."
+         end
+       end

元の#33740の実装↓より上の実装↑の方がよいとのことです。

      def before_retry(*filters, &blk)
        set_callback(:retry, :before, *filters, &blk)
      end

つっつきボイス:「(ゲストに)ところで皆さんActiveJobって使ったことあります?」「名前ぐらいは」「まだです〜」

参考: Active Job の基礎 | Rails ガイド

「ざっと説明すると、ActiveJobはRails 4.2で導入された機能で、それまでResqueSidekiqdelayed_jobなどでまちまちに行われていた非同期処理を、ジョブの抽象表現として提供するのが目的です」「ただしActiveJob自体には定期時間になったら実行するといった機能はありません: ActiveJobは一種の抽象化クラスとして、実際のジョブはそのバックエンドにSidekiqなりdelayed_jobを配置してそちらで動かすことになります

追記(2018/09/11): ActiveJobにはset(wait: 1.week).perform_laterなどのメソッドがあり、Sidekiqも対応しているというご指摘をいただきましたので訂正します。ありがとうございます。
参考: Active Job の基礎 | Rails ガイド — 3.2 ジョブをキューに登録する

「で今回の修正はというと、ジョブのリトライ時などにフックをかけられるようにしたということのようですね」「…だいたいChangelogに書いてあるとおりかも: とりあえずログに出してくれるようになったし」「あー確かにリトライのときとかログに出したい😊」「…リトライでログが出てくれれば、何だか無限ループっぽいときにも一瞬でわかるけど、ログがないとみっちり調べないとわからないし🧐

「ちなみにコードにも出ているActiveSupport::Notificationsは、ActiveSupport標準のpub/subの仕組みですね😋: Railsのあちこちで使われているヤツ」

参考: ActiveSupport::Notifications

「なおActiveJobはまだ足りない機能が結構あるんで、実はあんまり使ってない😆」「おほ😆」「使ってみるとわかるんですけど、たとえばSidekiqだったらできることができなかったり: SidekiqってそのまたバックエンドにRedisがあってRedisのブロッキング系コマンドが使えるので『何時間後に実行』みたいなスケジューリングができるんですけど、ActiveJobにその抽象化がないので、ActiveJobでジョブを作るとそういうのができない😅」「ActiveJobで試しに書いたものの結局Sidekiqを生で使いましたね」「このあたりもうひと頑張りして欲しいところ」

参考: リスト型 — redis 2.0.3 documentation — ブロッキング系コマンドの解説

「BasecampではActiveJobってどう使ってるんだろう?🤔「あ、スケジューリングとかしない普通の非同期ジョブならActiveJobでできます: 本来単なる非同期ジョブとスケジューリングは別の話ですし」</del「スケジューリングとかはwheneverとか使ってもできますが、cronが必要だし、RedisやSidekiqならそういうのもできるからそっちでやっちゃいますね😎」「まあwheneverよりRedisサーバーを立てる方が面倒ですが☺

⚓マルチDB対応を強化

  1. replicaオプションを追加
  2. config["replica"]をチェックするreplica?HashConfigUrlConfigに追加
  3. configs_forでキーワード引数を取れるようにした
  4. configs_forinclude_replicasのデフォルトをfalseにするキーワード引数を取れるようにした
    同PRより

つっつきボイス:「以前の(Rails 4系前半ぐらいまでの)ActiveRecord::Baseのコネクションってシングルトンだったんですよ: その頃にマルチDBやろうとしたらgemをインストールしないといけなかったんですが、何てgemだったかな…?🤔」「octopus?」「あれは最近ほとんど更新されてない気が」

そういえば以前のウォッチではmulti_dbswitch_pointも挙がってました。

参考: Rails4.2のコネクションプールの実装を理解する - Akatsuki Hackers Lab | 株式会社アカツキ(Akatsuki Inc.)

「それが最近になってconnectionがconnectionsになる形で公式がマルチDBをある程度サポートするようになった」「config["replica"]が入ってきたということは、Railsもますますエンタープライズアプリを志向するようになってきてこういうのが求められるようになったということでしょうね: 実際BPSでも業務系アプリの案件が増えてきてますし」「replicaだからリードオンリー向けの機能ですね」

「ところでマルチDBってやったことあります?」「いやぁまだですね☺」「なかなかハードです😭」「どんなときに使うんでしょう?」「既存のシステムのマスターデータを読みに行かないといけないときなんかがそうですね」「Railsで使うデータベースは、サロゲートキーやidカラムを使うみたいにRailsのルールで作られるんですけど、既存のデータベースは思想が違うのでそういうふうにできていない😎

「マルチDBをサポートするgemにもいくつかパターンがあって、Railsが使うメインのデータベースとは別にサブのデータベースをリードオンリーで使えるものもあれば、複数のデータベースをすべて読み書きできるgemもありますね」「😃」「そしてマルチDBだとたいていマイグレーションがめちゃめちゃハードになるんですねこれが😭

⚓permitted_scalar_filterのアロケーションを削減

Before:
 16199  /Users/rschneeman/Documents/projects/rails/actionpack/lib/action_controller/metal/strong_parameters.r

After:

  2280  /Users/rschneeman/Documents/projects/rails/actionpack/lib/action_controller/metal/strong_parameters.rb
# actionpack/lib/action_controller/metal/strong_parameters.rb#L926
-     def permitted_scalar_filter(params, key)
-       if has_key?(key) && permitted_scalar?(self[key])
-         params[key] = self[key]
+     def permitted_scalar_filter(params, permitted_key)
+       permitted_key = permitted_key.to_s
+
+        if has_key?(permitted_key) && permitted_scalar?(self[permitted_key])
+         params[permitted_key] = self[permitted_key]
        end
-        keys.grep(/\A#{Regexp.escape(key)}\(\d+[if]?\)\z/) do |k|
-         if permitted_scalar?(self[k])
-           params[k] = self[k]
-         end
+       each_key do |key|
+         next unless key =~ /\(\d+[if]?\)\z/
+         next unless $~.pre_match == permitted_key
+          params[key] = self[key] if permitted_scalar?(self[key])
        end
      end

つっつきボイス:「permitted_scalar_filter?」「こんな機能が元からあったんですね」「scalarってスカラー量のこと?」「パラメータータイプでscalarと言うとたいてい『オブジェクトではない』という意味になりますね🧐」「arrayとかhashとかでない、プリミティブな値ってことですね: ここで言うPERMITTED_SCALAR_TYPESは、strong parametersに渡してもいい値のリスト↓ってことか!」

PERMITTED_SCALAR_TYPES = [ String, Symbol, NilClass, Numeric, TrueClass, FalseClass, Date, Time, StringIO, IO, ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile, ]
api.rubyonrails.orgより

「たとえばuser_idが数値を受け取ることを期待しているのにarrayが渡されて素通しされるといろいろヤバいので、そういうのを防ぐ」「渡された値は必ずPERMITTED_SCALAR_TYPESのどれかになると」「これにないものは明示的に許可しないとrejectされるので、idにうっかりarrayが入ったりしなくなる」「UploadedFileがあることからしてそんな感じですね」「このフィルタ、毎回呼び出されそう」「そのあたりが高速化したということのようだ: ここではキーをgrepするよりeach_key使った方が速いってことかな」

参考: Action Controller の概要 / 4.5 Strong Parameters | Rails ガイド

⚓関連付けで複数形を指定した場合にもinverse_ofできるように修正

# 同PRより
class Post
  has_many :comments
end

class Comment
  belongs_to :post
end

つっつきボイス:「これって以前からできてませんでしたっけ?」「このinverse_of的なヤツ↓がいつの頃からかあって、それを使うとよしなにやってくれるんですけど、自分はこういうのあまり信用してないので基本使わない☺

# activerecord/lib/active_record/reflection.rb#L614
        def automatic_inverse_of
          if can_find_inverse_of_automatically?(self)
            inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym
          return unless can_find_inverse_of_automatically?(self)

「これこれ、この記事↓にもあるinverse_of」「これですかー」「その記事で言うとinverse_of: :commentableと書くとcommentableからもarticleを参照できるようになる: 改修はこれにplural associationを指定できるようになったってことでしょうね」

「打てなくてもとりあえず打席に立つ感じでRailsウォッチ毎週読んでるんですが、毎回知らないことだらけ😅」「私もです😅」「自分たちもウォッチで最初Railsのコミットを追い始めた頃はわけわからないことだらけでしたけど、1年もやってるうちにだんだん既視感が育ってきますね」「そうそう」「自分が覚えてなくてもつっつきに参加した誰かが覚えてたりとか: ウォッチをずっとやってきたメリットのひとつ😋

参考: Rails 5 以上 + ActiveRecord での inverse_of オプションの使いどころ - Qiita

⚓aroundコールバックでchanges_appliedが適用されていなかったのを修正

AR::AttributeMethods::Dirtyの修正です。

# activerecord/lib/active_record/attribute_methods/dirty.rb#L167
        def _update_record(*)
-         partial_writes? ? super(keys_for_partial_write) : super
+         affected_rows = partial_writes? ? super(keys_for_partial_write) : super
+         changes_applied
+         affected_rows
        end

         def _create_record(*)
-         partial_writes? ? super(keys_for_partial_write) : super
+         id = partial_writes? ? super(keys_for_partial_write) : super
+         changes_applied
+         id
        end

つっつきボイス:「先週も出てきたDirty周りですね」「来たなw: Dirtyつらい😭」「around系コールバックが行われた時点ではもう保存されてdirtyではなくなっているのに、dirty扱いになってたのはバグ🐞」「『should be cleared』はそういうことですね」「after系ではできてたのにaroundではできてなかったと」「この場合aroundというのは…?🤔」「aroundはbeforeとafterをyieldを使って両方いっぺんにやったのと同じ挙動ですね」「そうだったのか!」「そうそう、aroundの場合もyieldの後で状態が変わってないといけない」「ActiveModelにはこの種のコールバックがいろいろあって、それぞれにbeforeやafterやaroundがあります↓」

参考: Active Record コールバック 「利用可能なコールバック」| Rails ガイド

先週も少し話しましたが、ついでにDirtyも説明: ActiveRecordのattributeを=で更新してsaveしたときに、saveする前の値が取れるというキモい機能🧟‍♂️」「おぉ😳」「更新をsaveしたはずなのに」「直前の値が取れるというのは何かと便利なんですけど、トランザクションがネストするとわけわからなくなりがち😭」「saved_changes?(以前はchanged?)みたいに更新があったかどうかを保存後に調べられるメソッドもあります」

「…監査ログ的な実装で、管理者が変更すると『xxが更新されました』という通知を出すときに使ったりとか」「うーん、自分はそこではDirtyはあんまり使わないかなー🤔: papertrailとかにやらせてデータベース更新のときに取ればいいじゃん派」「自分はgem使わなくてもafter_createの後でDirtyで取ればいいじゃん派」「papertrailの方が自動でできるし楽じゃないですか😆

「ちなみにpapertrailはデータベースの変更履歴を全部取っておいてくれるgem」「ただしattributeが1つでも変わると1レコード追加されるので、これで保存したテーブルは死ぬほどでかくなる: まあひたすらINSERTするだけなんで、重いのはSELECTするときだけなんですけど😆」「…がっつり監査ログを取りたいというよりは、日本語的な意味でログを出したい: 『xxさんがパスワードを変更しました』通知をメールで出すみたいな」「そういう軽めの処理にはDirtyが向いてますね: とりあえずsaveした後にchanged?チェックすればさっとやれる😋

⚓ActionPackのcaching/fragments.rbのアロケーションを削減

こちらはコミットリストから見繕いました。

# actionpack/lib/abstract_controller/caching/fragments.rb#L88
      def combined_fragment_cache_key(key)
        head = self.class.fragment_cache_keys.map { |k| instance_exec(&k) }
        tail = key.is_a?(Hash) ? url_for(key).split("://").last : key
        [ :views, (ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]), *head, *tail ].compact
         cache_key = [:views, ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"], head, tail]
        cache_key.flatten!(1)
        cache_key.compact!
        cache_key
      end

つっつきボイス:「flatten!(1)って何やるんだっけ…ああ深さの指定か↓」「Rubyを使っていて感激するメソッドのひとつですね: PHPでこういうのやろうとするとダルい😆」「これはプルリクメッセージにあるとおりかな」「ですね: headとtailをsplatの*で取るより深さ1でflatten!(1)する方が速いよと」

# docs.ruby-lang.orgより
# 平坦化の再帰の深さを指定する例。
a = [ 1, 2, [3, [4, 5] ] ]
a.flatten(1)              #=> [1, 2, 3, [4, 5]]

参考: instance method Array#flatten (Ruby 2.5.0)

「こういう最適化してくれないJITしょぼいな😆」「まあまあ、C++みたいなコンパイル言語ならこのぐらいの最適化はやるでしょうけど、Rubyの場合オーバーライド可能ですし大変だと思いますよ」「フラグメントキャッシュは呼び出しすごく多いから、こういう最適化は効きそう」

参考: Rails のキャッシュ: 「フラグメントキャッシュ」| Rails ガイド

⚓番外: attendance: #present?を高速化するgem

#10539で一度はRailsにコミットされたものの、#29400で議論の末Railsから削除されたので、Schneemsさんがgemとして復活させたようです。

# #29400より
USING BLANK?
DEBUG -- :   Comment Exists (0.1ms)  SELECT  1 AS one FROM "comments" WHERE "comments"."post_id" = ? LIMIT ?  [["post_id", 1], ["LIMIT", 1]]

USING BLANK?
DEBUG -- :   Comment Load (0.1ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ?  [["post_id", 1]]

つっつきボイス:「LIMITを付けることで高速化できるみたいな話: 前にこのあたりのやりとりを見たことがある気がする」「改修するとメモリに全部読み込まれてしまうので削除されたという流れみたい」「キャッシュから読んだほうが速いこともあるだろうし」

「ところでこのattendance gemの最適化前の"users".*↓って何だかヤバい感😳」「おぉw、LIMITのあるなしとこれの違いが大きそう」

# 同gemより
# gemなし
User.where(github: 'schneems').present?
  User Load (1.5ms)  SELECT "users".* FROM "users" WHERE "users"."github" = $1  [["github", "schneems"]]
=> true

# gemあり
User.where(github: 'schneems').present?
  User Exists (1.2ms)  SELECT  1 AS one FROM "users" WHERE "users"."github" = $1 LIMIT $2  [["github", "schneems"], ["LIMIT", 1]]User.where(githu

⚓Rails

⚓名付けて「Event scout」ルール

イベントソーシングやイベント駆動設計の話です。Arkencyはイベント駆動関連の記事が多いので、コマンドソーシング記事も含めてイベント駆動布教の一環のようです。


つっつきボイス:「確かにイベントソーシングをアプリに適用するのは有効な手段👍」「こういうふうに、cancelのコールバックで処理するのではなくてpublish_event化する形でやると」「これ自体はService Objectのようなので、Service Objectの中をイベントソーシング的に設計するということなんでしょうね😋」「コールバックでやるとどんどん読みにくくなるから、イベントソーシングでやろうというのはわかりみある」

# 同記事より
class CancelOrdersService
  def call(order_id, user_id)
    order = Order.find_by!(
      customer_id: user_id,
      order_id: order_id,
    )
    order.cancel!
    publish_event(order)
  end

  private

  def publish_event(order)
    event_store.publish(
      OrderCancelled.new(data: {
        order_id: order.id,
        customer_id: order.customer_id,
      }),
      stream_name: "Order-#{order.id}"
    )
  end

  def event_store
    Rails.configuration.event_store
  end
end

そもそもScout ruleって何だろうと思ったら、ボーイスカウトガールスカウトの「来たときよりも美しく」のことらしく、それをイベント化でもやろうということのようです。

スカウトルールとは: コードに触ったら、必ず何か小さな改善を施す。単にコードを読んだときやチラ見したときであっても小さな改善を施す。改善はネーミングだったり構造だったりささやかなリファクタリングだったりメソッド切り出しだったり。改善はがっつりやらず、次にそのコードに触る人が少しでも気持ちよくいられるよう心がける。次というのは1か月後かもしれないしもっと後かもしれないが。
同記事より抜粋

「来たときよりも美しく!」「コードを開いたらちょっとでも改善してから立ち去るという」「心構えの話ですね」「リファクタリングのプルリクは別にしておいて欲しいですけどね😆

参考: ボーイスカウト・ルールってご存じですか? - Qiita

⚓Rails以外のWebフレームワーク

8月に開催されたEuRuKo 2018カンファレンスでPhusion社が発表したスピーチの紹介です。

SinatraHanamiTrailblazerPhoenix(これはElixirですが)はこれまでに見かけていましたが、以下は初めて見たので。

  • Padrino: Sinatraに近いが、PadrinoアプリはRailsアプリに変換可能。コミット数と★はこの中で最も多い。
  • Cuba: Rackベースで、Sinatraより速いらしい。現在はメンテされてない。
  • Volt: Opalを使ってクライアントもRubyで書ける。こちらもメンテされてない。


padrinorb.comより


cuba.isより


voltframework.comより

記事では「VoltかSinatraもプロトタイピングでお試しあれ、速度が欲しいならTrailbrazerもね: 古いからといってあなどらないように」と締めくくっています。後半はメタプロのスピーチでした。


つっつきボイス:「関係ないですけど、記事英語タイトルの?!?!の順序って、日本だと普通!?!?ですよね?🤣」「あー🤣」「これってたぶん言語や出版社ごとにスタイルが異なるヤツですね: Phusionはオランダの会社なのでもしかするとオランダ風スタイルなのかも?🤔

そういえばスペイン語は逆さの¿¡という独自の記号がありますが、英文で!を付けていてもスペイン語訳で¡...!を付けないことも多いようです(英文で!が乱用されているだけという気もしますが)。

参考: 逆疑問符 - Wikipedia
参考: 逆感嘆符 - Wikipedia

PhusionPassengerの会社なのでついでにその話: PassengerはRailsのWebサーバーで、Ruby 1.8系の時代に一世を風靡したんですが、後に有料化してgemインストール時にバナーを出すようにしたら、あちこちでボコボコに叩かれたという😆」「そんな歴史があったんですねー😲」「でも最近は復活してますね: 自分もPassengerのRuby Enterprise Editionとか使ってましたし」


phusionpassenger.comより

「有料のPassengerはサポートがあるのが強み: というのも昔はRailsサーバーって落ちるのが当たり前だったので」「マジで🤣」「Rails 3をRuby Enterprise Editionで動かしてた頃なんて、リクエスト数がある程度以上になるとメモリを食いすぎて死ぬというのが当たり前に起きてたし: ちなみに皆さんはいつ頃からRubyやRailsを?」「2年前ぐらいから」「4からですかね」「おおっ健康的!いいですね~🙆🏻‍♂️

「あの頃はRailsをずっと立ち上げてるとRailsワーカー1個で軽く1GBはいってましたしw、リクエスト数が一定数に達したらワーカーを自滅させるPassengerMaxRequestsというあんまりな設定項目↓ありましたし」「ひえ〜🤮

参考: Phusion Passenger ユーザガイド, Apache バージョンPassengerMaxRequests

unicorn-worker-killer、めっちゃ愛用してますが😆」「ワーカーがどうせメモリリークしてるなら数リクエストさばいたら死んでくれという諦めの境地」「潔いですね〜」「二百三高地ですか😆

「こういうの今でも使われてます?『たとえばGCを止めて』↓、適当に増えたら殺すみたいなの」「今はまず使わないっすね: 昔(Ruby 1.8頃)はOobGC入れないと全然ヤバかったけど」「OobGC?」「Out of…何だっけ?」「bandか」「bounceではなかったw」「😆」「OobGCは、要はリクエストが完了するまではGCを動かさないようにするという機能」「だってあの頃は半分以上GCがメモリ食ってて明らかにおかしかったし、止めたら倍早くなったし」「当時は普通にやってましたね〜☺

参考: 例えば GC を止める・Ruby ウェブアプリケーションの高速化 - 2nd life

「話逸れちゃいましたが、Phusionの記事はというと」「PadrinoとかCubaとかVoltという見慣れないRuby Webフレームワークですね」「VoltってJSにある気がするけど」「ElectronがあるぐらいだからVoltもありそう😆」「Voltはクライアント側もOpal使ってRubyで書けると言っています」「皆さんOpalはご存知?」「RubyをJavaScriptで動かせるヤツですよね↓」「正解!🎯productionで使うには度胸が要る、なかなかエグいヤツ」「エグいんですね😆」「RubyKaigiでも確か2年続けてスピーチされてましたね」「Opalは普通にトランスパイルしてる?」「そうですね、変換テーブル使ってて、integerとかはRubyとJSで挙動が違っちゃう😅」「WebAssemblyで動かすとかそういう楽しい話ではないと」「残念ながらそっち系の楽しい話ではないですね🤣


opalrb.comより

mrubyをWebAssemblyで動かす(翻訳)

⚓標準のCSVライブラリはどうしてコケるのか(Ruby Weeklyより)

# 同記事より
require 'csv'
require 'pp'

begin
  CSV.parse( %{1, "2"} )
rescue CSV::MalformedCSVError => ex
  pp ex
end
# => #<CSV::MalformedCSVError: Illegal quoting in line 1.>

begin
  CSV.parse( %{"3" , 4} )
rescue CSV::MalformedCSVError => ex
  pp ex
end
# => #<CSV::MalformedCSVError: Unclosed quoted field on line 1.>

pp CSV.parse( %{"","",,} )
# => ["", "", nil, nil]

つっつきボイス:「CSVは今はそこそこマシになった気がするけど」「どっちかというとCSVの仕様をどうにかせいというボヤキ記事みたいです」「CSVって結局『オレがCSVと言っているものがCSVだっ!』っていう程度のものだし😎」「マイクロソフトのCSVはそれなりに普及してますけどね」「どんなCSVが好き?と聞いてみるテスト」「…CSVライブラリで読み込めるのがいいCSV☺

「クォーテーションやカンマがセルで使われてない場合に省略できるかどうかとか、ほんとどうでもいいルールがいろいろあるし」「…一応RFCに仕様あるんですけど誰も使ってないし」「ひど😭」「W3CRFCとExcelどっちか選ぶとしたらExcelですよね☺

「私はタブ区切りテキスト(TSV)の方がエスケープ楽だし安心感あるんですけど、皆さんCSVを使うのはなぜなんだろう?と思って」「よくはわからないけど、結局業務システムでCSVが使われることが多いからですかね〜: TSVの方が使いやすいけど」「そうそう」

「一応CSVはCharacter Separatedなので、カテゴリ的にはTSVもCSVに一応含まれます」「へー!😲」「完璧に後付けですけどね🤣

「TSVならExcelにそのまま貼り付けられるという絶大なメリットあるのにー😤」「Webのフォームでも『Excelをここからここまでコピーしてそのまま貼り付けてください』っていうのよく使う😋

参考: library csv (Ruby 2.5.0)

⚓Railsアプリの基本的なセキュリティの注意(Ruby Weeklyより)


つっつきボイス:「割と普通の内容かなと思いましたが」「yamlに式展開↓、普通やらないっすよね〜😅」「これやっちゃうとhtml_safeしないと出せないから、i18nというよりはhtml_safeヤメレという話」

# 同記事より
# en.yml
en:
  hello: "Welcome <strong>%{user_name}</strong>!"

RailsビューのHTMLエスケープは#link_toなどのヘルパーメソッドで解除されることがある

「そして解決方法はというと、なになに、yamlのキー名を_htmlで終わらせると自動的にエスケープしてくれる↓…だと…?」「やべーこれ知らなかった〜!」「しかもRailsガイドにも載っている…だと?」「これは知らなかった…が、i18nに_htmlって書かせたくない感が残るw まあいいけど」

# en.yml
en:
  hello_html: "Welcome <strong>%{user_name}</strong>!"

「こういうのもそもそも書かないだろうし↓、記事にあるようにpermitリストを通してからやるべき」「ところでconstantizeは文字列からクラスをnewできるリフレクション向けの機能」「…機能限定版のeval」「ですです」「brakemanで絶対怒られるヤツ」「brakemanは静的セキュリティチェックの定番ツールで、こういう情けないレベルの穴はだいたい見つけてくれる優秀なヤツです💪

# 同記事より
class FooForm; end
class BarForm; end

form_klass = "#{params[:kind].camelize}Form".constantize # ダメ絶対
form_klass.new.submit(params)

参考: Ruby on Rails 5.2 / String#constantize — DevDocs

参考: module function Kernel.#eval (Ruby 2.5.0)

「これ↓はidを取ってるつもりがarrayが来てしまうヤツ」「1つ消すつもりが複数消してしまったりすると」「こういうのを防ぐためにStrong Parametersが導入された」「Strong Parameters使ってれば普通は大丈夫💪: Ransackとか使うときは要注意かな」

# 同記事より
  # POST /delete_user?id=xxx

  def can_delete?(user_id)
    other_user = User.find_by(id: user_id)
    current_user.can_delete?(other_user)
  end

  user_id = params[:id]

  if can_delete?(user_id)
    User.where(id: user_id).update(deleted: true)
  end

「最後の注意点↓も基本的なものなので当然知っとくべき」「ローカルsshログのリモートコード実行例もちょっと気になりました」「ああこれは割と古典的な手口ですね: 外から持ってきたファイルをinclude("$file");するとか、まずコードレビューとおらないっしょ😤

⚓marginalia: ActiveRecordのSQLクエリにコメントを付けられるgem(Ruby Weeklyより)

# 同リポジトリより
Account Load (0.3ms)  SELECT `accounts`.* FROM `accounts` 
WHERE `accounts`.`queenbee_id` = 1234567890 
LIMIT 1 
/*application:BCX,controller:project_imports,action:show*/

Basecampのgemです。以前翻訳したこの記事↓でも軽く紹介されていました。

Rails開発者のためのPostgreSQLの便利技(翻訳)


つっつきボイス:「あーなるほど!どこからSQLが発行されたかを追えるのか!」「しかもそれをpt-query-digestで追えるとは、これはかなり( ・∀・)イイ!!」「お、また知らないものが💦」「pt-query-digestは、例のPercona Toolkitのひとつで、高速化とかチューニングによく使います」「ちなみに昔はMaatkitという名前でした」

「pt-query-digestは、MySQLから出力されたでかいログを食わせると、期間内にどんなクエリがどのぐらいあるかを出してくれます」「並のツールだと1件ずつのスロークエリは調べられても、よく似たたくさんのクエリが少しずつ遅いみたいなN+1的なクエリだと調べきれないんですが、pt-query-digestはそういう頻度の高い類似クエリを調べることができる」「おぉ~😲」「まあ今だとnewrelicとか使えばわかるんですが」

「marginaliaを使うとそのpt-query-digestにかけられる形式のコメントが出るってことなんでしょうね」「marginalia、よく見たらBasecamp(Railsの作者DHHのいる会社)のgemじゃん!」「まぁBasecampならnewrelicとか死んでも使わなさそうではある🤣」「🤣」「🤣

⚓localhost: 自己署名ルート証明書をユーザーごとに生成する開発用gem

# 同リポジトリより
require 'socket'
require 'thread'

require 'localhost/authority'

# Get the self-signed authority for localhost:
authority = Localhost::Authority.fetch

ready = Thread::Queue.new

# Start a server thread:
server_thread = Thread.new do
    server = OpenSSL::SSL::SSLServer.new(TCPServer.new("localhost", 4050), authority.server_context)

    server.listen

    ready << true

    peer = server.accept

    peer.puts "Hello World!"
    peer.flush

    peer.close
end

ready.pop

client = OpenSSL::SSL::SSLSocket.new(TCPSocket.new("localhost", 4050), authority.client_context)

# Initialize SSL connection:
client.connect

# Read the encrypted message:
puts client.read(12)

client.close
server_thread.join

つっつきボイス:「localhostっていうgem名が凄すぎ」「ヤメレw: require "localhost"とか書きたくないし😆」「gem 'localhost'で入るってことはネームスペース空いてたのね☺

「このgemは自己署名証明書をローカルで開発用に立てられるということですけど、ときどき必要になる感じでしょうか?」「ですね: SSL/TLSがないと使えない機能があるとか」「Rackで使えるSSLとか使う手もありますが」「ローカルのVirtualHostが証明書を発行できればそれでいいのにと思ったりはしますが☺」「自分の手元のターミナルでopen sshして手元のNginxあたりに設定すればいいんじゃね?という気も」「今はDockerがあるからそれで十分ですしね: でも全部Rubyでやりたい人たちがいるんですよきっと😆

⚓google_sign_in: GoogleaアカウントでRailsにサインイン(GitHub Trendingより)

# 同記事より
# app/controllers/logins_controller.rb
class LoginsController < ApplicationController
  def new
  end

  def create
    if user = authenticate_with_google
      cookies.signed[:user_id] = user.id
      redirect_to user
    else
      redirect_to new_session_url, alert: 'authentication_failed'
    end
  end

  private
    def authenticate_with_google
      if flash[:google_sign_in_token].present?
        User.find_by google_id: GoogleSignIn::Identity.new(flash[:google_sign_in_token]).user_id
      end
    end
end

非常に新しいですがissueは2017年だったりするので社内で使ってたのかも。


つっつきボイス:「これもBasecampですね」「OmniAuthは絶対使いたくないとかあったりして😆」「BasecampってDevise使ってるんだろうか…?」「OmniAuthってそもそも抽象度が高すぎるんですよね〜: OmniAuthでOAuth2認証すると、プロバイダごとにデータ構造なんかが全然違ってたりしますし😢

⚓RailsのGraphQLでページネーションする(RubyFlowより)


つっつきボイス:「少し前のウォッチでもページネーションの話しましたが、Google APIで何か実装したことってあります?」「まだです〜」「あの手のAPIはだいたい50件ずつぐらいしか取れないんで何らかの形でページネーションが必要になってくる: 何の工夫もしなくてもページネーションはできちゃうんですが、ちゃんとやるなら次のページに進んでいる間にデータの件数が増えたりする場合にも対応しないといけなくなる」「でGoogleのはさすがによくできていて、最初のページを取ってきたときに、そういう点にも配慮したnext page urlも一緒に返してくれるのがありがたい」「ページネーションの整合性が取れるんですね😃」「で、GraphQLのページネーションでもたぶんそういうところをケアしないといけないんでしょうね」

RailsでGraphQL APIをつくる: Part 1 – GraphQLとは何か(翻訳)

⚓StimulusJSとActionCableにSidekiqを少々(RubyFlowより)

import cable from 'actioncable';

let consumer;

export default function (...args) {
  if (!consumer) {
    consumer = cable.createConsumer();
  }

  return consumer.subscriptions.create(...args);
}

つっつきボイス:「これは普通の非同期記事かな」「StimulusJSって何だっけ」「これもBasecampです」「そうだった: Turbolinksと相性がいいとか何とか」


stimulusjs.orgより

⚓RubyとEmacsのヒント集(RubyFlowより)


つっつきボイス:「VimmerからEmacsへの風評被害は最近落ち着いたのかなw」「🤣」「🤣」「Vimmerはネタに走りすぎ感」

(突然のエディター大喜利による音声輻輳)「Vim」「Emacs」「開発環境?それともターミナルでファイルを変更するとき?」「JetBrains IDE」「Sublime」「Atom」「VSCode」「VSCodeは最近快進撃っぽい?」「TypeScriptのお膝元だからかな」

「そうそう、EmacsでVimキーバインドをエミュレートするその名もEvil↓っていうのがありますヨ😈」「これはw」「凶悪」


emacswiki.orgより

⚓値ベースのページネーション


同記事より


つっつきボイス:「これは何がしたいのかな?🤔」「あー、OFFSETでページネーションするとDBの内部処理が発生して遅くなるので、インデックス化されているカラムを使ってWHEREで絞り込もうよってことか: めちゃくちゃでかいテーブルだとOFFSETの速度低下が顕著に出ますね」「それってRailsに限った話じゃなくね?って思うけど😆」「そういえばfind_eachってこんな感じの動きしますよね?」「そうそう、idベースでやってるし:activerecord-importとかもそんなのやってたかも」

# 同記事より
# ./models/application_record.rb

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  class << self
    def start_at(id: 1, timestamp: Time.now, column: :created_at, limit: 10)
      params = parsed_params(id, timestamp, column, limit)
      where("#{table_name}.#{params[:column_name]} < :value OR\
               (#{table_name}.#{params[:column_name]} = :value AND id > :id)",
            value: params[:timestamp], id: params[:id])
        .order(column => :desc)
        .limit(params[:limit])
    rescue ArgumentError
      order(created_at: :desc).first(params[:limit])
    end
...

参考: Ruby on Rails 5.2 / ActiveRecord::Batches#find_each — DevDocs

⚓RSpecは何を参考にするか


つっつきボイス:「RSpecは、本を読むよりも誰かが書いたRSpecを参考にできると嬉しいと思う」「その誰かは誰にすればいいでしょうか?🤔」「みんながまともと呼んでいるプロジェクト🤣」「🤣」「同僚がいない場合はどうしましょう?(ひとりでやってるの〜😭)」「それはつらそう😢: オープンソース系のプロジェクトを参考にすることになるけど、いきなりレベルの高すぎるRSpecを見ても難しすぎてよくわからないという🤣」「素振りに手頃なテストコードを知りたい気持ちです🏏」「そうそう、このぐらいユルくてもまずは書いてくれればいいよみたいなの😊: それこそこういう本を参考にするのがいいかな」

「ちなみに自分はletキライ派」「なぬっ、ワイはletしか使わない派w✨」「こうやって戦争が始まるのか🤣」「shared_examplesは悪みたいなのは最近広まったかな?」「share_examplesも使ってるですよこれがw✨: 使わないとコピペアレルギーが出る〜」「share_examplesは3つまでは許せるけど〜」「てな感じになるわけですよ😆」「これが宗教戦争というヤツですねおっかさん😊

「…テストあんまり真面目に書いてないし🤣」「いっそRSpecをやめるというのは?🤣みんなminitestでassertion使えば宗教が起きない、きっと」「テストコードのリファクタリングとか始めると宗教になりそうだな〜」「メソッドのリファクタリングならいいんですけど、凝りまくった器用なマッチャーとか書いて『これがテストのリファクタリングだ』とドヤ顔で言われても〜🤣」「🤣」「🤣」「英語っぽい自然なテストが書きたいんじゃないんだけどな〜🤣

「まあとにかく、RSpecはこういう本を参考にして、あんまりスゴイのを書くようにしなくても十分じゃないのって個人的には思うわけですよ」「…きっとassertだけでいいと思うし: イコールも== trueみたいなのでいいと思ってるし」「🤣」「自分もRSpecではほとんどeqでやってますね〜」「🤣」「RSpecのテストコードがちゃんと動いているかどうかが気になってくると本末転倒感ある🧐

⚓Ruby trunkより

まだスパムissueが続いているようです。

⚓提案: C APIの公式なドキュメントを作ろう


つっつきボイス:「一応あるはあった気がするけど公式ではない?」「↓これがそれに近いらしいですが、足りないものもあるようです」

参考: The Ruby C API

「確かにRubyのGILやFiberとか、モジュール読み込みのancestors周りとかも含めて公式のドキュメントがあるとありがたいという気持ちはワカル: Rubyの進化にどこまでドキュメントが追いつけるかというのはあるけど」「…わかっている人には要らないのかもという気はする」「そこまで潜らないといけないことは普段はそうそうありませんけどね☺

⚓keyword_initを指定せずにStruct使うと動作がおかしい => 却下

Info = Struct.new(:name, :country)  # キーワード引数を有効にしていない
c = Info.new(name: "myname", country: "Japan") # キーワード引数でインスタンス化される

Info = Struct.new(:name, :country, keyword_init: true) # これは動く
c = Info.new(name: "myname", country: "Japan")
p c #=> #<struct Info name="myname", country="Japan">

つっつきボイス:「自分でも動かしたらそうなりました」「ホントだ😲」「ハッシュとして最初の引数に全部入っちゃうと」「キーワード引数じゃない場合って、引数足りなくても動くんでしたっけ?」

↓やってみたら、キーワード引数であってもなくても引数足りない場合に動きました。

[1] pry(main)> Info = Struct.new(:name, :country)
=> Info
[2] pry(main)> a = Info.new(name: "myname")
=> #<struct Info name={:name=>"myname"}, country=nil>
[3] Info = Struct.new(:name, :country, keyword_init: true)
=> Info(keyword_init: true)
[4] pry(main)> a = Info.new(name: "myname")
=> #<struct Info name="myname", country=nil>

「自分はStruct.newってあまり使わないけど、kazzさんはValue Objectで割と使ってましたっけ?」「最近はそんなに頻繁には😅: Struct意外に難しくて、いろいろ足していくうちに普通にクラス書く方がよくね?っていう感じになっていったり」「確かに、クラス作るのが面倒だからと思ってStructでやろうとすると意外に思ったとおり行かなかったりしますね☺

⚓Ruby

⚓LinuxデスクトップアプリをRubyでがっつり作った(Ruby Weeklyより)


同記事より

やっぱりGTK+でやってます。かなり長い記事ですが、こんなに詳しく解説しているのは珍しいと思って。


つっつきボイス:「RubyのGUIアプリって、Railsが流行る前には結構ありましたね: 自分も初めて触ったRubyはGUIだったし❤」「この記事のは結構ガッツリ作ってる方かしら?」「このぐらいのは普通にありましたね」「…大抵の言語にはGTKのバインディングあるし、昔は流行ってたけど最近はあんまり流行ってない😆」「Ruby何だかんだで年いってる言語だし🎅


gtk.orgより

参考: GTK+ - Wikipedia

⚓Rubyをテーマにした論文リスト(Ruby Weeklyより)


つっつきボイス:「思ったより論文あるなと思って」「結構ありますよ」「知ってる著者はあっても、知らないカンファレンスがいっぱい😅

⚓ProcがCallableより遅い件について(Hacklinesより)

# 同記事より
Comparison:
     callable no arg: 10095848.2 i/s
   callable with arg:  9777103.9 i/s - same-ish: difference falls within error
     callable 3 args:  9460308.0 i/s - same-ish: difference falls within error
callable splat args (0):  6773190.5 i/s - 1.49x  slower
         proc no arg:  6747397.4 i/s - 1.50x  slower
       proc with arg:  6663572.5 i/s - 1.52x  slower
         proc 3 args:  6454715.5 i/s - 1.56x  slower
callable splat args (1):  5099903.4 i/s - 1.98x  slower
 proc splat args (0):  5028088.6 i/s - 2.01x  slower
callable splat args (3):  4880320.0 i/s - 2.07x  slower
 proc splat args (1):  4091623.1 i/s - 2.47x  slower
 proc splat args (3):  4005997.8 i/s - 2.52x  slower

つっつきボイス:「Callableってこの場合?」「↓PORO(素のRubyオブジェクト)のcallメソッドを呼ぶことみたいです」「callがいくつもあると早口言葉みたい😆」「トートロジー😆」「Procの方が遅い?」「Procが遅いのはもうしょうがない☺: その場で解決しないといけないし」

calling a PORO’s call method — that is, a callable object

⚓Rubyコミュニティのおすすめリソース(RubyFlowより)


同記事より

⚓strings-ansi: 文字列のANSIエスケープを検出/サニタイズするgem(Ruby Weeklyより)

# 同リポジトリより
Strings::ANSI.sanitize("\e[0;33;49mHello\e[0m")
# => Hello

つっつきボイス:「ANSIエスケープって何だっけと思って」「そうそう、\e[30m \e[40mみたいにターミナルの色を変えたりするときに使いますね」「そしてよくバグる😆」「自分のターミナルのPowerlineでもよく出力がおかしくなったりするし😭: screenrcとかいじるのは楽しいけどねっ☺」「自分もついやっちゃう😆

参考: ANSIエスケープシーケンス チートシート - Qiita


github.com/powerline/powerlineより

⚓Sider社がRuboCopのスポンサーに(Hacklinesより)


つっつきボイス:「Siderは日本のCIの会社です」「そういえばRubyKaigi 2017でもRuboCopの日本人コントリビュータが発表してましたね」「RuboCop、もうちょっと人情味があってもいい気が😭: デフォルト設定だと厳しすぎっ」「慣れると『へーそんな書き方あるんだー』『勉強になったなぁー』って心境になれる☺


sider.reviewより

その他Ruby



つっつきボイス:「確かIIJのルーターもmrubyで書かれてるって聞いた気がする」

参考: 軽量Rubyへの取り組み | IIJの技術 | インターネットイニシアティブ(IIJ)



つっつきボイス:「ささいなようだけど、エラーページをこのぐらいいい感じに作っておくだけで和みますよね☺」「和んだ〜」「この間札幌大学のサイトが落ちたときのエラーページにクラーク先生の御姿があってこれも和んだ☺」「Railsデフォルトの赤いアレよりは遊び心あるのが( ・∀・)イイ!!」「TechRachoもそういうエラーページ出せばいいのに😆」「WordPressが安定してるので出番がなかなかないかなー😆

参考: ウィリアム・スミス・クラーク - Wikipedia

⚓クラウド/コンテナ/インフラ/Linux/Serverless

⚓tcpdump101.com: パケットキャプチャ設定支援(WebOps Weeklyより)


つっつきボイス:「tcpdumpのコマンドビルダーだそうです」「101ってどういう意味だろう?🤔」「Wireshark(旧Ethereal)もGUIよくできてるしそっちでいいかなー☺

参考: Wireshark - Wikipedia


wireshark.orgより

⚓マイクロソフトがGoogleアカウントでシングルサインオンに踏み切る

参考: マイクロソフト、GoogleアカウントでWindowsやOffice 365へのシングルサインオンを可能に。Azure Active Directoryの新機能をプレビュー公開 - Publickey


つっつきボイス:「ほほー?」「これはいい文明?」「MSアカウントはマジ残念なこと多すぎるし😆なぜか使うたびにサインインを求められるのは気のせい?」「…結構気難しいんですよねMSアカウント🧐: 日によってEdgeでないと入れなかったりChrome Canaryじゃないと入れなかったり、でだいたい3つめで入れる🤣」「難しいってそれってアカウントとしてどうよ🤣」「レインボーカーソルのくるくる具合がおかしい🤪ときなんか、強制リロードしないとログインできなかったり」「ちなみにツイートで言ってるのはAzure ADの方ですね」「あそっちか」

参考: Azure Active Directory (Azure AD) とは | Microsoft Docs

⚓vegeta: HTTP/2に対応した負荷ツール(WebOps Weeklyより)


つっつきボイス:「そのまんまベジータ画像を堂々と使っちゃってるんで記事に引用できないー😭」「ちょさっけん的にアウトじゃん🤣」「ど真ん中でアウト🎯」「しかもターゲットがgokuだし」「ベジータから悟空に攻撃するんだよやっぱ😆

# 同記事より
GET http://goku:9090/path/to/dragon?item=ball
GET http://user:password@goku:9090/path/to
HEAD http://goku:9090/path/to/success

「しかしこれ、欲しいオプションはひととおりあるし、負荷テストツールとしては結構よさそうだし😍」「あとはマルチスレッド性能とか、どのぐらい凄いトラフィックを作れるかかな」「★9000近いし人気は抜群ですね」

「この種の負荷ツールは、単体でどれだけエグいトラフィックを作れるかというのも重要な指標」「やっぱ戦闘力の高さが大事なんですね😆」「エグい負荷を出せないと負荷測定にならないので」「これはRuby?」「えっと、Go言語でした」

⚓その他クラウド

⚓SQL

⚓PostgreSQLのログ先行書き込み(WAL)が肥大化する理由(Postgres Weeklyより)


つっつきボイス:「WAL: write ahead log」

⚓litetree: ブランチできるSQLite(DB Weeklyより)


同リポジトリより

PRAGMA branch=<name>.<commit>

つっつきボイス:「へぇえー?SQLiteのスナップショットを取ってブランチできる?」「ちょっと珍しいかなと思って」「最近のAWS Auroraにあるスナップショット機能みたい」「お、既にそういうのあったんですね💦

参考: Amazon Aurora(MySQL、PostgreSQL 互換のリレーショナルデータベース)|AWS

⚓JavaScript

NodeWeeklyというニュースサイトがあることに今頃気づきました。

⚓Babel 7がリリース(JavaScript Weeklyより)

主なbreaking changes:

  • Node.js 5以下のサポート打ち切り
  • @babel名前空間の導入(babel-core@babel/coreに変わる)
  • 年を含むpresetを廃止(preset-es2015など)
  • stage presetを廃止

変更量めちゃめちゃ多いです。

⚓CSS/HTML/フロントエンド/テスト

⚓手描きスケッチをHTMLコード化


つっつきボイス:「SketchといったらWeb屋はふつうこっち↓を指すだろうって🤣」「名前はともかく機能的にはよさそうなんですけどね」「MSが戦い挑んでる🏹

参考: 現役WebデザイナーがデザインツールをSketch一択にした理由 – Cntlog

⚓セキュリティ/ハッキング記事サイト

⚓Chromeのlazyload属性

<iframe src="ads.html" lazyload="on"></iframe>

つっつきボイス:「そうそうこれ最近出てましたね: lazyload="on"付けるだけでレイジーになってくれる」「へぇえー!😳」「まだChromeだけか…😢

参考: Chromeの新機能がすごい便利!imgやiframeにlazyload属性を加えるだけでLazyLoad対応に | コリス

⚓言語よろずの間

⚓TIOBEランキングでPythonがC++を抜く

参考: 「Python」が初のトップ3入り、「Julia」上昇--TIOBE - ZDNet Japan

⚓bat: シンタックスハイライトつきcatGitHub Trendingより)



同リポジトリより

Rustで書かれています。exaというRust版のlsもあるそうです。


つっつきボイス:「catからbatへ」「『翼の生えたネコ』ってあるから明らかに狙ってるし」「にゃ~ん(ΦωΦ)」「UnixコマンドをRustで書き換えるの最近よく見かけますね☺

私もripgrep(やはりRust製)を最近愛用してます😍

⚓rockstar: 100% Rubyで書かれた動的型言語(GitHub Trendingより)

poetic licenseを目指してるとか、何だか相当なクセ球の予感。新しいのに★4000超えてます。

参考: poetic licenseの意味・使い方 - 英和辞典 Weblio辞書

Modulus takes Number and Divisor
While Number is as high as Divisor
Put Number minus Divisor into Number
    (blank line ending While block)
Give back Number
    (blank line ending function declaration)
Limit is 100
Counter is 0
Fizz is 3
Buzz is 5
Until Counter is Limit
Build Counter up
If Modulus taking Counter, Fizz is 0 and Modulus taking Counter, Buzz is 0
Say "FizzBuzz!"
Continue
    (blank line ending 'If' Block)
If Modulus taking Counter and Fizz is 0
Say "Fizz!"
Continue
    (blank line ending 'If' Block)  
If Modulus taking Counter and Buzz is 0
Say "Buzz!"
Continue
    (blank line ending 'If' Block)
Say Counter
    (EOL ending Until block)

つっつきボイス:「自然言語っぽいプログラミング言語を目指してる感」「コメントはご法度だそうです😆」「poetic licenseって何だろうと思ったら『詩的許容』というやつでした」「どことなくVBっぽい匂いもする」

⚓その他

⚓英語辞書Chrome拡張


つっつきボイス:「これスピード速くてすごくいいんですが、手持ちの辞書はほとんど読み込めないので私にはまだ使いみちがない😭

⚓Electronで動くWindows 95


つっつきボイス:「やっぱりレガシーゲーム用?」「DOOMとかね」

⚓番外

⚓一発ネタ


⚓戦時中生まれ

Rob Pikeより15歳も年上でした😳


つっつきボイス:「『闘うプログラマー』好きでしたよね?」「…好きです❤、目の前の本棚のどこかにあったはず」「後で探そうっと☺


「今週は以上です」「お疲れさまでしたー!😊

お便り発掘

バックナンバー(2018年度後半)

週刊Railsウォッチ(20180827)Ruby Prize 2018募集開始、Interactor gemとReader Object、書籍『Real World HTTP』、Basecampのヒルチャート機能ほか

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

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

Rails公式ニュース

Ruby Weekly

RubyFlow

160928_1638_XvIP4h

Hacklines

Hacklines

WebOps Weekly

webops_weekly_banner

Postgres Weekly

postgres_weekly_banner

DB Weekly

db_weekly_banner

Frontend Weekly

frontendweekly_banner_captured

JavaScript Weekly

javascriptweekly_logo_captured

Golang Weekly

golangweekly_logo_captured

Railsは2018年も現役か?: 前編(翻訳)

$
0
0
  • 次記事: Railsは2018年も現役か?: 中編(翻訳)

概要

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

記事は3分割しました。画像は元記事からの引用です。

Railsは2018年も現役か?(翻訳)

Rubyから別の何かに乗り換えることを検討中のWeb開発者から、数日前にこんなメールを受け取りました。

まったく新規から始めるとしたら、バックエンドにRubyかElixirかJSのどれを選ぶ?

このメールで他に重要と思われる部分は、現在の彼はもっぱら自分のサイドプロジェクトに関心を寄せていることと、主な懸念点は開発速度と開発のしやすさであるということです。

メールに返信を書き始めたのですが随分と長くなってしまったので、この際記事にすることにしました。

ご興味をお持ちいただいた方に趣旨を簡単にまとめます。

本記事では最初に、Web開発の哲学や仕事と人生についてざっと眺め、それらの意義をよりよく説明できる技術的側面に光を当てます。

忙しい人向けのまとめ

長すぎて読んでいられないという方向けに記事の冒頭に結論を置きました。ここから先は読まなくてもOKです。

これまでを振り返って考えた結果、私なら2018年の今でもRuby on Railsを主要な技術として選択するでしょう。

私はこれまで自分とよく似たタイプの人々にたくさん出会ってきましたが、Railsの技術はそうした人々(主にスタートアップ)を魅了し続けているという点で、私はRailsというプロジェクトが好きです。私は自分の好みとしてRailsを選びますが、皆さんはまた違うことでしょう。

Railsは時代遅れではありませんし、Rubyも死にかけてなどいません。むしろこれまでにないほどよくなっていますし、この技術のマーケットも実に好調です(💰💰💰)。

反論がおありの方は記事に全部目を通していただいてからお願いします。くれぐれもよろしく。

私は自分の主要なツール(つまりRails)に注力すると同時に、素のRubyやElixir、PhoenixやJavaScriptについても学習を継続するつもりです(1日20分の学習時間ぐらい誰だって確保できますよね?)。

そして上に挙げていないプログラミング言語の学習には時間を使いすぎないようにするつもりです。もちろん自分のツールボックスのツールを増やしておく必要があるからですが、かといってツールボックスにツールをぎっしり押し込めてもスキルが伴わなければろくに使いこなせずに終わってしまいます。それよりも低レイヤの技術、つまりデータベース、キャッシュ、コーディングの実践、DevOps、チーム運営、テスティングを学びましょう。

開発経験の長い人なら、新しい言語を学ぶというのもありです。

「ただのツールじゃないか」

プログラミング言語も、ライブラリも、フレームワークも、データベースも、コーディング規則も、所詮ツールに過ぎません。

壁に釘を打つときはハンマーが欲しくなりますし、レンガ塀を解体するときにはもっと大きなハンマーが欲しくなるものです。しかし自動車の修理にハンマーを使うのは普通おかしいでしょう。

画像:「Web開発者ってのは職種じゃないんだ、生き方なんだよ!」

職歴を重ねる中で、さまざまな技術や製品開発アプローチを試したり学んだりということから逃れる道はありません。「Web開発者ってのは職種じゃないんだ、生き方なんだよ!」。自己鍛錬のサイクルに終わりはないのです。

「生き方」について: ここで言う生き方とは、「仕事と生活のバランス」のことではなく、「仕事と生活のマインドセット」の話です。開発者は言ってみれば総合格闘技の選手のようなもので、異種格闘技戦の習得と実戦は生涯に渡って続きます。

ですから、プログラミング言語だけが重要なのではなく、プログラミング言語と組み合わせられる技術(データベース、クラウドソリューション、キャッシュ、ライブラリ)も重要であることに気づく必要があります。もちろんアーキテクチャ(モノリスvsマイクロサービス)や実践的チーム編成(アジャイル、スクラム、カンバン、XPgithub-flowなどなど)、コーディングの実践やコーディングスタイル(SOLIDDCIOODDなどなど)。プログラミング言語は、それらをつなぎ合わせるニカワのようなものでしかありません。

そこで最速のプログラミング言語を使ってもよいのですが、たとえばMySQLデータベースのクエリを効率よく書く方法を知らなければ、アプリは耐え難いほど遅くなってしまいます。

開発者にとっての幸せとは

プログラミング言語を使う仕事は1日に8〜10時間(下手をすると16時間)にのぼります。「開発者としての幸せ」の重要性を見過ごしてはなりません

自分が使っているツールや、自分が携わっているプロジェクトで幸せになれないのであれば、それはプログラミング言語のせいではなく、自分のせいなのです。

私は今の仕事を愛しています。それというのも、正しい技術セットを正しいチームと一緒に使える、正しい価値観に基づいた正しいプロジェクトで働ける仕事を選んできたからです。

新米開発者は、経験したくもないような業務に耐えなければならなくなることがよくあるようですが、正直に申し上げると、今この2018年の世界で、一人前のシニアWeb開発者が業務で悲惨な目に遭う理由が私にはよくわかりません。もちろんストレスや不満だらけの日々も間違いなくありますが、それもしばらくの間だけです。しかしそれを超えて悲惨なのであれば、プロジェクトや業務を変えるか、違う技術に乗り換えるか、働き方を変えましょう(在宅勤務にシフトするとか)。どれも無理ということであれば、おそらくWeb開発者としてまだ一人前ではないので、もっと精進して腕を磨きましょう(数か月後にこのパラグラフをもう一度読み返してみましょう)。

Web開発10年というのは経験が10年?、それとも毎年同じことを10回やってるってこと?

勝ち組などいない

「悪い」プログラミング言語というものはありません。あるとすれば、プロジェクトでのプログラミング言語の選択が悪かったのです。

ソフトウェア開発者がプログラミング言語をたとえば「Ruby遅すぎ」などとdisるとき、言語の長所は決してスピードではないという点を見落としています。時間をかけてそうした批判の背景を調べてみれば、たとえばろくにロードバランシングしていないサーバーで数千リクエストをさばこうとしているとか、キャッシュをまったく理解していないといったことに気づくでしょう。

開発者は、newrelicなどのツールでアプリの総合的なパフォーマンスを最適化することに注力する必要があります。その言語が扱えるループサイクル数がいくつかなどという点ではありません。今構築しているのは、現実の人間が使う現実のアプリなのですから。

開発者たちが、やれJavaは複雑すぎるだの現実のアプリには向いてないだのと不満を垂れるのをよく目にします。そうした言語は、大規模なチームによって開発され、何十年にも渡って運用されるエンタープライズ向けに設計されています。銀行であれば、即座にRubyではなくJavaを選ぶでしょう。その主な理由は、Javaが機能を廃止することがほぼないからです(どんなにクソなアイデアであっても、一度導入されれば半永久的にサポートされます)。Rubyはそうしたエンタープライズの巨人たちから受ける圧力が少ない分、大胆な変更を頻繁に取り入れる余地が大きく、コードの互換性と引き換えに優れた機能を導入できます。

Googleのように数十億ユーザーを扱うプロジェクトであれば、Rubyを使おうとは思わないでしょう。そしてこれほどの規模になるとモノリスアプリでさばくのはほぼ不可能なので、ほとんどの場合マイクロサービスアーキテクチャの形でさばいて、個別のサーバーで動く小さなアプリの中に言語の制約を隠蔽するでしょう。

詳しくは別記事『モノリスvsマイクロサービス』をどうぞ。

現在の言語がシナリオにそぐわないというだけの理由で、一部の大企業が言語を切り替えることはよくあります。Twitterが何年か後にRubyから乗り換えたのも、TwitterがSNS界の巨人となったからです。GitHubもアプリをリファクタリングしていくつかの技術に分割する必要に迫られました。これは最初の言語の選択が間違っていたのではなく、企業がそのときの姿に合わせて、自らの成長に役立つ最適な言語を選んだまでのことです。

こうした切り替えはプログラミング言語だけではなく、データベース(SQL vs NoSQL)やストレージソリューション(DropboxがAWS S3から乗り換えた)などあちこちで普通に見られます。何か事あらば騒ぎ立てる傾向はプログラミング言語関係者独特のものです。

画像: 「一部の会社が使わなくなったくらいでプログラミング言語が死ぬわけねーだろ」

言語の乗り換えは、普通によくあることです。

並外れた成功を収めるとモノリスアプリをマイクロサービスに分割するというパターンもよく見かけます。

あなたがまだ数千ドルしかファンディングを調達できていないスタートアップ関係者で、アプリをスクラッチから開発して成功させたければ、JavaやElixir Phoenixのような無駄に複雑な言語を選んでいては、リリースを迎えないうちにあっという間に資金は底をついてしまうでしょう。

もちろんElixirもPhoenixも本当にいいものです。生産性も高いし。しかし正直なところ、製品開発速度においてはRailsの方がずっと上です。

Web開発経験すらないホヤホヤのスタートアップ企業が自分たちの「画期的な」プロジェクトに欲しいものといえば、ほぼ間違いなく「プロジェクトを支持してくれる数百万人のユーザー」がそのひとつでしょう。そんなユーザーがいれば成功間違いなしですから。たいていのスタートアップ企業は、そういう手前勝手な期待を当てにして没落します。サービスを立ち上げて半年経っても、ユーザー数はせいぜい数千人がいいところでしょう。大成功してトラフィックが押し寄せるときに備えるのも結構ですが、取らぬ狸の皮算用より現実を先に見ましょう。

成功への期待が大きすぎれば、それをサポートする投資側が期待する完成時の見返りも相応に膨れ上がります。同じ機能であっても、ユーザートラフィックが数百の場合、数千の場合、数十万の場合に応じて開発期間は数日であったり、数週間、数か月と変動します。

競争の激しい市場で生き残るのは、要件に応じた既存機能の改修や新機能の導入をすばやく行えるスタートアップ企業であり、そうしたフットワークがなければたちまち消えるのが定めです。顧客や投資家にしてみれば、箸にも棒にもかからないエクスペリエンスでユーザーがせいぜい数百人程度のアプリが、将来2百万ユーザーをさばける潜在能力を秘めていようが知ったことではありません。

このように、成り行きはプロジェクトによって千差万別です。今構築中のチャットアプリはRubyで作ると初期段階から苦労が絶えず、やがてその分野はElixirに支配される可能性がもしかするとあるかもしれません。そのプロジェクトのボトルネックがどこにあるのかを真剣に分析する必要があります。

私がRubyに(そして概ねRailsにも)こだわり続ける理由は単純そのものです。私が愛しているのは面白い機能を続々開発できるプロジェクトであって、ひとつの機能を何か月もかけて辛抱強く最適化することではありません。私がプロジェクトを選ぶときは、大きすぎるプロジェクトや小さすぎるプロジェクトをあえて避けています。私にとって楽しく難易度も手頃なのはいつも中くらいのプロジェクトです。

そういうわけで、皆さんも自分たちが選んだ技術に合わせて、これから数年どんなプロジェクトで働きたいかを真剣に自問自答すべきです。

コミュニティ

コミュニティの規模や特性も見逃すわけにはいきません。言語のコミュニティ規模が小さすぎると、ライブラリやバグを修正してくれるベテラン開発者が不足します。

かといってプログラミング言語のコミュニティがあまりに大きくなると、コミュニティの劣化が加速する可能性があります。ささいなことに思われるかもしれませんが、とある言語の周辺コミュニティは、小規模だった頃にはクールかつオープンマインドを保っていたのが、市場で支配的な地位を占めるようになって人気が高まるに連れて、ポリティカルコレクトネスグループとしての性格が強まりました。手短に言うと、一部の優秀かつ人当たりもよい開発者たちがSNSで他愛もないジョークをかました途端、非難轟々の嵐にさらされたのです。

どの言語なのかは言わないでおきます。書いたら最後、コミュニティの矛先がこちらに向かって爆発炎上するでしょう。それはそれで私の言説の裏付けにはなるでしょうが、そんな自殺行為は趣味ではありませんので。


  • 次記事: Railsは2018年も現役か?: 中編(翻訳)

関連記事

Ruby/Railsのプロ開発者としての5年間を振り返る(翻訳)

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

Railsは2018年も現役か?: 中編(翻訳)

$
0
0

言語

それでは、初めに述べた言語たちを分析することにしましょう。

Rubyはオブジェクト指向プログラミング言語として優れています。(私見では)Rubyは優れたOOPエクスペリエンスと開発者の幸せを主要な目標に置いています。しかしコードの実行速度は主要な目標ではありません。

訳注: ご存知のとおり、現在のRubyは「Ruby 3×3」の旗のもとでコードの実行速度改善の優先順位を以前よりも上げています。

Ruby on Railsは、Ruby言語上で構築されたWebフレームワークであり、生産性と開発者の幸せを主要な目標に置いています。コードの実行速度や、癒着のないOOPエクスペリエンスはRailsの主要な目標ではありません。ソケット接続はまあまあですが、優秀というほどでもありません。

Elixirは関数型プログラミング言語として優れています。(私見では)Elixirはマルチコア処理(コードの実行速度)とソケット接続のサポートを主要な目標に置きつつ、開発者の幸せや十分な生産性にも配慮しています。

ElixirにはPhoenixというWebフレームワークもあり、Phoenixについて知れば知るほど、PhoenixがElixirと同じ目標を目指してElixirと連携するライブラリの集合体であることが理解できます。

つまり対決の図式は、Elixir vs Ruby vs Ruby on Railsということになります😄

私の説明はどの言語でも似たり寄ったりに見えますが、「魔は細部に宿る」ものです。

Ruby

オブジェクト指向プログラミングの概念(SOLID原則やオブジェクトのコンポジション、DCI、オブジェクト指向解体設計(OODD)、シンプルデザイン、境界コンテキスト、DDD、BDD、TDDなどなど)の理解が深まれば深まるほど、Rubyがこうした概念と見事に調和していることに気付かされます。

私見では、Rubyは「開発者自身を記述するのに最適な言語」です。一部の開発者もおおむねこの点を支持していますが、このことにはそれ以上に重要な点があります。皆さんの職歴のほとんどはチーム作業で占められているのですから、自分や同僚がコードに込めた意図が相手に伝わることが重要です(それこそが言語というものですから)。

よくある誤解のひとつに、Railsを使わないとまともなRuby Webアプリは書けないというものがあります。実際にはHanamiSinatraなどさまざまな選択肢があります。

RubyはWeb開発に特化した言語ではなく、汎用的なプログラミング言語であることを思い出しましょう。私はこの9年間で、純粋なRuby CLIツールを何十個も個人用や職場向けに作成しています。Google SketchupのようなRubyを実行できるアプリもありますし(訳注: 現在はGoogleの所有ではありません)、Rubyは日本ではかなり人気の高い言語です。

「Rubyは死んだ」記事はいくらでも見かけますが、裏付けを示している記事はめったにありません。たいていはグローバルなデータに立脚していない、開発者の個人的な見解や経験をぶちまけた記事です。どこかの会社が別の技術に乗り換えたぐらいで言語全体が死ぬということにはなりません。

Rubyは今日も元気いっぱいに溌剌と生きています。

Rubyは決して大メジャーではありませんが、それがマイナス要因になっていません。コミュニティのところで説明したように、実際にはこの方がむしろ望ましい状態かもしれません。

Rubyはかれこれ25年も長らえています。Rubyがそう簡単に消えてなくなることはないでしょう。

本記事を多面的に捉える裏付けのひとつとして、Railsが縮小しつつある場面をこちらの記事でご覧ください。最近のコーディングブートキャンプの科目ではRailsが他のものに置き換えられつつあります。これについては後述します。

Ruby on Rails

Railsはフレームワーク(協調動作するライブラリの集合体)であると同時に、製品を短期間で構築するためのコードはいかにあるべきかという哲学でもあります。

他の言語からRails世界に飛び込んだ開発者は、多くの場合Railsの歴史や価値体系の大きな違いを見落としています。

これらを視聴してRailsの意図を正しく理解できるようになると、Railsの作者たちが2018年に提案するソリューションが10年前と変わっていないこと、つまり今も通用していることがわかるでしょう。サーバーサイドレンダリングされるHTMLやCSS、ちりばめられたJavaScriptは今もそのままです(例のRJSはともかく)。

BasecampやShopifyの収益(何しろ営利企業ですから)や、開発者が幸せそうにしている様子(ソース)は、Railsのアプローチがうまくいっている証拠です。

Railsでは、開発者の生産性を高めるためにさまざまな要素が互いに結合(癒着)しています。たとえばRailsのモデルはデータベースに直接書き込みますし、フロントエンドはデフォルトでバックエンドと結合(癒着)しています。

最近の大きな潮流といえばSPA(シングルページアプリケーション)ですが、RailsはとりたててSPAに逆らおうとはしていません。Railsの作者たちは、単にSPAを使うことを好んでいないだけです。

ですからRailsのフロントエンドでSPAを使っても別に構いません。しかし独特なものになってしまうのも確かです。Rails 5でWebpackが標準として導入されたのはつい最近の話です。ある時期までのRailsでは、(gemを使って)アセットパイプラインでSPAライブラリをレンダリングするのが一般的でした。これはRailsバックエンド開発者とフロントエンド専門の開発者の間に緊張をもたらし、JavaScriptとRailsの「ショットガンマリッジ(shotgun marriage: できちゃった婚)」は不評を買いました。

別のアプローチは、API専用のRailsアプリを生成してSPAをまったく別のVMに配置するというものですが、DevOpsと開発チームの同期を取るために文字どおり2つの異なるプロジェクトを立ち上げるという余分な手間がかかります。

ポイントは、RailsはSPAの利用を望んでいないこと、しかしSPAが必要なら3とおりの方法が使えるということです。これは、Rails初心者や「トレンディな」最新技術であるSPAなどを教えたいコーディングブートキャンプが「RailsでSPAをやっていいのかどうか」で戸惑うだろうと私は考えます。だからブートキャンプはRailsを捨ててピュアJSバックエンドを教えるようになったのです。

理由についてはJavaScriptのセクション(後編)で説明します。

Railsの人気が落ちているように見える理由はこれです。しかし人気が落ちたのではありません。ブートキャンプがRailsを教えなくなっているらしいというだけであって、スタートアップ企業は今も新規プロジェクトでRailsを選択しています。

ここで忘れてはならないことがひとつあります。何か新しいトレンドが出現したからといって、即それ一択ということにはなりません。何年か前にもNoSQLデータベースで同じことがありました。当時はMongoDBだのCassandraだのRiakだのが、MySQLPostgreSQLなどのSQLデータベースを置き換えようとする巨大なムーブメントが巻き起こりました。その結果どうなったかというと、置き換えは起こりませんでした。SQLデータベースを使いたいシナリオもあれば、NoSQLデータベースの方が適切なシナリオもある、それだけのことです。

昨今のマイクロサービスも似たような傾向を示し、いつか来た道をたどっています。マイクロサービスが2018年のアプリにおいていかに「ベストプラクティス」であるかが方々で力説されていますが、製品リリース前に資金を使い果たしてしまうことについての説明はとんと見かけません。

開発者はいずれ、Railsがモノリスアプリに適していることと、Railsのマイクロサービスは少々メモリを食い過ぎることに気づき、Sinatraのような軽めのソリューションを採用したりするでしょう。

同じことが「コーディングのベストプラクティス」「テストのベストプラクティス」「デプロイのベストなんちゃら」などにも言えます。

チームやプロジェクトは、ひとつとして同じものはありません。プロジェクトをどう構築するか、どんな技術を使いたいか、どんな計画で進めたいのかは、他ならぬあなたがじっくり腰を据えてチームと議論しなければ決まりません。プロジェクトにとって最も迷惑なのは、毎回意見がコロコロ変わる人です。

そういうわけで、たとえば「開発速度と開発の容易さ」が主要な懸案事項だとすると、私ならSPA抜きのRailsを採用するでしょう(Turbolinksは皆さんが思っている以上に強力ですよ)。特に、チーム開発でなくワンマンプロジェクトであればきっとそうするでしょう。

「SPAとJavaScriptに専念するフロントエンド開発チーム」と「APIの提供に専念するバックエンド開発チーム」の2チーム編成が目的であれば、RailsのAPI専用オプション(rails new myapp --api)を採用するでしょう。

そのAPIで膨大なトラフィックを扱う必要があるならRailsを使いますが、EventMachineなどの別のエンジンも検討するでしょう。とはいうものの、RailsのデフォルトWebサーバーであるPumaも実に強力です。Railsのキャッシュの適用方法をきちんと学べば、相当なトラフィックを扱えるようになります。

APIでメガ単位のトラフィック(数百万リクエスト級)を扱う必要があるなら、ElixirとPhoenixがずっと優れた選択肢になる可能性もあります。

とあるPodcastで知ったのですが、米国の某市が運営する犯罪レポーティングソフトウェアはかつてRailsサーバーで運用されていたのですが、平均トラフィックが大きいため6台ほどサーバーを使っていたそうです。同市がサーバーをリファクタリングしてElixirに置き換えたところ、2台のサーバーでまかなえるようになりました。平均負荷は30%だったので実際には1台のサーバーで回せるのですが、サーバーダウンに備えて2台編成にしているのだそうです。


関連記事

Ruby/Railsのプロ開発者としての5年間を振り返る(翻訳)

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

週刊Railsウォッチ(20180918)ビューテンプレート探索が高速化、mini_scheduler gem、レガシコントローラのリファクタリングほか

$
0
0

こんにちは、hachi8833です。先日の大江戸Ruby会議 07の白眉は、Rubyのキーワード引数分離についての議論↓と、Aaron PattersonさんのAsakusa.rb 10周年のお祝いメッセージが特濃だった(tenderlove/allocation_sampler)ところでした。Asakusa.rb10周年おめでとうございます!🎉🎉

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを社内有志でつっついたときの会話です👄

今回のエントリは少なめです🙇。

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

つっつき時点では公式の更新情報がなかったのでコミットログなどから見繕いました。

ビューテンプレート探索を高速化

かなり速くなったようです。

# actionview/lib/action_view/template/resolver.rb#L368
-     exts = EXTENSIONS.map do |ext, prefix|
-       if ext == :variants && details[ext] == :any
-         "{#{prefix}*,}"
-       else
-         "{#{details[ext].compact.uniq.map { |e| "#{prefix}#{e}," }.join}}"
+     def find_template_paths_from_details(path, details)
+       # 可能なパスをすべてチェックするのではなく、他のglobと同様、正しいprefixを持つファイルを検索する
+       query = "#{escape_entry(File.join(@path, path))}*"
+        regex = build_regex(path, details)
+        Dir[query].uniq.reject do |filename|
+         # この正規表現は、(prefix以外にも)詳細部分にもマッチするファイルの探索と、
+         # 大文字小文字を区別しないファイルシステム向けのフィルタという2つの役割を担う
+         !filename.match(regex) ||
+           File.directory?(filename)
+       end.sort_by do |filename|
+         # ファイルを1つずつチェックするのではなくディレクトリを探索しているので、
+         # そのままでは戻り値の順序が一定しない
+         # この正規表現でのマッチ結果を用いて詳細部分をインデックスでソートする
+         match = filename.match(regex)
+         EXTENSIONS.keys.reverse.map do |ext|
+           if ext == :variants && details[ext] == :any
+             match[ext].nil? ? 0 : 1
+           elsif match[ext].nil?
+             # No match should be last
+             details[ext].length
+           else
+             found = match[ext].to_sym
+             details[ext].index(found)
+           end
+         end
+       end
-     end.join
+     end
# 修正前
Warming up --------------------------------------
   lookup users/show     6.000  i/100ms
Calculating -------------------------------------
   lookup users/show     61.302  (± 4.9%) i/s -    306.000  in   5.011577s

# 修正後
Warming up --------------------------------------
   lookup users/show   353.000  i/100ms
Calculating -------------------------------------
   lookup users/show      3.845k (± 5.3%) i/s -     19.415k in   5.064962s

つっつきボイス:「globって何だろうと思ったらDir.globのことでした」「そういえばglobってあった」「コメントの日本語は🤔?」「あ、私が取り急ぎ訳しました」「今回の改善は著しいですね😍前よりコード量増えてるのに20倍近く速くなってる」「👍も30超えてるし」「ベンチマークではlookup_context.find_templateを調べてるのか↓」「裏方の改善だから何もしなくても恩恵が得られるヤツ😎」

# 同PRより
require './config/environment'

Benchmark.ips do |x|
  x.report "lookup users/show" do
    ApplicationController.new.lookup_context.find_template("users/show", [], false, {}, {})
  end
end

参考: Dir.glob と Find の違い - Qiita

ActionDispatch::IntegrationTestTestHelperをインクルード

DHHのあげた#33838への対応です。


つっつきボイス:「これは小物かなと思いつつ」「integration testにTestHelperがデフォルトでインクルードされたのか」「ActionMailer::TestHelperには何が入ってるのかな…↓assert_*系のメソッドか」「RSpecなんかだとこういうのを自分でインクルードしたりしますね」「せっかくテスト用ヘルパーがあるんだからデフォルトで入れてやろうぜってことなんでしょうね」「minitestですけどね☺️」

参考: ActionMailer::TestHelper

「今さらですけど、integration testはシステムテストとはまた別なんですよね?」「RSpecで言うとfeature testに相当するのがintegration testでしょうね: integration testはやったことないんですが」「そういえばintegration testでCapybaraは使われてるのかな?」「integration testはずっと前からRailsにあるんでCapybaraとは別でしょうね」「integration testはRailsフレームワーク自身のテスト用ですしね」「システムテストはCapybaraと共に導入されているし、間違いなくCapybaraと連携してる」

Rails 5.1以降のシステムテストをRSpecで実行する(翻訳)

Integer#multiple_of?の高速化

riganiさんのアバターにしばし見とれてしまいました。

# activesupport/lib/active_support/core_ext/integer/multiple.rb#L10
  def multiple_of?(number)
    number != 0 ? self % number == 0 : zero?
    number == 0 ? self == 0 : self % number == 0
  end

zero?より== 0が速く、!=より==が速いんだそうです。


つっつきボイス:「Integer#multiple_of?なんてのがあったの?」「倍数かどうかの判定ですね」「どれだけ速くなったのかな…1.33倍」「引数がゼロのときはこのぐらい速くなると」「ゼロ以外のときはそうでもない感じ」「numberが1のときもtrue?😆」

「アバター自分で描いてるっぽい❤️」「この方、Nim言語というのもやってますね」「これそのうちウォッチで取り上げようかな😋(忘れないようにしないと💦)」

ActiveRecordにfilter_attributes属性を追加

59cae07を追っているうちにfilter_attributes属性がつい最近追加されたことに気づきました。config.filter_parametersを設定してあっても、誤って#inspectされてログに出てしまうことがあったのを修正するためだそうです。

# activerecord/lib/active_record/core.rb#L129
+     # #inspect呼び出しで公開されたくないカラムを指定する
+     class_attribute :filter_attributes, instance_writer: false, default: []
# activerecord/lib/active_record/core.rb#L495
    def inspect
      filter_attributes = self.filter_attributes.map(&:to_s).to_set
      # 初期化されていないオブジェクトが割り当てられている場合にwarningを生成しないよう
      # defined?(@attributes)をチェック
      inspection = if defined?(@attributes) && @attributes
        self.class.attribute_names.collect do |name|
          if has_attribute?(name)
-           "#{name}: #{attribute_for_inspect(name)}"
+           if filter_attributes.include?(name) && !read_attribute(name).nil?
+             "#{name}: #{ActiveRecord::Core::FILTERED}"
+           else
+             "#{name}: #{attribute_for_inspect(name)}"
+           end
          end
        end.compact.join(", ")
      else
         "not initialized"
       end

       "#<#{self.class} #{inspection}>"
     end

参考: ActiveRecord::Core::ClassMethods#filter_attributes


つっつきボイス:「filter_parametersは確かに昔からありますね」「↓こうすると内部でinspectが呼ばれてログに出てしまう」

# 同記事より
@account = Account.find params[:id]
payload = { account: @account }
logger.info "payload will be #{ payload }"

参考: Rails4でfilter_parametersのネスト - Qiita

rails newすると、"password"というカラム名がフィルタされる設定がconfig/initializers/filter_parameter_logging.rbに書き込まれるんですね: フィルタしたいカラム名をこのファイルに追加できる」「Railsのログに[FILTERED]って出ているとすげ~って思う💪: いつの間にって感じ」

「そういえばfilter_parametersでフィルタするカラム名はシンボルや文字列で指定できるんですけど、たとえばうっかり:nameとか指定してしまうと、nameを含むカラム名と部分一致してことごとくフィルタされちゃったりするんですよね😅」「ありそう〜」「シンボルだからカラム名と完全一致しないとフィルタされないだろうと思うじゃないですか、でもそうじゃない: シンボルも文字列も実はカラム名と部分一致する😭」「😆」「だからフィルタが間違って効きすぎないようにするには、シンボルや文字列じゃなくて、正規表現を使って始まりと終わりもびしっと指定しないといけないんですよ」「まあpasswordという文字を含むカラム名は原則ログに出しちゃあかんというのはワカル: だからこそpassword_confirmationとかもフィルタされるし」「安全側に倒すという意味ではそういう作りになるのはしょうがないのかも」「親切設計なんですけど、その親切を理解してないとつまらないワナを踏んじゃう」

参考: Railsのlogに出したくない情報をちゃんと出さないようにする - Qiita

Edgeguidesには「パラメータは正規表現の部分一致でフィルタされる」とあります。日本語版Railsガイドにはまだ反映されていませんが、現在作業中です。

なお、このPRでedgeapi.rubyonrails.orgというサイトがあることを今頃知りました。

「edgeapiはmasterブランチのRails APIをいち早く見ることができるサイトですね」「へぇえ〜、これちょっといいかも😋」

archive.orgで調べたらedgeapiは2010年からあるんですね💦。

DatabaseLimitsで使われてないメソッドを非推奨化


  • column_name_length
  • table_name_length
  • columns_per_table
  • indexes_per_table
  • columns_per_multicolumn_index
  • sql_query_length
  • joins_per_query

つっつきボイス:「kamipoさんが『このメソッドたち絶対使わないっしょ』ということで非推奨にしたようです」「DatabaseLimitsって何だろそもそも?🤔」「たとえばsql_query_length()なら、クエリの長さがこれ以上増えると実行できないから実行時に動的に調べる…やらねーよそんなこと😆」「😆」「遅くなるだけだし」「PRメッセージを見ると、単に使われてないだけじゃなくて、ほぼほぼテストされてないってありますね😵」「まあたとえばマイグレーションでクエリがすごく長くなって落ちる前に、ちょっと気の利いたメッセージを出すとかクエリを調整するとかしたい、なんて使いみちならあるかも?やらないだろうな〜やっぱり😆」

参考: Ruby on Rails 5.2 / ActiveRecord::ConnectionAdapters::DatabaseLimits — DevDocs

追いかけボイス: 「DatabaseLimits 、SQLを文字列として生成しているコードなんかではWHERE文のサイズが爆発してMySQLのmax_allowed_packetを超過しちゃうということは割とあるので、その辺を見るメソッドだったんだろうなあと思った: AR/Arelで吸収して欲しいから使わないけど」「"WHERE hoge IN (#{bigger_ar_obj.pluck(:id).join(',')})"とかやると溢れる」

Rails

Knapsackでテストを高速安定実行(RubyFlowより)

Knapsackは以前のウォッチでも取り上げましたが一応。同記事で紹介されているスライド↓は、その名もRUG::BというベルリンのRubyユーザーグループが主催した9月のミートアップ発表されたものです。


knapsackpro.comより

mini_scheduler: Sidekiqで反復ジョブを登録するgem(Ruby Weeklyより)

# 同リポジトリより
class MyHourlyJob
  include Sidekiq::Worker
  extend MiniScheduler::Schedule

  every 1.hour

  def execute(args)
    # some tasks
  end
end

Discourseのgemです。

discourse.orgより


つっつきボイス:「every 1.hourって書けるのね😲」「daily at: 12.hoursという書き方もできるみたいです」「役に立ちそうなgemでしょうか?」「使ってみないとわからないけど、こうやって表現できるのはいいことじゃないかな?」「Sidekiqでさっとスケジューリングしたいときにはいいのかも😋」

これとはあまり関係ありませんが、最近Railsdm Podcast #3↓に登場したy-yagiさんもactivejob-cancelというgemを作っています。

「ジョブのキャンセルってやっぱり面倒なんでしょうか?」「ジョブが動き出す前でもそもそもキャンセルしていいのかを判断しないといけないし、動き出してからだとキャンセル処理で原状復帰しないといけないし」「50%進めちゃったけどキャンセルしてよいのそれ?みたいなところを考えないといけないし、タイミングによってはシビアになるだろうし」

sourcemaking.com: デザパタ/アンパタ/リファクタ/UMLを網羅した解説サイト


同サイトより

自著の販売促進サイトのようです。以下の3冊が紹介されています。


つっつきボイス:「たまたま見つけたんですが、デザパタもアンチパターンもリファクタリングもUMLもカバーしているサイトです」「ほほ〜?」「サイトのコンテンツのまとまりがとても良くて、おっと思いました: アンチパターンも開発アンチパターンにアーキテクチャアンチパターンにプロジェクト管理アンチパターン、リファクタリングもコードの臭いとリファクタリング手法と分かれています↓」「おー、デザパタのあたりを見てるけど、コード例にJavaとかPHPとかDelphiとかはあるけどRubyは入ってないか(残念!)」「アンチパターンはあまりコードを使わずに説明しているっぽい」「シングルトンパターンは雑にやるとマルチスレッドに対応できないとかちゃんと書いてあるのがエライ😋」


同サイトより

Martin Fowlerさんのサイトとかの方が一次情報ですし内容も濃いと思うんですけど、あまりに雑然としていて追うのが大変なので😭」「😆」「Wolf ticketとか、知らないアンチパターンもありますね🤔」「こんなにいろいろあったかなと」「そういえばアンチパターンって凝った言い回しが多くて日本語にするのに四苦八苦します」

wolf ticketは「空約束」「看板に偽りあり」的な意味のようです。

[保存版]人間が読んで理解できるデザインパターン解説#1: 作成系(翻訳)

Rails OOP上級編(RubyFlowより)

# 同記事より
# lib/quote_connector.rb
require 'httparty'

class QuoteConnector
  URL = 'https://talaikis.com/api/quotes/random/'.freeze
  # since this is constant, we REALLY don't want to mutate it

  def initialize(adapter: HTTParty)
    @adapter = adapter
  end

  def call
    adapter.get(URL).body
  end

  private

  attr_reader :adapter
end

つっつきボイス:「記事の中に『DHHはDIを信じない』(2013年の記事)↓ってリンクがあるけど、知らなかった💦」「DIは基本的にJavaの問題解決手法だし、DHHがRubyでDIするか〜?っていう気持ちになるのはワカル」

参考: Dependency injection is not a virtue in Ruby (DHH)

「記事に出てくるinline bundler syntaxって何だろう?と思ったら、↓こういうふうにコードの中でbundlerを呼び出すのか😲」

# bundler.ioより
require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'
  gem 'json', require: false
  gem 'nap', require: 'rest'
  gem 'cocoapods', '~> 0.34.1'
end

puts 'Gems installed and loaded!'
puts "The nap gem is at version #{REST::VERSION}"

「こういう書き方って再現コードとかでよく使われてたと思います: gemがなくてもREPLでその場で動かしたいコードとか」「へ〜!😳」「たとえばActiveRecordのバグを再現するときとかに、こうやってbundlerでこのgemとこのgemを読み込んで、ActiveRecordのテーブルはメモリ展開しておくとか」「確かに説明のためにRailsをフルでセットアップしてられないし」「プレゼンのときにも便利そう」「Gemfileを用意しなくてもいいですしね😎」

レガシコントローラをSOLIDパターンでリファクタリング(Hacklinesより)

# 同記事より
 module Api
   module Admin
     module Analytics
       class ProductsController < V1::ApiController
         def top_sold
           raise ParamsMissing, 'params missing' if params_missing
           raise InvalidDateInterval, 'range of days is not allowed' unless check_date_interval
           response = perform(
             merchant_id: current_merchant.id,
             url: analytics_params[:url]
             start_date: analytics_params[:start_date],
             end_date: analytics_params[:end_date]
           )

           render json: ::Analytics::FormatService.top_product_format(response['metrics']), status: :ok
         rescue RestClient::Exception => ex
           render json: JSON.parse(ex.response), status: :unprocessable_entity
         rescue => ex
           render json: { message: ex.message }, status: :unprocessable_entity
         end

         ...
         ...
         ...
       end
     end
   end
 end
end

つっつきボイス:「リファクタリング前↑はやたらraiseとかrescueとかしまくっててごちゃごちゃしてる」「よく起こりがちなコード👓」「ざっとしか見てないけど、リクエストとレスポンスの処理を分けつつ、エラーハンドリングやバリデーションを切り出してそこに委譲する感じかな〜」

# 同記事より
module Anaylytics
  class Request
    module ErrorsHandler
      include AcctiveSupport::Concern
      include ActiveSupport::Rescuable

      included do
        rescue_from RequestError, with: :deny_access
      end

      protected

      class RequestError < StandardError
         ...
         ...
         ...
      end

      private

      def deny_access
        ...
        ...
      end
    end
  end
end

「ぱっと見Railsと関係ないコードかな?っと思ったけど、rescue_from↑が出てくるのはもうRailsのコードに間違いない😋」

参考: Ruby on Rails 5.2 / ActiveSupport::Rescuable::ClassMethods#rescue_from — DevDocs

「最終的に以下のようにErrorsHandlerをインクルードすればRequest.getでエラー処理ができるようになる感じかな」「よしよし☺️: 本線の処理とエラーの処理は別にしたいし」

# 同記事より
...
...
include Analytics::Request::ErrorsHandler

def top_sale
  response = Anaylytics::Request.get(:top_sale, params: {
    merchant_id: current_merchant.id,
    start_date: analytics_params[:start_date],
    end_date: analytics_params[:end_date]
  })

  render json: response, status: response.code
end
...
...

Ruby

ネイティブ拡張を使うgemを書く(Ruby Weeklyより)

#include <ruby.h>
#include <libclipboard.h>
#include "extconf.h"

static clipboard_c *cb = NULL;

VALUE set_text(VALUE _self, VALUE val) {
    Check_Type(val, T_STRING);
    VALUE result = Qnil;
    char *text = clipboard_text(cb);
    if (NULL != text) {
        result = rb_str_new(text, strlen(text));
        free(text);
    }
    if (false == clipboard_set_text(cb, StringValueCStr(val))) {
        rb_raise(rb_eRuntimeError, "Failed to write to clipboard.");
    }
    return result;
}

VALUE get_text(VALUE _self) {
    VALUE result = Qnil;
    char *text = clipboard_text(cb);
    if (NULL != text) {
        result = rb_str_new(text, strlen(text));
        free(text);
    }
    return result;
}

void Init_simple_clipboard() {
    cb = clipboard_new(NULL);
    if (NULL == cb) {
        rb_raise(rb_eRuntimeError, "Failed to create clipboard context.");
    }
    VALUE mod = rb_define_module("SimpleClipboard");
    rb_define_module_function(mod, "get_text", get_text, 0);
    rb_define_module_function(mod, "set_text", set_text, 1);
}

つっつきボイス:「いわゆるやってみた系の記事かなと思いますが」「お、クリップボードを扱うのか」「クリップボードの中身を取り出すにはどこかでシステムにアクセスしないといけないからC拡張でやったんでしょうね」「素のRubyからクリップボードを扱うのは大変そう」

後でclipbordというそれ用のgemがあるのを見つけました。

RubyとRustでそれぞれパスワード生成してみた(Ruby Weeklyより)

# 同記事より
Options.number.times do
  password = Options.length.times.map do
    dict.sample(random: SecureRandom)
  end.join(Options.separator)

  puts password
end
# 同記事より
    use std::iter::repeat_with;

    let mkpass = || {
        repeat_with(|| rng.choose(&dict).expect("dictionary shouldn't be empty"))
            .take(opts.length)
            .map(|s| *s)
            .collect::<Vec<&str>>()
            .join(&opts.separator)
    };

Redisでインメモリのブルームフィルタを実装する(Ruby Weeklyより)


元記事より

記事では、当初は先月のウォッチでも扱ったbloomfilter-rbを使ったのが、コンカレンシーの問題と更新の遅さを解決するため自分たちで再実装したとのことです。

# 同記事より
temporary_bloom_filter = TemporaryBloomFilter.new(user)

temporary_bloom_filter.insert(['user3@example.com', 'user4@example.com', 'user5@example.com'])

temporary_bloom_filter.count
# => 5

temporary_bloom_filter.include?('user5@example.com')
# => true

temporary_bloom_filter.include?('user6@example.com')
# => false

参考: What is a Bloom Filter Pattern? | Redis Labs — Redisでサポートされているrebloomというデータ型(記事では使わなかったとのこと)


つっつきボイス:「ブルームフィルタはfalse positiveはあってもfalse negativeはないんだそうです」「つまり間違って『ある』と判定する可能性はあっても間違って『ない』とは判定しないってことね😋」

参考: ブルームフィルタ - Wikipedia

bindef: Rubyで書かれたバイナリ生成DSL(Ruby Weeklyより)

# 同記事より
# 以後の文字列エンコードをUTF-32にする
pragma encoding: "utf-32"
str "hello"
str "there"

# 以後の文字列エンコードをUTF-8にする(デフォルト)
pragma encoding: "utf-8"
str "utf-8!"

# pragmaにブロックを渡してスコープを絞れる
pragma endian: :big do
  # このブロック内ではビッグエンディアンのみを出力
  # Rubyの名前空間と定数は通常どおり機能することと、
  # `bindef`は`f32`と`f64`でfloatを使うことに注意
  # of `f` and `d`.
  i64 0xFF00FF00
  f64 Math::PI
  f32 Float::INFINITY
end

つっつきボイス:「ビンデフ?」「pragmaとか出てくるとCとかC++みたい」「pragmaってAda言語が元だったのか」「AdaというとAda Lavelaceから命名されたという古典ネタをつい思い出してしまいました」

参考: C言語 プリプロセッサ
参考: Ada について話します - Qiita

クラウド/コンテナ/インフラ/Linux/Serverless

AWS System Managerにsshなしでコンソールログインできる「Session Manager」機能登場


つっつきボイス:「ssh鍵なしでブラウザからコンソールが使えるのはありがたいかも☺️」「それがいいのかというのはさておいて😆」「自分のPCがない客先とか旅行先でも使えるし」

参考: SSH不要時代がくるか!?AWS Systems Manager セッションマネージャーがリリースされました! | Developers.IO

SQL

PostgreSQLのchar型に使いみちはあるか(Postgres Weeklyより)

xof=# select length(c) from chars;
 length 
--------
      1
(1 row)

xof=# select substring(c from 8 for 1) = ' '::char(1) from chars;
 ?column? 
----------
 t
(1 row)

xof=# select substring(c from 8 for 1) = ' '::varchar(1) from chars;
 ?column? 
----------
 f
(1 row)

xof=# select length(substring(c from 8 for 1)) from chars;
 length 
--------
      0
(1 row)

xof=# select c || 'y' from chars;
 ?column? 
----------
 xy
(1 row)

つっつきボイス:「charを使っても節約できるのはたかが知れてるし、挙動も何だか謎なので、長さが1より大きいならvarchartextでいいんじゃね?という話のようです」「' '::char(1)だとtrueだけど' '::varchar(1)だとfalse…」「charはやっぱり固定幅なのね」「使わないかな〜」「使わないですね」

参考: 文字型 — PostgreSQL 9.4

追いかけボイス: 「DBMSのchar型は、アラインメントを合わせたりできるのでカリカリにチューニングするDBAな人達とかは結構神経質にやってる人もいると思う: DBAがいるようなプロジェクトでないとあまり見ないけど」

ZomboDB: PostgreSQLをElasticsearchと連携(Postgres Weeklyより)


ロゴはリポジトリより

-- 同リポジトリより
CREATE EXTENSION zombodb;

CREATE TABLE products (
    id SERIAL8 NOT NULL PRIMARY KEY,
    name text NOT NULL,
    keywords varchar(64)[],
    short_summary text,
    long_description zdb.fulltext,
    price bigint,
    inventory_count integer,
    discontinued boolean default false,
    availability_date date
);

CREATE INDEX idxproducts 
          ON products 
       USING zombodb ((products.*)) 
        WITH (url='localhost:9200/');

つっつきボイス:「★1600超え」「普通にElasticsearchを使えばいいのかな?と思いつつ」「PostgreSQLで一貫して使いたい人向けなんでしょうね」「↑上みたいにCREATE EXTENSION zombodb;してUSING zombodbとすると使えるようになると」「WITH (url='localhost:9200/')は全文検索サーバーのURLを指定するってことなのかな?🤔」「するとこんなふうに==>でElasticsearch風なクエリを書ける↓と」「アプリ層からは普通にSQL叩いているように見えるのはちょっとうれしいかも」「この追加クエリ部分はSQLじゃないですけどね🤣」「ちなみにまだベータ版だそうです」

SELECT * 
  FROM products 
 WHERE products ==> '(keywords:(sports OR box) OR long_description:"wooden away"~5) AND price:[1000 TO 20000]';

当初Elasticsearchがなくても使えるのかなと思ったのですが、system requirementで5.6.xか6.xと指定されていました。

その他SQL

CSS/HTML/フロントエンド/テスト

Priority Hints

     <img src="img/1.png" importance="high">
     <img src="img/2.png" importance="low">
     <img src="img/3.png" importance="low">
     <img src="img/4.png" importance="high">

つっつきボイス:「importance="high"とか書けるのっていい!😍」「何だか夢みたいじゃないですか😆」

HTTP発展小史


  • HTTP 0.9
  • HTTP/1.0
  • HTTP/1.1
  • HTTP/2

つっつきボイス:「HTTP 0.9なんてのがあったんですねっ😆」「😆」「HTTP/1.1ってどんだけ長く使われてるんだと」「この日進月歩の時代に☺️」「このサイトにはHigh Performance Browser Networking (O’Reilly)を掲載しているようですが何だか丸ごと載せてるみたい」「太っ腹🤩」

言語よろずの間

日本語Wikipediaエンティティベクトル


同記事より(CC BY-SA 4.0)


つっつきボイス:「比較的シンプルな処理でいい感じにできるのがいいなと思って: 『ヤマハ』と『ヤマハ発動機』の類似度の違いとか、北海道の札幌に相当するのは沖縄県のどこか、とか」「おぉ〜面白い☺️」

その他言語よろず

その他

イノベーションのジレンマ

Inbox by Gmailが消える?


つっつきボイス:「個人的にちょっとショックだったので😭」「これも消えるんですね〜: Gmailに注力するって書いてあるみたいだし」「一瞬使ってたけど☺️」「これだけ大きい企業なのにあっさりやめるあたりが、上の『イノベーションのジレンマ』の逆を行ってる感😆」「Google、Pixelは日本で出さないのに…😢」「そういえば今度Pixelを日本で発売するとかしないとかでざわついてますね↓」

参考: Google、新型スマホのティーザーサイト公開 「Pixel 3」日本発売か - ITmedia NEWS

番外

メイドイン軌道の高性能光ファイバー

参考: 史上初の宇宙製品は地球産の100倍高性能な光ファイバーかもしれない - GIGAZINE


今回は以上です。

バックナンバー(2018年度後半)

週刊Railsウォッチ(20180910)公開つっつき会#2、RSpecは何を参考にするか、イベントソーシング、marginalia gem、負荷テストツールvegetaほか

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

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

Rails公式ニュース

Ruby Weekly

RubyFlow

160928_1638_XvIP4h

Hacklines

Hacklines

Postgres Weekly

postgres_weekly_banner

Railsは2018年も現役か?: 後編(翻訳)

$
0
0

ElixirとPhoenix

Elixir関数型プログラミング言語として優れており、(私見では)Elixirの目標は、開発者の幸福やまっとうな生産性にも配慮しつつ、マルチコア処理(コードの実行速度)とソケット接続をサポートすることにあります。

PhoenixはElixir言語で構築されたWebフレームワークですが、Phoenixを知れば知るほど、PhoenixとはElixir言語と同じ目標を共有しつつElixirと協調動作するライブラリの集合体であることがわかってきます。すなわち、言語とフレームワークの間に哲学上の大きな葛藤がありません。

Elixirは、きわめて強力な関数型言語であるErlang VM上に構築されています。Erlangは、フォールトトレーランスを保証するシステムを必要とする通信業界向けに設計されており、山間部の通信塔設備において99.9999999%という優秀なフォールトトレーランスで動作します。

Erlangが満たしている通信業界の厳しい要求のひとつに、ある通信塔が他の通信塔の責務を代行することで発信元からの通信が途絶しないようにすることがあります。そしてErlangのこの分散処理能力がマルチコア通信の要請に最適であることに一部の賢い人々が気づきました。つまり、多数のCPUコア(32、64、あるいはそれ以上)を持つサーバーマシンでは、ErlangがアプリをすべてのCPUコアで効果的に走らせることができます。

「マルチコアを扱えるオブジェクト指向言語なら既にたくさんあるじゃないか(JavaはもちろんRubyも含めて)」というご意見もおありかと思います。ここで皆さんに知っていただきたいのは、あらゆるオブジェクト指向言語はマルチコアではいいところがないという点です。もちろんマルチコアでも動くは動くのですが、関数型プログラミング言語ほど目覚ましい成果は得られません。
諸悪の根源は「ステート」です。CPUコア同士がミューテーションなしでステートをやりとりするのは恐ろしく困難です。オブジェクト指向とは、行った結果であるステートを関数に加えることだからです。
関数型プログラミング言語はステートを改変しないので、副作用が生じないということになります(詳しくはこちらのブログ記事をどうぞ)。

画像:「何がむかつくって、グローバル変数を普段ほいほい改変しておきながら『AIが世界を支配する』とかぬかす開発者だよ」

ElixirはErlang上で構築されているがゆえに、この恩恵も受けられるわけです。

ソケット接続も重要な点です。あらゆるWebサーバーは、通信をリクエストとレスポンスという形で取り扱うことはご存知でしょう。しかしチャットやビデオ会議などのリアルタイムアプリを構築するのであれば、「ソケット」でサーバー上の接続を維持する必要があります。

現在のRailsやJavaは、1台のサーバーで一度に数千個の接続を扱えます。

一方、ErlangとElixirなら2百万接続を扱えます(傍証: ErlangElixir)。実を言えば40コア程度のCPUとメモリ128GBのサーバー上の軽量な接続なのですが、それを差し引いてもかなり印象的です。

つまりElixirとPhoenixの凄さは本物なのです。それならもっと使われてもよさそうなものですが、上述のとおり、何事もコストを無視するわけにはいきません。PhoenixとElixirは生産性の向上に最善を尽くしてくれるのでしょうか?将来の大規模なスケーリングを約束する技術を取り入れるとなると、癒着を切り離す方法を導入する必要が生じ、その分開発者の進捗が少々遅くなります。

「モデル」からデータベースへの書き込みはそのよい例です。(スキーマを持つ)モジュールが変更セットを呼び出し、その変更セットがリポジトリを呼び出し、そしてそのリポジトリがデータベースに書き込む必要があります()。Railsでは、この3つのステップが1つにまとめられています。

ステップを3つに分ける方がコーディングにおいてずっとよい結果をもたらします。コードが分離されることでテストが容易になるからです。しかしその分、Railsで書くときよりも明らかに時間がかかります。Railsはコーディングの実践という点では見劣りしますが、Railsは生産性と実装速度を高めることに特化しているのです。

これは「設計上の選択」とは別の話です。関数型プログラミングではコードの書き方がより明示的になる分、すべてを渡す必要があるので、よくも悪くもコードは複雑になります。

高トラフィックに耐えうるアプリを構築する場合、ソケット通信に強く依存するアプリを構築する場合、納期に余裕を持てる会社で働いている場合は、ElixirとPhoenixを使いましょう。

慌てて列車に飛び乗る前に、ElixirとPhoenix関連のトークをご覧ください。これらのトークは、皆さんが迷いを振り切って意欲的に取り組み、コードのみならず哲学をも理解するのに役立ちます。次の2つをおすすめします。

JavaScript

フロントエンドWeb開発者がサブプロジェクトをフルスタックで構築するのであれば、最小限のJavaScriptの知識は不可欠です。したがって、どの道に進もうとJavaScriptはある程度ものにしておいてください。

私はバックエンド方面で数年ほどあるサブプロジェクトに従事していましたが、当時についてはろくな思い出がなく、もう戻りたいとは思いません。当時の良し悪しを云々したいのではなく、単に私の選んだ道が正しくなかったということです。バックエンド系のJavaScript開発者で幸せそうにしている人がたくさんいることもある程度承知しています。私はその一人ではなかったというだけのことでした。そういうわけで本記事ではJavaScriptについてあまり書かない予定です。

最近よかった議論としては、現代のWebアプリではバックエンドをNode.js(JavaScript)で書き、フロントエンドをReact.jsかAngular.jsかVue.js(いずれもJavaScript)で書き、バックエンド開発者とフロントエンド開発者がJSON(JavaScript Object Exchange)でやり取りし、データベースはJSONドキュメントを保存できるMongoDBにすればいい、というのがあります。

Web開発のエクスペリエンス全体が、プログラミング言語を1つだけ習得すればいいという流れになっているように感じられます。コーディングブートキャンプがJavaScriptを好むのも、その方が単純になるのですから当然でしょう。

しかし1つ前のセクションでも申し上げたとおり、プログラミング言語を1つ学べばすべてOKということは永遠にありえません。プロとして仕事をしていれば、いずれ自分のツールボックスに新しい言語というツールを付け加えなければならなくなるでしょう。

Node.jsでバックエンドを構築していれば、同期的なRuby MRIの思想といささか異なる「イベントループ」というものと取り組むことになるでしょう。私が何を言っているのかわからない方で、かつRubyを使える方であれば、Node.jsのイベントループと似た原則を用いるEventMachineで遊んでみるとよいでしょう。

JavaScriptのもうひとつのメリットは、AWS Lambdaなどの多くのFaaSプロバイダでサポートされていることです。

最後に

学ばなければならないことは、本当にいくらでもあります(私自身そうです)。何をするにしても、暇を見つけては本を読んで勉強し、多くのコーディングを実装しなければなりません。さもないと、どんな言語を選ぼうと無駄になります。

今後数年の間役に立つおすすめのトピックをリストアップします。

  • オブジェクト指向と関数型プログラミングの両方に挑戦する
  • SOLID原則、DCI、シンプルデザイン、オブジェクトのコンポジションを学ぶ
  • デザインパターンを学ぶ(Martin Fawlerなどで)
  • DDD(ドメイン駆動設計)を学ぶ
  • Bounded Contexts(コンテキスト境界)を学ぶ
  • TDDやBDDを用いる理由と、TDDやBDDをやめる理由
  • バックエンド開発者であってもフロントエンド開発に挑戦する
  • 開発のプロになる方法を学ぶ(書籍『Clean Coder』など)
  • マイクロサービスに挑戦し、それからマイクロサービスを使うのを「やめる」

TDDやSOLID原則などの技法は有用ですが、いつどんなときも正しいというものではありません(もう申し上げましたよね!)。こうした技法を盲信しないことです。これらの技法の値打ちは、それらを学んで必要に応じて適用することにあり、技法に偏りすぎて生産的なチームを損なうことではありません。チームのよい一員であることの方がよほど値打ちがあります。

最も重要なことは、空き時間に自分だけのプロジェクトを持つことです。実験的なことは業務以外のプロジェクトで行い、そこで得られた知見を仕事に反映しましょう。

「自分の時間を潰してまで何かを学ぶつもりはない、今の会社で必要なことをその場で学べばいい」という態度は、開発者として最悪です。Web開発者とは単なる仕事ではなく、生き方なのです。Web開発者としての人生を歩みましょう。

本記事に関連する議論


訳注: Martin Fowler「Refactoring 2nd Edition」がつい最近リリースされました。

関連記事

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

Viewing all 1381 articles
Browse latest View live