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

こんにちは、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

デザインも頼めるシステム開発会社をお探しならBPS株式会社までどうぞ 開発エンジニア積極採用中です! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833 コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。 これまでにRuby on Rails チュートリアル第2版の半分ほど、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れてそれぞれ一部を翻訳。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 実は最近Go言語が好き。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

週刊Railsウォッチ

インフラ

ActiveSupport探訪シリーズ