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

Ruby 3パターンマッチング応用(3)パターンマッチング高速化マクロ(翻訳)

$
0
0

概要

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

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

Ruby 3パターンマッチング応用(3)パターンマッチング高速化マクロ(翻訳)

はじめに

baweaver/matchable - GitHub

私は最近Matchableという新しいgemをリリースしました。Matchableは、パターンマッチングのインターフェイスに使えるクラスレベルのマクロメソッドを導入します。

class Person
  include Matchable

  deconstruct :new
  deconstruct_keys :name, :age

  attr_reader :name, :age

  def initialize(name, age)
    @name = name
    @age  = age
  end
end

本記事は、このMatchable gemを支える概念やしくみについての解説です。

難易度と必要な予備知識

  • 難易度: 先に進むに連れて難しくなります。ある程度の高度な知識が求められ、メタプログラミングを深堀りします。

本記事では以下の予備知識が必要です。

今後evalについての記事を書くかもしれませんが、それまではこの記事の基礎を理解するのにこれらが役立つはずです。本記事ではこれらの概念のいくつかについて簡単に説明しますが、深掘りはしません。

Matchable gemについて

Matchable gemの屋台骨となるアイデアは、パターンマッチングフック用にいい感じのインターフェイスをRubyクラスのトップレベルに作るというものです。

class Person
  include Matchable

  deconstruct :new
  deconstruct_keys :name, :age

  attr_reader :name, :age

  def initialize(name, age)
    @name = name
    @age  = age
  end
end

生成されたコード

上のコードから、以下のような効率の良いコードが生成されます。

class Person
  MATCHABLE_KEYS = %i(name age)
  MATCHABLE_LAZY_VALUES = {
    name: -> o { o.name },
    age:  -> o { o.age },
  }

  def deconstruct
    to_a
  rescue NameError => e
    raise Matchable::UnmatchedName, e
  end

  def deconstruct_keys(keys)
    return { name: name, age: age } if keys.nil?

    deconstructed_values = {}
    valid_keys           = MATCHABLE_KEYS & keys

    valid_keys.each do |key|
      deconstructed_values[key] = MATCHABLE_LAZY_VALUES[key].call(self)
    end

    deconstructed_values
  rescue NameError => e
    raise Matchable::UnmatchedName, e
  end
end

他の実装であれば、以下のようなより動的なアプローチを採用するでしょう。

class Person
  VALID_KEYS = %i(name age).freeze

  def deconstruct() = VALID_KEYS.map { public_send(_1) }

  def deconstruct_keys(keys)
    valid_keys = keys ? VALID_KEYS & keys : VALID_KEYS
    valid_keys.to_h { [_1, public_send(_1)] }
  end
end

生成されたコードの方が好ましい理由ですか?ベンチマーク結果をご覧ください。有効なキーの数がかなり多いオブジェクトの場合、パフォーマンスの差は40~50%ほどに達する可能性があります。このgemの目標は、100%エレガントなコードを生成することではなく、極めて頻繁に実行される可能性が高い機能向けに、すぐ使えるコードを生成することです。

このコードにはさまざまなものが盛り込まれています。それでは一緒にコードを探検してみましょう。よろしいですか?

prependされたモジュール

attr_ メソッドのようなマクロスタイルのメソッドを動かすための最初のコツは、メソッドを動かすためのモジュールをincludeすることです。ここではprependしているので、これらのメソッドを明確な名前のエンティティにすべてアタッチして後でバックトレースを少し読みやすくできます。

module Matchable
  MODULE_NAME = "MatchableDeconstructors".freeze

  def self.included(klass) = klass.extend(ClassMethods)

  module ClassMethods
    def deconstruct() = nil # TODO
    def deconstruct_keys(keys) = nil # TODO

    private def matchable_module
      if const_defined?(MODULE_NAME)
        const_get(MODULE_NAME)
      else
        const_set(MODULE_NAME, Module.new).tap(&method(:prepend))
      end
    end
  end
end

このようなコードを読み慣れていない方は、ぜひDecorating Rubyシリーズをお読みください。

他のインターフェイスメソッドの定義については後ほど説明しますが、手始めにdeconstructdeconstruct_keysメソッドを結びつける最初のモジュールを利用可能にしたいと思います。

パターンマッチング用の新しいメソッドは、上のコードにあるmatchable_moduleですべてアタッチします。これは新しいモジュールのコンテナとお考えください。

MatchableDeconstructors > Object > ...

ここでincludeを代わりに使ってもよいのですが、prependを使えばユーザーが後で作る可能性のあるデコンストラクションメソッドにフックできるようになり、使うAPIオプションも少なくて済みます。

ここでのポイントは、deconstructがモジュール上でdeconstructメソッドを定義し、deconstruct_keysも同様にdeconstruct_keysを定義することです。フックができたので、実際のメソッドを見てみましょう。

deconstruct (ソースコード)

上の見出しでリンクしているソースコードにはみっちりコメントとドキュメントを付けてあります。

deconstructはRubyのArray的なマッチに対応していて実装も簡単な方なので、ここから見ていきましょう。

module Matchable
  module ClassMethods
    def deconstruct(method_name)
      return if matchable_module.const_defined?("MATCHABLE_METHOD")

      method_name = :initialize if method_name == :new
      matchable_module.const_set("MATCHABLE_METHOD", method_name)

      if method_defined?(method_name)
        attach_deconstructor(method_name)
        return [true, method_name]
      end

      @_awaited_deconstruction_method = method_name
      [false, method_name]
    end
  end
end

定数と再代入

最初に、deconstructが定義済みであることを示すためにMATCHABLE_METHOD定数にバインディングされています。定数が定義されていない場合は、その後すぐに定数を設定して、この部分が複数回呼ばれないようにしています。

また、:newの場合は:initializeを再代入しています(一般的に:newと解釈される可能性が高いため)。このメソッドの特殊な振る舞いについては後ほど解説します。

method_defined?

ひとつ興味深いメソッド呼び出しがあります。

if method_defined?(method_name)

メソッドが定義済みかどうかをチェックする必要がある理由とは何でしょうか?この先を読み進める前に、ちょっと立ち止まって考えてみてください。私もここで数分ほど頭をかきむしってしまいました。

その理由とは、マクロスタイルのメソッドが「クラスの冒頭部分」で定義されているからです。つまり、同じメソッドが初期化されるより「前に」定義されるのです。マクロスタイルのメソッドはattr_のようなSymbolメソッドでは動作しないので、最初に存在している必要があります。

つまりメソッドが定義済みかどうかをチェックしないと、たとえば以下のコードが正常に動かなくなります。

class Person
  include Matchable

  deconstruct :new
  attr_reader :name, :age

  def initialize(name, age)
  end
end

動かない原因は、initializeがまだ定義されていないためです。このチェックがコードで重要な理由についてはこの後で解説しますが、最初にこの問題を回避する方法について説明しておきます。

method_added

先ほどのコードのすぐ下で、以下のようにフラグが設定されています。

@_awaited_deconstruction_method = method_name

冒頭の予備知識などで読んだことがあるかと思いますが、method_addedにはフックがあり、新しく定義されたメソッドに対して呼び出されます。今はメソッドが存在していなくても、いずれ存在することになるので、これをフックしておきます。

def method_added(method_name)
  return unless defined?(@_awaited_deconstruction_method)
  return unless @_awaited_deconstruction_method == method_name

  attach_deconstructor(method_name)
  remove_instance_variable(:@_awaited_deconstruction_method)

  nil
end

探しているメソッドが存在すればデコンストラクタをアタッチし、存在しない場合は先に進みます。戻り値がnil になっているのは、戻り値が重要でないのと、後で戻り値を誰かに使われないようにするためです。

これでメソッドが最終的に作成されたときにメソッドをインターセプト(intercept: 傍受)するしくみができたので、attach_deconstructorとそのしくみ、そしてこのトリックが必要な理由について見てみたいと思います。

attach_deconstructor

先に進む前に、parametersが何をするのか、どう役に立つのかについてご存じない方は、もしお読みでなければ『Destructuring Methods in Ruby』に必ず目を通しておいてください。

それではattach_deconstructorメソッドを見ていくことにしましょう。

private def attach_deconstructor(method_name)
  deconstruction_code =
    if method_name == :initialize
      i_method    = instance_method(method_name)
      param_names = i_method.parameters.map(&:last)
      "[#{param_names.join(', ')}]"
    else
      method_name
    end

  matchable_module.class_eval <<~RUBY, __FILE__ , __LINE__ + 1
    def deconstruct
      #{deconstruction_code}
    rescue NameError => e
      raise Matchable::UnmatchedName, e
    end
  RUBY

  nil
end
初期の振る舞いをブランチさせる

最初に行うのは、 initialize 呼び出しであるかどうかの確認です。すべてのパラメタ名を取得してArrayで囲み、メソッドを直接呼び出す代わりにそれを返したいと思います。

Personの場合は、これを返します。

def deconstruct() = [name, age]

attr_が存在していることを確認しておきましょう。さもないと問題が発生します。

通常のメソッドの振る舞い

initializeでない場合は、呼び出すメソッド名を返します。

この動作はaliasと似ていますが、これをカスタム例外でラップしたいので、to_aの部分は以下のようになります。

def deconstruct
  to_a
rescue NameError => e
  raise Matchable::UnmatchedName, e
end

これは、これを動かすのに必要なメソッドが不足していることを示すためにNameErrorに追加メッセージをラップしています。

class_eval

いよいよ本記事の白眉であるclass_evalにさしかかりました。

ここではclass_evalを使って、上で定義したモジュール内で組み立てたコードをもう少し詳しく手動で評価し、それをクラスの手前にprependしてパターンマッチングメソッドを渡します。

matchable_module.class_eval <<~RUBY, __FILE__ , __LINE__ + 1
  def deconstruct
    #{deconstruction_code}
  rescue NameError => e
    raise Matchable::UnmatchedName, e
  end
RUBY

ブロックを使わずに文字列にした理由ですか?ここでやりたいことの中には、RubyコードでRubyコード自身を書かないとできないことがあるからです。より最適化されたメソッドを提供するために、evalメソッドを用いてさまざまなものを「コンパイル」する必要があります。

なお、コード中の__FILE____LINE__ + 1は、この評価されたコードがRubyプログラムの適切な流れの中に置かれていることを後で確認するときに、デバッガやバックトレースなどのツールで見つけられるようにするためのものです。

これらを置いた理由について詳しくはこちらの記事をご覧ください。要は、こうしておけばトラブルシューティングが後々ずっとやりやすくなるのです。

deconstructについては以上です。お次はdeconstruct_keysセクションですが、ここはちょっとした旅行です。

keysをデコンストラクトする(ソースコード

上の見出しでリンクしているソースコードにはみっちりコメントとドキュメントを付けてあります。

ここでは以下のような考慮すべき点がいくつかあるため、deconstructよりもかなり複雑になっています。

  1. 最適化のために扱うべきkeyのリストが存在する
  2. keyが意味する応答はキーごとに異なる
  3. keysnilの場合、返せるキーをすべて返す必要がある

そのため、このコードの理解や動的コンパイルがやや難しくなるかもしれませんが、これを動かす方法はまだあります。

まずはコードを見てみましょう。

def deconstruct_keys(*keys)
  return if matchable_module.const_defined?('MATCHABLE_KEYS')

  sym_keys = keys.map(&:to_sym)

  matchable_module.const_set('MATCHABLE_KEYS', sym_keys)
  matchable_module.const_set('MATCHABLE_LAZY_VALUES', lazy_match_values(sym_keys))

  matchable_module.class_eval <<~RUBY, __FILE__ , __LINE__ + 1
    def deconstruct_keys(keys)
      if keys.nil?
        return {
          #{nil_guard_values(sym_keys)}
        }
      end

      deconstructed_values = {}
      valid_keys           = MATCHABLE_KEYS & keys

      valid_keys.each do |key|
        deconstructed_values[key] = MATCHABLE_LAZY_VALUES[key].call(self)
      end

      deconstructed_values
    rescue NameError => e
      raise Matchable::UnmatchedName, e
    end
  RUBY

  nil
end

定数とキー

最初にいくつかの定数を定義し、念のためキーをSymbolmapしておきます。

return if matchable_module.const_defined?('MATCHABLE_KEYS')

sym_keys = keys.map(&:to_sym)

matchable_module.const_set('MATCHABLE_KEYS', sym_keys)
matchable_module.const_set('MATCHABLE_LAZY_VALUES', lazy_match_values(sym_keys))

ここではunknown valuesエラーを防ぐためにMATCHABLE_KEYSVALID_KEYS として使っています。これを定義しておけば、このメソッドが既に終了していることがわかるので早期脱出できます。

定義がない場合はシンボルキーを設定します。

それが終わると、MATCHABLE_LAZY_VALUESという何やら面白そうなものが登場します。今度はこれを見ていきましょう。

MATCHABLE_LAZY_VALUES

この定数はいったい何をしているのでしょうか?これはmethod_nameの値をlazyにフェッチする方法へのマッピングを提供しています

def lazy_match_values(method_names)
  method_names
    .map { |method_name| "  #{method_name}: -> o { o.#{method_name} }," }
    .join("\n")
    .then { |kv_pairs| "{\n#{kv_pairs}\n}"}
    .then { |ruby_code| eval ruby_code }
end

keysはこのgemのmethod_namesと同義です(値のフェッチにメソッドを使っているので)。method_namesに含まれる名前のそれぞれについて「Hashのキーバリューペア」「lambdaを指すmethod_name(このlambdaはObjectを受け取ってそのメソッドを直接呼び出す)」を作成したいと思います。

nameの場合は次のようになります。

name: -> o { o.name }

この呼び出し方法はpublic_sendよりもかなり高速で、マッチしたときに個別の値を直接算出せずに取得する方法を提供します。

同じことを行う元のメソッドは以下のようなものでした。

<<~RUBY
  if keys.nil? || keys.include?(:#{method_name})
    deconstructed_values[:#{method_name}] = method_name
  end
RUBY

こちらは運悪く内部ループが発生し、コードが大幅に遅くなります。

話を戻すと、これらのキーバリューペアができたら、それらを結合して{}で囲み、evalで RubyのHashに変換して、再びレースに復帰します。この使いみちについては、もう少し後で説明します。

keysnilの場合

先ほどのclass_evalに戻りましょう。ここは以下のようなコードになっています。

matchable_module.class_eval <<~RUBY, __FILE__ , __LINE__ + 1
  def deconstruct_keys(keys)
    if keys.nil?
      return {
        #{nil_guard_values(sym_keys)}
      }
    end

    # ...

おっと、keysnilの場合はすべての値を返す必要がありますね。さっきのif keys.nil? || keys.include?というチェックはここでは既に不要ですが、nilの対応がまだでした。

そこで登場するのがnil_guard_valueです。

nil_guard_value

このメソッドは実際には大したことはしていません。

def nil_guard_values(method_names)
  method_names
    .map { |method_name| "#{method_name}: #{method_name}" }
    .join(",\n")
end

これはすべてのメソッド名をキーバリューペアに変換し、部分一致の場合にlazyにならないようにしたうえで、それらをjoinします。そしてそれを{}でくくることで、必要なすべてのキーに対応する「ブランチ」が得られます。

deconstructed_valuesvalid_keys

すべてのキーの「ブランチ」を処理した後は、フィルタリングが必要になります。最初に、新しい値の保存場所と、返すべきキーのセットが必要です。

deconstructed_values = {}
valid_keys           = MATCHABLE_KEYS & keys

{}がここに値を追加するためのものであることは見ればわかりますが、valid_keysはもう少し興味深いものです。ここでやっていることは、マッチする有効なキーと、提供されたキーとの「交点」を見つけることです。これは、処理方法が不明な怪しいキーを取得しないためです。

lazyな値を取得する

次に、これらの有効なキーに基づいて実際に値を取得したいと思います。

valid_keys.each do |key|
  deconstructed_values[key] = MATCHABLE_LAZY_VALUES[key].call(self)
end

deconstructed_values

キーのリストをeachして、先ほど説明したMATCHABLE_LAZY_VALUESを有効なキーで参照し、deconstructed_valuesに値を設定します。これをcall(self)してオブジェクトから値を取得し、抽出した値をすべて返します。

マクロについての私のつぶやき

警告: ここに書かれているのは本物のRubyではなく、私が空想するRubyです。

public_sendが遅くなるのをマクロで回避する代わりに、以下のように書けたらいいなという気持ちがあります。

valid_keys.each do |key|
  deconstructed_values[key] = ${key}
end

deconstructed_values

${}の部分は、(値をsendする必要なしに)関連するコードを直接インライン化できるマクロシステムです。Matzはマクロが使える日がいつか来るかもしれないと言っていましたが、現実はまだそうではないので、しばらくはこうやって楽しく回避用マクロを書きながらマクロがやってくる日を夢見ることになりそうです。

訳注

以下のQuora記事でRubyのマクロに関する質問へのMatzからの回答を読めます。

以上でdeconstruct_keysの説明が終わりましたので、そろそろまとめに入ります。

ベンチマーク

このマクロを使う価値は本当にあるのでしょうか?詳しくはこちらのベンチマークでご覧いただけますが、ここにまとめておきます。

テストされるオブジェクトは2種類で、1つは属性を2つ持ち、もう1つは26の属性を持ちます。Dynamicと表記されているのが通常の方法で、Macroが上記の方法です。

Person (2 attributes):
  Full Hash:
    Dynamic: 5.027M
    Macro:   5.542M
    Gain:    9.3%
  Partial Hash:
    Dynamic: 7.551M
    Macro:   8.436M
    Gain:    10.5%
  Array:
    Dynamic: 7.105M
    Macro:   10.689M
    Gain:    33.5%

BigAttr (26 attributes):
  Full Hash:
    Dynamic: 984.300k
    Macro:   3.248M
    Gain:    69.7%
  Partial Hash:
    Dynamic: 2.594M
    Macro:   2.956M
    Gain:    12.3%
  Array:
    Dynamic: 1.488M
    Macro:   7.957M
    Gain:    81.3%

ご覧のように、属性の数が増えるほど違いが際立ってきます(特にArray)。もっとも、Arrayでこんなにたくさんの属性とマッチさせることに私は反対ですが。

ではこのマクロにそれだけの価値があるでしょうか?正直に申し上げれば、これは元々マクロの効果を確かめるためのテストであり、パフォーマンス向上はうれしいおまけのようなものです。このマクロで得られるメリットは、必ずしもマクロをすべて自前で実装する労力に十分見合っているわけではないと思いますが、このMatchable gemを用いてメリットを享受したい方がいらっしゃれば、もちろん大歓迎です。

まとめ

今回の作業では、メタプログラミングやRubyで面白いことをたくさん経験できました。中には、まともな解決策にたどり着くまでに多少調べたり考えたりが必要なこともありましたが、それはそれで楽しい経験でした。

自分のやっていることが既に完全に分かりきったものだとしたら、果たして私は楽しめたでしょうか?コードの謎を探検したり、新しいことに挑戦したり、プログラミングの可能性の限界を試したりするのは、それ自体が楽しいことです。皆さんもたまには外に出て、こんなふうに自分でやってみましょう。

そういうわけで、今回は以上でおしまいです。動的なコードをevalでコンパイルする楽しさを皆さんが味わってくだされば幸いです。

関連記事


週刊Railsウォッチ(20210315前編)Active Recordのenum関連改修、Active SupportのEnumerableでpluckが使えるほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

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

以下のコミットリストのChangelogを中心に見繕いました。

🔗 config.action_text.attachment_tag_nameが追加

Railsフォーラムで、Action TextのHTMLタグ名を変えたいと思う人たちがいる。今はデフォルトではaction-text-attachmentとなっている。現時点でそうする方法のひとつは、リッチテキスト形式の出力をパースしてnokogiriでタグ名を変更すること。
このプルリクは、action-text-attachmentを任意の文字列に変えられるconfig.action_text.attachment_tag_nameオプションを追加する。
同PRより大意


つっつきボイス:「従来のActionText::Attachment::TAG_NAMEは名前がaction-text-attachmentに固定されていたのをユーザーがコンフィグで名前を変えられるようにしたのか↓」「Action Text周りでは最近こんな感じの変更がちょくちょく入ってるようですね」「コンフィグで変えられるものが増えるのはいいと思います👍

# actiontext/app/helpers/action_text/content_helper.rb#L5
module ActionText
  module ContentHelper
    mattr_accessor(:sanitizer) { Rails::Html::Sanitizer.safe_list_sanitizer.new }
-   mattr_accessor(:allowed_tags) { sanitizer.class.allowed_tags + [ ActionText::Attachment::TAG_NAME, "figure", "figcaption" ] }
+   mattr_accessor(:allowed_tags) { sanitizer.class.allowed_tags + [ ActionText::Attachment.tag_name, "figure", "figcaption" ] }
    mattr_accessor(:allowed_attributes) { sanitizer.class.allowed_attributes + ActionText::Attachment::ATTRIBUTES }
    mattr_accessor(:scrubber)
...
# actiontext/lib/action_text/attachment.rb#L6
  class Attachment
    include Attachments::TrixConversion, Attachments::Minification, Attachments::Caching

-   TAG_NAME = "action-text-attachment"
-   SELECTOR = TAG_NAME
+   mattr_accessor :tag_name, default: "action-text-attachment"
+
    ATTRIBUTES = %w( sgid content-type url href filename filesize width height previewable presentation caption )
...

🔗 enumの予約済みオプション名に_を付けずに書けるようになった


つっつきボイス:「これは今週のPRではありませんが、以下の記事で知りました↓」

参考: Rails introduces new syntax for enum | Saeloun Blog

「なるほど、enumの引数受け取りをenum(attr_name, ..., **options)とすることで、オプションを最後に付ければアンダースコア付きの_prefix_scopesなどではなくアンダースコアなしのprefixscopesなどを使えるようにしたのか」「アンスコなしで書けるのは嬉しい👍

Attribute APIに組み込まれている他の機能と違い、enumの予約済みオプション名の冒頭には_が付いている。

_付きだった理由は、enumがハッシュ引数を1個しか受け取らないため(このハッシュ引数はenumの定義と予約済みオプションの両方を含む)。

enumの予約済みオプション名でこの_を避けるために、他のAttribute APIの構文のようなenum(attr_name, ..., **options)を使える新しい構文をここに提案する。

変更前:

class Book < ActiveRecord::Base
  enum status: [ :proposed, :written ], _prefix: true, _scopes: false
  enum cover: [ :hard, :soft ], _suffix: true, _default: :hard
end

変更後

class Book < ActiveRecord::Base
  enum :status, [ :proposed, :written ], prefix: true, scopes: false
  enum :cover, [ :hard, :soft ], suffix: true, default: :hard
end

同PRより大意

「既存の_付きオプションとの互換性も確保されているようですね↓」

-     default = {}
-     default[:default] = definitions.delete(:_default) if definitions.key?(:_default)
+     definitions = options.slice!(:_prefix, :_suffix, :_scopes, :_default)
+     options.transform_keys! { |key| :"#{key[1..-1]}" }

コメントを見ると、このリリースでは既存の書式をdeprecateにしないけど、cop(注: RuboCopのルール)でオートコレクトするのが難しくなければdeprecateすべきと書かれてるので、今後非推奨になるのかも」「たぶん最終的には_なしの書式が正式になるんでしょうね」「_付きの_prefix_suffixあたりは既に使っている人がいそう」「そういえば_prefix_defaultは使ったことあります」

参考: ActiveRecord::Enum

enumを本格的に使うようになると、_scope_defaultなどのオプションが欲しくなりますね: 特に_prefixが使えないと不便」「なるほど」「次の2つはこの#41328に関連していそうなenum関連のプルリクです」

🔗 Enum型の属性aggregationを修正


つっつきボイス:「summinimummaximumといった集約系の処理を修正したのか: テストコードが変わっている↓」「”aggregation”は集約関数の集約なんですね」

# activerecord/test/cases/calculations_test.rb#L1165
- def test_aggregate_attribute_on_custom_type
-   assert_nil Book.sum(:status)
-   assert_equal "medium", Book.sum(:difficulty)
-   assert_equal "easy", Book.minimum(:difficulty)
-   assert_equal "medium", Book.maximum(:difficulty)
-   assert_equal({ "proposed" => "proposed", "published" => nil }, Book.group(:status).sum(:status))
-   assert_equal({ "proposed" => "easy", "published" => "medium" }, Book.group(:status).sum(:difficulty))
-   assert_equal({ "proposed" => "easy", "published" => "easy" }, Book.group(:status).minimum(:difficulty))
-   assert_equal({ "proposed" => "easy", "published" => "medium" }, Book.group(:status).maximum(:difficulty))
+ def test_aggregate_attribute_on_enum_type
+   assert_equal 4, Book.sum(:status)
+   assert_equal 1, Book.sum(:difficulty)
+   assert_equal 0, Book.minimum(:difficulty)
+   assert_equal 1, Book.maximum(:difficulty)
+   assert_equal({ "proposed" => 0, "published" => 4 }, Book.group(:status).sum(:status))
+   assert_equal({ "proposed" => 0, "published" => 1 }, Book.group(:status).sum(:difficulty))
+   assert_equal({ "proposed" => 0, "published" => 0 }, Book.group(:status).minimum(:difficulty))
+   assert_equal({ "proposed" => 0, "published" => 1 }, Book.group(:status).maximum(:difficulty))
  end

参考: PostgreSQL 12.4文書9.20. 集約関数
参考: MySQL :: MySQL 5.6 リファレンスマニュアル :: 12.19.1 GROUP BY (集約) 関数

「たとえばテストコードのBook.group(:status).sum(:difficulty))は従来だと{ "proposed" => "easy", "published" => "medium" }を返していたけど、修正後は{ "proposed" => 0, "published" => 1 }を返すようになった: enumが設定されたカラムで#groupしたときの挙動を修正したんですね」

issue #39248#39271を修正するため、#39255#39274では集約結果をattributeの型ごとにキャストするようにした。
しかし#41431を実装したときに、enumのマッピングを回避できることに気づいた。
今回の変更によって、#39039のexpectationが復帰する(特にEnumの場合)。
修正対象: #41600
同PRより大意

🔗 enum attributeに対するserialize(value)がサブタイプごとにキャストされるよう修正


つっつきボイス:「これもenumのattribute関連ですね」「この#39039を含むChangelogが別プルリクになっていたので併記しました↓」「SQLite3ではnilが返り、MySQLとPostgreSQLではエラーになってたのが修正されたんですね」

enum値を元のattributeの型で型キャストするようになった。
注目すべき変更点は、不明なラベルがMySQLの0にマッチしなくなったこと。

class Book < ActiveRecord::Base
  enum :status, { proposed: 0, written: 1, published: 2 }
end

変更前:

# SELECT `books`.* FROM `books` WHERE `books`.`status` = 'prohibited' LIMIT 1
Book.find_by(status: :prohibited)
# => #<Book id: 1, status: "proposed", ...> (for mysql2 adapter)
# => ActiveRecord::StatementInvalid: PG::InvalidTextRepresentation: ERROR:  invalid input syntax for type integer: "prohibited" (for postgresql adapter)
# => nil (for sqlite3 adapter)

変更後:

# SELECT `books`.* FROM `books` WHERE `books`.`status` IS NULL LIMIT 1
Book.find_by(status: :prohibited)
# => nil (for all adapters)

Ryuta Kamizono
同Changelogより大意

🔗 RAILS_DEVELOPMENT_HOSTS環境変数のサポートを追加


つっつきボイス:「RAILS_DEVELOPMENT_HOSTSとは?」「なるほど、ホスト名でアクセス制限するHostAuthorizationで許可済みのホストを、development環境でのみ環境変数からも渡せるようになったんですね↓」「言われてみれば欲しいときがありそう」「現状ではRAILS_ENV=productionでは効かないようなので注意ですね」

# railties/lib/rails/application/configuration.rb#L28
      def initialize(*)
        super
        self.encoding                            = Encoding::UTF_8
        @allow_concurrency                       = nil
        @consider_all_requests_local             = false
        @filter_parameters                       = []
        @filter_redirect                         = []
        @helpers_paths                           = []
        @hosts                                   = Array(([".localhost", IPAddr.new("0.0.0.0/0"), IPAddr.new("::/0")] if Rails.env.development?))
+       @hosts.concat(ENV["RAILS_DEVELOPMENT_HOSTS"].to_s.split(",").map(&:strip)) if Rails.env.development?
        @host_authorization                      = {}

Rails API: ActionDispatch::HostAuthorization

「CI連携で動作確認をプルリク単位で用意するようなHeroku Review Appsのなど環境を、ログ出力の関係などからRAILS_ENV=developmentで動かしたいようなケースでは、環境変数から許可済みホスト名を渡したいというケースがありそうですね: これまでは必要になったら自分でそういうコードを書いていたでしょうけど、Railsが用意してくれるならそれを使いたい👍」「なるほど!」

参考: 環境変数 - Wikipedia

🔗Rails

🔗 Active Recordのconcernをテストする(Ruby Weeklyより)


つっつきボイス:「ActiveSupport::Concernじゃないのかなと思ったら、Active RecordにincludeするActiveSupport::Concernモジュールが対象なのね」

参考: ActiveSupport::Concern

「Active Recordのconcernそのものをテストするには、たしかに記事にもあるようにFakeReviewableのようなfakeのクラスを作ることになるでしょうね↓」

# 同記事より
require_relative 'path/to/reviewable/shared/examples'

class FakeReviewable < ApplicationRecord
  include Reviewable
end

describe Reviewable do
  include InMemoryDatabaseHelpers

  switch_to_SQLite do
    create_table :fake_reviewables do |t|
      t.datetime :reviewed_at
    end
  end

  describe FakeReviewable, type: :model do
    include_examples 'reviewable' do
      let(:reviewable) { FakeReviewable.create }
    end
  end
end

「Rubyのモジュールは、ActiveSupport::Concernも含めて単体ではテストできないので、このようにモジュールをincludeするテスト用のfakeクラスを作ってテストするしかないでしょうね」「ふむふむ」

「これはテストにおけるfakeのパターンというヤツです」「fakeはテスト用語としてのfakeなんですね」「そうです」

参考: xUnit Test PatternsのTest Doubleパターン(Mock、Stub、Fake、Dummy等の定義) - 千里霧中

「テスト関連の記事にfakeとかdoubleという語が出てきたときに用語だとわかりにくくて😅」「慣れれば大丈夫です: テスト関連ドキュメントの文脈でたとえばfakeという語が出てくれば、それはテスト用語としてのfakeのはずです」「なるほど」「fakeやdoubleのような一般的な語が用語に使われるのは、コンピューターサイエンスだと割とあります」「こういう用語はそのつもりで読まないといけないんですね」

morimorihoge注)

専門用語は理解できる人たちにとっては解釈の揺れのない便利な用語なのですが、理解できない人たちにとっては意味不明だったりミスリードを誘ってしまう厄介なものでもあります。
# 類似のもので代表的なのはプロセスにSIGNALを送信するkillコマンドですね

専門用語を知らない人にも分かるよう説明することももちろん大事ですが、技術者同士の会話では専門用語を使う方がより短い時間で精度の高い情報が伝えられるという要素もあるので、こうした技術用語の脳内辞書を育てておくのはエンジニアとして大事なポイントかもしれません。

🔗 Railsセットアップ時に既存データを永続化するときの注意

Everyday Railsサイトの技術ブログです。


つっつきボイス:「Railsのセットアップで注意すべき点に関する記事だそうです」

新しい開発者がプロジェクトに参加しやすくできるよう、Railsアプリのbin/setupスクリプトを活用していますか?そうなっていないのであれば、セットアッププロセスをできるだけ自動化しておく価値があると思います。rails newで提供されているデフォルトのbin/setupは最初に手を付けるのに手頃です。そうしておけば今後参加する開発者の貴重な初動時間を節約できますし、セットアップ手順の潜在的な抜け落ちも見つけられます。(bin/setupは)Visual Studio Codeでコンテナベースの開発環境を構築する際にも非常に便利です。
しかしbin/setupの利用には注意点があります。
同記事冒頭より大意

「なるほど、この記事に書かれているような問題はRailsで開発しているとときどきありますね: Railsアプリのプロジェクトをフルスクラッチでgit cloneしたときはbin/setupが完走するのに、開発を始めた後に「一度全部やり直したい」と思ってbin/setupするとdb:prepareあたりで既にDBが作られていてエラーになるといったことがあります」「あ、心当たりが…😅

「記事にもあるように、bin/setupのRubyコードを以下のように変更すれば↓、DBが存在していればdb:migrateを実行し、存在しなければdb:setupを実行するようになります」「へ〜、bin/railsコマンドにはdb:existsっていうオプションもあるんですね」

# 同記事より
puts "\n== Preparing database =="
system! 'bin/rails db:exists && bin/rails db:migrate || bin/rails db:setup'

後で調べると、bin/setupの該当部分はデフォルトでは以下のようになっていました(Rails 6.1.2)。

puts "\n== Preparing database =="
system! 'bin/rails db:prepare'

「たとえばCI(Continuous Integration)環境などでも、コンテナおよび依存する一時ファイルが存在していれば使い回し、存在しなければ新規で作成することを意識しないといけませんが、それに通じるものを感じますね」「なるほど」

🔗 Active SupportはEnumerablepluckを追加している

つっつきボイス:「お、再びboringrails.comの記事」「記事冒頭はActive Recordのpluckの使い方の説明、これは普通かな」「mapするよりpluckする方がいいというのはよく言われますね」「単にpluckすると結果が重複することがあるので、一意にしたければdistinct.pluckする、そうそう」

Rails: pluckでメモリを大幅に節約する(翻訳)

「おぉ、Active Supportにもpluckがあるって、マジで?」「やや」「Active SupportがEnumerablepluckを追加しているとは知らなかった!」

# 同記事より
[
  { id: 1, name: "David" },
  { id: 2, name: "Rafael" },
  { id: 3, name: "Aaron" }
].pluck(:name)
# rubydoc.infoより
[{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pluck(:name)
# => ["David", "Rafael", "Aaron"]

[{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pluck(:id, :name)
# => [[1, "David"], [2, "Rafael"]]

「これができるということはハッシュにもpluckできますね: 元記事でもJSON.parseで取得したハッシュにpluckを実行している」「おぉ〜!」「いいこと知りました!」「pluckEnumerableに生えてるのって夢が広がりそう」「Ruby 3.0ならパターンマッチでやりそうな処理ですが、それ以前のRubyでもEnumerable#pluckでやれるのはいい👍

🔗 その他Rails


つっつきボイス:「お、Rails.benchmarkをいろんな場所で呼べるようになったのか」「割と最近の修正ですね」

# 同記事より: 従来
mattr_accessor :logger, default: Rails.logger
extend ActiveSupport::Benchmarkable
include ActiveSupport::Benchmarkable

def process
  benchmark("=== Processing invoices ===") { process_invoices }
end

「従来だとモデルなどでbenchmarkを呼ぶときは上のようにextendincludeなどを使ったややこしい書き方をしないといけなかった↑けど、変更後は以下のようにRails.benchmarkのブロックに書けばできるようになったんですね↓」「なるほど!」「extendincludeなどを書かなくてもどこにでも書けるのは便利👍

# 同記事より: 変更後
def process
  Rails.benchmark("=== Processing invoices ===") do
    logger.debug("=== Processing started ===")

    process_invoices

    logger.debug("=== Processing done ===")
  end
end
=== Processing started ===
=== Processing done ===
=== Processing invoices === (400.7ms)

Rails.benchmarklevel: :debugsilence: trueを指定できるのも便利ですね↓」

def process
  Rails.benchmark("=== Processing invoices ===", level: :debug, silence: true) do
    logger.debug("=== This won't be logged ===")

    process_invoices

    logger.debug("=== This won't be logged ===")
  end
end

前編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210309後編)RubyのIRBに隠れているイースターエッグ、Power Automate Desktop、SQLクエリのありがちなミス6つほか

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

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

Rails公式ニュース

Ruby Weekly

週刊Railsウォッチ(20210316後編)testdouble/standard gem、DockerfileベストプラクティスとDockerfileのlintツールhadolintほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

🔗Ruby

🔗 RuboCopがオーガニゼーションをgithub.com/rubocop-hqからgithub.com/rubocopへ移行(現在は完了)


つっつきボイス:「そうそう、これまでrubocop-hq/rubocopだったのがrubocop/rubocopに変わるそうですね🎉」「これまで他で取得されていたrubocopオーガニゼーションを昨年10月ぐらいに譲渡してもらったんですって」

従来の rubocop-hq オーガニゼーション経由でのアクセスでも、GitHub の方で良きにリダイレクトしてくれるのでユーザー影響はあまりないと思いますが、無駄なリダイレクトを挟むこともないので rubocop-hq になっている箇所があれば rubocop に指定し直すと良いです。
同記事より


その後見に行ってみると、rubocop-hqにあったリポジトリは3日ほど前にrubocopに移行完了していました(2021/03/16時点)。

特に設定を変えずに手元でgem updateしても問題なくRuboCopをアップデートできました。

...
Updating rubocop
Fetching rubocop-1.11.0.gem
Successfully installed rubocop-1.11.0
...

🔗 testdouble/standard: Rubyスタイルガイドとlinterとフォーマッタのセット(Ruby Weeklyより)

testdouble/standard - GitHub


つっつきボイス:「testdouble/standardは最近 1.0になったというのをどこかで見たかも」「先週取り上げようと思って漏れてました😅

このgemは、JavaScriptのstandard JSのエッセンスを移植したもので、以下の3とおりの方法で自分や他の人の時間を節約する。

  • コンフィグ不要: プロジェクトに投入するだけで統合スタイルを矯正する最も簡単な方法
  • コードのオートフォーマット: standardrb --fixを実行するだけでコードのばらつきとおさらば
  • スタイルの問題やプログラマーミスの早期発見: レビュアーやコントリビューターとのやりとりを減らして貴重なコードレビュー時間を節約
    同リポジトリより大意

「testdouble/standardはコンフィグ不要が売りのひとつなんですね」「testdouble/standardはRubyMineやVSCodeなどでも動かせるのか↓」「お〜」「testdouble/standardはRuboCopのラッパーなんですね」

「RuboCopがあるのにtestdouble/standardを使うのかという意見が出ることも予想されますけど、RuboCopは新しいルールがどんどん追加されていくので、あれについていくのが大変だと感じる人がいる気持ちもわかります」「たしかに」「Rubyで何か作ろうとすると、RuboCopを黙らせるのに時間がかかったりしますよね」「コンフィグ不要でやれるのはいい」「このツイート↓でも言っているように、RuboCopほどstrictにしないプロジェクトによさそう」


「そういえば最近rufoってどうなってるのかな?」「あ、最近触ってなかった😅

RuboCop作者がRubyコードフォーマッタを比較してみた: 前編(翻訳)

🔗 Ruby組み込みライブラリの型をRBSで書く

# 同記事より: 最終的な型定義
def self.chmod: (int mode, *(string | _ToPath) file_name) -> Integer

つっつきボイス:「永和システムマネジメントさんの技術ブログです」「Ruby 3.0のRBSが使われ始めているようですね」「この記事の組み込みライブラリはCで書かれたライブラリなのか」「RBSの記事がまだ少ないのでありがたいです🙏

ruby/rbs - GitHub

🔗 その他Ruby

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

🔗 Dockerfileのベストプラクティス Top 20


つっつきボイス:「はてブで知った記事です」「この記事はDockerfileのセキュリティを中心に解説しているのがいいですね: ざっと見た感じでは細かくなりすぎずにちゃんと書かれているようなので一度目を通しておくとよいと思います👍

「この記事ではGoogleが公開しているdistrolessについても解説されてますね↓」

GoogleContainerTools/distroless - GitHub

参考: 軽量Dockerイメージに安易にAlpineを使うのはやめたほうがいいという話 - inductor’s blog

「よく言われているように、Dockerではマルチステージビルドして実行バイナリだけを集めるのが理想で、Go言語のシングルバイナリのようにシンプルな実行バイナリならやりやすいんですけど、Railsなどで外部ライブラリに依存していてファイル数が多いとマルチステージビルドが大変なんですよね」「たしかに!」

参考: マルチステージビルドの利用 | Docker ドキュメント

「そういえば日本語版の記事も英語版と同じドメインで公開されていますね↓」

参考: Dockerfileのベストプラクティス Top 20 | Sysdig

🔗 Dockerfileのlinter: hadolint

「お、記事の末尾にhadolintというDockerfileのlinterが紹介されている↓」「ホントだ」「Haskell言語で書かれているそうです」

hadolint/hadolint - GitHub

「上のDockerfileベストプラクティス記事に書かれているようなチェックをhadolintでやれるのか」「hadolintのルールを見た感じではなかなかよくできていそう」「お〜」「メッセージのDL3000とかSC2002は何だろうと思ったらlinterのルール名だった」「SCはshell checkの略みたいだけどDLは何の略だろう?🤔

「hadolintのオンライン版もあるんですね↓」「これはなかなかよさそう👍」「Dockerfileのlinterはたしかにあってもいいですね」

参考: Dockerfile Linter


後で自分のDockerfileに試しにhadolintをかけてみたらだいぶ怒られました😅

hadolint .dockerdev/Dockerfile
.dockerdev/Dockerfile:10 DL3008 warning: Pin versions in apt get install. Instead of `apt-get install <package>` use `apt-get install <package>=<version>`
.dockerdev/Dockerfile:23 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check
.dockerdev/Dockerfile:27 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check
.dockerdev/Dockerfile:30 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check
.dockerdev/Dockerfile:35 SC2046 warning: Quote this to prevent word splitting.
.dockerdev/Dockerfile:35 SC2002 style: Useless cat. Consider 'cmd < file | ..' or 'cmd file | ..' instead.
.dockerdev/Dockerfile:35 DL3005 error: Do not use apt-get upgrade or dist-upgrade
.dockerdev/Dockerfile:35 DL3008 warning: Pin versions in apt get install. Instead of `apt-get install <package>` use `apt-get install <package>=<version>`
.dockerdev/Dockerfile:35 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check

🔗 git cloneに脆弱性(StatusCode Weeklyより)


つっつきボイス:「gitの脆弱性、久しぶり」「自分も今brew upgrade gitで更新中です」

「この脆弱性はgit LFSに関連しているのか: git config --global core.symlinks falseでシンボリックリンクをオフにするとリスクを軽減できるとあるので、その部分がLFSで問題があったんでしょうね」「git LFSというと、動画やAdobe系ファイルのようなdiffがうまく取れない巨大なバイナリ系ファイルをgitリポジトリの外で管理するしくみでしたね」「BPSのアプリチームではgit LFSを使っているらしいです」

参考: Git LFS をちょっと詳しく - Qiita

🔗CSS/HTML/フロントエンド/テスト/デザイン

🔗 書籍『Web ブラウザセキュリティ』


つっつきボイス:「記事を書いているのはChromiumブラウザの開発に携わっている人だそうです」「Webセキュリティの新しい話題、特に最近のブラウザでやっているプロセス分離アーキテクチャについても詳しく解説されているのがよさそう👍

参考: Chromium - Wikipedia

「少し前の本ですが、この記事の中でも紹介されている『めんどうくさいWebセキュリティ』↓はBPS CTOのbabaさんが推していました」


「Webのセキュリティは、Web技術自体が歴史的に大きく進化していく中で建て増しを繰り返しているので複雑になりがちですね」「HTML5でだいぶリセットはかかった感じはありますけどね」「そういえばDOCTYPEにHTML 4.01 Transitionalとか書いてた頃もありましたよね」「そうそう」

参考: HTML5 - Wikipedia
参考: <!DOCTYPE>-HTMLタグリファレンス

「記事を見ていて、若手向けにWebの歴史も含めて詳しく解説してくれる本もあるといいなと思いました」「今のWebはなぜこうなっているのかを知ろうと思うと、歴史を調べることになりますよね」「歴史を完全に網羅しなくてもいいんですが、現在までにWebの歴史にどんなものが現れて何が消えていったかというざっくりした流れだけでも脳内地図を作れるといいですよね」「XMLが華々しく登場してその後JSONが台頭した流れとか」

参考: Extensible HyperText Markup Language - Wikipedia — XML
参考: JavaScript Object Notation - Wikipedia — JSON

🔗言語/ツール/OS/CPU

🔗 QEMUでARMエミュレータ


つっつきボイス:「プログラミング言語のSlackで、ARMのエミュレータを構築するにはQEMUしかないんでしょうかという質問にこのリンクが貼られていたので拾ってみました」「ARMのエミュレーションだけなら他にもありそうですけど、フリーで速度もそこそこ出せて、かつ広く使われていて実績があることを考えるとQEMUがやりやすいでしょうね: 特にARMのバージョンを切り替えてエミュレーションしたい場合とか」「なるほど」

参考: QEMU - Wikipedia

「こんなふうにgccに長いプレフィックスを指定してビルドするとか昔やりましたよ↓」「懐かしいですね」「クロスコンパイル用のgccは通常のgccと名前がかぶらないようにこんな名前になっていました」「Zaurus SL-C700のIntel XScaleで動かすためにクロスコンパイルしたことがあったのも思い出した」

# 同記事より
sudo apt-get install gcc-arm-linux-gnueabi

参考: GNUコンパイラコレクション - Wikipedia – gcc
参考: Zaurus - SL-C700|仕様表
参考: XScale - Wikipedia


後編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210315前編)Active Recordのenum関連改修、Active SupportのEnumerableでpluckが使えるほか

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

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

Ruby Weekly

StatusCode Weekly

statuscode_weekly_banner

週刊Railsウォッチ(20210322前編)Active Recordのstrict loadingの修正、セキュリティリリースのポリシー追加、N+1チェッカーprosopite gemほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

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

以下のコミットリストから見繕いました。コミットは少なめで、Changelogの変更はありませんでした。

🔗 Active Recordでprecisionなしのnumericalityバリデーションを修正

#32852で追加されたテストケースはActive Modelではパスするが、#38210Float::DIGBigDecimal.double_figに変更されてからパスしなくなっていた。

irb(main):001:0> require 'bigdecimal/util'
=> true
irb(main):002:0> 65.6.to_d
=> 0.656e2
irb(main):003:0> 65.6.to_d(Float::DIG)
=> 0.656e2
irb(main):004:0> 65.6.to_d(BigDecimal.double_fig)
=> 0.6559999999999999e2

同PRより大意


つっつきボイス:「precision(DBの有効桁数)が指定されてないnumericalityバリデーションを修正したのね」「to_dはBigDecimalへの変換で、以前は桁数をFloat::DIGで15桁に指定していたのがBigDecimal.double_figに変わって桁数が違ってしまったのでFloat::DIGに戻したんですね」

# activerecord/lib/active_record/validations/numericality.rb#L4
  module Validations
    class NumericalityValidator < ActiveModel::Validations::NumericalityValidator # :nodoc:
      def validate_each(record, attribute, value, precision: nil, scale: nil)
-       precision = [column_precision_for(record, attribute) || BigDecimal.double_fig, BigDecimal.double_fig].min
+       precision = [column_precision_for(record, attribute) || Float::DIG, Float::DIG].min
        scale     = column_scale_for(record, attribute)
        super(record, attribute, value, precision: precision, scale: scale)
      end

Float::DIGBigDecimal.double_figはFloatの最大桁数の定数だけど、前者は最大の10進桁数で後者はFloatクラスが保持できる有効数字の数だから同じではないということか↓」

Float が表現できる最大の 10 進桁数です。通常はデフォルトで 15 です。

Ruby の Float クラスが保持できる有効数字の数を返します。

# docs.ruby-lang.orgより
require 'bigdecimal'
p BigDecimal::double_fig  # ==> 20 (depends on the CPU etc.)

「お〜、C言語では有効桁数をこうやって計算するのね↓」「これは参考になりそう」

// docs.ruby-lang.orgより
double v = 1.0;
int double_fig = 0;
while (v + 1.0 > 1.0) {
   ++double_fig;
   v /= 10;
}

🔗 Preloader::Association::LoaderQueryが追加


つっつきボイス:「今週はPreloader周りの改修がほかにもいくつかあったんですが、それぞれの関連がよくわからなかったのでこれを代表としてピックアップしてみました」

「このプルリクで引用されている#41385はこれか↓」「サマリーが長いですね…」

# 41385より
# ケース1: 親モデルは異なり、associationは同じ
ActiveRecord::Associations::Preloader.new(records: [book, post], associations: :author).call

# ケース2: 親モデルは同じで、同じテーブルに複数のassociationがある
ActiveRecord::Associations::Preloader.new(records: favorites, associations: [:author, :favorite_author]).call

「従来は以下のように?でプレースホルダ化されるクエリが上のどちらでも同じになっていたけど↓、同じクエリを2回実行するのは冗長なので#41385でクエリをグループ化して少し速くなり、#41597でそのロジックをLoaderQueryで整理したということみたい」「#41385にはベンチマークも貼られていますね」「#41597や#41385は、アプリ開発者が普段それほど意識することはなさそうかな: 普通に使っていれば恩恵を得られそう」

SELECT "authors".* FROM "authors" WHERE "authors"."id" = ?
SELECT "authors".* FROM "authors" WHERE "authors"."id" = ?

#41385のフォローアップ。
このクラスはPreloader::Associationがそのレコードを読み込むのに使うクエリを表す。これは従来のgrouping_keyの概念を置き換えて、Preloader::Associationをクエリごとにグループ化することとそのレコードを読み込むクエリの実行の両方に使われる(これで.firstをうまい具合に回避できる)。
この変更で機能は変わらないはずだが、不要なscope.to_sql呼び出しを回避できるのでほんのわずか速くなるはず。
同PRより大意

🔗 Active Recordのstrict loadingの修正


つっつきボイス:「strict loadingは最近Railsに入った機能だったかな(ウォッチ20200302)」「strict loadingは昨年3月頃に入ってたんですね」

「なるほど、モデル全体にstrict_loading_by_defaultを設定したうえで、個別のリレーションにstrict_loadingを設定した場合に、前者の設定だけが効いてしまうバグだったのか」「あ、そういうことですか」「詳細度は個別のリレーションに設定するstrict_loadingの方が高いので、モデルにデフォルトを設定するstrict_loading_by_defaultより優先されるべきということなんでしょうね」

「モデルではデフォルトでstrict loadingをオンにするけど特定のリレーションでオフにしたいときは、プルリクのコード例のように書くでしょうね↓」「リレーションのstrict_loading: falseが効くはずが効いてなかったのか、なるほど」


従来は、モデルのstrict_loading_by_defaultをtrueに設定している状態で、strict_loadingをfalseに設定すると、strict loadingに関するraiseやwarningを行うかどうかの決定でこのfalseが考慮されていなかった。

class Dog < ActiveRecord::Base
  self.strict_loading_by_default = true

  has_many :treats, strict_loading: false
end

上の例では、strict_loadingをfalseにしているにもかかわらずdog.treatsがraiseされてしまう。このバグはActive Storage以外にも影響するバグなので、#41461(closed)よりも広範囲に渡るPRにした。自分はこの挙動に少々驚かされたので、この問題はすべてのアプリケーションで解決する必要がある。
#41461のテストと#41453の提案を元にいくつかの追加を行った。
同PRより大意

🔗 ドキュメント: メンテナンスポリシーに追記


つっつきボイス:「Railsガイドの更新です」「Railsのメンテナンスポリシーはちょうどこの間話題にしたような気がしますね」

「なるほど、セキュリティリリースのポリシーのこの部分が明文化されたのか↓」「stableブランチとの関係についても解説されてますね」「Railsを最近始めた人にはありがたい情報🙏

セキュリティリリースには直接的なセキュリティパッチのみが含まれる。セキュリティパッチに起因する、セキュリティに関連しないバグの修正は、リリースのx-y-stableブランチで公開され、バグ修正ポリシーに基づいて新しいgemとしてのみリリースされる。
同PR更新部分より大意

🔗Rails

🔗 スライド: Ganbaranai wo ganbaru


つっつきボイス:「スライドのタイトルが2つあったので、埋め込みに出ていない短い方を見出しにしてみました」「がんばらないを頑張る」

「リリースを2段階に分けて、本番では通知だけ行って例外は握りつぶす↓、なるほど」「本番で動いているアプリのリリースではこういう感じでリリースすることもありますね」「いろいろ参考になりそう」

「このように本番で運用中のアプリをいかに障害を避けてリリースするかというノウハウは、ある程度運用の経験を積んでくるとありがたみがわかってきますね👍」「たしかに」

🔗 fast_blank: Active SupportのString#blank?を高速化するgem

SamSaffron/fast_blank - GitHub


つっつきボイス:「String#blank?をCで高速化するgemだそうです」「C拡張でやってるのか」「よく見るとfast_rubyを作ったのはSam Saffronさんでした」

# 同リポジトリより抜粋
================== Test String Length: 136 ==================
Calculating -------------------------------------
          Fast Blank   201.772k i/100ms
  Fast ActiveSupport   189.120k i/100ms
          Slow Blank   129.439k i/100ms
      New Slow Blank    90.677k i/100ms
-------------------------------------------------
          Fast Blank     16.718M (± 2.8%) i/s -     83.534M
  Fast ActiveSupport     17.617M (± 3.6%) i/s -     87.941M
          Slow Blank      3.725M (± 3.0%) i/s -     18.639M
      New Slow Blank      1.940M (± 4.8%) i/s -      9.702M

Comparison:
  Fast ActiveSupport: 17616782.1 i/s
          Fast Blank: 16718307.8 i/s - 1.05x slower
          Slow Blank:  3725097.6 i/s - 4.73x slower
      New Slow Blank:  1940271.2 i/s - 9.08x slower

「RubyのJITはこういう高速化に効きそうな気もしますけどね」「String#blank?は、Railsを使っててありがたいと思うRubyらしいメソッドだと思います」「そうですね」「PHPなどのように暗黙の型キャストをあてにして直接比較する方法よりも明示的に書けるのがいい👍」「オレオレblank?を定義しなくて済みますよね」

参考: Ruby 3.0のJIT変更解説 - Qiita

String#blank?はActive Supportのメソッドですが、Ruby本体に取り込まれてもよさそうな気がしました」「今のところRuby本体には取り込まれてないようですね」「Active SupportのメソッドがRuby本体に取り込まれることがたまにありますよね」「Active Supportのpresent?ならもうRubyに入っているかなと思ったけど入っていなかった」

後で調べると、to_procはActive SupportからRubyに取り込まれたそうです↓。

参考: 開発コアメンバが語るRubyの今とこれから(前編) - @IT

「ちなみにfast_blank gemは以下のgemから参照されていて知りました↓」「こちらはRustでString#blank?を高速化しているんですね」

malept/rusty_blank - GitHub

🔗 RuboCop Performance 1.10がリリース


つっつきボイス:「RuboCopがいくつかに分かれたうちのひとつがRuboCop Performanceですね」

rubocop/rubocop-performance - GitHub

Performance/RedundantSplitRegexpArgumentというcop(RuboCopのルール)が追加されてる↓」「区切り文字を,と指定するのに正規表現で/,/と書く必要はない、たしかに」「gsubなどのような文字列でも正規表現でも渡せるメソッドで、しかも完全一致を使うなら正規表現で書かなくてもいいんですよね」「そういえばgsubで無駄に正規表現を書いてしまったことありました😅」「文字列の方が正規表現よりも高速になるというのはRubyだとちょくちょくあります」

# 同記事より
# bad
'a,b,c'.split(/,/)

# good
'a,b,c'.split(',')

Performance/RedundantEqualityComparisonBlockというcopも@kamipoさんのリクエストで追加されていますね」「こちらは安全ではないとあるので-aオプションで自動修正されないヤツか」

# 同記事より
# bad
items.all? { |item| pattern === item }
items.all? { |item| item == other }
items.all? { |item| item.is_a?(Klass) }
items.all? { |item| item.kind_of?(Klass) }

# good
items.all?(pattern)

🔗 prosopite: false positive/negativeゼロをうたうN+1クエリチェッカー

charkost/prosopite - GitHub


つっつきボイス:「プロソパイト?」「造語かと思ったら鉱物の名前でした↓」

参考: Prosopite(プロソパイト)

「prosopiteは、よく使われているbullet gem↓のようなN+1クエリ検出gemなんですね」

flyerhzm/bullet - GitHub

「READMEに偽陽性と偽陰性がゼロって書かれてますけど、そんなことができるんですね」「N+1クエリが確実に発生している場合だけを検出するということは、bulletと検出方式が違うんでしょうね: 実際に発行されるSQLから検出するか、Rubyのコードから検出するかなど、方法はいくつか考えられます」「bulletでは検出できないN+1クエリも検出できるとも書かれてる」「確実に発生しているN+1クエリだけを検出できるならなかなかよさそう👍

# 同リポジトリより: レコード作成で発生するN+1
FactoryBot.create_list(:leg, 10)

Leg.last(10).each do |l|
  l.chair
end

prosopiteはActive Support instrumentationを用いてすべてのSQLクエリを監視し、すべてのN+1クエリケースに存在する以下のパターンを検出します。
「同じコールスタックと同じクエリフィンガープリントが複数のクエリに存在する場合」
同リポジトリより

参考: Active Support の Instrumentation 機能 - Railsガイド


Rails: Bulletで検出されないN+1クエリを解消する

🔗 その他Rails


つっつきボイス:「自分はRailsアプリをこう書く、というZennの記事を見つけました」「どれが正解かとかではなく、自分はこういうときにはこう書く、というポリシーをこうやって見えるところに書いておくのはいいですね👍

「Railsアプリをチームで開発するときに各メンバーの『自分はこういうときにこう書く』という嗜好が事前にわかっていると、それを踏まえた上で設計を議論できるので話が早くなる」「たしかに」「この人はこういう好みがあるから自分のこの設計にはたぶん反対するだろう、そしてそのうえでチームリーダーやプロジェクトの方針に合わせてもらう、というのは普段からよくやっています」

「長年一緒に組んで仕事をしてきたメンバーとならお互いの嗜好もわかっているのでいいんですが、新しく加わったメンバーだと設計の嗜好が最初のうちはなかなか見えてこないんですよ」「そうそう」「そうした部分が明文化されているとそのあたりが多少やりやすくなりそうですね」「こういう試みはもっと行われてもいいと思いました」


前編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210316後編)testdouble/standard gem、DockerfileベストプラクティスとDockerfileのlintツールhadolintほか

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

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

Rails公式ニュース

週刊Railsウォッチ(20210323後編)GitHub Actionsで使えるruby/setup-ruby、中高生国際Rubyプログラミングコンテスト2020ほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

🔗Ruby

🔗 RubyのRBS: soutaroさんスライドとgem_rbs_collection


つっつきボイス:「soutaroさんのスライドです」「Ruby 3の静的型検査を開発した経緯などを解説しているんですね」

「お、Javaなどの言語にあるジェネリクス的な構文も検討していたのか↓」「コレクションの扱いを考えたらこの落とし所はわかる気がします」



参考: ジェネリックプログラミング - Wikipedia

Rubyスタイルガイドを読む: コレクション(Array、Hash、Setなど)

「スライドで以下のリポジトリも紹介されていました↓」「こちらはコレクションはコレクションでも別のコレクションですね」「gemsディレクトリを開くと、RailsのコンポーネントなどのRBSファイルが集められている」「RBSファイルはまだGitHubでシンタックスハイライトが効かないのか…」

ruby/gem_rbs_collection - GitHub

untypedな部分もまだありますね↓」「通常のRBSファイルの他にgeneratedファイルも置かれています」

# https://github.com/ruby/gem_rbs_collection/blob/main/gems/actionpack/6.0.3.2/actioncontroller.rbs#L95
module AbstractController::Callbacks::ClassMethods
  def before_action: (*untyped) -> void
  def around_action: (*untyped) -> void
  def after_action: (*untyped) -> void
  def skip_before_action: (*untyped) -> void
  def skip_around_action: (*untyped) -> void
  def skip_after_action: (*untyped) -> void
  def prepend_before_action: (*untyped) -> void
  def prepend_around_action: (*untyped) -> void
  def prepend_after_action: (*untyped) -> void
  def append_before_action: (*untyped) -> void
  def append_around_action: (*untyped) -> void
  def append_after_action: (*untyped) -> void
end

RubyのRBSについては『WEB+DB PRESS Vol.121』の「特集: Ruby 3」でもsoutaroさんが詳しい記事を書いています↓。

🔗 ruby/setup-ruby: ビルド済みRubyを5秒でセットアップ

ruby/setup-ruby - GitHub


つっつきボイス:「お、ruby/setup-rubyですね」「fork元はどこかな?」「あれ、リポジトリの左上に”generated from actions/javascript-action“って書かれてる」「generated fromってforked fromとどう違うんだろう?」

後で探すと、以下の記事でgenerated fromってforked fromの違いについて解説されていました。

参考: GitHubのTemplate Repository機能のすゝめ - Qiita

「このactionsはGitHub Actionsのことです↓」「あ、なるほど」

「ruby/ruby-setupにあるRubyのビルド済みバイナリをGitHub ActionsのCIなどで手軽に利用できるんですね」「以下のOSがサポートされてる↓」

OS 推奨バージョン その他のサポート対象バージョン
Ubuntu ubuntu-latest (= ubuntu-18.04) ubuntu-20.04, ubuntu-16.04
macOS macos-latest (= macos-10.15) macos-11.0
Windows windows-latest (= windows-2019) windows-2016

「READMEにあるGitHub Actions用yamlファイルのようにuses: ruby/setup-ruby@v1と書いてRubyのバージョンなどを指定するだけで使えるのか↓」「Ruby公式が提供しているのはありがたい🙏」「こうやって手軽に使えるのは嬉しいですね」

# 同リポジトリより
name: My workflow
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - uses: ruby/setup-ruby@v1
      with:
        ruby-version: 2.6   # .ruby-versionファイルは不要
        bundler-cache: true # 'bundle install'を実行してインストール済みgemを自動でキャッシュする
    - run: bundle exec rake

つっつきの後で、ruby/setup-ruby以前に使われていたactions/setup-rubyがアーカイブ化されたことを以下の記事で知りました。

こうして、晴れて全プラットフォームのVMでRubyコミュニティが提供する公式バイナリに一本化された後で、actions/setup-ruby はarchivedされました。引き続きactions/setup-ruby を使っても、ruby/setup-rubyに乗り換えても使われるバイナリは同じということになりますから、丁寧な移行ですね。action実行時にdeprecatedのメッセージが出るようになったので、これまで ruby/setup-ruby の存在を知らなかった開発者も乗換えをしているようです。VMのtoolcacheに入っているRubyバイナリは actions/setup-ruby を使わなければ自力でPATHを通さない限り使われることはないはずなので、今後新しくVMとして追加されるOSでは toolcacheにそもそもRubyが入らない(必要無い)ということになるかもしれません。
同記事より

actions/setup-ruby - GitHub

🔗 パッチモンスター中田さんインタビュー


つっつきボイス:「パッチモンスターの異名を持つnobuさんこと中田さんのインタビューです」「これだけ長い間継続的にRubyにコミットする活動を続けているのは本当に凄い」「Rubyの歴史のごく初期から活動していたんですよね」「そうそう、RubyKaigi↓などでRubyの歴史の話になるとたいていnobuさんの話も出ますね」

以下はつっつき後に見つけたツイートです。なお今年7月に予定されていたRubyKaigi 2021はキャンセルとなり、RubyKaigi Takeout 2021として秋にオンライン開催されます。

「Rubyのコミットログからコミットの動きとコミッターの活動をアニメーション化した動画がありましたよね、あそこにもnobuさんの姿がしょっちゅうあった覚えがあります」「前にウォッチで取り上げたことがありましたね」

後で過去記事を探しましたが当該動画がなくなっていたので、社内で教えてもらった以下のツイートを貼ります。


「ところで、このインタビュー記事を書いた中薗昴さんという方はシステムエンジニアと技術ライターの仕事を半分ずつやっていてすごいなと思いました」「技術記事は専門性が重要なのでそういう素養は大事ですね: 専門性が高まりすぎると読者が少なくなるのが悩みどころですが」

「技術に強いライターと言えば、CPU関連の濃い記事を昔からたくさん書いている大原雄介さんの記事はとてもアツいですよ↓」「お〜」「プロフィールを見るとハードウェア製作に携わってて、ある時期からライターに転じたんですね」

参考: Yusuke Ohara -Top Page-
参考: ASCII.jp:ロードマップでわかる!当世プロセッサー事情

「失敗したCPUについて大原さんが書いた記事は半端なく面白いので時間のあるときに読んでみるといいと思います」「これですね↓」

参考: 人気連載「忘れ去られたCPU黒歴史」の書籍が10日に発売! - 週刊アスキー

🔗 ブロックで設定するアレは何と呼ぶのか


つっつきボイス:「Rubyでコンフィグをブロックで設定する、ああこれのことですね↓」

# 同記事より
Foo.configure do |config|
  config.name = "foo"
  config.email = "foo@example.com"
end

「Railsのコンフィグもこういう感じで書けますよ」「あ、たしかに」「この記事ではそういうコンフィグを自分で書けるようにする方法が紹介されていますね」

参考: Rails アプリケーションを設定する - Railsガイド

# railsgudes.jpより
config.generators do |g|
  g.orm :active_record
  g.test_framework :test_unit
end

「この書き方に名前があってもいい気がしてきました」「結局『blockで設定するヤツ』になっちゃうかな😆」

「ちなみにこういう書き方ではRubyのクラスインスタンス変数が使われるので、マルチスレッドのアプリで使う場合は注意が必要です」「あ、そうか」

参考: クラス変数とクラスインスタンス変数を理解する - Qiita

「起動後にグローバル設定してその後変更されないならいいんですが、マルチスレッドで動かしながら設定値を更新すると死を招くので、Railsであれば設定をCurrentAttributesに入れるなどの対策を取る必要があります」「なるほど」「マルチスレッドで更新するときに気をつけるべきことなので、記事のコードの書き方は問題ありません」

「なお、クラスインスタンス変数を使わずにコンフィグそのものをインスタンス化するなど、他にも方法はいろいろ考えられます」

🔗 2021年のmruby情報


つっつきボイス:「最近のmruby事情がまとまっていてありがたい」「mrubyをこれから学ぶ人向けの情報があるのもいいですね👍」「紹介されている書籍も新しい↓」

🔗 その他Ruby


つっつきボイス:「中高生国際Rubyプログラミングコンテスト2020、しかもin Mitaka?」「Mitakaって東京の三鷹市?」「恥ずかしながら、このイベントを初めて知りました」「これは知らなかった」「しかも国際」「公式サイトを見ると2011年から毎年開催されているんですね↓」「サイトを見ると三鷹市が後援団体にリストアップされてるから確かに三鷹市だ」「三鷹市は以前からベンチャーの投資が盛んなことで知られていてインキュベーション施設もありますが、プログラミング教育にも力を入れているんですね」

参考: 中高生国際Rubyプログラミングコンテスト in Mitaka
参考: 三鷹市SOHOパイロットオフィス 施設概要 | 株式会社 まちづくり三鷹

以下はつっつき後に見つけたツイートです。

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

🔗 Graviton 2ベースのAmazon Aurora(Publickeyより)


つっつきボイス:「BPS社内Slackに貼られていたのを拾いました」「AWSが独自開発したArmベースのGraviton 2は、RDSでの利用だと比較的使いやすいのがいいですね👍: Graviton 2をEC2などで使おうとすると、Dockerイメージのバイナリ互換性などの問題がつきまとうので簡単に移行できませんが、TCPレベルで接続するRDSならプロセッサの種類はそれほど関係なくなるので」「なるほど」

参考: AWS、自身でプロセッサを開発していく姿勢を明らかに 独自開発の第二世代ARMプロセッサ「Graviton 2」発表:AWS re:Invent 2019 - ITmedia NEWS
参考: Amazon RDS(マネージドリレーショナルデータベース)| AWS

「何かあるとすれば、PostgreSQLやMySQLをカリカリにチューニングして動かそうとするとGraviton 2での最適化がまだあまり進んでいなくて思ったほどパフォーマンスが出ない、といったことはあるかもしれませんが、普通に使う分にはほぼ影響はないだろうと思います」「お〜」「機会があったら使ってみようかな」


後編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210322前編)Active Recordのstrict loadingの修正、セキュリティリリースのポリシー追加、N+1チェッカーprosopite gemほか

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

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

Publickey

publickey_banner_captured

週刊Railsウォッチ(20210329前編)特集: Rails更新版の臨時リリースとmimemagic gemのGPL問題

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

🔗 特集: mimemagic gemのGPL問題とRails更新版リリース

現状: Rails 5.2.5、6.0.3.6、6.1.3.1がリリース

最初に本日(2021/03/29)時点の情報から。
後述するmimemagicの問題を解決したRailsが日本時間の2021/03/27(土)03:24にリリースされました。

上の更新版Railsにアップデートすることでmarcelがmimemagicに依存しなくなります。

mimemagicが必要な場合の対応方法やRails以外のサードパーティgemの問題について詳しくは、ruby-jp有志がまとめている以下の情報もどうぞ。随時更新されています。

参考: mimemagicの最新動向 - HackMD

なおgemごとのライセンス情報表示はbundle licensesコマンドでやるのが一番手軽でした。

🔗 経緯

つっつきボイスは2021/03/25夜(木)時点のものです。更新版Railsがリリースされる前の流動的な状態につき、ご了承ください。


つっつきボイス:「今ちょうどmimemagicのGPL問題であちこちが騒ぎになっていますね」「気づいてなかった〜」「気づかずに済んだのは幸い: 組織内gemサーバーを使っていないところや、gemのキャッシュが残っていないRailsアプリがこの問題を踏むとbundle installでmimemagicを取得できずにCI(Continuous Integration)がコケます」「げ」

「単にmimemagic gemが取れなくなっただけなら参照先を変えるなどすれば対応可能なんですが、mimemagicで見つかったGPL汚染が現時点で解決していないのが問題なんですよ(注: Rails更新版で対応済み)」「なるほど」

参考: GPL汚染 ‐ 通信用語の基礎知識

「今の時点(3/25 20時頃)ではbundle update mimemagicを実行すれば更新版のmimemagicを取得してエラーを解決できますが、更新版mimemagicがGPL-2.0ライセンスのまま(注: 3/29時点では後述の対応でMITライセンスに変更済みです)なので、Railsアプリ全体のライセンスがGPL-2.0になってしまいます」「mimemagicは随分前からRailsで使われてたのか」

「こういう依存関係があったそうです↓」

mimemagicの依存関係

  • RailsのActive Storageは、rails/marcel gemに依存している
  • rails/marcel gemは、Railsリポジトリの外にあるmimemagic gemに依存している

rails/marcel - GitHub

旧minad/mimemagic↓は、現在minemagicrb/minemagicにリダイレクトされます(3/29時点)。

minad/mimemagic - GitHub

「以下は発端となったRailsのissue #41750ですが↓、既にロックされて書き込めなくなってます」「それほど書き込みが激しかったということですね」「また新しいissue(41517)が開いてる」

「以下が現時点(3/25夜)までのおおよその流れです↓」

ここまでの流れ

  • minad/mimemagic gemで使っているMIMEリストXMLファイルfreedesktop.org.xmがGPL-2.0ライセンスであることが発覚した(#97
  • minad/mimemagic 0.3.6で以下を行った
    • ライセンスをMITからGPL-2.0に変更
    • minad/mimemagicのリポジトリをアーカイブ化
    • 従来MITライセンスとして公開されていたminad/mimemagic gemをyankした

issue: freedesktop.org.xml file license · Issue #97 · minad/mimemagic

「mimemagicはActive Storage添付ファイルのMIMEタイプの判別に使っているんでしょうね」

参考: MIME タイプ (IANA メディアタイプ) - HTTP | MDN

「現時点(3/25夜)では、少なくともrails/marcel gemのminad/mimemagic依存を「ruby-magicに切り替える」「libmagicに切り替える」という2つの方向が検討されているようです↓」「#26のCIが全部失敗しているのでまだ作業中か」「緊急の対応がどうしても必要でなければ、Railsフレームワーク側でmimemagicを置き換えて対応するのを待つのが確実でしょうね」「そうします」

その後

(3/29夜の時点)最終的に冒頭の更新版Railsでは、rails/marcel gemがアップグレードしてmimemagic↓に依存しなくなり、rails/marcel gemでMIMEデータファイルにApache Tika(Apache License 2.0)のファイルを用いるようにしたことで対応しました。

mimemagicrb/mimemagic - GitHub

参考: Apache Tika – Apache Tika
参考: Apache License - Wikipedia

Railsのバージョンが微妙に古いなどの理由でmarcelをアップグレードできない場合は、minimagicを更新したうえでshared-mime-infoパッケージ↓でインストールされるfreedesktop.org.xmlをFREEDESKTOP_MIME_TYPES_PATHに指定して使う必要があるでしょう。

参考: shared-mime-info
参考: mimemagicの最新動向 - HackMD

🔗 GPL-2.0の解釈

ライセンスの解釈は最終的に弁護士など法務関係者の判断や法的手続きが必要と思われます。本記事におけるライセンスの解釈についてはあくまで参考情報にとどめていただくようお願いします。

「SaaSとしてのRailsアプリであれば、GPL-2.0に基づいたソースコード開示の対象にはならないのではないかという情報をruby-jpで見かけました: 以下のNate Berkopecさんのツイートにも同趣旨のことが書かれています↓」「そこはどうだったかな…GPL-3.0やLGPLではそうした点の明文化が改善されていたと思うんですが、GPL-2.0はそのあたりが曖昧だったような覚えがあります」「う〜む」「GPL-2.0は古くからあるライセンスですし、SaaSのような利用形態を想定していない可能性もありそうなので、この話を信じていいかどうかは判断できないかな」「ソフトウェアライセンスに強い弁護士じゃないと無理そうですね」(しばし議論)

参考: GNU General Public License - Wikipedia
参考: GNU Lesser General Public License - Wikipedia
参考: PDF IoT時代におけるOSS の利用と法的諸問題Q&A集

「近年MITライセンス↓などのより制約の少ないライセンスが広く使われるようになったのはそうした理由が大きいと思います: GitHubにリポジトリを作るときもたしかデフォルトでMITライセンスファイルが作られますよね」「はい、GitHubはそうなってます」

参考: MIT License - Wikipedia

🔗 ユーザー環境でRailsベースの製品を動作させるプロダクトへの影響

「これもruby-jpで見かけたのですが、この問題の影響が大きいのは主にGitLabやGitHub EnterpriseのようにRailsアプリをライセンス販売している会社だそうです」

「GPLライセンスのソフトウェアを自分達の作ったソフトウェア内に含めてしまうと、配布した際にソフトウェア全体をGPL規約に従ってユーザーに公開する必要があります: 非公開のコードをユーザー側環境のアプリケーションサーバーで動かせるようなプロダクト(GitHub EnterpriseやGitLab EEなど、クラウドサービスのEnterprise版でオンプレ環境に対応したものなど)の場合、大きな問題になります」

🔗 DMCA takedown

「以下はGo言語のmimemagicリポジトリですが、こちらには同様の指摘がメールでメンテナーに届いたとのことで、やはりライセンスをGPL-2.0に変更しています」「DMCA takedownの通告を受けたのか、これは大変そう」

zRedShift/mimemagic - GitHub

「GitHubのようなソフトウェア配布事業者やコンテンツ事業者がDMCA takedownの通告を受けたら、DMCA違反かどうかの詳細を確認するよりも先に配布を速やかに停止しないといけないんですよ」「えぇ?」「もちろん異議申し立てなどのプロセスもありますが、手順としては最初に公開を停止してから事実確認や審議を行うことになっていて、その意味では事業者よりも利用者の保護を優先していると言えますね」

参考: デジタルミレニアム著作権法 - Wikipedia — DMCA
参考: DMCAテイクダウンポリシー - GitHub Docs

通知がリポジトリのコンテンツ全体、またはパッケージが著作権侵害に該当すると主張する内容の場合、当社はステップ 6 に移動し、リポジトリ全体またはパッケージを迅速に無効にします。 そうでない場合、GitHub はリポジトリ内の特定のファイルへのアクセスを無効にすることはできないため、リポジトリを作成したユーザに連絡し、約 1 営業日以内に通知で指定されたコンテンツを削除または変更するよう求めます。 ユーザに変更を行う機会を提供した場合、当社はその旨を著作権者に通知します。 パッケージはイミュータブルであるため、パッケージの一部が著作権を侵害している場合、GitHub はパッケージ全体を無効にする必要がありますが、侵害している部分が削除された場合は復帰を許可しています。
docs.github.comより(強調は編集部)

「GitHubのドキュメント↑にもあるように、通告を受けた事業者は1営業日以内にコンテンツを削除または変更しないといけません」「たった1日ですか?」「通知が来た日の翌営業日までだと思います」「それでもキビシイ🥲」「リポジトリのメンテナーがたまたま休んでたりしたら間に合わなさそう」「ドキュメントには『うっかり期間内に変更できなかった場合』の項目もあるから対応はできそうかな」

「DMCA takedownは強い指示で、それでいて申し立ても比較的やりやすいので、これまでも炎上記事の火消しなどのために記事公開を差し止めさせるといった、DMCA本来の目的から外れた使われ方がしばしば話題になってますね」「あぁなるほど!」「言われてみればその手の記事をときどき見かけますね」

参考: 史上最高に馬鹿げた著作権侵害のDMCA通告 | TechCrunch Japan

「その代わり、悪用防止のためにDMCA takedownを依頼した側の情報は事業者がすべて公開しています」「なるほど、そうしないといくらでもイケないことに使われそう」「それでもしばしばそういうふうに使われていますけどね」「『DMCA 悪用』でググるといっぱい出てくる…」「今見ている記事でも、DMCAを悪用するとかえってブランドイメージを傷付けるから止めた方がいいよって書かれてました」

参考: Google 透明性レポート
参考: DMCAとは 意味・用途 - アイオイクスの社員ブログ

🔗 まとめ

「minad/mimemagicの場合はDMCA通告ではなくshared-mime-infoメンテナーからのissue(#97)でGPLの件が知らされていたようですが、今回の件でminad/mimemagicリポジトリをアーカイブしたのはDMCA違反回避のためにやむを得ず緊急で行ったのだろうと推測しました」「悪意とかではなく、事が重大なだけに真っ先に公開を止めてそれからRails側に知らせるしかなかったんでしょうね、何となくわかってきました」「リポジトリを止めたときのRailsへの影響の大きさがわかっていてもリポジトリを一時的に復旧できなかったのは、そういうことだったのかも」「学びを得た気持ちです」

無事に復旧して何よりでした。関係者の皆さまお疲れさまでした!


「ところで、#41750のコメント↓に貼られているxkcd: Dependencyの絵は身につまされますね」「あと、mimemagicという名前がminimagickに似てて何度も間違えそうになりました」「たしかに」

minimagick/minimagick - GitHub


前編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210323後編)GitHub Actionsで使えるruby/setup-ruby、中高生国際Rubyプログラミングコンテスト2020ほか

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

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

Rails公式ニュース

Ruby Weekly

週刊Railsウォッチ(20210330後編)Active Recordモデル属性暗号化が標準で入る可能性、Flipper Cloud、awesome_printほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

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

今週は以下のコミットリストのChangelogを中心に見繕いました。

🔗 RemoteIPミドルウェアでtrusted_proxiesに単独の値代入を非推奨化


つっつきボイス:「app.config.action_dispatch.trusted_proxiesに渡すIPアドレスは単独でも配列でも渡せるけど、前者を非推奨化したということのようですね↓」

# 同PRより
# Adds 4.2.42.0/24 to default list of trusted proxies
app.config.action_dispatch.trusted_proxies = IPAddr.new("4.2.42.0/24")

# Replaces default list of trusted proxies with 4.2.42.0/24
app.config.action_dispatch.trusted_proxies = [IPAddr.new("4.2.42.0/24")]

「RackミドルウェアのRemoteIptrusted_proxiesは、たとえばRailsの手前にリバースプロキシを置いてある場合、リクエストの流れがリバースプロキシ->Railsアプリの場合はレスポンスを返すけど、リバースプロキシ以外の怪しいIPアドレスからRailsアプリにリクエストが来た場合はレスポンスを返さないようにするのに使ったりします(IP Spoofing対策)」「あ、そうやって使うんですね」

参考: IPスプーフィング - Wikipedia

「こうした制約をインフラレベルだけでかけられれば理想なんですが、CDNなどの制約が絡んだりしてインフラが複雑になると穴を塞ぐのが大変になるので、そういうときにtrusted_proxiesを使うことがあります」

参考: コンテンツデリバリネットワーク - Wikipedia

trusted_proxiesに配列を渡せるなら単独の値もカバーできるけど、単独の値を非推奨化する必要はあるのかな?🤔」「まだしばらくは単独の値も使えそう」「今後はenumerableな値を渡せば大丈夫ですね」

🔗 個別のテストファイル実行でパラレルテストが無効になった


つっつきボイス:「bin/test test/controller/parameters/accessors_test.rbのようにファイルを1個だけ指定する場合はパラレルテストをオフにするようになったのか: たしかに1ファイルだけのテストでパラレルテストのワーカーをセットアップするのは完全に無駄ですね」「たしかに」「IDEの保存時実行機能やguard gemなどを使って保存ファイルだけテストを自動実行するのが速くなりそう👍」

# 改修前
actionpack $ bin/test test/controller/parameters/accessors_test.rb
Run options: --seed 48261

........................................................................

Finished in 0.211923s, 339.7460 runs/s, 552.0873 assertions/s.
72 runs, 117 assertions, 0 failures, 0 errors, 0 skips
# 改修後
actionpack $ bin/test test/controller/parameters/accessors_test.rb

Run options: --seed 5461

........................................................................

Finished in 0.008411s, 8560.2189 runs/s, 13910.3557 assertions/s.
72 runs, 117 assertions, 0 failures, 0 errors, 0 skips

guard/guard - GitHub

🔗 strict_loading!n_plus_one_onlyモードが追加


つっつきボイス:「ビックリマーク付きのstrict_loading!n_plus_one_onlyオプションが入ったそうです」「Changelogの方が見やすそう↓」

  • レコードレベルのstrict_loading!にモード引数を追加
    この引数は、単一レコードに対してstrict loadingを有効にし、N+1クエリに対してのみエラーをraiseできる。
developer.strict_loading!(mode: :n_plus_one_only)
developer.projects.to_a # Does not raise
developer.projects.first.client # Raises StrictLoadingViolationError

従来はstrict loadingを有効にすると、lazy loadされたすべての関連付けでエラーがraiseされた。n_plus_one_onlyモードを指定すると、単一のクエリでフェッチされたbelongs_tohas_manyなどの関連付けをlazy loadできる。
Dinah Shi
同Changelogより大意

n_plus_one_onlyを有効にすると、N+1問題が発生していないとき(レコードを1件だけ読み込む場合)なら関連付けをlazy loadingできて、N+1問題が発生している場合だけエラーをraiseするようになったということのようですね: strict loadingはN+1クエリ問題が発生しないときに行いたいことが多いし、N+1が発生したときはstrict loadingのところで止めたくなると思うので、これが欲しい気持ちはわかります」「なるほど」「n_plus_one_onlyモードは自分的にはなかなか実用的な印象👍」

レコードをstrict_loadingモードに設定する。レコードが関連付けをlazy loadingしようとするとエラーをraiseする。

user = User.first
user.strict_loading!
user.comments.to_a
=> ActiveRecord::StrictLoadingViolationError

api.rubyonrails.orgより大意

🔗 Enumerable#in_order_ofが追加

ActiveRecord::Base#findは、引数として使われている主キーの順でレコードを返す。これはindex_bymapの合せ技で、#find以外でも有用。このプルリクはそのパターンをEnumerable#in_order_ofに切り出したもの。
同PRより大意


つっつきボイス:「これは今年2月にマージされたので少し前のPRですが、DHH自らによるものでした」「Active Supportに入ったんですね」「このコードサンプルがわかりやすいかな↓: [1, 5, 3]を渡すと、idを持つものをその順序で返す」「あ、なるほど」

  #   [ Person.find(5), Person.find(3), Person.find(1) ].in_order_of(:id, [ 1, 5, 3 ])
  #   => [ Person.find(1), Person.find(5), Person.find(3) ]

「Enumerableに追加されたのがいいですね」「順序を指定せずに取りたいことの方が多いと思うけど、このメソッドがあることを知っていれば順序を指定して取りたい場合に使うかも」

# activesupport/lib/active_support/core_ext/enumerable.rb#L194
+ def in_order_of(key, series)
+   index_by(&key).values_at(*series).compact
+ end

🔗 (マージ前)Active Recordモデルの属性を暗号化する機能(Ruby Weeklyより)


つっつきボイス:「現時点(2021/03/25夜)ではまだマージされていませんが、Ruby Weeklyで取り上げられていました」「おぉ、属性の暗号化がついにRailsに統合されそう?」

# 同PRより
class Person < ApplicationRecord
  encrypts :name
end

「Action TextやActive Recordなどにも変更が入っている」「よく見たら更新ファイルが82個もあるんですね」「変更多いな〜」「これまでなかった機能が丸ごと入ったから変更量は多いでしょうね」

「Active Record属性の暗号化といえばattr_encrypted gemがかなり昔からよく使われています↓」「そうそう」「GitLabでもattr_encryptedを使っていたと思います」「類似のlockboxやvault-railsもプルリクメッセージで紹介されていますね↓」

attr-encrypted/attr_encrypted - GitHub
ankane/lockbox - GitHub
hashicorp/vault-rails - GitHub

「しかもこれはBasecampがやっているHEYのコードから切り出したそうですよ↓」「お〜」「HEYで実績を積んでいるコードが使われているのはありがたい」「encryptorもちゃんと独自に指定できる: これはないと困る機能」「HEYを立ち上げる前にセキュリティファームがこのライブラリをレビューしたと書かれていますね」

# 同PRより
config.active_record.encryption.encryptor = MyEncryptor.new

「属性暗号化機能は多くのアプリで使われるので、Rails標準で入っていてもいい気がしますね」「今どきのフルスタックWebフレームワークなら入っていてもおかしくないかも」「”いいね”もすごくたくさん付いてますね」「これはRailsにあっていい機能👍」「Rails 7にこれが入るなら目玉機能になるでしょうね」


本日(2021/03/30夕方)時点ではまだマージされていません。

🔗Rails

🔗 authorizationのつらみをFlipper Cloudで軽減する(Ruby Weeklyより)


つっつきボイス:「上のFlipper Cloudというサービスを推している記事のようです」「サイトを見た感じでは、いわゆるフィーチャーフラグをクラウドベースで管理できるオープンソースのサービスということらしい」

参考: フィーチャートグル - Wikipedia

「gemも公開されている↓:gemでやる場合は自分たちで管理する必要があると思いますが」「設定はIRBでもFlipper::UIでもできるとありますね」

jnunemaker/flipper - GitHub


同リポジトリより

「コードはフィーチャーフラグそのものという感じなので↓、シンプルなフィーチャーフラグの実装に使えそうかな」「おぉ」

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

# check if search is enabled
if Flipper.enabled?(:search)
  puts 'Search away!'
else
  puts 'No search for you!'
end

puts 'Enabling Search...'
Flipper.enable(:search)

# check if search is enabled
if Flipper.enabled?(:search)
  puts 'Search away!'
else
  puts 'No search for you!'
end

「眺めた限りでは、リポジトリの★も多いし、フィーチャーのオンオフとかも含めて割とよくできていそうな予感がしますね: 興味があれば調査してみてもいいかも」


「元記事の方は、たとえばこれはよくあるポリシー型の権限管理ですね↓」「Flipper Cloudはユーザーごとの権限管理までやれそうに見える」

# 同記事より
class TokenPolicy < ApplicationPolicy
  def show?
    project_member?
  end

  def update?
    project_member?
  end

  def destroy?
    project_member?
  end

  private 

  def project_member?
    record.project.member?(user)
  end
end

「URLエンドポイントレベルのシンプルな権限管理ならこうしたgemやサービスでやれると思いますが、エンティティレベルで権限管理をしようとするとおそらくpunditのようなものが必要になってくるでしょうね」「なるほど」「そこにGraphQLが絡むとさらに複雑になるんですよ」

varvet/pundit - GitHub


「ところで、記事ではmemoistというgemも紹介されていますね↓」「サンプルコードのとおり、いわゆるメモ化を簡単に行えるようですね」「今はRailsからなくなったActiveSupport::Memoizableから切り出したらしい」「ポリシーは実行中に変えないのが普通なので、記事ではこれでメモ化して高速化を図っているんでしょうね」

matthewrudy/memoist - GitHub

# matthewrudy/memoistより
require 'memoist'
class Person
  extend Memoist

  def social_security
    puts "execute!"
    decrypt_social_security
  end
  memoize :social_security
end

person = Person.new

person.social_security
# execute!
# => (returns decrypt_social_security)

person.social_security
# => (returns the memoized value)

参考: メモ化 - Wikipedia

🔗 sqldef: 冪等なスキーマ管理ツール

k0kubun/sqldef - GitHub

Goで書かれています。


つっつきボイス:「k0kubunさんの上の記事でsqldefを知りました」「ridgepoleのオルタナ的なツールをk0kubunさんが自分で作ったんですね」「スキーマ管理をSQLで書けるというツールなのか: そういえばずっと前に見たことがあったかも」「実はだいぶ前のウォッチで取り上げてたんですが、ridgepole的なツールとは気づきませんでした(ウォッチ20191119)」

winebarrel/ridgepole - GitHub

「スキーマ管理をSQLで書きたいor書く必要がある場合にいいかも: たとえばプロジェクト横断的な案件で、Railsでないシステムのデータベースをリードオンリーで共有している場合を考えると、RailsのマイグレーションはRubyがないと動かないのでRubyをインストールするなどの対応が必要ですが、こうやってSQLで書けるならそういう場合に便利かもしれませんね」「ちょうどk0kubunさんの記事の脚注にもそのことが書かれていたので引用しました↓」「そうそう、こういうふうに非Railsプロジェクトとデータベーススキーマを共有したいとか、Railsを知らない人がスキーマ管理するときなどに使えそう」

元々ActiveRecordが入っているRailsだとRidgepoleを使わない理由はほとんどない気がするけど、同じDBをActiveRecord以外からも読むので素のSQLで管理されてる方が扱いやすい (今回のアプリがそれ) とか、DDLは覚えてるけどActiveRecordのDSLは調べないとわからないというようなニッチな用途には便利かもしれない。
同記事脚注より


以下のスライドでもsqldefが取り上げられているのを見つけました。

🔗Ruby

🔗 dead_end: endの対応関係の誤りを検出する(Ruby Weeklyより)

zombocom/dead_end - GitHub


つっつきボイス:「endの誤りを検出するという、よくある感じのgemですが、このdead_endという名前がいいですね👍」「ネーミングセンスが光ってる」「RubyMineなどのIDEもやれますけどね」

# 同リポジトリより: endが足りない場合
class Dog
  def bark
    puts "bark"

  def woof
    puts "woof"
  end
end
# => scratch.rb:8: syntax error, unexpected end-of-input, expecting `end'
# 同リポジトリより: endが余分な場合
class Dog
  def speak
    @sounds.each |sound| # Note the missing `do` here
      puts sound
    end
  end
end
# => scratch.rb:7: syntax error, unexpected `end', expecting end-of-input

🔗 シェルのディレクトリ一括作成


つっつきボイス:「koicさんのRuboCop記事でシェルの話が載っていました」「mkdir {a,z}は当然できるけど、mkdir {a..c}mkdir {1..10}でディレクトリを一括作成できるとは知らなかった」「これ初めて見ました」「そういえばシェルに..記法があったのを今思い出した」「最近シェルスクリプト書いてないな〜😅」

# 同記事より
% mkdir {a,z}
% ls 
a z

% mkdir {a..c}
% ls 
a b c 

% mkdir {1..10}
% ls 
1 10 2 3 4 5 6 7 8 9

% mkdir {01..10}
% ls
01 02 03 04 05 06 07 08 09 10

参考: Bashによるパス名の展開まとめ | LFI

「そして{}が空だと{}というディレクトリができてしまう、たしかに」

「そういえばシェルのtestコマンドに[という別名がありますよね」「あ〜、ありますあります」「あれを最初知ったときはびっくりしましたけど、わかってみるとシェルすごいかもという気持ちになりますね」「人によりそうですけどね😆」

参考: test と [ と [[ コマンドの違い - 拡張 POSIX シェルスクリプト Advent Calendar 2013 - ダメ出し Blog

🔗 awesome_printが強くなって帰ってきた(Ruby Weeklyより)

awesome-print/awesome_print - GitHub


つっつきボイス:「awesome_printは、以前は色付けに使うgemという印象でしたが、知らないうちにいろいろ機能が足されているようです」「お〜、オプションがずいぶん増えてますね」

「こういう感じで表示できるのはいい↓👍」「いいな〜」

# 同リポジトリより
$ rails console
rails> require "awesome_print"
rails> ap Account.limit(2).all
[
    [0] #<Account:0x1033220b8> {
                     :id => 1,
                :user_id => 5,
            :assigned_to => 7,
                   :name => "Hayes-DuBuque",
                 :access => "Public",
                :website => "http://www.hayesdubuque.com",
        :toll_free_phone => "1-800-932-6571",
                  :phone => "(111)549-5002",
                    :fax => "(349)415-2266",
             :deleted_at => nil,
             :created_at => Sat, 06 Mar 2010 09:46:10 UTC +00:00,
             :updated_at => Sat, 06 Mar 2010 16:33:10 UTC +00:00,
                  :email => "info@hayesdubuque.com",
        :background_info => nil
    },
    [1] #<Account:0x103321ff0> {
                     :id => 2,
                :user_id => 4,
            :assigned_to => 4,
                   :name => "Ziemann-Streich",
                 :access => "Public",
                :website => "http://www.ziemannstreich.com",
        :toll_free_phone => "1-800-871-0619",
                  :phone => "(042)056-1534",
                    :fax => "(106)017-8792",
             :deleted_at => nil,
             :created_at => Tue, 09 Feb 2010 13:32:10 UTC +00:00,
             :updated_at => Tue, 09 Feb 2010 20:05:01 UTC +00:00,
                  :email => "info@ziemannstreich.com",
        :background_info => nil
    }
]

「リポジトリにcodeclimate.ymlが置かれている: ちなみにCode Climateは自動コードレビューなどが使えるサービスです」「Code Climateの設定はこういう感じで書くんですね」

「こういうgemが環境に入っていて使えたら嬉しいけど、ただGemfileには書いて欲しくない気持ちがあります」「あ、それわかります」「このgemならrequireに書いてもいいぐらい好きですけど、Gemfileのdevelopmentブロックにgemをいっぱい足すのはあまり好みではないんですよ」「たしかに」

🔗 AWS SDK RubyでRuby 2.2以前のランタイムサポートが終了


つっつきボイス:「お、サポート終了のお知らせか」「ここにリストされているバージョンならRubyソースコードも更新されなくなっていますし、影響はそんなにないと思いますね」

  • Ruby 1.9.3 – EOL began on 2015-02-23
  • Ruby 2.0.0 – EOL began on 2016-02-24
  • Ruby 2.1 – EOL began on 2017-03-31
  • Ruby 2.2 – EOL began on 2018-03-31
    aws.amazon.comより

後編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210329前編)特集: Rails更新版の臨時リリースとmimemagic gemのGPL問題

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

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

Rails公式ニュース

Ruby Weekly

Ruby 3.0.1/2.7.3/2.6.7/2.5.9セキュリティ修正がリリースされました

$
0
0

Ruby 3.0.1/2.7.3/2.6.7/2.5.9セキュリティ修正がリリースされました。

詳しくは上記リリースノートをご覧ください。お気づきの点がありましたらhachi8833までお知らせください。

Ruby 2.5のサポートが終了

Ruby 2.5は今回の2.5.9が最終リリースとなり、ステータスがEOL(End Of Life)に変わりました(今後2.5のセキュリティ修正はリリースされません)。Ruby 2.5をお使いの場合はできるだけ速やかにRuby 3.0/2.7/2.6にアップグレードしましょう。

After this release, Ruby 2.5 reaches EOL. In other words, this is the last release of Ruby 2.5 series. We will not release Ruby 2.5.10 even if a security vulnerability is found. We recommend all Ruby 2.5 users to upgrade to Ruby 3.0, 2.7 or 2.6 immediately.
同2.5.9リリースノートより

(Ruby 2.5の本来のEOLは「2021/03/31」と以下に記載されています↓)

参考: Ruby Maintenance Branches

🔗 修正の概要

以下の脆弱性が修正されました。

CVE-2021-28965: XML round-trip vulnerability in REXML

  • 影響を受けるRubyバージョン: 3.0.0、2.7.2以前、2.6.6以前、2.5.8以前
  • 影響を受けるrexmlバージョン: 3.2.4以前
  • 修正されたRubyバージョン: 3.0.1、2.7.3、2.6.7、2.5.9
  • 修正されたrexmlバージョン: 3.2.5

CVE-2021-28965はrexml gem(3.2.4以前)に関連する脆弱性です。詳しくはリンク先の情報をご覧ください。

ruby/rexml - GitHub

CVE-2021-28966: Path traversal in Tempfile on Windows

CVE-2021-28965はWindows環境のtmpdirライブラリに関する脆弱性です。詳しくはリンク先の情報をご覧ください。

  • 影響を受けるRubyバージョン: 3.0.0、2.7.2以前
  • 修正されたRubyバージョン: 3.0.1、2.7.3

参考: library tmpdir (Ruby 3.0.0 リファレンスマニュアル)

CVE-2020-25613: Potential HTTP Request Smuggling Vulnerability in WEBrick

  • 影響を受けるRubyバージョン: 2.7.1以前、2.6.6以前、2.5.8以前
  • 影響を受けるwebrickバージョン: 1.6.0以前
  • 修正されたRubyバージョン: 2.6.7、2.5.9
  • 修正されたwebrickバージョン: 1.6.1以降

CVE-2020-25613のwebrick gemの脆弱性情報は昨年2020年9月に公開済みです(以下の記事を参照)。今回リリースされた2.6.7と2.5.9に反映されています。

Ruby: WEBrick 1.6.1セキュリティ修正がリリース

ruby-buildとDocker Hub

rbenvで使われるruby-buildでは、既にRuby 3.0.1/2.7.3/2.6.7/2.5.9が利用可能になっています。

rbenv/ruby-build - GitHub

Docker Hubでも各種ディストリビューション向けのRuby 3.0.1/2.7.3/2.6.7/2.5.9が利用可能になっています。


週刊Railsウォッチ(20210406前編)GitHubが修正したRailsセッションハンドリングの競合、erb/haml/slimの速度比較ほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

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

今回は以下のコミットリストから見繕いました。Changelog更新はほとんどありませんでした。


つっつきボイス:「先週mimemagicの件があったせいか今週の変更は少なめでした」

週刊Railsウォッチ(20210329前編)特集: Rails更新版の臨時リリースとmimemagic gemのGPL問題

🔗 マイグレーション生成の--pretendオプションのエラーを修正

# railties/lib/rails/generators/actions/create_migration.rb#L22
        def invoke!
+         return super if pretend?
+
          invoked_file = super
          File.exist?(@destination) ? invoked_file : relative_existing_migration
        end

つっつきボイス:「マイグレーションに--pretendオプションがあるとは知らなかった」「dry run的なものかな?」

後でbin/rails g migration --helpして確認しました。

Runtime options:
  -f, [--force]                    # Overwrite files that already exist
  -p, [--pretend], [--no-pretend]  # Run but do not make any changes
  -q, [--quiet], [--no-quiet]      # Suppress status output
  -s, [--skip], [--no-skip]        # Skip files that already exist

また、手元のRails 5.2.5ではマイグレーションの--pretendはエラーにならず、Rails 6.1.3.1だとこのプルリクにかかれているのと同じエラーになりました。

🔗 ActiveSupport::NumericWithFormat#to_sを最適化


つっつきボイス:「to_snilチェックを移動して早期脱出させたようですね」

# activesupport/lib/active_support/core_ext/numeric/conversions.rb#L109
    def to_s(format = nil, options = nil)
+     return super() if format.nil?
+
      case format
-     when nil
-       super()
      when Integer, String
        super(format)
      when :phone
        ActiveSupport::NumberHelper.number_to_phone(self, options || {})
      when :currency
        ActiveSupport::NumberHelper.number_to_currency(self, options || {})
      when :percentage
        ActiveSupport::NumberHelper.number_to_percentage(self, options || {})
      when :delimited
        ActiveSupport::NumberHelper.number_to_delimited(self, options || {})
      when :rounded
        ActiveSupport::NumberHelper.number_to_rounded(self, options || {})
      when :human
        ActiveSupport::NumberHelper.number_to_human(self, options || {})
      when :human_size
        ActiveSupport::NumberHelper.number_to_human_size(self, options || {})
      when Symbol
        super()
      else
        super(format)
      end
    end

case format when nilは最終的にNilClass === nilを呼ぶので非常に効率がよい。これはObject#==のエイリアスなので、基本的には最終的にnil == nilとなる。
しかしformat.nil?は専用のopコードのおかげで著しく速い。
この場合Integer#to_sは引数なしで呼ばれることが非常に多いので、頻度が最も高いケースについて最適化する価値がある。
同PRより大意

🔗 issue: Rails 5.2.5とCSRFトークンフォーマット

mimemagic に依存しなくなった rails 5.2.5 に CSRF トークンのフォーマットが Urlsafe Base64 になる変更が(意図せず?)入っているようで 5.2.5 で生成されたセッションが 5.2.4.x 以下 or 6.0.x で読めなくなってしまっているようなので rails 5.2.5 と 6.1.x 以外の rails を組み合わせて使っている人は要注意です。
hackmd.io『mimemagicの最新動向』より

参考: mimemagicの最新動向 - HackMD


つっつきボイス:「mametterさんやruby-jp Slackの有志がまとめてくださった上のmimemagic動向記事に追記されていたのを見て知りました」「Rails 5.2.5でRailsがmimemagicに依存しなくなったときにCSRFトークンフォーマットが変わってしまったということですね」

「Railsサーバーコンテナを複数動かして段階deployする場合は、deployが完了するまで旧フォーマットで動くRailsサーバーと新フォーマットで動くRailsサーバーが混在してエラーレートが跳ね上がる可能性があるので、規模の大きなサービスほど問題になるでしょうね(#41783コメント)」「なるほど」「mimemagicの件でアップグレードする時にチェックしておく必要はあると思います」「Rails 5.2向けに#41797がオープンしてますね↓」

🔗Rails

🔗 rails_multisite: Discourseから切り出されたRailsマルチサイトgem(Ruby Weeklyより)

discourse/rails_multisite - GitHub


つっつきボイス:「DiscourseのRailsから切り出されたマルチサイトgemだそうです」

参考: Discourse - Civilized Discussion

「以下のようにホスト名ベースでデータベースを切り替えられるようですね↓」

# config/multisite.yml

db_one:
  adapter: ...
  database: some_database_1

db_two:
  adapater: ...
  database: some_database_2

参考: Active Record で複数のデータベース利用 - Railsガイド

「いわゆるマルチテナントのRailsアプリで、データベースもテナントごとに分けたいというときに使うのかな?案件の事情によってはこういう設計にしないといけない場合もあるのかもしれないけど、マイグレーションやデプロイが大変そうですし、このようにひとつのRailsアプリでデータベースをテナントごとに分けるよりもRailsアプリのインスタンスを分ける方が一般的かなと思いました」「それもそうですね」「自分たちが運用することを考えるとアプリを分ける方が楽だと思います」

参考: Apartment でマルチテナントサービスを作成する - Qiita

🔗 GitHubが発見・修正したRailsセッションハンドリングの競合(Ruby Weeklyより)


つっつきボイス:「GitHubの技術ブログです」「この図がポイントかな↓」


同記事より

「Railsのセッションcookieでレアなrace conditionが発生したということらしい」「race conditionの解決は大変そう」「記事冒頭を見ると、少し前にGitHubからユーザーが突然ログアウトさせられた件について書かれていますね: そのときの修正内容の解説なのか」「あ、そういえば1か月ほど前にそんなことがありましたね」

参考: GitHub security update: A bug related to handling of authenticated sessions - The GitHub Blog

「これは読んでみてもよさそう👍」「もう少ししたらGitHubブログの日本語版にも公開されるかもしれませんね↓」

参考: GitHubブログ - 製品アップデートや開発に関するアイディアやインスピレーションなど、エンジニアの皆さんに役立つ情報を発信します。

🔗 erbとhamlとslimの速度を比較してみた


つっつきボイス:「単純に出力するならerbが一番速いんじゃないかな?」「条件を変えたりして測定してますね」

haml/haml - GitHub

slim-template/slim - GitHub

「ループの中で毎回newしないで、以下のように最初にnewしたインスタンスを使い回せば速くなる↓、たしかに」

# 同記事より
erb_engine = ERB.new(erb_example, 0, '-', '__result')
slim_engine = Slim::Template.new { slim_example }
haml_engine = Haml::Engine.new(haml_example)

Benchmark.bmbm(10) do |bcmk|
  bcmk.report("erb_test") { (1..2000).each { erb_engine.result binding } }
  bcmk.report("slim_test") { (1..2000).each{ __result = slim_engine.render(context) } }
  bcmk.report("haml_test") { (1..2000).each { __result = haml_engine.render(binding) } }
end

「ただ、CMSなどでキャッシュを一括生成するような場合なら上のように書けるでしょうけど、通常のWebレンダリングではこういう書き方はできないと思います」「あ、それもそうか」

「記事末尾にある現実的な例だと予想通りerbが一番速い↓」「そんなに大きくは違わなそうですね」

# 同記事より
$ hey -n 1200 http://localhost:3000/notes_erb/index

Summary:
  Total:        52.2586 secs
  Slowest:      19.2837 secs
  Fastest:      0.0389 secs
  Average:      0.6960 secs
  Requests/sec: 22.9627

$ hey -n 1200 http://localhost:3000/notes_haml/index
Summary:
  Total:        61.7637 secs
  Slowest:      18.5290 secs
  Fastest:      0.0442 secs
  Average:      0.8557 secs
  Requests/sec: 19.4289

$ hey -n 1200 http://localhost:3000/notes_slim/index

Summary:
  Total:        63.1625 secs
  Slowest:      19.9744 secs
  Fastest:      0.0874 secs
  Average:      0.7959 secs
  Requests/sec: 18.9986

🔗 Railsのジェネレータを改造する(Ruby Weeklyより)


つっつきボイス:「Railsのジェネレータを自分で作ったことあったのを思い出した」「ジェネレータを作るのって大変そうですけど」「既存のジェネレータのコードを見ながらやれば作れますよ: erbが二重になっている箇所があってそこは少し面倒ですが」

「自分はジェネレータを手作りまでしようとはあまり思わないかな」「ジェネレータに手を加えたらRailsをアップデートしたときに困ったりしませんか?」「既存のジェネレータを上書きするならともかく、独自のジェネレータを作るなら影響は小さいと思います」「なるほど」「単純なジェネレータなら一度自分で作ってみると勉強になりますよ🎓

🔗 RailsでキャッシュをクリアしたらSidekiqジョブが吹っ飛んた話(RubyFlowより)


つっつきボイス:「あ〜、redisのキャッシュをクリアしたらSidekiqジョブが飛んだのか、結構でかい事故」「これはキツそう」

mperham/sidekiq - GitHub

「redisはたしかデフォルトで16個のデータベースが使えて、キャッシュ用やジョブ用でデータベースを使い分けるものなんですが、それを同じデータベースに保存したらキャッシュをクリアした瞬間にジョブも消えるでしょうね」「う〜む」

参考: RedisのDB番号を増やす - tsunokawaのはてなダイアリー

redis/redis - GitHub

# 同記事より: Active Supportのコード
# Redisサーバーのすべてのキャッシュをクリアする。
# キャッシュが名前空間化されていれば共有サーバーでも安全。
#
# Failsafe: Raises errors.
def clear(options = nil)
  failsafe :clear do
    if namespace = merged_options(options)[:namespace]
      delete_matched "*", namespace: namespace
    else
      redis.with { |c| c.flushdb }
    end
  end
end

「元記事ではredisを名前空間化して修正したとあるけど↓、Redisのdatabase idを分ける方がいいんじゃないかな…」

# 同記事より
# config/application.rb
config.cache_store = :redis_cache_store, { url: ENV["REDIS_URL"], namespace: "rails" }

🔗 その他Rails

つっつきボイス:「お〜、Rubyのバックトレースをフィルタできるんですね」「add_silencerはどこかで見たことあったかも」

# api.rubyonrails.orgより
bc = ActiveSupport::BacktraceCleaner.new
bc.add_filter   { |line| line.gsub(Rails.root.to_s, '') } # strip the Rails.root prefix
bc.add_silencer { |line| /puma|rubygems/.match?(line) } # skip any lines from puma or rubygems
bc.clean(exception.backtrace) # perform the cleanup

前編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210330後編)Active Recordモデル属性暗号化が標準で入る可能性、Flipper Cloud、awesome_printほか

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

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

Rails公式ニュース

Ruby Weekly

RubyFlow

160928_1638_XvIP4h

週刊Railsウォッチ(20210407後編)エイプリルフールのRuby構文プロポーザル、AWSのVPC Reachability Analyzerほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

🔗Ruby

🔗 エイプリルフールのRuby構文2つ


つっつきボイス:「mameさんがエイプリルフールに提案したこのRuby文法は強烈」「issueのタグにjokeと書いてあるのを見るまですっかり本気にしちゃってました😅」「downward assignmentという名前まで付けるとは」「あまりのことについ笑っちゃいました😆

# 17768より
puts("Hello" + "World")  #=> HelloWorld
     ^^^^^^^x  ^^^^^^^y

p x  #=> "Hello"
p y  #=> "World"

^^^^^^^xで『この上がxだよ』、^^^^^^^yで『この上がyだよ』という二次元的な表記」「すごい構文」「mameさんよく思いつくな〜」「下向きのvvvvまで作ってますね↓」「irbではつらいのでは?というレスも付いてる(#17768)」

# 17768より
      vvvv line
while gets

「本当にこんなふうに書けたら面白いかも」「行を超える解析になると大変そう」「パーサーの前で前処理すればやれるかも?」


その後mameさんが自らissueをrejectしていました。後で気づきましたがパッチまで作ってあったんですね。


ugs.ruby-lang.orgより

以下はつっつき後に見つけたツイートです。


同じ日にmrknさんもジョーク構文をアップしていたことに後から気づきました↓。Julia言語のように「係数を掛け算記号なしで書ける」構文だそうです。

# #17769より
irb(main):001:0> x = 3
=> 3
irb(main):002:0> 2x
=> 6
irb(main):003:0> def pi = Math::PI
=> :pi
irb(main):004:0> 2pi
=> 6.283185307179586

🔗 fast_statistics: Ruby向け高速な統計ライブラリ


つっつきボイス:「統計的な処理をC++で高速化したライブラリですね」「さすがに速い」

# 同リポジトリより
require "fast_statistics"

FastStatistics::Array2D.new(data).descriptive_statistics
# Result: 
#
# [{:min=>0.1477,
#   :max=>0.6269,
#   :mean=>0.347575,
#   :median=>0.30785,
#   :q1=>0.214975,
#   :q3=>0.44045,
#   :standard_deviation=>0.18100761551658537},
#  {:min=>0.1055,
#   :max=>0.8,
#   :mean=>0.38217500000000004,
#   :median=>0.3116,
#   :q1=>0.1781,
#   :q3=>0.515675,
#   :standard_deviation=>0.26691825878909076},
#  ...,
#  {:min=>0.3918,
#   :max=>0.8546,
#   :mean=>0.639025,
#   :median=>0.6548499999999999,
#   :q1=>0.536025,
#   :q3=>0.75785,
# 同リポジトリより
Comparing calculated statistics with 10 values for 8 variables...
Test passed, results are equal to 6 decimal places!

Benchmarking with 100,000 values for 12 variables...
Warming up --------------------------------------
descriptive_statistics   1.000  i/100ms
           Custom ruby   1.000  i/100ms
                narray   1.000  i/100ms
ruby_native_statistics   1.000  i/100ms
        FastStatistics   3.000  i/100ms
Calculating -------------------------------------
descriptive_statistics   0.473  (± 0.0%) i/s -      3.000  in   6.354555s
           Custom ruby   2.518  (± 0.0%) i/s -     13.000  in   5.169084s
                narray   4.231  (± 0.0%) i/s -     22.000  in   5.210299s
ruby_native_statistics   5.962  (± 0.0%) i/s -     30.000  in   5.041869s
        FastStatistics   28.417  (±10.6%) i/s -    141.000  in   5.012229s

Comparison:
        FastStatistics:   28.4 i/s
ruby_native_statistics:    6.0 i/s - 4.77x  (± 0.00) slower
                narray:    4.2 i/s - 6.72x  (± 0.00) slower
           Custom ruby:    2.5 i/s - 11.29x  (± 0.00) slower
descriptive_statistics:    0.5 i/s - 60.09x  (± 0.00) slower

🔗 geminabox(Ruby Weeklyより)

geminabox/geminabox - GitHub


つっつきボイス:「rubygems.orgでやっているようなgemのホスティングを簡単に行えるようですね」「★が1400超えてる」「プロキシも簡単に設定できるらしい↓」

RUBYGEMS_PROXY=true rackup

「そういえばRubyGemsのgemコマンドにも似たような機能がありませんでしたっけ?」「使ったことはありませんが、そういえばあったような」

以前gem serverコマンドについて記事を書いたのを後で思い出しました↓。

gem serverでローカルgemサーバーを立ててみよう

「もしかするとこの間のminimagicの件↓でこうしたgemのセルフホスティングのニーズがにわかに高まったりするかも」「それありそうですね」

週刊Railsウォッチ(20210329前編)特集: Rails更新版の臨時リリースとmimemagic gemのGPL問題

🔗 その他Ruby

こちらは日本時間今夜18:00の開催です。もう始まっている頃ですね。

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

🔗 AWSのVPC Reachability Analyzerは便利


つっつきボイス:「VPC Reachability Analyzerは昨年末にリリースされているみたいですね」

参考: 新機能 – VPC Reachability Analyzer | Amazon Web Services ブログ

「へ〜、このツールは到達グループやセキュリティグループも可視化してくれるのがなかなかいいですね」「おぉ」「この図↓の上の端と下の端はインスタンスのアイコンなので、インスタンスからインスタンスへの接続の可視化もできるらしい」


aws.amazon.comの同記事より

「VPC接続でどこまで到達できているのかを確認するのは地味に面倒なんですが、こういうツールの存在を知っていれば便利そうですね: 今度使ってみよう👍」「いいこと知りました!」

追記(morimorihoge)

VPC Reachability Analyzerその後実際に業務で使ってみた感想です。

設定できる到達パスのsource / destination設定がAWSリソース(ENIインターフェースやEC2インスタンス、各種Gateway)であり、IPアドレスなどでの指定ができないためtracerouteなどのIPネットワーク系ツールのつもりで使おうとすると最初ややとっつきにくいところがあります。

ただ、AWSリソースベースであるが故にセキュリティグループなども参照して見てくれること、一度作った設定を再テストさせることが容易なことから、ある程度複雑な構成(複数VPCだったり、外部のAWSアカウントとVPC Peering / Transit Gateway接続する)の場合には有用なトラブルシューティングツールとして利用できそうです。知っておいて損はないツールかなと思いました。

🔗言語/ツール/OS/CPU

🔗 MacBookの物理キーボードが廃止?


つっつきボイス:「まさかこの記事もエイプリルフールじゃないかと心配になってきました」「日付は3/31だから大丈夫でしょう😆

「MacBookのタッチバーは触ったときの手応えがなくて使いにくかったけど、あれにフォースフィードバックがあれば随分違っていただろうと思うので、Appleの新しいキーボードがそこを解決したなら見込みがあるかもしれませんね」「それたしかに!」「一方トラックパッドの場合は、随分前に物理的な押し込みスイッチがなくなってハプティック(感圧タッチ)トラックパッドに変わったときは使いにくくなるのかなと思ったけど、実際はまったく問題なく使えましたね」「あれをハプティックって言うとは知りませんでした」

参考: 次期MacBook Pro、ついにTouch Bar廃止で物理ファンクションキー復活のうわさ - Engadget 日本版
参考: 感圧タッチトラックパッドの使い方 - Apple サポート

「記事にあるAppleの新しいタッチ式のキーボードも、実際に登場して触ってみたら案外印象が変わって流行ったりするかも」「早く触って確かめたいです」「物理キーボードがなくなれば故障の可能性も減りますよね」


そういえばiPhoneの「戻るボタン」も割と前から物理ボタンではなくなっていますね。たまたまiPhoneの電源を落としたときに戻るボタンの手応えが消滅していて、初めてそれに気づきました。

参考: ASCII.jp:iPhone 7のホームボタンは“ボタン”ではない

🔗 CPUの話題2つ


つっつきボイス:「新しいモバイル向けのRyzenは、アイドル時の消費電力がかなり抑えられていたりして徐々によくなってきてるみたいですね」「モバイルRyzenの今後に期待したくなってきました」「Intel CPUが今後大きく進化するのは難しそうかなと思っています」

「ARMも新しいアーキテクチャを発表」「今さらですけど、ARMが出しているのはアーキテクチャだけということでいいんでしょうか?」「現在のARMはアーキテクチャの仕様を設計してライセンス販売しているので、そうですね」「なるほど」「そのうちQEMUでArmv9が動くようになるかも」

参考: ARMアーキテクチャ - Wikipedia

🔗 その他

🔗 最近のセキュリティ関連

つっつきボイス:「kramdownというmarkdown用gemで見つかった脆弱性が2.3.1で修正されたそうです」「3日前(3/31)に出ているからかなり最近なのか」「markdownでリモートコード実行はなかなかヤバいですね」「kramdown自体はそこそこメジャーなGemなので、CMSなどを運用・開発しているプロジェクトでは念のため確認した方がよいと思います」

gettalong/kramdown - GitHub

追記(morimorihoge)

その後確認したところ、Redmineが採用しているMarkdown対応Gemはredcarpetだったので、別途プラグインとかを入れてなければ恐らく大丈夫かなと思います。

vmg/redcarpet - GitHub


「もうひとつはphp.netにバックドアが仕掛けられてgit.php.netが奪われた: これはキツい」「この件でPHPリポジトリがGitHubに引っ越したそうですね↓」「久しぶりに大きな事案かも」「今回の場合は早期に発見・対応されたのが幸いですね」「たしかに」「PHPのnightlyビルドを自動取得していたところは影響があったかもしれませんが、リリース版PHPに影響が出たという話は見かけていないので普通にPHPを使っているところは今のところ大丈夫そうかな」

php/php-src - GitHub

「開発者のアカウントがいったん乗っ取られたらどうしようもないので、2要素認証(2FA)や多要素認証(MFA)を義務付けたのはわかる」「正直とても面倒ですが、そうするよりしょうがないですよね」

参考: 多要素認証 - Wikipedia
参考: 二要素認証と二段階認証の違いを理解していますか? | サイバーセキュリティ情報局

🔗 パブリックIPアドレスをDNS経由で知る方法(StatusCode Weeklyより)


つっつきボイス:「この記事で紹介されている方法って珍しいんでしょうか?」「どれどれ👀

「以下↓を実行すると自分がいるネットワークのパブリックIPアドレスがわかるとある、本当かな?」(しばしやってみる)「本当に取れた!」「こっちも取れました」

# 同記事より
dig @ns1.google.com TXT o-o.myaddr.l.google.com +short

参考: dig コマンド|Linuxコマンド

o-o.myaddr.l.google.comって公に公開されているのかな?」「ググってみるとAWSのドキュメントにもこの方法が載ってた↓」「ホントだ」「他にもいくつか方法が載ってますね」「知る人ぞ知る機能という感じ」「使っても大丈夫そう」

参考: Route 53 の DNS リゾルバーの IP アドレスが ECS 拡張機能をサポートしているかどうかを確認する


後で、Googleのドキュメントにもo-o.myaddr.l.google.comについて記載があったのをやっと見つけました↓。

参考: Google Maps Platform ルート認証局(CA)の移行に関するよくある質問  |  Google Developers

🔗 EDNS

「AWSのドキュメントに出てくる”EDNS”という言葉をググったらこれが出てきた↓」「RFC7871で仕様が定義されているんですね」

参考: インターネット用語1分解説~EDNS Client Subnetとは~ - JPNIC
参考: RFC 7871 - Client Subnet in DNS Queries

EDNS Client Subnetとは、 DNSの拡張プロトコルであるEDNS01を用いて、 問い合わせ先のDNSサーバに対して問い合わせ元の情報を伝達する技術であり、 RFC78712で標準化されています。
www.nic.ad.jpより

「以下の記事↓にはEDNSはGoogle public DNSやCloudFrontやAkamaiが対応していると書かれているということは、どうやらCDNに関連する技術らしい」「知らなかった」「推測ですが、CDN業者がクライアントのDNSリクエストを最寄りのエッジに振り向けるのにEDNSのような技術が必要なのかもしれませんね」

参考: JANOG38 :: EDNS-client-subnetってどうよ? 改めRFC7871ってどうよ

増加するコンテンツトラフィックに対応するため送信側は複数個所から送信を行っている。適切な送信箇所を選択するための技術としてEDNS-client-subnetが提案され、Google public DNS(8.8.8.8)やAmazon CloudFront, Akamaiが対応している。
www.janog.gr.jpより

参考: コンテンツデリバリネットワーク - Wikipedia


「ついでにcurlコマンドでIPを取る方法も見つけた↓」「こんなのまであるんですね」「ifconfig.ioやifconfig.me、myip.opendns.comとかいろんなのが使えるらしい」「lvh.meのような感じで誰かが考えそうなサービスかも」

参考: 【Linux】curlでグローバルIPを確認 (まとめ) | Startialab Dev Blog

# dev.startialab.blogより
curl ifconfig.io

lvh.me ドメインを使って、サブドメイン形式の WordPress マルチサイトの Docker 開発環境構築をしよう


後編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210406前編)GitHubが修正したRailsセッションハンドリングの競合、erb/haml/slimの速度比較ほか

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

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

Ruby Weekly

StatusCode Weekly

statuscode_weekly_banner

Ruby 3でprivate/public/protectedとattr_*アクセサを1行で書けるようになった

$
0
0

WEB+DB PRESS Vol.121の「特集 Ruby 3」を読んでいて、「その他の追加機能」に以下がありました。

  • private attr_reader :fooのようにシンボルを書けるようになった
  • privateがシンボルの配列を受け取れるようになった

1つ目はともかく2つ目がよくわからなかったので調べてみました。

RubyのModuleにある可視性変更用のprivate/public/protectedメソッドを本記事では「アクセス制御メソッド」と総称します。

また、RubyのModuleにあるattr_accessor/attr_reader/attr_writerを本記事では「アクセサメソッド」と総称します。アクセサメソッドで定義されるメソッドについて、便宜上「ゲッター」「セッター」というJavaの用語も使っています。

Ruby 3.0で改善された点

結論から言うと、Ruby 3.0からは以下のように書けるようになりました。

class Foo
  private attr_accessor :foo, :bar
# ここから下はpublic
end
  • たとえばprivateなアクセサメソッドが欲しい場合なら、private attr_accessorにシンボルまたは文字列を渡す形で1行に書けるようになった
  • 引数ありのアクセサメソッドはその行にだけ効くので、その後publicで解除しなくてもよい
  • それによってprivate attr_accessorをクラスの冒頭に簡潔に書けるようになった

さらにありがたい改修も含まれています。

  • attr_accessor/attr_reader/attr_writerにシンボル(文字列でもよい)を渡すと、それぞれのアクセサメソッドに応じて適切なゲッター/セッターメソッドをシンボルの配列で返すようになった

具体的にはこうです。

# Ruby 3.0.0
class Foo
  @@accessor = attr_accessor :foo, :bar
  @@reader   = attr_reader   :baz, :bam
  @@writer   = attr_writer   :hoge, :huga
  def accessor
    p @@accessor
  end
  def reader
    p @@reader
  end
  def writer
    p @@writer
  end
end


Foo.new.accessor  #=> [:foo, :foo=, :bar, :bar=]

Foo.new.reader    #=> [:baz, :bam]

Foo.new.writer    #=>  [:hoge=, :huga=]

つまり、attr_accessorの場合はゲッターとセッター、attr_readerの場合はゲッターだけ、attr_writerの場合はセッターだけを、シンボルの配列で返すようになりました。

従来は、アクセサメソッドを定義した後にアクセス制御をかけようとすると、以下のように:foo:foo=を毎回自分で指定しなければならず、ついつい:foo=のセッターを書き忘れてしまったりしました。

# Ruby 2.7.2まで
class Foo
  attr_accessor :foo, :bar
  private :foo, :foo=, :bar, :bar=
end

なお、上のような書き方は、アクセサメソッドをprivateで保護しながら、クラス内のインスタンス変数にアクセサメソッド経由でアクセスする方法です。クラス内で@を書かずにインスタンス変数にアクセスできるので、この書き方を好む人もいるそうです。

参考: Rubyのインスタンス変数の直接参照について - 雑草SEの備忘録

Ruby 3.0では、それと同じことを以下のように1行で簡潔に書けるようになったわけです。地味にありがたい機能です。

class Foo
  private attr_accessor :foo, :bar
end

Ruby 2.7.2まではどうだったか

クラス内のアクセス制御メソッドは、引数を与えればその引数にだけアクセス制御が効きますが、引数なしだと以後の行すべてに効きます。

以下のように、1行の中でprivateなどのアクセス制御メソッドに続けてattr_*などのアクセサメソッドを書くとエラーになります。書けそうなのに書けなかったんですね。

class Foo
  private attr_accessor :foo, :bar # TypeError (nil is not a symbol nor a string)
    def debug
      @aa
   end
end

これを回避するには、たとえばprivateなどのアクセス制御メソッドと、attr_*を別の行に書く必要がありました。

アクセス制御メソッドはクラスの冒頭に書きたいところですが、以下のように冒頭でprivateを使った場合、以後の行をprivateのままにしたくなければその後publicを呼んで、以後のアクセス制御を解除する必要がありました。

class Foo
  private
  attr_accessor :foo, :bar
  public

  def debug
    @aa
   end
end

publicを呼びたくない場合はprivateとアクセス制御メソッドをクラスの末尾に書くことになりますが、そうするとattr_*をクラスの冒頭に置けません。

class Foo
  def debug
    @aa
  end

  private
  attr_accessor :foo, :bar
end

いずれにしろ1行では書けませんでした。

アクセサメソッドはnilを返していた

attr_accessorattr_readerattr_writerは従来nilを返していました。publicprivateなどと1行内で組み合わせる利用法は想定されていなかったのだろうと想像しました。

# Ruby 2.7.2
class Foo
  @@accessor = attr_accessor :foo, :bar
  @@reader   = attr_reader   :baz, :bam
  @@writer   = attr_writer   :hoge, :huga
  def accessor
    p @@accessor
  end
  def reader
    p @@reader
  end
  def writer
    p @@writer
  end
end


Foo.new.accessor  #=> nil

Foo.new.reader     #=> nil

Foo.new.writer      #=> nil

なお、Rubyにはattrという短いアクセサメソッドも一応あり、Ruby 1.9以降はattr_readerと同等ですが、attrはRuboCopで怒られます。

参考: 5-11【統一】attrは原則使わない: Rubyスタイルガイドを読む: クラスとモジュール(2)クラス設計・アクセサ・ダックタイピングなど

アクセス制御メソッドはシンボルを配列で受け取れなかった

Ruby 2.7.2までは、private/public/protectedには文字列かシンボルしか渡せませんでした。以下のように配列を渡すとエラーになりました。

# Ruby 2.7.2
class Foo
  def foo
    "foo"
  end
  def bar
    "bar"
  end
  private [:foo, :bar] # TypeError ([:foo, :bar] is not a symbol nor a string)
end

その他

#17314のコメントを見ると、シンボルの配列を受け取れるようにする改修は元々public/protected/privateを対象としていたのが、結果としてprivate_class_methodpublic_class_methodとトップレベルのprivateprivateでも同じことができるようになっていたことがマージ後にわかったそうです。

# #17314#17より
class Foo
  def self.foo; end
  def self.bar; end
  private_class_method [:foo, :bar] # No error
end
# #17314#17より
def foo = nil
def bar = nil

private [:foo, :bar]
public [:foo, :bar]

関連記事

Ruby: 文字列リテラル同士はスペース文字で結合される

Ruby: injectとeach_with_objectをうまく使い分ける(翻訳)

$
0
0

概要

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


  • 2018/07/24: 初版公開
  • 2021/04/01: 更新

Ruby: injectとeach_with_objectをうまく使い分ける(翻訳)

最近私がDevMemo.ioに追加した練習問題の中に、Enumerable#inject(エイリアスのreduceも使えます)やEnumberable#each_with_objectの練習問題がいくつかあり、以来どちらをどんなときに用いるべきかという簡単なガイドを書いてみたいと思っていました。

inject

Enumerable\#inject (Ruby 3.0.0 リファレンスマニュアル)

  • ミュータブル(変更可能)なオブジェクトやコレクションを操作して新しい値を返す場合はinjectの方がよい
  • 変更時に新しい値を返すイミュータブルなプリミティブ(訳注: Integerやシンボルなど)やValue Objectにも向いている

each_with_object

Enumerable#each_with_object (Ruby 3.0.0 リファレンスマニュアル)

  • オブジェクトやコンテナへのミュータブルな操作にはeach_with_objectの方がよい
    • 特にHashArrayが対象の場合
  • 作業の開始地点となる新しいオブジェクトを提供してそこでビルドする場合にも向いている
    • 既存のオブジェクトを変更したい場合にはさほど便利ではない

例1

いくつかコード例を見てみることにしましょう。オブジェクトのコレクションがひとつあり、それらを用いて新しいHashをひとつ組み立て、何らかの対応付け(mapping)操作を行いたいとしましょう。

  • 新しいオブジェクト(lower_to_upperというHash)をひとつ組み立てる
  • {}という新しい開始地点がある

each_with_objectはこのような場合に大変便利です。

lower = 'a'..'z'
lower_to_upper = lower.each_with_object({}) do |char, hash|
  hash[char] = char.upcase
end

一方、injectはそこまで便利ではありません。

lower = 'a'..'z'
lower_to_upper = lower.inject({}) do |hash, char|
  hash[char] = char.upcase
  hash    # これは省略できない
end

injectの場合は、直後のブロック呼び出しに渡すメモ化された値が直前のブロック呼び出しから返される必要があるからです(hashの初期値は{})。つまり、同じオブジェクトに対して操作を繰り返す場合であっても、渡すブロックの最後の行で必ずオブジェクトを返してやる必要があります。

一方each_with_objectのブロック呼び出しでは、メソッドの第1引数として最初に渡された同じ初期オブジェクトが常に使われます。

例2

しかし、改変したい既存のオブジェクトが既にあるとしましょう。この場合はeach_with_objectよりも単にeachで回す方が好ましいことが多いのですが、改変されたオブジェクトを返す必要があるならeach_with_objectの方がほんのわずか短くて済みます。

以下の3つの例はいずれも同じ結果を生成します。

mapping = {'ż' => 'Ż', 'ó' => 'Ó'}
lower = 'a'..'z'
lower.each do |char|
  mapping[char] = char.upcase
end
return mapping # 必要なら
mapping = {'ż' => 'Ż', 'ó' => 'Ó'}
lower = 'a'..'z'
lower.each_with_object(mapping) do |char, hash|
  hash[char] = char.upcase
end
mapping = {'ż' => 'Ż', 'ó' => 'Ó'}
lower = 'a'..'z'
lower.each_with_object(mapping) do |char|
  mapping[char] = char.upcase
end

既存のコレクションを改変するのであれば、eachが好ましいでしょう。改変されたコレクションは返す必要がないことが多いためです。つまるところ、引数としてそのオブジェクトを誰がどこから渡そうと、このオブジェクトへの参照はコード内のその場所に残ります。

例3

今度はオブジェクトの初期ステートを改変せず、常に新しいオブジェクトを生成するとしましょう。ここでの操作は常に新しいオブジェクトを返します。

最もシンプルな例は、数値の+演算子でしょう。

a = 1
b = 2

a.frozen?
# => true
b.frozen?
# => true

c = a + b
# => 3

変数aから参照される(訳注: イミュータブルな)Integerオブジェクトは、3に改変しようがありません。ここでできるのは、変数aなりbなりc別のオブジェクトを代入することだけです。

今の例は端的でしたが、Dateの場合はそこまで端的ではありません。

require 'date'
d = Date.new(2017, 10, 10)

別の日付が欲しい場合、既存のDateインスタンスは改変できません。

d.day=12
# => NoMethodError: undefined method `day=' for #<Date:

e = Date.new(2017, 10, 12)

今のは軽いジャブでしたが、injectではこれをどのように行わなければならないでしょうか。初期値がイミュータブルなオブジェクトならinjectの出番です。

(5..10).inject(:+)
(5..10).inject(0, :+)
(5..10).inject{|sum, n| sum + n }

(5..10).inject(1, :*)

以下も同様です。

starting_date = Date.new(2017,10,1)
result = [1, 10].inject(starting_date) do |date, delay|
  date + delay
end
# => Date.new(2017,10,12)

以下も同様です。

# gem install money
require 'money'
[
  Money.new(100_00, "USD"),
  Money.new( 10_00, "USD"),
  Money.new(  1_00, "USD"),
].inject(:+)
# => #<Money fractional:11100 currency:USD>

訳注

上を実行するにはgem install moneyなどでmoney gemをインストールする必要があります。

例4

今度も毎回新しいオブジェクトを作成しますが、その理由が初期ステートを改変できないからではなく、特定のメソッドが新しいオブジェクトを返すからだとしましょう。

result = [
 {1 => 2},
 {2 => 3},
 {3 => 4},
 {1 => 5},
].inject(:merge)
# => {1=>5, 2=>3, 3=>4}

Hash#mergeは2つのハッシュをマージして新しいハッシュを1つ返します。だからこそ、ここではinjectを使う方が簡単です。

each_with_objectの場合と比べてみましょう。

[
 {1 => 2},
 {2 => 3},
 {3 => 4},
 {1 => 5},
].each_with_object({}) {|element, hash| hash.merge!(element) }
# => {1=>5, 2=>3, 3=>4}

ぼやき

ブロックに渡される引数の順序がinjecteach_with_objectで逆なのってちょっとイラッときませんか?

lower_to_upper = lower.each_with_object({}) do |char, hash|
  hash[char] = char.upcase
end

lower_to_upper = lower.inject({}) do |hash, char|
  hash[char] = char.upcase
  hash
end

参考情報

DevMemo.ioでは、間もなくRubyのEnumerable学習コースを公開しますので、ぜひお試しください。気に入っていただけましたらご登録をお願いします。

関連記事

Ruby: eachよりもmapなどのコレクションを積極的に使おう(社内勉強会)

Ruby: ループには一時変数ではなくEnumerableを使おう(翻訳)

週刊Railsウォッチ(20210412前編)Active Record属性暗号化機能がRails 7にマージ、RailsNew.ioでrails newオプションを生成ほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

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

今週は以下のコミットリストのChangelogを中心に見繕いました。

🔗 Cache-Controlヘッダーにprivate, no-storeを指定できるようになった


つっつきボイス:「Cache-Controlヘッダーは前にも改修がありましたね」「あ、そういえばありました(ウォッチ20201012)」「以前はno-storeを指定したら他の設定を含めないように変えられてたけど、今回の修正でprivate, no-storeを指定できるようになったということは、この組み合わせに意味があるということなんでしょうね」

#39461ではCache-Controlヘッダーのno-storeディレクティブで他の指定を含まないようにした(つまりCache-Controlヘッダーに’private, no-store’を指定しても’no-store’だけになる)。privateは不要であることが多いが、常に不要とは限らない。
たとえば、Fastlyドキュメントには「現在はno-storeno-cacheディレクティブを尊重しない」かつ「FastlyとWebブラウザの両方でキャッシュを行わないようにする必要がある場合は、privateディレクティブにmax-age=0no-storeを組み合わせることを推奨する」とある。
#39461で変更されたディレクティブを減らす振る舞いはオーバーライドできないので、FastlyユーザーがRailsをアップグレードできなくなっている。
このプルリクは、privateを指定した場合はprivate, no-storeヘッダーを設定できるように変更する。no-cacheの場合にpublicを指定できるのと同様だが、デフォルトではない。
修正されるissue: #40798

「FastlyのドキュメントにCache-Control: private, no-storeには意味があると書かれてた↓」「使いたい組み合わせを使えるようにしたという修正ですね」

参考: Configuring caching | Fastly Help Guides
参考: Cache-Control - HTTP | MDN

「変更点を見ると、privateno-storeを同時に指定できるように変わった↓」「今までは両方指定してもno-storeだけになってたのか」

# actionpack/lib/action_dispatch/http/cache.rb#L198
+         options = []
+
          if control[:no_store]
-           self._cache_control = NO_STORE
+           options << PRIVATE if control[:private]
+           options << NO_STORE
          elsif control[:no_cache]
-           options = []
            options << PUBLIC if control[:public]
            options << NO_CACHE
            options.concat(control[:extras]) if control[:extras]

-           self._cache_control = options.join(", ")
          else
            extras = control[:extras]
            max_age = control[:max_age]
            stale_while_revalidate = control[:stale_while_revalidate]
            stale_if_error = control[:stale_if_error]

-           options = []
            options << "max-age=#{max_age.to_i}" if max_age
            options << (control[:public] ? PUBLIC : PRIVATE)
            options << MUST_REVALIDATE if control[:must_revalidate]
            options << "stale-while-revalidate=#{stale_while_revalidate.to_i}" if stale_while_revalidate
            options << "stale-if-error=#{stale_if_error.to_i}" if stale_if_error
            options.concat(extras) if extras

-           self._cache_control = options.join(", ")
          end

🔗 ActiveSupport::TimeWithZone.nameが非推奨化


つっつきボイス:「今までのnameメソッドは”Time”という文字列をそのまま返してたの?↓」「えぇ?😳」「プルリクにも何でこういう実装になったのかわからないって書かれてますね」「文字列リテラルの”Time”を返す実装、マジで謎」

# activesupport/lib/active_support/time_with_zone.rb#L43
    def self.name
+     ActiveSupport::Deprecation.warn(<<~EOM)
+       ActiveSupport::TimeWithZone.name has been deprecated and
+       from Rails 7.1 will use the default Ruby implementation.
+     EOM
+
      "Time"
    end

「機能をdeprecatedにするときはこういうふうに書くのか↑」「この書き方は定番ですね」

c00f2d2でnameメソッドが実際のクラス名”ActiveSupport::TimeWithZone”ではなく”Timeを”返すようオーバーライドされていた。このようにした理由が不明だし、nameが実際のクラス名を返すことを期待するる開発者が混乱する可能性がある。この変更が追加された理由がわからないので、ひとまず非推奨化して開発者からのフィードバックを待ち、issueが上がったら適宜修正することにする。
同PRより大意

ActiveSupport::TimeWithZone.nameが非推奨化されてRails 7.1でデフォルトの実装に戻される予定なのか」「次のRailsメジャーバージョンは7だから、リリースごとに非推奨化->削除が最短で行われれば7.1で削除されるでしょうね」


「ところで、Railsのメジャーバージョンが7に上がる理由のひとつに、おそらくRailsで必要な最小限のRubyバージョンが上がるからというのもあるんじゃないかな」「Rails 7だとRuby 3.0が最小バージョンになるんでしょうか?」「Rails 7で最小バージョンをいきなり3.0にしたら追従できない機能やgemが続出すると思うので、そこまではやらないでしょうね」「たしかに」

後でedgeガイドを見ると以下のように書かれていました↓。と思ったら以前のウォッチでも言及していました(ウォッチ20210208)。

  • Rails 7 requires Ruby 2.7.0 or newer.
  • Rails 6 requires Ruby 2.5.0 or newer.
  • Rails 5 requires Ruby 2.2.2 or newer.

以下はつっつき後に教わったツイートです。

🔗 Active Record属性暗号化がついにマージされた


つっつきボイス:「少し前のウォッチ↓ではマージ前だったActive Record属性暗号化がついにマージされました」「お、きたか🎉」「これは嬉しい😋」「思ったよりスムーズにマージされましたね」

週刊Railsウォッチ(20210330後編)Active Recordモデル属性暗号化が標準で入る可能性、Flipper Cloud、awesome_printほか

「Changelogがコンパクトにまとまっててありがたい↓」「HEYでセキュリティ監査を経て実績を積んだ属性暗号化機能が入るのはいいですね👍

属性暗号化のサポート
暗号化された属性はモデルレベルで宣言される。これらは背後に同じ名前のカラムを備えた正式なActive Record属性である。「データベース保存前の属性暗号化」や「値読み出し前の解読」はシステムが透過的に行う。

class Person < ApplicationRecord
  encrypts :name
  encrypts :email_address, deterministic: true
end

詳しくは以下のガイドを参照。
Jorge Manrubia
Changelogより大意

「属性暗号化のガイドもまるまる追加されたんですね」「edgeガイドには実用的なコード例も丁寧に書かれていますし、このまま使えそうなくらい充実してる👍」「key_providerの指定や暗号化キーのローテーションもちゃんとサポートしてる↓」「お〜優秀!」「キーのローテーションは後付けでやろうとするとつらいんですよ…」「Rails 7の目玉機能のひとつですね」

# edgeガイドより
config.active_record.encryption.previous = [ { key_provider: MyOldKeyProvider.new } ]
# edgeガイドより
active_record
  encryption:
    master_key:
        - bc17e7b413fd4720716a7633027f8cc4 # Active, encrypts new content
        - a1cc4d7b9f420e40a337b9e68c5ecec6 # Previous keys can still decrypt existing content
    key_derivation_salt: a3226b97b3b2f8372d1fc6d497a0c0d3

参考: 鍵のローテーション  |  Cloud KMS ドキュメント  |  Google Cloud


「ところでRails 7、いつ出るのかな」「時が来たらとしか言いようがない😆」「マイルストーン↓を見るとまだ10件しかプルリクがないので、しばらくは出なさそうですね」

「お、以前話題になったRails Conductorが7に持ち越されてる↓」「そういえばRails Conductorってありましたね(ウォッチ20190311)」

🔗 ActiveSupport::Cachewritefetchexpires_at:で絶対時刻のTTLを設定できるようになった

ActiveSupport::Cachewritefetchに、キャッシュエントリのTTLを絶対時刻で設定するexpires_at:引数を追加。

Rails.cache.write(key, value, expires_at: Time.now.at_end_of_hour)

Jean Boussier
同Changelogより大意


つっつきボイス:「キャッシュを絶対時刻で期限切れにするexpires_at:オプションが追加された、なるほど」「何月何日の何時というふうに指定できるようになったんですね」「今までだと『あと60分』みたいな相対指定しかできなかったので、絶対時間にするなら自分で計算して渡すことになる」「と思ったら改修も絶対時間を計算してますね↓」「あると嬉しい機能👍

# activesupport/lib/active_support/cache.rb#L805
-     # +:compress+, +:compress_threshold+, +:version+ and +:expires_in+.
-     def initialize(value, compress: true, compress_threshold: DEFAULT_COMPRESS_LIMIT, version: nil, expires_in: nil, **)
+     # +:compress+, +:compress_threshold+, +:version+, +:expires_at+ and +:expires_in+.
+     def initialize(value, compress: true, compress_threshold: DEFAULT_COMPRESS_LIMIT, version: nil, expires_in: nil, expires_at: nil, **)
        @value      = value
        @version    = version
-       @created_at = Time.now.to_f
-       @expires_in = expires_in && expires_in.to_f
+       @created_at = 0.0
+       @expires_in = expires_at&.to_f || expires_in && (expires_in.to_f + Time.now.to_f)

        compress!(compress_threshold) if compress
      end

🔗 update_alldelete_alldestroy_allの後で@cache_keysをクリアするようになった

calculatedなキャッシュキーはリレーションにキャッシュされるが、リレーションでコレクションに改変が発生した場合に再計算する方法がない。
@cache_keysは少なくともupdate_alldelete_alldestroy_allではクリアすべき。
関連issue: #41784
同PRより大意


つっつきボイス:「以下みたいに1リクエストの処理中でrelationを再利用する場合に、最後のscoped.destroy_allで消されたときのkeyがキャッシュに残ってしまう問題に対応したようですね」「なるほど」

scoped = Hoge.where(name: 'piyo')
scoped.destroy_all
pp scoped.map(&:name)

「今までこれを回避するために何かトリッキーなことしてた覚えがありますけど、それをやらなくてよくなった😂」「少なくとも*_all系のメソッドでは@cache_keysがクリアされるようになったので、その種の対応が少し楽になりそう👍

「1レコードを指すActive Recordオブジェクトは何度も参照されることが多いので速度の関係からキャッシュヒットさせたいけど、複数レコードを対象とするRelationオブジェクトの更新系メソッドに対して、更新前に参照していたキャッシュを参照させたいというようなニーズはまずあり得ないだろうということですね、わかる」

🔗Rails

🔗 Tailwind CSS JITでCSSコンパイルを20倍高速化


つっつきボイス:「TechRachoのRails系翻訳記事でお世話になっているEvil Martiansの記事です」「Evil Martiansさんは最近Tailwind CSSがお気に入りみたい」

tailwindlabs/tailwindcss - GitHub

「Tailwind CSSって何だっけと思ったらBootstrapみたいなCSSフレームワークなんですね」「はい」「最近CSSフレームワークを選ぶ機会があったんですけど、これも検討しとけばよかったかな…」「CSSフレームワークみたいなものは作業者が慣れてないとよさを発揮しにくい面もあるので、Bootstrapみたいに長く使われているものが今も幅広く使われていますね」「それもそうか」

「ちなみにCSSフレームワークはBootstrap 5にしちゃいました」「お、5がもう出たんですか?」「実は5-beta3でした(後で正式版にする前提で)」「公式サイト↓のバージョンがv5.0.0-beta1と表示されているので正式版まであともう少しですね」

「そういえばBootstrap 5はIEサポートやめたってどこかに書かれてました」「IEは今やTwitterすら満足に表示できないのでサポート打ち切られても仕方ないでしょうね」「あ、そういえばそんな話もあったかも」「今どきのIE対応は特別に指定がない限り基本的にはサポートしない流れでいいと思います」

Internet Explorer はサポート外です。 Internet Explor のサポートが必要な場合は Bootstrap v4 を使ってください。
ブラウザとOS · Bootstrap v5.0より

🔗 RailsライブラリなしでRubyのWebアプリを作る(Ruby Weeklyより)


つっつきボイス:「Railsライブラリを使わずにRubyでWebアプリを作るという企画か」「おぉ、TCPServerを立ち上げるところから始めている↓」「何とプリミティブな😳」「ここはソケットプログラミングで最初にやるところですね」「Rackサーバーもない状態から始めるのが徹底してる」「without Rails libraryどころか、Ruby付属のgem以外使わないぞという勢いですね」

# 同記事より
#Use Rubys Socket Library
require 'socket'

server = TCPServer.new(1337)

参考: class Socket (Ruby 3.0.0 リファレンスマニュアル)

「なかなか長い記事ですね」「真ん中過ぎぐらいでやっとRackが登場↓」「文明が来た」

# 同記事より
require 'rack/handler/puma'

class HelloWorld
  def call(environment)
    status  = 200
    headers = { 'Content-Type' => 'text/plain' }
    body    = ['Hello', ' world!']

    [status, headers, body]
  end
end

Rack::Handler::Puma.run(HelloWorld.new)

rack/rack - GitHub

「最終的にこういう感じになったんですね↓」

「Webアプリをスクラッチで作ったことがない人やソケットプログラミングをやったことのない人にとっては、かなりいい勉強になりそう👍」「たしかに!」「Railsのようなフレームワークで覆い隠されている各種レイヤをこうやって実際に触ってみると何かしら学びになると思います」

参考: ソケットプログラミング

🔗 Railsでビューのテストを書く理由(Ruby Weeklyより)

# 同記事より
# spec/views/books/index.html.erb_spec.rb

require 'rails_helper'

RSpec.describe "books/index", type: :view do
  before(:each) do
    assign(:books, [
      Book.create!(
        title: "Title",
        description: "MyText",
        download_url: "Download Url",
        status: "Status"
      ),
      Book.create!(
        title: "Title",
        description: "MyText",
        download_url: "Download Url",
        status: "Status"
      )
    ])
  end

  it "renders a list of books" do
    render
    assert_select "tr>td", text: "Title".to_s, count: 2
    assert_select "tr>td", text: "MyText".to_s, count: 2
    assert_select "tr>td", text: "Download Url".to_s, count: 2
    assert_select "tr>td", text: "Status".to_s, count: 2
  end
end

つっつきボイス:「ビューのテストを書く理由を自分なりに考えてみた記事のようです」

「たしかにビューのテストは実際には書かないことも多いんですが、view specが必要な場合なら書くのはありだと思います」「どんな場合でしょう?」「たとえば外部からクローリングされることが前提のビューだと、ユーザーにとっての見た目よりもHTMLのDOM構造自体が仕様になるので、そういう場合はview specでDOM構造をテストしたいですよね」「あ、そうか」「あるいはSEO要件で特定のHTML構造が必要な場合とか」「なるほど」「必要な理由はいろいろ考えられると思うので、頭の体操的に考えてみるのも楽しいですよ」

🔗 Railsのアレを生成するWebサイト2つ


つっつきボイス:「ruby-jp Slackで見かけました」「1つ目のrails.helpというサイトは、Railsのモデルやマイグレーションのジェネレータ文字列をGUIで生成できるんですね↓」「こういうの地味に嬉しいかも」「いつもテキストにメモしてからやってました」「textフィールドにlimitを指定できるとは知らなかった」


rails.helpより

「主にRailsを学び始めたばかりの人が、ジェネレータのタイプミスで失敗してやる気をくじかれるのを防ぐのに便利そう」「たしかに」「マイグレーションでどのフィールドタイプにどんなオプションを指定できるかをGUIで確認できるのも教育向けによさそう: decimalを指定したときはprecisionとscaleも必要になる、とか」「なるほど」「なお、自分は空のマイグレーションファイルを作ってそこに書く派です」


「2つ目のrailsnew.ioはrails newコマンドのオプションを生成するジェネレータサイトです」「omakaseオプションなんてのがあるのね」


railsnew.ioより

「へ〜、--skip-gemfileなんてオプションがあるとは」「Gemfileなしってどんなときに使うんでしょうね」「システムインストールされるgemを使う前提のときとかかな?」「minitestをスキップできない、と思ったらminimalにすると全部スキップされました」

「railsnew.ioは慣れた人にとっても便利そう👍」「rails newは使う頻度が少ないので、turbolinksをオフにするのとか忘れがちですよね」「あ、それ自分もこないだ忘れてました😆

🔗 devise-two-factor: Deviseで二要素認証(Ruby Weeklyより)

tinfoil/devise-two-factor - GitHub


つっつきボイス:「Deviseで二要素認証が使えるgemだそうです」「ちょっと嬉しいかも: 今度使ってみようかな」

参考: 多要素認証 - Wikipedia

「だいぶ昔ですけど、sshに二要素認証を付けてみたのを思い出しました」「sshでですか?」「すぐ飽きてやめちゃいましたけど😆

参考: sshの二段階認証(二要素認証)設定の方法3個 | 俺的備忘録 〜なんかいろいろ〜


前編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210407後編)エイプリルフールのRuby構文プロポーザル、AWSのVPC Reachability Analyzerほか

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

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

Rails公式ニュース

Ruby Weekly

週刊Railsウォッチ(20210413後編)RubyMineのRBSサポートとCode With Me、GitHub ActionとDockerレイヤキャッシュほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

🔗Ruby

🔗 RubyMineがRBSに対応


つっつきボイス:「お〜、Ruby 3のRBSがRubyMineでサポートされた🎉」「MDN ドキュメントのバンドルやパーシャルのプレビューなど他にもいろいろ機能が増えてますね: パーシャルのプレビューはどのぐらいうまくできるかわかりませんが」

ruby/rbs - GitHub

「Dockerサポートもいろいろ改善されたようですね↓: 今すべての作業をDockerベースにしているので即役に立ちそう」

参考: Docker RubyMine 2021.1 Beta 4: Code With Me and Improvements for Docker | The RubyMine Blog


「ちなみに最近のDocker Desktop for Windowsは随分よくなってきていて、DockerをRubyMineと組み合わせてHyper-V抜きで快適に使えるようになったのがホントありがたい🙏」「以前のDocker Desktop for Windowsからそんなに変わったんですか」「それまではHyper-VにインストールしたManjaro LinuxにRDP接続してGUIを使っていましたが、WindowsネイティブのGUIを使うのに比べて操作遅延が数フレームあったので、それがなくなって快適に開発できるようになりましたね👍

参考: Windows に Docker Desktop をインストール — Docker-docs-ja 19.03 ドキュメント

🔗 JetBrains IDEの「Code With Me」機能は優秀

「お〜、Code With Me機能もRubyMineで正式にリリースされたのか、これも嬉しい🎉」「これは?」「自分のIDE環境の操作をリモートの相手と共有して同時編集もできる、ペアプロなどで便利な機能です」「なるほど、VSCodeのLive Shareみたいですね」

参考: Code With Me: The Ultimate Collaborative Development Service by JetBrains

「Code With Meは、単なる画面共有やブラウザ共有と違って参加者全員がカーソルを動かせますし、JetBrains IDEのコード補完などの機能も参加者全員が使えます」「スゴい」「しかもホスト役の人がRubyMineでCode With Meで共有すれば、他の参加者はRubyMineを持っていなくてもプレーヤー的なクライアントソフトをダウンロードするだけで使えます」「ますますスゴい」


「今ちょっと自分のRubyMineをアップデートしてCode With Meを動かしたので入ってみてください(invitation linkを共有): さて誰が最初にこのRubyMineのscratch bufferに書き込むかな?」(しばし一同で動かす)

「シェルが開いてクライアントのダウンロードに30秒ほどかかりましたが、今入れました!」「こちらも成功です」「入れました」「動いた動いた、コード補完も使える」「チーム系ツール以外はすべて使えますよ」「音声や動画も出せるんですね」「ゲストをfull syncモードにすると画面切り替えなども許可できます」(しばし一同で動かす)

「というように、Code With MeはIDEの機能ごと共有できるのが、単なるブラウザ画面共有などと違う点です」「いろいろスゴい」「IDE内ターミナルの利用も個別に許可できますし、ホストが”Force all to follow you”を実行すればゲストを黙らせて見るだけに切り替えることもできます」「そんな機能まで😆」「レスポンスもローカルとほとんど変わらないぐらいスムーズ👍」「ペアプロもはかどりそう」「ちなみにCode With Meは一定時間何も操作しないとオフになります」

ペアプロを極めて最強の開発チームをつくる(1/4)ペアの組み方(翻訳)

「RubyMineにCode With Meが入ったのはかなり大きいと思います」「たとえばruby-jp Slackのような場で質問するときにも、その人の環境でCode With Meが使えたらinvitationリンクを投げるだけで環境を共有できるので、サポートする側にも質問する側にも便利かなと思いました」「たしかに」「質問側は現象や環境を正確に記述するのが大変ですし、回答側はそれを再現するのが大変なんですが、そういう時間を大幅に節約できそう」

🔗 JetBrains IDEを使うときのコツ

「何だかRubyMineを買いたい気持ちがにわかに高まってきました」「JetBrains IDEはサムライズムさん経由で日本円で買うと本家より安いですよ↓💰」「まずはダウンロードしてみます〜」「ちなみに自分はだいぶ前からIntelliJ IDEA Ultimateを年払いで使ってますけど、JetBrainsのIDEは長く使うほど年払い額がディスカウントされるのも嬉しい👍

参考: 株式会社サムライズム - 開発者向け生産性向上ツール、サービス

「とりあえずRubyMineダウンロードしました」「JetBrains IDEでおすすめの設定は、とりあえずVMに割り当てるメモリを増やすことですね↓」「やっておきま〜す」

参考: 起動オプション .vmoptions ファイルの場所 – 株式会社サムライズム

「デフォルトのキー操作をカスタマイズするとググラビリティが下がるので、可能ならJetBrains標準のキー操作に慣れておく方が良いと思います」「了解です!」「あと、機能名を正式な英語名で覚えておくようにしておくとShiftキーを2回押せばすべての機能を名前で呼び出せます(Search Everywhere): ショートカットキーやメニュー位置がうろ覚えでも使えるので便利👍


以下はつっつき後に見つけたツイートです。

🔗 Rubyist: iOSで実行できるRuby環境


つっつきボイス:「Rubyのどのバージョンが動くんでしょう?」「MRuby 3 VMだそうです」

mruby/mruby - GitHub

「Rubyのコードをショートカットに登録して実行できるのか」「ちょっと動かしてみた感じではhttpリクエストも動かせるみたいですね」

後で自分のiPhoneで動かしてみました。

「App Storeはこういうアプリを許可するようになったんでしたっけ?」「私もそこが気になりました」「たしか以前は任意のコードを実行できるアプリはApp Storeで許可が出ませんでしたよね」「そうでした」「それが嫌だったのでMacもiPhoneも使ってません😆

後で調べてみると、現在のガイドラインは以下のようになっています(以前のガイドラインについては不明)。ここを見る限りでは、コードを実行可能なアプリは条件付きで許可される可能性はありそうですね。

2.5.2
Appはバンドル内で完結している必要があります。他のAppを含め、指定されたコンテナエリア外に対するデータの読み書き、またはAppの特徴や機能を導入したり変更したりするコードをエリア外からダウンロード、インストール、実行することは許可されません。実行形式のコードの学習や開発、学生によるテストを目的とした教育用Appでは、コードが他の目的で使用されないという、限られた状況での使用に限り、コードのダウンロードが許可される場合があります。こうしたAppでは、ユーザーがApp上でソースコードの全体を確認し、編集できることを許可しておく必要があります。
App Store Reviewガイドライン - Apple Developerより

🔗 debugger-tatakidai


つっつきボイス:「_ko1さんが作っているデバッガーの叩き台」「早速名前がdebuggerからdebugger-tatakidaiに変わってますね」「皆さまのフィードバックお待ちしてますとありました」

「このツールはRubyをリモートデバッグできるんですね: READMEを見た限りでは、標準のdebugライブラリのreplacementとして使える、よりリッチなデバッガライブラリという感じかなと思います」「最終的にRubyにバンドルされるといいですね」

参考: library debug (Ruby 3.0.0 リファレンスマニュアル) — 従来のデバッガ

「ちなみにJetBrains IDEにもリモートデバッガが入ってます↓: RubyMineではdebaseというdebug.rbのfaster implementationを使うようになっていて、ruby-debug-ideというgemを使ってIDEからTCP経由でattachするような構成になっています」

参考: リモートデバッグ | RubyMine

ruby-debug/ruby-debug-ide - GitHub

🔗 その他Ruby(Ruby Weeklyより)


つっつきボイス:「RubyCards?」「RubyやRailsコードの問題を数日おきぐらいにツイートしているアカウントだそうです」「面白そう〜、フォローしました」「Ruby Goldの試験対策に使えるかも」

参考: Ruby技術者認定試験

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

🔗 GitHub ActionsのイメージビルドにDockerレイヤキャッシュを効かせる


つっつきボイス:「TechRachoのRails関連翻訳記事でよくお世話になっているEvil Martiansの記事です」「そういえばDockerのレイヤキャッシュはGitHub Actionsで使えますね」

参考: Dockerfile のベスト・プラクティス — Docker-docs-ja 19.03 ドキュメント

「こんなふうにGitHub Actionsの設定を書けるのか↓」「お〜」

# 同記事より: docker/build-push-actionの公式サンプル
name: ci

on:
  push:
    branches:
      - "master"

jobs:
  docker:
    runs-on: ubuntu-latest
    steps:
      # Check out code
      - name: Checkout
        uses: actions/checkout@v2
      # This is the a separate action that sets up buildx runner
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v1
      # So now you can use Actions' own caching!
      - name: Cache Docker layers
        uses: actions/cache@v2
        with:
          path: /tmp/.buildx-cache
          key: ${{ runner.os }}-buildx-${{ github.sha }}
          restore-keys: |
            ${{ runner.os }}-buildx-
      - name: Login to DockerHub
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
      # And make it available for the builds
      - name: Build and push
        uses: docker/build-push-action@v2
        with:
          context: .
          push: false
          tags: user/app:latest
          cache-from: type=local,src=/tmp/.buildx-cache
          cache-to: type=local,dest=/tmp/.buildx-cache-new
        # This ugly bit is necessary if you don't want your cache to grow forever
        # till it hits GitHub's limit of 5GB.
        # Temp fix
        # https://github.com/docker/build-push-action/issues/252
        # https://github.com/moby/buildkit/issues/1896
      - name: Move cache
        run: |
          rm -rf /tmp/.buildx-cache
          mv /tmp/.buildx-cache-new /tmp/.buildx-cache

参考: Build and push Docker images · Actions · GitHub Marketplace

「初歩的な質問ですけど、これができるとどんな点が嬉しいんでしょうか?」「GitHub ActionsでCIを回すときにキャッシュが残っていればそこから取れるので速くなりますね」「あ、なるほど!」「いわゆるCIキャッシュです」

「記事の最後の方にどのぐらい速くなったかが載ってる」「お〜、2分以上短縮されることもあるらしい」「bundle installyarn installのような時間がかかりがちなコマンドにキャッシュが効いて速くなる、たしかに」

「この記事はキャッシュによる速度差が具体的にわかるのがいいですね👍」「これ翻訳してください」「了解です〜!」

🔗CSS/HTML/フロントエンド/テスト/デザイン

🔗 Chrome DevToolsの機能


つっつきボイス:「Chrome DevToolsのtips記事です: 知らないものもあったので取り上げてみました」

「へ〜、capture node screenshotという機能があるのか」「おぉ?」「特定の要素だけをきれいにスクショで撮れる機能ですね」

「お、コンソールで$_が使えるそうだ」「これは?」「$_は直前の評価値を返す変数です: シェルやRubyにもありますし、他の言語にも似たようなものがあります」「あ、なるほど」「JavaScriptのコンソールならこれがあっても不思議じゃないですね」

[Ruby] Kernelの特殊変数をできるだけ$記号なしで書いてみる

「XHRリクエストのreplayは有名かな」「コンソールでcopyすると変数をクリップボードにコピーできるのか、これは知っておくと便利そう👍」「配列を表に整形する機能はJypiter Notebook↓を思わせますね」「要素を非表示にするショートカットもある」

参考: Project Jupyter | Home

「割と長いので翻訳記事なのかなと思ったら、最後まで見ると翻訳って書いてありました😅」「Chrome DevToolsにはいろいろ機能が隠れているから、こうやったらできるかなと思ってつついてみると意外にできたりしますね」「DevTools、要素を見るときぐらいしか使ってなかった…」

🔗 Chrome 90のDevToolsで使える新機能

「ChromeのDevToolsといえば最近感心したのは、弊社CTOのbabaさんがSlackに貼ってくれた、CSSのflexboxのオプションをビジュアル表示する機能ですね↓」

「これは?」「よく見てもらえればわかると思いますが、writing-modeがたとえばvertical-rlかそうでないかで、flexboxの各種オプションに表示される方向の絵(赤枠)までちゃんと切り替わってくれるんですよ」「あ、たしかにflex-directionがrl(right to left)のときは矢印のアイコンが右から左に変わってる!」「お〜」

「top to bottom、left to right、right to leftはもちろん、btt(bottom to top)みたいに下から上に進むパターン↓まであるので、たぶんflexboxの全パターンを網羅していると思います」「これはスゴいな〜」「かなりスゴい」「flexboxのこういう挙動って忘れがちなので助かる👍

「おや、自分のChromeでは表示されないな…」「今Slackでbabaさんに聞いてみたら、Canary版のChrome↓でやってたそうです」「あ、そういえばbabaさんは普段からCanary版Chrome使ってますね」

参考: Chrome Canary のデベロッパー向け機能 - Google Chrome

「以下の記事によると、Chrome 90(未リリース)新機能のflexbox debugging toolsというのがそれみたいです↓」

参考: What’s New In DevTools (Chrome 90) - Chrome Developers

「Canary版Chromeをインストールしたら出せました」「出た🎉」「左のサジェスチョンは前から使えますが、右のアイコンの矢印がちゃんとrlに合わせて右から左になってる」「お〜見やすい」

「Chromeは着々と強くなってますね」「そしてChromeが強くなるとElectronも強くなってくれるので、Slackもよくなってくる」「そうそう」

🔗 その他

ケーブル保護チューブ


つっつきボイス:「これちょっと欲しいです」「工具を使ってケーブルをびしっとまとめられる業務用のチューブ、ありますね」「チューブの途中からも線を出せるのがよさそう」「ホームセンターで買えるかな?」「ダイソーに類似品があるらしいという噂がありますね」


後編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210412前編)Active Record属性暗号化機能がRails 7にマージ、RailsNew.ioでrails newオプションを生成ほか

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

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

Ruby Weekly

はじめての正規表現とベストプラクティス#4 先読みと後読みを極める

$
0
0

更新情報

  • 2018/11/20: 初版公開
  • 2021/04/08: 更新

今回あたりから正規表現が本領発揮しつつ、魔界入りし始めます。今回の記事は自分でもかなり苦しみました。まだ見落としがあるかもしれませんので、今後も更新すると思います。

正規表現の先読み・後読みは言葉で説明するとわかりづらいので、具体例から先に学ぶのがよいと思います。Rubularの実行例を用意しましたので、ぜひ自分で動かして遊んでみましょう。

⚓正規表現はじめの十二歩: 「先読み」と「否定先読み」

(?=正規表現)
先読み(look-ahead)次の場合に使う
・その位置の直後正規表現がある場合にのみマッチさせたい
・だが直後のその正規表現マッチに含めたくない
(?!正規表現)
否定先読み(negative look-ahead): 次の場合に使う
・その位置の直後正規表現がある場合にのみマッチから除外したい
・だが直後のその正規表現マッチに含めたくない

  • 注: 今回の記事では、位置という言葉を「文字と文字のを指す」ものとして使います。位置は文字そのものを指さないのでご注意ください。

⚓先読みの例

  • 例: /(通過|通化)(?=スワップ)/という正規表現(Rubular

上の例は、「通過スワップ」というタイポの「通過」、または「通化スワップ」というタイポの「通化」にマッチします。それ以外にはマッチしません。

  • 例: /(?=を祈)/というパターン(Rubular

これはあえて先読みだけを記述したものです。「検討」と「を祈る」の間、つまり文字と文字の「間」にマッチしていることにご注目ください。これが「位置」です。

⚓否定先読みの例

  • 例: /せっかく(?!なら)(?!だから)/というパターン(Rubular

(?!なら)(?!だから)と2つ置いていることにご注目ください。上述したように、位置指定子はそれ自体は文字ではないので、このように複数置いて条件を追加できます。言葉で表せば「その文字の直後はならでもだからでもない」という意味です。

⚓正規表現はじめの十三歩: 後読みと否定後読み

(?<=正規表現)
後読み(look-behind): 次の場合に使う
・その位置の直前正規表現がある場合にのみマッチさせたい
・だが直前のその正規表現マッチに含めたくない
(?<!正規表現)
否定後読み(negative look-behind): 次の場合に使う
・文字列の直前正規表現がある場合にのみマッチから除外したい
・だが直前のその正規表現マッチに含めたくない

⚓後読みの例

  • 例: /(?<=再)コンパル/というパターン(Rubular

「再コンパル」というタイポを検出します。「再」をハイライトせず「コンパル」だけをハイライトできます。

⚓否定後読みの例

  • 例: /(?<!テ)クノロジー(?!パーク)/というパターン(Rubular

「クノロジー」というタイポを検出します。否定後読みを用いて、タイポでない文字列を除外しています。

否定後読み(?<!テ)と否定先読み(?!パーク)を同時に使っているのがポイントです。なお、クノロジーパークという施設は実在します。

⚓先読み/後読みとは

改めて説明します。

(?=正規表現)
先読み(look-ahead)
(?!正規表現)
否定先読み(negative look-ahead)
(?<=正規表現)
後読み(look-behind)
(?<!正規表現)
否定後読み(negative look-behind)

先読みと後読みは、条件付け、限定、フィルタに利用できます。正規表現のパワーを飛躍的に高める有用な表現トップクラスなのでぜひ使いこなしましょう。特に、マッチした部分をハイライトするときや置換するときにぜひとも欲しくなる記法です。割と覚えにくいので、私はカンペを作りました。

なお、先読みと後読みの両方をまとめて「lookaround」と呼ぶこともあります。

先読みや後読みは難しく言うと「位置指定子」ですが、「position specifier」みたいな英語があるわけではないようです。

先読みや後読みはあくまで位置を指定する記法なので、それ自体は文字ではありません。つまり文字カウントとしてはゼロです。上述の例で先読みを単独で使うと文字と文字の「間」にマッチしましたが、その理由がこれです。

なお、先読みと後読みは残念ながらライブラリによって機能にかなり差があります(後述)。

⚓参考: 「先」と「後」はマリオになったつもりで考えよう

日本語訳の「先読み」「後読み」は誤解を招きやすいという問題があります。というのも、日本語の「先」や「後」がそもそも曖昧さを含んでいるからです(「前」「後」も同じく曖昧です)。

  • 時間軸上のイベント順序を表す「先」「後」: 例「先に片付けよう」「その作業は後でいいよ」
  • 空間上の方向を表す「先」「後」: 例「先が見えない」「後ろに目があるようだ」(正規表現はこっち

その意味で、英語の「look-ahead」「look-behind」で覚える方が間違えにくいかもしれません。

「先」「後」は、自分がマリオになったつもりで文字を読み進めるときの進行方向で考えるとよいでしょう。

  • 「先読み」は現在の位置より先(進行方向、文末に近い方)を対象とする
  • 「後読み」は現在の位置より後ろ(進行と逆方向、文頭に近い方)を対象とする

これならたとえアラビア語のような「右から左に書く言語」(BiDi)であっても統一的に扱えます(というよりそう考えるしかありません)。そういえば最近のマリオは3Dと2Dを行き来できますね。

⚓参考: (?なんちゃら)って?

高機能な正規表現ライブラリの多くは、先読みや後読みといった拡張機能を(?なんちゃら)の形で表します。

  • ?の直後に来る文字で機能を指定する
    • =: 先読み
    • !: 否定先読み
    • <=: 後読み
    • <!: 否定後読み
    • 他にもいろいろある(今後ご紹介)

POSIX系にありがちな[: :][[: :]]形式の拡張は普遍性(特にUnicode対応)に不安があるため、私は使わないようにしています。

⚓先読み/後読みのポイント

⚓1. 原理的にはいくつでも追加できる

先読みや後読みは、前回学んだ\A\zと異なり、原理的には正規表現の途中にいくつでも置けます(もちろん文字セット[]の中は除きます)。

その気になれば/(?<!(?<=トテ))/のように入れ子にすることすら可能です(実装に依存する可能性がありますが)。今のところ意味のある例をちょっと思いつきません。

  • 例: 全部使った場合: /(?<=東京)(?<!大阪)特許(?=許可)(?!許諾)/(Rubular

言葉で説明すると、以下をすべて満たすもののみがマッチします。

  • 「特許」の直前は「東京」である
  • 「特許」の直前は「大阪」ではない
  • 「特許」の直後は「許可」である
  • 「特許」の直後は「許諾」ではない

参考: 正規表現にも「AND」が隠れているでも説明しましたが、正規表現の文字やメタ文字(|は除く)は「AND」の関係を表すので、(?<=東京)(?<!大阪)と続けて書くことで、その位置に関する条件を追加できます。指定できるのはあくまで位置であり、文字ではないことに注意しましょう。

ただし上の例は実用上は非常に冗長ですので真似しないでください。上では条件を4つも指定していますが、条件は先読みに1つ、後読みに1つあれば十分です。

⚓2. 先読みや後読みはいくつ連続しても「1つの位置」に集約される(重要)

今回の最大の目玉です。ちょっとわかりにくいので、具体例を出します。

  • 例: /(?<!東京)(?<!大阪)(?<!神戸)(?<!京都)(?<!博多)特許事務所/というパターン(Rubular

(?<!東京)のような否定後読みを5つ連続で置いていることにご注目ください。連続している限り、先読みや後読みをいくつ置いても、その位置はただ1箇所を指します。

先読みや後読み以外の正規表現をすべて削除してみるとこのことがよくわかります。

  • 例: /(?<=東京)(?<!大阪)(?<!神戸)(?<!京都)(?<!博多)/というパターン(Rubular

上は、最初の1つだけ後読み、後は否定後読みにしたものですが、5つある後読みが1箇所だけを指しているのがおわかりいただけるかと思います。

この連続する後読み同士を試しにRubularで入れ替えてみてください。結果はまったく変わりません。

言い換えると「連続する先読み後読み同士の位置関係や順序は消滅する」ということです。これは、以下の性質から導かれます。

  • 先読みや後読みは文字ではない
  • 正規表現で連続する2つの表現同士の関係はANDになる

なお、連続する先読みや後読みは、たとえ丸かっこ()で仕切っても位置を分断できません。

  • 例: /((?<=東京))((?<!大阪))/(Rubular

上は連続する(?<=東京)(?<!大阪)をそれぞれ()に入れていますが、位置はやはり1箇所に収束しています。

⚓⚠注意: 連続で無意味な組み合わせを作らないこと

これで安心して先読みや後読みを連続させられると思いたいところですが、連続しているもの同士はANDの関係になっていることに注意しましょう。

たとえば、肯定先読みの2つ以上の連続や、肯定後読みの2つ以上の連続は、たいてい無意味です。

  • 例: /(?<=東京)(?<=大阪)/という無意味なパターン(Rubular

/(?<=東京)(?<=大阪)/は「その位置の直前にあるのは東京であり、かつ大阪である」ということになるので、正規表現としてはvalidでも、マッチすることは永久にありません。メタ文字が入ればまた違うとは思いますが。

また、肯定先読みと肯定後読みの連続は冗長です。これもメタ文字が入ればまた違うとは思いますが。

  • 例: /(?<=東京)(?=特許)/という冗長なパターン(Rubular

この場合、/(?<=東京)(?=特許)/と書くぐらいなら/(?<=東京特許)/などと1つにまとめて書く方が素直です。

また、否定先読みと否定後読みの連続も無駄の多いパターンです。これもメタ文字が入ればまた違うとは思いますが。

  • 例: /(?<!東)(?!京)/(Rubular

この場合、/(?<!東)(?!京)/などと書くぐらいなら/(?<!東京)/などとまとめて書く方が素直です(それでもパターンとして有意義とは言えませんが)。

⚓3. パターンの途中にも置ける

先読み・後読み・否定先読み・否定後読みは位置指定子なので、一応パターンの途中にも置けます。

  • 例: /..(?<!には)(?<=、)にわ/というパターン(Rubular

なお、通常は位置指定子をパターンの途中に置く積極的な意味はそれほどないと思われます。

⚓4. 先読みや後読み「そのもの」には量指定子を付けられない

先読みや後読みが位置のみを表すので、先読みや後読みそのものに?+といった量指定子を付けられません(付ける意味もありません)。

  • 例: /(?<![青赤\n\r\t])+巻紙/というパターン(regex101

regex101で試すと上のようにエラーになります。

なぜかRubularではエラーになりませんが、これはエラー扱いにする方がよいように思えます。

⚓⚠注意: 先読み/後読みでも「否定」にはご用心

否定表現は、すなわち文字セットの補集合を表します。

[^文字]のような否定文字セットは改行文字にもマッチすることを前回説明しましたが、似たようなことが否定先読みや否定後読みでも起きます。

  • 例: /(?<![青赤])巻紙/というパターンで想定外のマッチが発生(Rubular

上の例では、「黄巻紙」以外に単なる「巻紙」にまでマッチしてしまいました。改行文字や非文字(冒頭位置や末尾位置など)も/(?<![青赤])巻紙/に該当するからです。

文字セットの補集合は途方もなくデカイことを思い知らされます。そんなものに+*のような凶悪な量指定子を付けたらどれだけパフォーマンスが落ちるかと思うと🤢。もっとも先読みや後読みそのものには量指定子を付けられませんが。

単なる「巻紙」を除外したい場合は、次のように\n\r\tといった改行文字や非表示文字を文字セットの除外に追加するか、/(?<![青赤])(?<=[黄紫緑])巻紙/などのように明示的な条件を追加するなどの対策が必要です。

  • 例: /(?<![青赤\n\r\t])巻紙/というパターン(Rubular

  • 例: /(?<![青赤])(?<=[黄紫緑])巻紙/というパターン(Rubular

ことほど左様に、否定表現はコワいと改めて思います。正規表現は最初に極力肯定的な表現を追求し、否定表現は最後の手段ぐらいに考える方がよいと思います。私も知らずにまだまだ否定表現のワナを踏んでいるかもしれません💦

興味のある方は「ド・モルガンの法則」を調べてみるとよいでしょう。

参考: ド・モルガンの法則 - Wikipedia

⚓先読み/後読みはライブラリごとの差が大きい

先ほども書きましたが、正規表現の先読み/後読みは残念ながらライブラリによって差が大きいのが現状です。

たとえば、Go言語に組み込みの正規表現には、後読み機能自体がそもそもありません

JavaScriptは数年前まで後読み機能がありませんでしたが、現在(2021/04/08)はChromeとFirefoxで先読みも後読みもフルに使えるようになりました。Safariは残念ながらまだ後読みに対応していません(caniuse.com)。

JavaScript: Chrome V8なら正規表現で後読み(look behind)がフル機能で使える

なお、Go言語向けのdlclark/regexp2というサードパーティパッケージは.NET Frameworkの正規表現ライブラリを移植したもので、パフォーマンスはともかく機能は.NET Framework並です。

先読みは、メジャーな正規表現ライブラリなら問題なく使えます。

後読みで問題なのは、多くのライブラリで制約がかけられている点です。Ruby、PHP、Python(PCRE)など、ほとんどの正規表現ライブラリでは、後読み/否定後読みの中のパターンの長さを不定にできないようになっています(先読みにはこうした制約はありません)

これは(私にとっては)かなり厳しい制約です。その場合?+*のような長さ不定の量指定子は使えません

  • (?<=.+)(?<=.*): *+はもちろんダメ
  • (?<=テクノロジー?): ?ですらダメ
  • (?<!テクノロジー?): 否定後読みでも?すらダメ

たとえ量指定子を使わなくても、長さが不定になる表現は後読みの中で使えません

  • 例: /(?<=(東京|大阪|北海道))特許事務所/というパターンはダメ(Rubular

ここは私の推測ですが、多くの正規表現ライブラリではこの書き方を自粛しているのだと思います。理由としては特に後読みや否定後読みはただでさえ検索の効率が落ちやすく、その中で長さ不定の量指定子などを許すとさらに効率が落ちてしまう可能性があるためです。

なお、.NET Frameworkや現在のJavaScriptなら後読みや否定後読みでも長さ不定の量指定子をフルに使えます。詳しくは以下の記事にあるスライド「正規表現のlook behindで量指定子を使いたい」をご覧ください。

『Regex Festa— 正規表現を楽しもう』に参加してきました

⚓「後読みで長さ不定の表現が使えない」問題の回避方法

「長さが不定の文字列をどうしても後読みの中で使いたい!」という方に、いくつかの回避方法をご紹介します。

⚓1. 全体を|で分割して回避(否定でない後読みなら)

この|による全体分割は強力な味方です。応用範囲が広く、可読性もよいのでぜひ活用しましょう。

  • 例: /(?<=(東京|大阪))特許事務所|(?<=(北海道|神奈川))特許事務所/というパターン(Rubular

それぞれの後読みの中で文字列の長さを揃えるのがコツです。

ただし、これも否定後読みでは注意が必要です。

そもそも「AまたはB」でない「Aでない」または「Bでない」という否定がらみのロジックの可読性が低い(非常に間違えやすい)という問題があります。

また、Rubyの否定後読みでは、長さが同じであっても/(?<!(東京|大阪))特許事務所/という書き方自体が許されません。否定後読みの中で()を使うだけでもエラーになります(ちょっと厳しすぎる気もします)。

  • 例: /(?<!(東京|大阪))(?<!(北海道|神奈川))特許事務所/というパターンは許されない(Rubular

PHPやPythonでは長さが同じ否定表現/(?<!(東京|大阪))特許事務所/であれば許されます(regex101.com)。

⚓2. 複数の肯定後読みを()で囲んで|でつなぐ

文字列の長さごとに肯定後読みを書き、それらを()で囲んで|でつなぐ方法もあります。()の入れ子が増えるのが難点ですが、可読性はさほど下がりませんし、この方がコンパクトに書ける場合もあるので、これもおすすめです。

  • 例: /((?<=(東京|大阪))|(?<=(北海道|神奈川)))特許事務所/というパターン(Rubular

前述のように、Rubyの否定後読みの中ではそもそも()を書けないので、肯定後読みが対象です。

ここはあくまで想像ですが、否定後読みの中で()を許すと((?<!(東京|神奈川))のような人間が間違えやすい「否定とORの併用」ロジックの乱用を誘発するので、戒めのために禁止しているのかもしれません。
しかし否定先読みでは((?!(東京|大阪))という書き方は許されています。
このことについては私の中で戒めだと思うことにします。

⚓3. {N}で長さ指定して回避

{N}量指定子による一意の長さ指定は例外的に使えます。{N,M}などの範囲量指定子は使えません。

これを1.や2.のように|でつなげても構いませんし、要注意ながら否定後読みでも使えます(否定表現を|でつなぐのは避けたい)。

もっとも、普段から+*のような凶悪な量指定子ではなく、{N}{N,M}のように大人しい量指定子を積極的に使いたいものです。

  • 例: /(?<=宮[一-龠]{2})特許事務所/というパターン(Rubular

⚓まとめ

  • 先読みや後読みは非常に強力な正規表現
  • 先読みや後読みは複数置いても大丈夫
  • 連続する先読みや後読みは1つの位置に集約される
  • 無意味な連続を作らないよう注意しよう
  • 否定表現は巨大な補集合を呼び込むので、原則として避けよう(特に|と混ぜると間違えやすい)
  • パターンA|パターンB」のように、論理OR |で素直に表せる表現を検討しよう

正規表現は常に冒頭から末尾に向けて探索を進めるので、マッチの頻度が高いものを左に寄せると速くなります。たとえば( | | )は途中でマッチすればそこで処理を終えるので、頻度の高いものを左に置くようにしましょう。

⚓参考: 正規表現についてめちゃくちゃ詳しく学べるサイト

最後に、本記事を書いていて見つけたおすすめサイトをご紹介します。

サイト自体に、主要な正規表現ライブラリ同士の機能や構文を比較できる機能があります(一度に2つですが)。


regular-expressions.infoより

しかも正規表現について極めて詳細な解説が惜しげもなく公開されていて、私もちょっと読んだだけでいくつもの発見がありました。長年追い求めていた正規表現の神⛩を見つけたような思いです。英語圏の神なので、日本語特有のノウハウはさすがにありません。

このサイトでは強力な正規表現チェックアプリも販売していますが、惜しくもWindows用のみです(´・ω・`)。


関連記事

正規表現の先読み・後読み(look ahead、look behind)を活用しよう

正規表現: 文字クラス [ ] 内でエスケープしなくてもよい記号


週刊Railsウォッチ(20210419前編)RailsのN+1クエリを定番以外の方法で修正する、GitLabのセキュリティ修正リリースほか

$
0
0

こんにちは、hachi8833です。RailsConf 2021をすっかり見逃してました😇


つっつきボイス:「RailsConf 2021、そういえば開催時期だったか」「つっつき中の今日(04/15)がラス日でした🙇」「キーノートのページにコミットでよく見かける顔が並んでますね」

「RailsConf 2021のfaqを見た感じでは有料イベントのようですね」「あ、そうでしたか」「参加者は後で動画を見られるそうですが、一般公開するとは書かれてない: たしかに有料でしかもリモート開催のイベントの動画を後で無料公開したら有料で登録する人がほとんどいなくなってしまいますよね」「それもそうか…」「スライドだけでも見られればと思ったけどしょうがないですね」


以下はつっつき後に見つけたツイートです。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

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

今回は以下のコミットリストのChangelogを中心に見繕いました。

🔗 エラーページのCSSとアクセシビリティを改善


つっつきボイス:「developmentモードで表示されるエラーページのスタイルをインライン(style=による直書き)ではなくちゃんとCSSのクラスで書き直したのか↓」「開発中によく見かける赤いエラーページですね」

# actionpack/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb#L13
    <% corrections.each do |correction| %>
-     <li style="list-style-type: none"><%= h correction %></li>
+     <li class="correction"><%= h correction %></li>
    <% end %>

<div>タグも、意味を表せるHTML5の<main>タグに置き換えてる↓」「考えてみれば、developmentモードのエラー画面をスクリーンリーダーで読む人がいる可能性もありそう」「だからアクセシビリティ改善なんですね」

# actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb#L4
-<div id="container">
+<main role="main" id="container">
  <h2>To allow requests to <%= @host %>, add the following to your environment configuration:</h2>
  <pre>config.hosts << "<%= @host %>"</pre>
-</div>
+</main>

参考: HTML: アクセシビリティの基礎 - ウェブ開発を学ぶ | MDN


「ところで、accessibilityをa11yって略さなくてもいいんじゃないかしら」「i18n(internationalization)やm17n(multilingualization)ぐらい長ければ略すのもやむなしな気もしますけど」「Kubernetesも大して長くないけどK8sと略されたりしますね」

🔗 ActiveSupport::Duration#partsのハッシュをfreeze


つっつきボイス:「partsが返すハッシュをfreezeして返すことで高速化したらしい」「ベンチマークでも速くなってますね」「ライター系メソッドも削除したのか」「freezeしたんだからライター系メソッドはあったらマズいでしょうね」「あ、たしかに」

# activesupport/lib/active_support/duration.rb#L212
-   def initialize(value, parts) #:nodoc:
+   def initialize(value, parts, variable = nil) #:nodoc:
      @value, @parts = value, parts
      @parts.reject! { |k, v| v.zero? } unless value == 0
+     @parts.freeze
+     @variable = variable
+
+     if @variable.nil?
+       @variable = @parts.any? { |part, _| VARIABLE_PARTS.include?(part) }
+     end
+   end
+
+   # Returns a copy of the parts hash that defines the duration
+   def parts
+     @parts.dup
    end

「Active SupportのDurationは処理によっては利用頻度が高そうなので、高速化されるのはありがたい🙏


「DateやTimeの差からDurationを得られるのは便利ですよね」「他の言語だといったんmsecに変換するとかいろいろ面倒になりがちですが、ActiveSupport::Duration.build(time1 - time2)のようにするとDurationが得られますね」

「もっとも、たとえばActiveSupport::Duration.build(time1.beginning_of_day - time2.beginning_of_day)がサマータイム切り替わりをはさんだときに常に日単位になってくれるかどうかは解釈によっては難しいですよね: 1日が23時間や25時間でも1日と解釈するのか、それともあくまで1日=24時間として解釈するのか、とか」「う、そうですね😅」「日付は難しい…」

DurationはValue Objectであり、ミューテーションされるべきではない。
この変更によって、Durationが変数であるかどうかにかかわらずキャッシュできるようになり、ActiveSupport::Duration+メソッドや-メソッドのパフォーマンスが改善される。さらにany?やインラインのarray定義を避けられるので、arrayが2個アロケーションされるのも回避できる。アプリ全体のパフォーマンスが著しく改善されるとまではいかないが、duration_of_variable_length?メソッドは2.5倍速くなる。

Warming up --------------------------------------
             current   325.663k i/100ms
                 new   828.177k i/100ms
Calculating -------------------------------------
             current      3.274M (± 2.9%) i/s -     16.609M in   5.076744s
                 new      8.337M (± 1.9%) i/s -     42.237M in   5.067887s

Comparison:
                 new:  8337279.2 i/s
             current:  3274297.7 i/s - 2.55x  (± 0.00) slower

parts=メソッドとvalue=メソッドはDurationで決して使うべきではないので、削除した。

さらにpartsがfrozen hashのコピーを返すようにし、内部でdupを使わずにpartsにアクセスできるよう_partsリーダーも追加した。
同PRより大意

🔗 ActiveSupport::TimeZone#utc_to_localを修正

utc_to_local_returns_utc_offset_timesがfalseでtimeインスタンスに小数の秒がある場合、Time.utc コンストラクタが小数秒の値ではなくusec(マイクロ秒)の値を取るため、新しいUTC timeインスタンスが1,000,000のファクターでずれていた。
Changelogより大意


つっつきボイス:「usecって何だったかなと思ったらマイクロ秒(μsec)の簡易表記なんですね」「そうそう」

参考: ミリ秒とは - IT用語辞典 e-Words

「ローカルのシステムはマイクロ秒も扱えたと思うので、手元のWSLのコンソール(Ubuntu 20)でdateコマンドやってみよう」(しばし操作)「dateにナノ秒の書式(%N)があったのでナノ秒まで出せた↓(マイクロ秒の書式は見当たらず)」

$ date +"%Y-%m-%d %H:%M:%S.%N"
2021-04-15 20:20:15.172406400

「で、従来はTime.utcで小数点以下の値が小数秒として扱われていなかったので* 1_000_000を追加して修正したようですね↓」「このバグよく見つけたな〜」

# activesupport/lib/active_support/values/time_zone.rb#L513
    def utc_to_local(time)
      tzinfo.utc_to_local(time).yield_self do |t|
        ActiveSupport.utc_to_local_returns_utc_offset_times ?
-         t : Time.utc(t.year, t.month, t.day, t.hour, t.min, t.sec, t.sec_fraction)
+         t : Time.utc(t.year, t.month, t.day, t.hour, t.min, t.sec, t.sec_fraction * 1_000_000)
      end
    end

🔗 Rack::Runtimeミドルウェアが非推奨化

  • Rack::RuntimeFakeRuntimeに置き換えられる。FakeRuntimeはリクエストを渡すだけのダミーのミドルウェアで、ミドルウェア操作では利用できない。
  • ミドルウェア操作(相対的なinsertmoveなど)でRack::Runtimeを使うとFakeRuntimeを使うようdeprecation warningを表示する。
  • アプリケーションが(useunshiftなどで)Rack::Runtimeを明示的に追加する場合はdeprecation warningは表示されず、FakeRuntimeは無視される。
  • RailsガイドはRack::Runtimeを参照しないよう更新される。
    同PRより大意
# actionpack/lib/action_dispatch/middleware/stack.rb#L6
module ActionDispatch
  class MiddlewareStack
+   class FakeRuntime
+     def initialize(app)
+       @app = app
+     end
+
+     def call(env)
+       @app.call(env)
+     end
+   end
+

つっつきボイス:「Rack::Runtimeって何をするんだろう?」「このissue↓を見ると、HTTPのX-RUNTIMEヘッダーを追加するミドルウェアみたいですね」「X-RUNTIMEでググると結構古い記事が出てきた」「元々デバッグ用なのかも?」

X-RUNTIMEはサーバーの処理にかかった時間を返すんですね」「今まではRack::RuntimeX-RUNTIMEを返してたけど、X-RUNTIMEが必要な人はほとんどいなさそうだし、セキュリティ上の懸念もあるので削除しようという流れっぽい」「こんなふうにRack::Runtimeに依存したコンフィグを書いてる↓人がいる可能性もあるので、いったんdeprecationをはさんでから削除するんでしょうね」

# #38412コメントより
config.middleware.insert_before(::Rack::Runtime, SomeCoolMiddleware.new)

参考: x-runtime は消すべきなのか - Qiita

「このプルリクを見なかったらRack::Runtimeを知ることは一生なかったかも😆」「X-RUNTIMEを使う人って相当マニアックな感じしますね」「New RelicのようなアプリケーションメトリクスツールならX-RUNTIMEヘッダーを使うことがあるかもしれませんね」「あ、ありそう!」

参考: New Relic | パフォーマンス分析プラットフォーム

🔗 番外: Webpackerがいつの間にかv6.0.0.betaに

rails/webpacker - GitHub


つっつきボイス:「Evil Martiansの以下の記事↓を翻訳しながらチェックしていたら、Webpacker 6の開発が思ったより進んでいることに気づいて、今記事を書きかけています(公開済み)」

参考: Set up Tailwind CSS JIT in a Rails project to compile styles 20x faster — Martian Chronicles, Evil Martians’ team blog

「Webpacker 5がリリースされたのは昨年ぐらいでしたっけ?」「リリースタグを見ると2020年3月だから1年前ぐらいですね」「早いな〜」「Webpacker 5から6への移行がそんなに手間でなければ大丈夫かなと思いますけど」「でも現時点の6.0アップグレードガイド↓を見ると割と変更点あるようです」「あ〜、source_pathとかも変わるのか」「コンフィグ洗い替えが必要っぽいですね」「Webpacker 5を頑張って導入してカスタマイズしていたら大変そう…」「容赦なき変更」


つっつき後、以下の記事に現状をメモしました↓。

Webpacker v6.0.0.beta.6の現時点の変更点について

🔗Rails

🔗 マネーフォワードのマイクロサービス化インタビュー記事


つっつきボイス:「マネーフォワードの中の人へのインタビューが本当に生々しかったので取り上げてみました」「なるほど、現在はリクエストの途中にRailsをはさんでいるのを将来Goで直接処理しようとしてるんですね」

中出: まず前提として先ほど申し上げたように、まずRuby on Railsでできた巨大なSaaSがありまして、それを今マイクロサービスに置き換えている最中です。現在のアーキテクチャとしては、ユーザーのリクエストがRailsに届き、それがGoでできたマイクロサービスを呼び出すという形になっています。将来的には、Railsを通さずに直接Goのマイクロサービスを呼び出す形に移行していくことを考えています。
同記事より

「マイクロサービス化はまだ始まったばかりだそうです」「マネーフォーワードのようにお金に関連するサービスだと大変そう」「マネーフォワードのサービス内容を考えると、周辺機能はともかくコア機能はマイクロサービスにしにくそうな気がしますね: 機能を横断しないと取れないものはマイクロサービス化が難しくなる」「そうですよね…」


「ところで、ひと頃のマイクロサービス化しようぜみたいな話も最近だいぶ落ち着いてきた感じはありますね」「言われてみれば前より静かになったかも」「表に出ていない部分も含めて、マイクロサービス化に挑戦して失敗したところは結構あるんじゃないかな」

「もしかすると本当に欲しかったのはマイクロサービスじゃなくて『多少モジュラライズされたモノリス』だったのかも」「気持ちわかります」「巨大モノリスのままだとつらいのは確かなので、モノリスにマイクロサービスのパラダイムを一部取り入れることで、モノリスを適切に分割するときの設計上の参考にするのはありだと思いますし、上の記事はそういうときに参考になりそうですね👍


つっつきの後で、以下の「シタデルアーキテクチャ」をやっと思い出しました↓。

Rails: AppSignalが採用する「シタデルアーキテクチャ」(翻訳)

🔗 rollout gemで「フィーチャーゲート」を実現する

fetlife/rollout - GitHub

fetlife/rollout-ui - GitHub


つっつきボイス:「Engine Yardの記事です」「フィーチャーゲートはいわゆるフィーチャーフラグ的な機能かな: この機能はいろんな呼び方があるんですよ」「あ、そうなんですね」

参考: フィーチャートグル - Wikipedia

「ちなみに、ちょうどこの構成によく似た案件をこれから始めるところです↓」「へ〜」「RailsのAPIをURLベースでカレントから新しいものに切り替える感じで、よく行われる作業ですね」


同記事より


「そういえば自分も今Rails 4の案件やってます」「Rails 4ぐらい古いものをアップグレードする場合、小さなアプリだったら頑張って少しずつRails 6までアップグレードするより、rails newしてそっちにコードを移す方が早いんじゃないかって思いますね」「そうそう、少しずつアップグレードするのって意外としんどいですよね」「その途中でテスト系のgemがつかえたりするとアプリ本来の挙動と関係ないところで力を使わないといけなくなってつらい」

「でもアプリが大きいとなかなかそうもいかないので、順当に少しずつアップグレードすることになりますけど」「手間はかかりますけど、ステップバイステップでアップグレードする方が明らかに安全ですよね」「進捗も出しやすいですし」「rails newしてコードをコピペする方式だと、完全に移し終わるまで全体をテストできないんですよね」「コードベースが大きいとリスクが高すぎる」「そういうことがありうるので巨大モノリスにするのは避けたい気持ちがあります」

🔗 .countでRailsアプリが落ちることがある(Ruby Weeklyより)


つっつきボイス:「.countでそんなに遅くなるものかな?」「あ、テーブルの行数がでかいのか」「30秒経過してもクエリが返ってこないのはつらそう」「クエリが返るまで数分かかるRedmineの対応したことならありますよ」「お〜」「なかなか最適化しがいのあるクエリでした😆

「特定のクエリだけがものすごく遅い場合なら、頑張って最適化すればたいてい速くできますし、結果の整合性も取りやすいですね」「お〜」「逆に、複雑にテーブルをまたぐようなN+1クエリを手動で定数回に最適化する方がしんどい: 数百msecのクエリが数百件とか」「たしかに」「N+1の最適化は油断すると結果を変えてしまうことがあるんですよ」

「記事の結論は、.count > 0.count.positive?をもっと速いメソッドに置き換えましょうということか」「遅いクエリの最適化は頑張りが大事」「たしかActiveRecord::Relationempty?はレコードが1件あるかどうかがわかればいいので、COUNT()を使わずLIMIT 1を使った実装になっていて速かった覚えがありますね」「なるほど」

参考: 【Ruby on Rails】ActiveRecordのexists?の逆はempty?を使う - Qiita

🔗 N+1クエリを定番以外の方法で修正する


つっつきボイス:「ちょうどN+1クエリの記事です」「non standardなN+1クエリ修正ですか」

「ちなみに最初の見出し『N+1 queries 101』の101は、英語圏では『入門』の意味ですね」「そうそう、海外の大学のシラバスだと最初に履修する授業の番号が101になってることが多い」「へ〜、101ってそんな意味なんですね」

# 同記事より
# app/models/post.rb

class Post < ApplicationRecord
  belongs_to :user

  def author_name
    user.name
  end
end

# app/models/user.rb

class User < ApplicationRecord
  has_many :posts
end

# app/controllers/posts_controller.rb

class PostsController < ApplicationController
  def index
    @posts = Post.published
  end
end

「入門の次は、Scout APM↓を使ってメトリクスを取ってる」「APM(Application Performance Monitoring)ツールはN+1クエリの検出には有効ですね: N+1クエリが確実に発生していて、かつ実際に遅くなっていることを確かめてから対策する方がいい👍」「そうですね」「N+1クエリが発生していても実用上問題ない速度が出ていれば、結果が変わってしまう可能性のある更新を行わずに済むときもあります」


「方法その1は、Active Recordのキャッシュを作って手動でヒットさせる方式↓」「たしかにRailsだとnon standard」「この方法が有効なケースは、限定的ですがたしかにあります: SQLでJOINを駆使してもきれいに取れないケースがたまにあるんですが、そういうときはこのように展開済みの一時的なハッシュを作ったりしますね」「なるほど!」「これをRedisに入れられればリクエストをまたいで共有できるので便利です」

# 同記事より
class PostsController < ApplicationController
  def index
    @posts = Post.published.includes(:comments)
    @users = User.active
    @users_cache = @users.reduce({}) do |agg, user|
      agg[user.id] = user
      agg
    end
  end
end

「Rails標準の方法だとeager loadingするんですが、それが効かないケースにもこういう書き方をすることがありますね」


「方法2は、引数や戻り値をオブジェクトではなくプリミティブな値にする ↓: スコープでpostオブジェクトを渡すとpost.user.idの部分でクエリが発生するので、生のuser_idを受け取る方がいい、たしかに」「あ〜、なるほど」「効くかどうかは内部の処理にもよりますし、呼び出し方も変えないといけないので、効果を確かめてから使う方がいいと思います」

# 同記事より
class Post < ApplicationRecord
  scope :by_author, -> (user_id) {
    where(user_id: user_id)
  }
end

「方法3はリレーションシップのショートカットを作る」「この手法もときどき使いますね: リレーションを普通にたどるとuser.account.activity.last_active_atのように長くなりますけど、UserのidをActivityにも置くことで以下のようにactivity.last_active_atと書けるようにする↓」

# 同記事より
class Post < ApplicationRecord
  belongs_to :user
  belongs_to :activity

  def author_last_active_at
    activity.last_active_at
  end
end

「正規化するならuser.accountをたどればUserのidを取れるのでActivetyにUserのidを置く必要はないんですが、置くことで参照の距離を縮めるという発想ですね」「お〜、なるほど!」

「マルチテナントのアプリケーションなどでこの方法を使うことがあります: テナントのidは利用頻度が高いんですが、きれいに正規化すると距離が離れてしまってJOINを何度も行わないと取れなくなるので、テナントのidを途中のテーブルにも持たせたりします」「記事には『やりすぎ注意』とありますね」「そう、この方法だと正規化を崩すことになるので、データ不整合が発生したときに悲惨なことになります😇」「たしかに」「あと、外部キーを付けすぎるとINSERTが重くなる可能性もあります: いずれにしろ十分検討してからにしましょう」


「方法4の『リレーションシップのデータ複製』もよく使いますね: マテリアライズドビューあたりがこれに近い方法かな」「なるほど」「テーブルパーティショニングもこの一種でしょうね: 直近1か月のデータだけ別テーブルに切り出してすぐ取り出せるようにするとか」「ログデータなんかもそうやったりしますね」

参考: マテリアライズドビュー(マテビュー)とは - IT用語辞典 e-Words
参考: 5.11. テーブルのパーティショニング


「記事タイトルはnon standard wayとあるけど、これはRailsの標準ではないというだけで、Railsに限らない一般的なN+1クエリの解決ではどれも普通に使われる方法だと思います」「あ、そういうことでしたか」「むしろRailsでN+1解決というとすぐeager loadingの話になる方が一般的でないんじゃないかな」「まあたしかに😅」「記事で紹介されている方法は少なくともバッドプラクティスではありませんね: いい記事👍

🔗 GitLabのセキュリティ修正がリリース


つっつきボイス:「さっきHacklinesに流れてたので取り上げました」「あ、ログインユーザーが画像アップロードしてコード実行できるのは結構エグい!社内GitLabサーバーにも赤ランプ点灯してるし↓、週末に適用しなきゃ」

つっつき後の日曜日に無事アップデート完了しました。


前編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210413後編)RubyMineのRBSサポートとCode With Me、GitHub ActionとDockerレイヤキャッシュほか

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

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

Rails公式ニュース

Ruby Weekly

週刊Railsウォッチ(20210420後編)ShopifyのJITコンパイラYJIT、PicoRuby、DynamoDBの3つの制約ほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

🔗Ruby

🔗 YJIT: もうひとつのJITコンパイラ(Ruby Weeklyより)


つっつきボイス:「YJITのYはyet another」「Shopifyがやっているそうです」「Shopifyは例のSorbet↓をはじめ、いろいろ手広くやってますね」

「Shopifyが自分たち向けにRubyをカスタマイズしている様子は、FacebookがPHPをカスタマイズしてHack言語を作ったのを思わせますね: ShopifyはRuby標準にこだわらないけどRuby関連イベントにも頻繁に登場していますし、いいものはRubyに還元しようというスタンスも感じられます」「たしかに」「Hackっていう言語名、もうちょっと何とかして欲しかった」

参考: Hack (プログラミング言語) - Wikipedia

「ちなみにRuby Weeklyに載っていたのは上の動画でしたが、リポジトリとしては以下のドキュメントとyjit-benchしか今のところ見つかりませんでした↓」「early stageとあるのでこれからかな」「MJITだけだと限界に突き当たるかもしれないので、YJITのようないろんなアプローチがあるのはいいと思います👍

Shopify/yjit-bench - GitHub


「ところで、今のCRubyの構成だとJITは大変そうなので、今後は言語のエンジン(VM)もJITに適したものに仕様が変わっていくこともあり得るかなと思いました」「ふむふむ」「JRubyが速いのは、JVMの上で動いているからというのも大きいですよね」

「最近のRubyでは、従来C言語で書かれていたメソッドをRubyで書き直すことがちょくちょく行われていますけど、あれはJITを効きやすくするという側面もあると思います」「たしかに最近よくやってますよね」「Cより速くなることもあると何かで見ました」「CライブラリのままだとJITでのメモリ再配置が難しいと思うので、 C -> Rubyへの書き換えはそれはそれで進みつつ、今後どこかのタイミングでVM自体もJITが効きやすくなる変更を入れていこう、みたいな話になるのかもしれませんね」

🔗 PicoRuby: ワンチップマイコン向けRuby


つっつきボイス:「ピコルビー?」「ラズパイとかに乗っける用ですか?」「Raspberry Piよりもっともっと制約の大きいワンチップマイコン向けですね: ラズパイならピコどころかメガ・ギガの世界ですよ」「ホントだ、ROM 256KB以下、RAM 64KB以下とか書かれてる」

参考: Raspberry Pi - Wikipedia

「PicoRubyに近いのは、いわゆるmrubyではなくmruby/cの方でしょうね↓: PicoRubyはGPIOにも対応していますし、チップに焼き込む前提っぽい」

mrubyc/mrubyc - GitHub

参考: GPIO - Wikipedia

「記事ではキーボードファームウェアをRubyで作っていて、その中のkeymapなどのカスタマイズ部分について解説しているようですね」「自作キーボード勢の間でこうやってキーボードファームウェアを作るのが流行ってるみたいですよ」


同記事より

🔗 mechanize:クローラ作りに便利なgem(Ruby Weeklyより)

sparklemotion/mechanize - GitHub


つっつきボイス:「mechanize、久しぶりに見たな」「あ、これ有名なgemなんですか?」「mechanizeはまさにクローラ的なインターフェイスを持っているので、昔からRubyでクローラを作るのによく使われます」「へ〜!」「nokogiri gemはHTTPをパースするのによく使われますが、mechanizeはクローラ専用と言ってもいいぐらい特化しているのが特長です」

sparklemotion/nokogiri - GitHub

「mechanizeにあるこういうfield_withcheckbox_withみたいなメソッドは、クローラやE2E自動テストみたいなブラウザの挙動を模した操作ができるインターフェースですね↓」「お〜」

# https://docs.seattlerb.org/mechanize/Mechanize/Form.html#method-i-field_with-21-28criteria-29
form.field_with(:id => "exact_field_id").value = 'hello'
# https://docs.seattlerb.org/mechanize/Mechanize/Form.html#method-i-checkbox_with-28criteria-29
form.checkbox_with(:name => /woo/).check

「おそらくCapybaraなどでやるより速いんじゃないかな: Capybaraはもう少し上のレイヤの動きも模倣しますけど、mechanizeはクローラ的なこと以外はやらないのでその分速そう」「そういえば最近クローラ作ってないな、また作ろうかな」

teamcapybara/capybara - GitHub

🔗DB

🔗 知っておきたいDynamoDBの3つの制約(DB Weeklyより)

参考: Amazon DynamoDB(マネージド NoSQL データベース)| AWS

  1. The item size limit;
  2. The page size limit for Query and Scan operations
  3. The partition throughput limits
    同記事より

🔗 1. The item size limit

つっつきボイス:「最大400KB/行なのか」「これに引っかかるほど大きなデータは普通入れないと思いますけどね」「MongoDBの16MBより小さいですね」「MongoDBのつもりで使うとハマりそう」「巨大なJSON blobをツッコむなと記事にも書かれていますね」「そもそもDynomoDBはMongoDBみたいなネステッドなクエリをあまりサポートしていなかったと思うので、MongoDB的な使い方は普通しないんじゃないかな」

参考: BLOB(Binary Large OBject)とは - IT用語辞典 e-Words

🔗 2. The page size limit for Query and Scan operations

「2.にもクエリの制約の話があるように、DynamoDBにはあまりにも巨大なデータを入れない方がいいでしょうね」「小さいものをたくさん入れるのはいいけど、バカでかいものを入れるなよと」「それにDynamoDBは、レコード数があまりに多いときにインデックスにヒットしないクエリを投げてしまうとリソースを一瞬で使い切ってしまいます」「そうそう!」「記事で1リクエストあたり最大1MBとあるのが2.で言うクエリのpage size limitです」

「DynamoDBはread capacity unitの設定が難しいんですよ: 内部的にはHTTPリクエストを細かく分割したような感じでページングというかブロック転送的にデータを取ってくるんですけど、ヒットした結果が大きいと1リクエストに入れられるレコード数が減ってしまうんですよ」「ああなるほど!」「逆にヒットした結果が小さければ1リクエストに入れられるレコード数は増えます: read capacity unitは課金にも関連しますし、単純にレコード数だけで見積もれないので行サイズなども考慮しないといけないのが面倒」

参考: Managing Settings on DynamoDB Provisioned Capacity Tables - Amazon DynamoDB

「2.のスキャンはどのキーでも検索できるんですが、テーブルの全行をスキャンするのでテーブルサイズが巨大になるとすごいことになります」「へ〜」「2.のクエリは必要なものだけを取れますが、partition keyが設定されているものしか絞り込みができない」「なるほど」「このあたりはDynamoDBを一度使ってみるとわかります」

🔗 3. The partition throughput limits

「3.のpartition throughput limitは、記事によると1パーティションあたりのreadとwriteのcapacity unitにそれぞれ上限があるのか」

🔗 DynamoDBのモード

「DynamoDBにはオンデマンドキャパシティーモードとプロビジョニング済みキャパシティモード(デフォルト)という2種類の課金体系があって↓、後者は消費したユニット数が単位時間ごとに回復するんですが、完全に消費するとAPIエラーになるのがつらい」「本番で起きたら悲しいですね…」

参考: 料金 - Amazon DynamoDB | AWS

「一方前者のオンデマンドキャパシティーモードにはその制限はなく、完全に従量課金になります」「なるほど〜」「使い方に応じてどちらかを選ばないといけません」

「なお以下は2018年の記事↓なので現在の金額と違うと思いますが、料金のグラフの伸び方などは参考になると思います」「ふむふむ」

参考: Amazon DynamoDBをオンデマンド(従量課金)で利用した場合の料金体系まとめ #reinvent | DevelopersIO

「プロビジョニング済みキャパシティモードはlimit exceededエラーになる可能性がある点がつらいので、よほど余裕があるかエラーが起きても構わないものでもない限り、ユーザーリクエストベースのクエリに使うのは怖い」「ごもっともです」「TerraformやCloudFormationのログ置き場とか、バッチ処理のログ置き場に使うなら、プロビジョニング済みキャパシティモードでもまず制限は超えないと思います」

参考: AWS CloudFormation(テンプレートを使ったリソースのモデル化と管理)| AWS

「一般にDynamoDBは、productionに導入する前に自分である程度触っておくのがよいと思います」「そうですね」「いきなり使うと想定外の事態に出くわす可能性がありますし、DynamoDBのクエリ形式は特殊なので慣れないと使いにくいかもしれません」「DynamoDB、いろいろ懐かしいです」「DynamoDBはAWSの中でも歴史が相当長いサービスで、公開前にもAWS内部で相当使われていたはずですね」

🔗 その他DB


つっつきボイス:「遅れてきたエイプリルフールねたで恐縮ですが、SQLクエリにPLEASEを付けないと遅くなるというジョーク記事に、後から続々とそれを実装しましたという追記が伸びてました」「そうそう、SQLは問い合わせ(query)のための言語ですからポリティカルコレクトネスということにしておく感じで」

🔗 pleaseと英語のニュアンスよもやま話

「ところで上の記事では誰も触れていなかったんですが、実は英語のニュアンスとしては、命令形にpleaseを付けても丁寧になるわけではないんですよ」「えぇ!そうなんですか?」「たしかにplease付けても命令形であることは変わらない😆」「丁寧に言うときはWould you~とかCould you〜で始めたりしますよね」

参考: 【英語で依頼】丁寧さを使い分け!ネイティブがお願いに使う表現と、ビジネスメールの依頼文

「日本人としてはついpleaseを日本語の『〜してください』と同じように考えがちなんですけど、どちらかというと『お願いだからやって!』みたいな強調のニュアンスに近くて、結局命令には変わりないそうです」「やべぇ、気をつけようっと😅」「逆に英語の命令形だからといって必ずしもぞんざいとも限らなかったりするのがややこしい…」「しょうがないですよね、自然言語だから」「結局文脈次第ですね」


「それで思い出したんですが、以前ある技術書籍の翻訳許可をメールで出したときにすごく丁寧なお断りメールをもらったことがあって、文面で1箇所たりともnotが使われていないのに全体としてはちゃんとお断りになっていて、これがビジネス英語かと逆に感心した覚えがあります」「notを使わない、わかる!」「ご多幸をお祈りしますメールみたいな」「イギリス英語はそういう婉曲的表現が発達してる印象ありますよね」「イギリス英語はよくわかってないんですがそう思います」

参考: イギリス英語と京都弁の共通点(小野昌弘) - 個人 - Yahoo!ニュース

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

🔗 Lambda@Edgeの課金変更


つっつきボイス:「Lambda@Edgeの課金の時間が1msec単位になった!」「お〜」

「AWS CloudFrontでBASIC認証を使おうとするとLambda@Edgeに置くしかないんですけど、BASIC認証の処理は数行しかないので一瞬で終わるんですよ」「たしかに10msecもかからなさそう😆」「そんなふうに50msecもかからない処理って割とよくあるので、50msec分も課金されなくなったのはありがたい🙏

参考: Basic認証 - Wikipedia

「Lambdaも少し前にミリ秒単位課金になったので、ごく短時間で終わる処理を書いたときの課金の違いは大きいですね」

参考: 料金 - AWS Lambda |AWS

🔗言語/ツール/OS/CPU

🔗 ジュニアと達人

SuperPaintman/the-evolution-of-a-go-programmer - GitHub

# 同リポジトリより: ジュニアの場合
package fac

func Factorial(n int) int {
    res := 1

    for i := 1; i <= n; i++ {
        res *= i
    }

    return res
}
# 同リポジトリより: シニアおよびRob Pikeの場合
package fac

// Factorial returns n!.
func Factorial(n int) int {
    res := 1

    for i := 1; i <= n; i++ {
        res *= i
    }

    return res
}

つっつきボイス:「Go言語で書かれた素朴な処理なんですが、ジュニアが書いたコードとGoの作者であるRob Pikeのコードがコメント以外まったく同じになっていてウケました」「なるほど、ジュニアの愚直なコードが、関数型やジェネリクスを覚えたりマルチスレッド化対応やasyncをやったりするうちにどんどん複雑になっていく、あるある」「これは面白い😆」「同じコードをRob Pikeが書くと何も言われないのに、ジュニアが書くとレビューで修正依頼が来たりして」「誰が書いたかで変わるのもあるあるですね」

「ちなみにこれは前からあるネタですね↓」「なるほど!これをGo言語でもじったのか」「高校生、大学生、新人と進んで、マスターになるとスゴいのを書いてる」「そしてグルはecho "Hello, world."だけでおしまい😆

参考: The Evolution of a Programmer

「その下の管理職のコードをよく見ると、部下にやっといてメールを出してるだけじゃないですか」「最後のChief Executiveのコード↓が最高」「その辺も含めてもろもろネタですね」

# 同サイトより: 役員の場合
  % letter
  letter: Command not found.
  % mail
  To: ^X ^F ^C
  % help mail
  help: Command not found.
  % damn!
  !: Event unrecognized
  % logout

🔗 その他

🔗 くずし字を読み取るアプリ


つっつきボイス:「お〜、きれい✨」「くずし字を読み取るとふわっとタブレットに文字が浮かび上がるあたりが、Outer Wildsというゲームで古代種族が石版に記録した文字を古代文字スキャナーで読み取るシーンを思わせますね↓」「Outer Wildsもう終わったんですか?」「まだです: このゲームはネタバレされる前にやっとくのがおすすめ」「よ〜し」

参考: 22分後、太陽系は消滅する。宇宙を舞台にしたオープンワールド探索アドベンチャー『Outer Wilds』がNintendo Switchで2021年夏配信決定。 | トピックス | Nintendo

🔗 VSCodeで小説支援

ttrace/vscode-language-japanese-novel - GitHub


つっつきボイス:「VSCodeで小説!」「こんな拡張があるんですね」「青空文庫記法の対応とか便利そう」「こちらの@ttraceことSF作家の藤井太洋氏は、実は大学で3つ下ぐらいの後輩でして」「へ〜!」「デビュー作の『Gene Mapper』という小説は、たしか3.11をきっかけに通勤電車の行き帰りにiPhoneで書いたそうです」

参考: 藤井太洋 - Wikipedia

「しかも小説を書く前は、むしろShade↓という3Dモデリングソフトの専門家というかエバンジェリストとしてその道では有名だったそうです」「Shadeって今も使われてますよね」「そうそう」「大学の頃、彼がAppleの初代ノートパソコンのPowerbookを背中にしょったまま3Dレンダリングしながら学内を駆け回ってたのを思い出します」

参考: Shade3D 公式 | 3DCGソフトウェア
参考: PowerBook - Wikipedia


後編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210419前編)RailsのN+1クエリを定番以外の方法で修正する、GitLabのセキュリティ修正リリースほか

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

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

Ruby Weekly

DB Weekly

db_weekly_banner

docker-composeを便利にするツール「dip」を使ってみた

$
0
0

更新履歴

  • 2019/11/27: 初版公開
  • 2020/05/01: サブコマンド実行部分に追記
  • 2021/04/14: dip 7.0に合わせて更新

dipとは

dipは、Rails開発会社のEvil Martiansのメンバーが作った、docker-composeでの作業を能率的に行えるツールです。dipはDocker Interaction Processの略だそうです。

dipはRubyで書かれているのでRuby実行環境が必要です。元々Rails向けに作られたツールのようですが、Evil MartiansのReactアプリにもdip.xmlがあることからわかるように、Railsに限らず一般のdocker-composeでも使えます。私の場合はローカル開発環境でのみ使っています。

追記(2021/04/14)

以前は単独の実行可能バイナリ版dipもリリースされていましたが、バージョン7からは廃止されました(参考: dipインストール方法)。これに伴い、gemコマンド以外のインストール手順を削除しました。

Evil Martiansはdipの記事を書くと言ってましたが、未だに出ていないので自分で記事を書いてみました😆

追記(2021/04/14)

その後Evil Martiansもdip記事を公開しました。

dipのREADMEにはシェルと統合してdipを省略する方法も書かれていますが、ちょいやりすぎ感あったので自分はやっていません。

必要なもの

  • Ruby 2.5以上
  • Dockerとdocker-composeがインストールされていること
  • docker-compose.ymlファイル

インストール

以下の方法でdipをインストールし、dip.ymlをdocker-compose.ymlと同じ階層に置きます。

gemコマンドでのインストール

gem install dip

dip.ymlの設定

dipリポジトリのREADMEにある以下のdip.ymlを自分の用途に応じてカスタマイズします。interaction:の下のサブコマンドを適宜カスタマイズします。

# Required minimum dip version
version: '7.0'

environment:
  COMPOSE_EXT: development

compose:
  files:
    - docker/docker-compose.yml
    - docker/docker-compose.$COMPOSE_EXT.yml
    - docker/docker-compose.$DIP_OS.yml
  project_name: bear

interaction:
  shell:
    description: Open the Bash shell in app's container
    service: app
    command: bash
    compose:
      run_options: [no-deps]

  bundle:
    description: Run Bundler commands
    service: app
    command: bundle

  rake:
    description: Run Rake commands
    service: app
    command: bundle exec rake

  rspec:
    description: Run Rspec commands
    service: app
    environment:
      RAILS_ENV: test
    command: bundle exec rspec

  rails:
    description: Run Rails commands
    service: app
    command: bundle exec rails
    subcommands:
      s:
        description: Run Rails server at http://localhost:3000
        service: web
        compose:
          run_options: [service-ports, use-aliases]

  sidekiq:
    description: Run sidekiq in background
    service: worker
    compose:
      method: up
      run_options: [detach]

  psql:
    description: Run Postgres psql console
    service: app
    default_args: db_dev
    command: psql -h pg -U postgres

  clean_cache:
    description: Delete cache files on the host machine
    command: rm -rf $(pwd)/tmp/cache/*

provision:
  - dip compose down --volumes
  - dip clean_cache
  - dip compose up -d pg redis
  - dip bash -c ./bin/setup

なおprovision:というコマンドは固定されているらしく、他の名前にできませんでした。

自分のカスタマイズ例

これは以下の記事で自分が使っているdip.ymlです。

Rails 6.1のDocker開発環境構築をEvil Martians流にやってみた(更新)

dip bashdip rubocopは長いのでdip shdip copにしてみました。

なおtest:というサブコマンドは予約語なのか認識できなかったので、minitest:としました。

version: '7.0'

environment:
  RAILS_ENV: development

compose:
  files:
    - docker-compose.yml

interaction:
  sh:
    description: Open the app container bash shell
    service: backend
    command: /bin/bash
    compose_run_options: [no-deps]

  bundle:
    description: Run bundler command
    service: backend
    command: bundle
    compose_run_options: [no-deps]

  rake:
    description: Run rake command
    service: backend
    command: bundle exec rake

  rails:
    description: Run rails command
    service: backend
    command: bundle exec rails
    subcommands:
      s:
        description: Start rails server
        service: rails
        compose_run_options: [service-ports]

  yarn:
    description: Run yarn command
    service: backend
    command: yarn
    compose_run_options: [no-deps]

  minitest:
    description: Run minitest
    service: backend
    environment:
      RAILS_ENV: test
    command: bundle exec rails test

  cop:
    description: Run rubocop
    service: backend
    default_args: -a
    command: bundle exec rubocop
    compose_run_options: [no-deps]

  psql:
    description: Run psql console
    service: postgres
    command: psql -h postgres -U postgres -d postgres

  inspect_all:
    description: Run all checkers
    service: backend
    command: bundle exec license_finder
    command: bundle exec rails test
    command: bundle exec rails_best_practices .
    command: bundle exec bundle-audit
    command: bundle exec brakeman

provision:
  - dip compose up -d postgres
  - dip compose up -d webpacker
  - dip yarn install --check-files
  - dip rails db:prepare
  - dip rails db:prepare RAILS_ENV=test

使い方

dip.ymlのあるディレクトリでdip lsすると、dip.ymlで設定したサブコマンドの一覧が表示されます。dip helpとするとさらに詳しいヘルプが表示されます。

$ dip ls
sh           # Open the app container bash shell
bundle       # Run bundler command
rake         # Run rake command
rails        # Run rails command
rails s      # Start rails server
yarn         # Run yarn command
minitest     # Run minitest
cop          # Run rubocop
psql         # Run psql console
inspect_all  # Run all checkers

あとはdip サブコマンドを実行するだけです。

サブコマンドにはdip bundle installdip rails consoleのようにオプションも追加できます。

dip sshdip nginxなど凝った機能がいろいろあるようですが、その辺りはまだ追求していません。

dipの便利な点

Dockerが公式にこういうツールを出せばいいのにと思っちゃいました。

1. コンテナにログインせずに作業できる

bundle installrails dbconsoleyarn install --check-filesといった決まりきったコマンドを実行するのに、いちいちシェルでログインするのは面倒です。また、Dockerのシェルの安定性にもちょっと不安があります。

dipならdip bundle installdip rails dbconsoledip yarn install --check-filesで実行できます。

なおGemfileがない状態でのrails newはさすがにログインが必要でした。

2. コンテナの起動状態を気にしなくてよい

docker-composeでコンテナに入る場合、コンテナが起動していなければdocker-compose run app bash、コンテナが起動していればdocker-compose exec app bashとするなど、面倒な使い分けが必要になります。

そもそもdocker-composeというコマンドが長すぎですよね。自分はdcomというbashエイリアスを作っているほどです。

dipなら、たとえば自分の設定したdip shdip rails sはコンテナが起動していなくても使え、dipが終了すればコンテナも終了してくれます。コンテナの二重起動を気にしなくてよくなるのは本当にありがたいです😂

その分起動は少々遅くはなると思います。

まあ自分は何か作業した後はdcom downを実行する癖が付いてしまいましたが。

3. 複数のサブコマンド実行が書きやすい

追記(2020/05/01)この方法は現在使えなくなったようです😢
追記(2021/04/22)dipバージョン7で再び使えることを確認しました。

docker-composeの中でシェルコマンドを複数実行しようとすると、たとえば以下のように&&でつなげてbash -cで実行するというダルい書き方になってしまいます。

command: bash -c "elasticsearch -d && elasticsearch --http.port=9250"

コマンドを5つも6つもつなげると美しくありませんし、シェルスクリプトに追い出すのも何だか敗北感があります。

dipなら自分のサブコマンドにcommand:を複数書けます。ただしこの方法はドキュメントに見当たらないので、サポート外機能のつもりで使っています。

  inspect_all:
    description: Run all checkers
    service: backend
    command: bundle exec license_finder
    command: bundle exec rails test
    command: bundle exec rails_best_practices .
    command: bundle exec bundle-audit
    command: bundle exec brakeman

4. Dockerfileやdocker-compose.ymlの変更が基本的に不要

dip.ymlはDockerfileやdocker-compose.ymlと別に設定できるので、うかつにこれらを変更してチームの人に怒られずに済みます。dip.ymlを~/.gitignore_globalに加えておけば自分用に心ゆくまでカスタマイズできます。

試していませんが、さすがにsshログイン機能などを使う場合はdocker-compose.ymlの変更も必要そうです。

services:
  web:
    environment:
      - SSH_AUTH_SOCK=/ssh/auth/sock
    volumes:
      - ssh-data:/ssh:ro

volumes:
  ssh-data:
    external:
      name: ssh_data

おたより発掘

関連記事

クジラに乗ったRuby: Evil Martians流Docker+Ruby/Rails開発環境構築(翻訳)

テストの抽象化を増やしすぎてはいけない理由(翻訳)

$
0
0

概要

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

テストの抽象化を増やしすぎてはいけない理由(翻訳)

はじめに

コードには「プロダクションコード」と「テストコード」があります。プロダクションコードとは、コードベースのうち本番環境で実行される部分を指し、同様にテストコードはテスト環境でのみ実行されるコードベースの部分を指します。プロダクションコードの予行演習はテストコードを介して行われます。

テストコードは「明快で」「読みやすく」「何が行われているのか理解しやすい」ものでなければなりません。shared exampleのようなテストの抽象化は、必ずしもテストコードで不可ではありません。

テストコードをDRYにしない理由

プロダクションコードのテストをむやみに抽象化すると、リファクタリングが難しくなります。その理由は、1個のテスト抽象化がテストコードの複数の場所で再利用されるからです。そのようなテストを変更すると、テストの他のどこかが壊れる可能性があります。しまいには、面倒な抽象化部分でテストのリファクタリングが発生したり、下手をすると抽象化が合わなくなって失敗するようになってしまったテストを更新して、抽象化を消し去るはめになったりします。

失敗を含むテストは読みにくいものです。抽象化を用いるテストが抽象化の内部で失敗すると、失敗した場所がレポートの冒頭に表示されますが、どのテストが実際に失敗しているかを突き止め、どのテストが抽象化を呼び出したかを確認するには、開発者がスタックトレースの隅々まで目を通す必要があります。

以下はRSpecのShared Exampleの出力です。

Finished in 3.17 seconds (files took 2.49 seconds to load)
47 examples, 5 failures

Failed examples:

rspec ./spec/requests/api/v1/users_spec.rb[1:2:2:2:1:1:1] # Api::V1::Users PATCH #update behaves like ...
rspec ./spec/requests/api/v1/users_spec.rb[1:2:4:1:1:1] # Api::V1::Users PATCH #reset_change_email behaves like ...
rspec ./spec/requests/api/v1/users_spec.rb[1:2:5:1:1:1:1] # Api::V1::Users PATCH #request_remove_user behaves like ...
rspec ./spec/requests/api/v1/users_spec.rb[1:2:1:1:1:1] # Api::V1::Users GET #show behaves like ...
rspec ./spec/requests/api/v1/users_spec.rb[1:2:3:1:1:1] # Api::V1::Users PATCH #reset_api_key behaves like ...

失敗の発生箇所をずばり当てたらビールおごりたい

どうか誤解なさらないよう

テストコードに多数の抽象化を導入するというのは、ちょうど読んでいる本の一字一句すべてにカラーマーカーで線を引くようなものです。マーカーで強調したかったのは最も重要な部分だけのはずですよね。

テストコードも同様です。私たちがやりたいのは、テストを隅々まで抽象化することではなく、基本的にできる限り明快かつ読みやすいベタなテストコードを書き、抽象化はテストケースのうち重要性が低い部分で行うことです。たとえば、テストコード用のオブジェクトを作成するFactoryパターンは(訳注: 適切な抽象化の)例として申し分ありません(FactoryBot)。カスタムマッチャーも同様に、テストコードを明快かつ宣言的に保ちつつテストに抽象化を導入するまっとうな手法です。

要するに、テストコードのうちで重要性の低い部分を抽象化するのは「あり」ですし、実際にとても有用です。逆に、システムの実際の振る舞いを記述するテストコードに抽象化を持ち込むと決してよい結果を生みません1

関連記事

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


  1. 私が先月shared exampleを1本書いたときに、コードレビューで指摘を受けたおかげで命拾いしました。 

週刊Railsウォッチ(20210426前編)Hotwireの詳細な解説記事3本、Rails 7に入る予定の機能ほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

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

今回は以下のコミットリストのChangelogを中心に見繕いました。

🔗 disable_joins:オプションが追加

  • 関連付けのjoinsを無効にするオプションを追加

マルチプルデータベースでは、関連付けをデータベース間でjoinできない。
Railsでこのオプションを設定すると、関連付けのJOINを生成する代わりに、2つ以上のクエリを生成するようになる。

このオプションは、以下のようにhas_namy through関連付けに設定する。

class Dog
  has_many :treats, through: :humans, disable_joins: true
  has_many :humans
end

これにより、SQLでJOINを生成するのではなく@dog.treatsで2つのクエリが使われる。

SELECT "humans"."id" FROM "humans" WHERE "humans"."dog_id" = ?  [["dog_id", 1]]
SELECT "treats".* FROM "treats" WHERE "treats"."human_id" IN (?, ?, ?)  [["human_id", 1], ["human_id", 2], ["human_id", 3]]

Eileen M. Uchitelle, Aaron Patterson, Lee Quarella
changelogより大意


つっつきボイス:「disable_joinsはマルチプルデータベースでは欲しい機能でしょうね」「Active Recordが賢くJOINを生成するのを黙らせるのね」

「コミットメッセージを見ると、GitHubの中で使っているコードを切り出したと書かれてました↓」「お〜、GitHubもRailsのマルチプルデータベース機能を使ってるのかな?」

このコードはGitHub内部で用いているgemから切り出したものである。つまりこの実装はproduction環境で常用しているものであり、実験的コードではない。
同コミットメッセージより

「たしかPostgreSQLやMySQLでは同一RDBMS上のデータベース間でJOINが可能ですね」

参考: 4 Methods for joining data from multiple PostgreSQL databases
参考: mysql - Joining two tables from different Databases [SOLVED] | DaniWeb

🔗 新機能: Enumerable#soleが追加

ActiveRecord::FinderMethods#soleに対応するEnumerable#soleを追加。
このメソッドは、enumerableから1個だけ項目を返す。項目がない場合や2個以上の場合はエラーになる。
Asherah Connor
同Changelogより


つっつきボイス:「そういえばActive Recordにもsoleが追加されていましたね(ウォッチ20210112)」「あったあった」「それのEnumerable版なのか」「1件だけあるかどうかをチェックするコードは自分でもよく書くので、Enumerableにもこの機能があるのはいいですね👍」「知っておくとありがたいメソッドになりそう」

「Active Recordのsoleは該当が1件でない場合はエラーになるんですね」「たしかメソッド名をどうするかが議論になってfind_sole_byfind_solely_byなどの候補が出ていた覚えあります」「そうそう」

🔗 ロケールファイルのパスをデフォルトで再帰探索するようになった


つっつきボイス:「デフォルトのロケール読み込みパスが*.{rb,yml}から**/*.{rb,yml}に変わってyamlの再帰読み込みが可能になったんですね↓」「そういえば今までは手動でパスを書いていたかも」「/en//ja/のように言語ごとにロケールのyamlファイルをネストする方が便利ですし、既にベストプラクティスとしてそう書いている人も多いと思うので、再帰読み込みの方をデフォルトにしようということでしょうね」

# railties/lib/rails/engine/configuration.rb#L57
          paths.add "config"
          paths.add "config/environments", glob: "#{Rails.env}.rb"
          paths.add "config/initializers", glob: "**/*.rb"
          paths.add "config/locales",      glob: "*.{rb,yml}"
          paths.add "config/locales",      glob: "**/*.{rb,yml}"
          paths.add "config/routes.rb"
          paths.add "config/routes",       glob: "**/*.rb"

🔗Rails

🔗 willnetさんのHotwire記事


つっつきボイス:「これ、とてもいい記事だと思いました」「そうそう、Hotwireそのものの解説も丁寧ですし、Turbolinksが流行らなかった理由や歴史的経緯も詳しく書かれていてありがたい: Hotwireとは何かという解説だけだとわかりにくくなりがち」「たしかに」

「たしか続き物記事でしたよね?」「はい、『次回に続く』となっていました」「これは楽しみ😋

同記事で紹介されていたwillnetさんの以下の記事もよさそうですね。

参考: TurbolinksからTurboへの移行 - おもしろwebサービス開発日記


速報: Basecampがリリースした「Hotwire」の概要

🔗 Evil MartiansとEngine YardのHotwire記事


つっつきボイス:「こちらはEvil MartiansによるHotwireの解説記事ですね」「あとEngine YardもHotwire推しの記事を書いているのを見つけました↓」

「今週はHotwire記事が3本も出揃いましたね」「Hotwireのように、HTMLを動的に部分更新をするときにもHTML partialのレンダリングは全部サーバーサイドでやりたいというのは、クライアントJavaScriptとサーバーサイドViewの間をあっちこっち行き来して苦労したことのある人なら一度は思ったことがあるんじゃないかな」「たしかに」「実際似たようなことは昔から行われていて、render_async gem↓もそれに近いと思いますし、大昔のRailsでもlink_to_remoteでパーシャルを直接取得してDOMを更新するなど、泥臭い感じでやってましたね」「ふむふむ」

Rails: render_async gemでレンダリングを高速化(翻訳)

参考: jQueryで link_to_remoteを使う - satake7’s memo

「その意味でHotwireはまったく新しい技術ではないと思いますが、以前は個別に泥臭くやっていた方法を統合された方法で、かつモダンなHTMLでやれるのがメリットなのだろうと考えています」「なるほど!」

🔗 Rails 7に入る機能(Hacklinesより)


つっつきボイス:「Rails 7にどんな機能が入るかを現時点でまとめた記事だそうです」「Railsウォッチで見たことのあるものが大半のようですが、知らない機能もありそうかな」


「画像をlazy loadingする機能が入る↓」「imgタグにloading="lazy"属性を追加できるんですね: ブラウザの画像lazy loading機能を有効にするだけなのでRailsアプリケーションが頑張る話ではなかったか」

参考: Chromeに実装される新機能『loading属性』について解説、ついにブラウザがネイティブで遅延ロードをサポート | コリス


「お、Active Recordにinvert_whereが入るのか↓」「これは知らなかった」「スコープのWHERE条件にNOT ()を付けるんですね」「使うかどうかは微妙かな」

# 同記事より
good_students = Student.where(grade: 80..100)
# SELECT \"students\".* FROM \"students\" WHERE \"students\".\"grade\" BETWEEN 80 AND 100

bad_students = good_students.invert_where
# SELECT \"students\".* FROM \"students\" WHERE NOT (\"students\".\"grade\" BETWEEN 80 AND 100)

has_one: throughも使えるようになるのね↓」「今までできなかったのか!」

# 同記事より
class Dog
  has_many :toys
  has_one :toy_box
end

class Toy
  belongs_to :dog
  has_one :toy_box, through: :dog
end

class ToyBox
  belongs_to :dog
end
toy.build_toy_box
# <ToyBox:0x00007f572007e170 id: nil, dog_id: 3>

toy.create_toy_box
# <ToyBox:0x00005601f2ac09a0 id: 5, dog_id: 3>

「新機能はRails 7が出るまでにまだまだ増えたり変わったりするので、そのつもりで読むのがよいと思います」「そうですね」

🔗 ActiveModel::Attributesの隠された型システム


つっつきボイス:「Active Modelの内部にはビルトイン型がある↓という記事ですね: 内部を解説しているのは貴重かも」「カスタム型の登録をたまに自力でやることがありますけど、Active Model wayでカスタム型を登録できるんですね」「昨年1月の記事ですが、このあたりは大きく変わらないと思うのでよいと思います👍

# 同記事より
register(:big_integer, Type::BigInteger)
register(:binary, Type::Binary)
register(:boolean, Type::Boolean)
register(:date, Type::Date)
register(:datetime, Type::DateTime)
register(:decimal, Type::Decimal)
register(:float, Type::Float)
register(:immutable_string, Type::ImmutableString)
register(:integer, Type::Integer)
register(:string, Type::String)
register(:time, Type::Time)

前編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210420後編)ShopifyのJITコンパイラYJIT、PicoRuby、DynamoDBの3つの制約ほか

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

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

Rails公式ニュース

Ruby Weekly

Hacklines

Hacklines

Viewing all 1100 articles
Browse latest View live