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

週刊Railsウォッチ(20190304-1/2:前編)Rails 6.0.0 beta2リリース、Ruby 2.7の新しい記法、各種自動レビューツール、ULIDとはほか

こんにちは、hachi8833です。Railsウォッチ制作がそろそろ本気で身が持たないので、今回からウォッチを分割公開することにしました。今回は前編となります。引き続きよろしくお願いします🙇。

しかしそう決めた途端、何とつっつきの録画に失敗してしまいました...😇。可能な限り記憶を元に再現します🙏。

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを社内有志でつっついたときの会話の再構成です👄
  • 毎月第一木曜日に「公開つっつき会」を開催しています: お気軽にご応募ください

週刊Railsウォッチ「公開つっつき会#8」開催のお知らせ

次回は今週の3/7(木)開催です。参加枠は余裕あります。鍋でもつっつくようなお気軽なご参加をお待ちしております🙇。

臨時ニュース: Railsガイドが5.2に対応

半年以上前から更新翻訳をすすめていましたが、このたびリリースいただきました🙇。次の目標はRails 6ですね。

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

Rails 6.0.0 beta2リリース


rubyonrails.orgより


つっつきボイス:「出ましたね🎉」「来たか(すっく)」「予定では「February 1: Beta 2.」「March 1: Release Candidate 1」なのでまずまずですね☺️」

beta2のZeitwerk情報

Rails 6 Beta2時点のZeitwerk情報(要訳)


つっつきボイス:「先行して上の要訳記事を出しました↑」「社内でもオートローダーの話からモジュール/クラスを入れ子にしたときの名前空間の話になったりしましたね」

このときrequire_dependencyについてもSlackで話題になったのですが、私がまだまとめきれないのでウォッチ20181022をどうぞ。

参考: 7 require_dependency -- 定数の自動読み込みと再読み込み - Rails ガイド

その後autovivifiedの話題になりました。

参考: fxn/zeitwerk: Efficient and thread-safe code loader for Ruby -- implicit namespaces
参考: Autovivification - Wikipedia

Perlにおけるautovivificationとは、未定義の値が参照解決(dereference)されるたびに新しいarrayやhashを自動生成すること。
Wikipediaより大意

参考: autovivificationとうまく付き合う - Qiita

以下のように、素のRubyのirbでMyNameを定義してない状態からいきなりMyName::MyModelクラスを定義すると1行目でいきなりエラーになることもわかりました。

class MyName::MyModel #=> `<main>’: uninitialized constant MyName (NameError)
  def hello
    'hello world'
  end
end

puts MyName::MyModel.new.hello

なお、Railsでもコンソールで定数Fooが未定義のままFoo::Barclassで定義するのはさすがに無理でした。

アウターボイス:「忘れがちだけど、クラスの継承関係と名前空間はまったく別物なのよね😎」「あー言われてみれば🤭」

参考: Railsでモデルクラスの名前をネームスペースとして流用してはいけません(warning: toplevel constantが発生します) - Qiita

generated_relation_methodsをリファクタリング

# activerecord/lib/active_record/relation/delegation.rb#L3
+require "mutex_m"
+
module ActiveRecord
  module Delegation # :nodoc:
    module DelegateCache # :nodoc:
...
      def inherited(child_class)
        child_class.initialize_relation_delegate_cache
        super
      end

+     def generate_relation_method(method)
+       generated_relation_methods.generate_method(method)
+     end
+
...
      private
        def generated_relation_methods
-         @generated_relation_methods ||= Module.new.tap do |mod|
-           mod_name = "GeneratedRelationMethods"
-           const_set mod_name, mod
-           private_constant mod_name
-         end
+         @generated_relation_methods ||= GeneratedRelationMethods.new
        end
+   end
+
+   class GeneratedRelationMethods < Module # :nodoc:
+     include Mutex_m
+
+     def generate_method(method)
+       synchronize do
+         return if method_defined?(method)

-       def generate_relation_method(method)
          if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method)
-           generated_relation_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
+           module_eval <<-RUBY, __FILE__, __LINE__ + 1
              def #{method}(*args, &block)
                scoping { klass.#{method}(*args, &block) }
              end
            RUBY
          else
            generated_relation_methods.define_method(method) do |*args, &block|
            define_method(method) do |*args, &block|
              scoping { klass.public_send(method, *args, &block) }
            end
          end
        end
      end
    end
    private_constant :GeneratedRelationMethods

つっつきボイス:「コードのかぶりをなくすリファクタリング」「require "mutex_m"なんてのを使ってますね」「排他制御が必要になるからですね☺️」

参考: module Mutex_m (Ruby 2.6.0)

「そういえばmutexってmutual exclusionの略だった覚えが: 相互排他というか」「マジで😆: ずうっとミューテックスはミューテックスだと思ってたし」

mutexとくればセマフォもよく登場しますね。遠い昔、職場で日本語ペラペラの米国人エンジニアがクライアント/サーバー通信の文脈で「セマフォ」という言葉を使ったときに思わず「それ何ですか?」と聞き返してしまったのを思い出しました。「まあ信号機みたいな」と説明されましたが、ネットのない時代につき本を買うまで結局よくわからずじまいでした😅。

参考: セマフォ - Wikipedia

語源となった本物のセマフォ↓です。

これまたついでに、昔の単線鉄道では、列車の衝突を回避するために「タブレット」というものを一種のトークンのように使っていたんだそうです。タブレットを所有している列車のみがその区間を通過できるというルールで、受け渡しのときに落っことすこともちょくちょくあったとか。コンピュータがない時代の苦肉の策的な排他制御というか。

参考: タブレットとは - はてなキーワード
参考: トークン - Wikipedia
参考: トークンリング - Wikipedia -- ほぼ消えましたね😇

もっとも鉄道のトークンにもいろんな意味があったりして、イギリスやニューヨークあたりの地下鉄のチケットもそう呼ばれてたと思います。

テンプレートレンダリングの改良

このコミットではテンプレートオブジェクトのビルド時にlocalsを渡し、次にテンプレートオブジェクト側でlocals=ミューテーターメソッドを削除するようにした。意図としては、decorateメソッドでlocalsを用いてテンプレートオブジェクトを改変して欲しくないということである。
#35408#35406と同様。
同PRより大意

# actionview/lib/action_view/template/resolver.rb#L203
    private

-     def find_templates(name, prefix, partial, details, outside_app_allowed = false)
+     def find_templates(name, prefix, partial, details, outside_app_allowed = false, locals)
        path = Path.build(name, prefix, partial)
-       query(path, details, details[:formats], outside_app_allowed)
+       query(path, details, details[:formats], outside_app_allowed, locals)
      end

つっつきボイス:「テンプレート周りでlocalsを渡すようになったのね」「地の文でlocalsって書いてあると割と何だかわからない😅」

# actionview/test/template/template_test.rb#L
  def test_refresh_with_partials124
-   @template = new_template("Hello", virtual_path: "test/_foo")
-   @template.locals = [:key]
+   @template = new_template("Hello", virtual_path: "test/_foo", locals: [:key])
    assert_called_with(@context.lookup_context, :find_template, ["foo", %w(test), true, [:key]], returns: "partial") do
      assert_equal "partial", @template.refresh(@context)
    end
  end

STIのサブクラスでfind_byをキャッシュしないようにした

> STIのサブクラスでfind_byをキャッシュするのは、type IN (?,?,?,?)の部分が動的であるため安全ではない。なお、STIサブクラスが作成または削除されるときにSQLステートメントのキャッシュを無効にすることまでは当面行わない。

同コミットより大意

フィードバックありがとうございます!😂。

# activerecord/lib/active_record/core.rb#L184
      def find_by(*args) # :nodoc:
-       return super if scope_attributes? || reflect_on_all_aggregations.any?
+       return super if scope_attributes? || reflect_on_all_aggregations.any? ||
+                       columns_hash.key?(inheritance_column) && !base_class?

        hash = args.first

        return super if !(Hash === hash) || hash.values.any? { |v|
          StatementCache.unsupported_value?(v)
        }
        # We can't cache Post.find_by(author: david) ...yet
        return super unless hash.keys.all? { |k| columns_hash.has_key?(k.to_s) }
        keys = hash.keys
        statement = cached_find_by_statement(keys) { |params|
          wheres = keys.each_with_object({}) { |param, o|
            o[param] = params.bind
          }
          where(wheres).limit(1)
        }
        begin
          statement.execute(hash.values, connection)&.first
        rescue TypeError
          raise ActiveRecord::StatementInvalid
        end
      end

Rails

自動レビューツールいろいろ


prontolabs/prontoより


gitcolony.comより


haya14busa/reviewdogより


reviewable.ioより


codeclimate.comより


つっつきボイス:「前回のウォッチでVelocityやGitPrimeやSiderの話から自動レビューツールの話になったのと(ウォッチ20190225)、その後チームミーティングでも自動レビューツールが検討され始めたので取り急ぎ集めてみました」「お、ちょうどいいタイミングですね👌🏽」「prontoやreviewdogはどことなく手作り感あって暖かみありそう🥘」「reviewdogのシンボルマークがいらすとやさんにしか見えない件😆」「エッジのざらつき感がそこはかとなくそれっぽいし😆」「Siderよさげだったんで軽く問い合わせてみたんだけどGitLabへの対応予定は今のところないんですって😭」「BPSはGitHubも使ってるけどGitLabがメインですしね」「さてどれにしようかな🤔」


sider.reviewより

Rails 6でenum値用の負のスコープを持つメソッドが追加(RubyFlowより)

# 同記事より
# アクティブでない全デバイスの取り方
# 従来の書き方
Device.where.not(status: :active)
# 今後はこう書ける
Device.not_active

# 止められていないすべてのデバイス
# 従来の書き方
Device.where.not(status: :disabled)
# 今後はこう書ける
Device.not_disabled

つっつきボイス:「enumのスコープがネガティブというのがよくわからなくて😅」「enumで上のようにwhere.not(status: :active)と書く代わりにnot_なんちゃらで書けるということですね☺️」

その後公式情報にも出ました↓。

参考: reselect, negative enum scopes and more! | Riding Rails

この頃流行りのULIDとは

以下のツイートで知りました。


つっつきボイス:「ユニバーサリーユニークレキシコグラフィカリィソータブルアイデンティファイアー」「長っ🐍」「ソートできるUUIDっていいですね😋」「↓こんな感じのUUIDになって、しかも大文字小文字も区別しないし記号も使わないからURLに使っても安心ということみたいです」「いいじゃないの~🥰」「リポジトリは仕様しか載ってませんが、既にいろんな言語で続々実装されてるみたいです」

ulid() // 01ARZ3NDEKTSV4RRFFQ69G5FAV

「タイムスタンプとランダムでビットが分かれてるのね」

  01AN4Z07BY      79KA1307SR9X4MV3

 |----------|    |----------------|
  Timestamp          Randomness
   48bits             80bits

「お〜、IとかLとかOとかUみたいな見た目に紛らわしい文字を排除してるのはとてもいい👍」「Webサービスのパスワードなんかもシリアルコード(ソシャゲの事前登録とか)なんかもこの点に配慮しておくと問い合わせがぐっと減りますし😋」

以下のようにCrockfordのBase32を用いる。I、L、O、Uのような紛らわしい文字や乱用される可能性のある文字を排除する。
0123456789ABCDEFGHJKMNPQRSTVWXYZ <= この文字だけを使う

参考: Base32 Encoding -- Douglas Crockford

Crockford氏のサイトを見ると、IやLやOがいろいろ紛らわしいのはわかるのですが、UはVと紛らわしいのかなと思いきや、accidental obscenity(はずみで下品な意味になる)という説明が気になりました。おそらくf●ckとかs●ckみたいな語を偶然含むULIDが生成されるということだと思うのですが。


crockford.comより

以下の記事でも「そんなの組み合わせ次第でいくらでも下品になるから無理じゃない?」という見解がありますね。

参考: Base36 accidental obscenity - Treating PHP Delusions
参考: Online PHP editor | output for Kc5gN -- U使ってない下品な例

rack-queries: プレビルドされたクエリを高速実行(RubyFlowより)


同リポジトリより


つっつきボイス:「クエリを事前に作っておいてRackで高速実行するとかそんな感じみたいです」「ルーティングみたいなRailsアプリを経由しないから速そうではある🤔」

使い道についてもいろいろ話が出たのですが思い出せません🙇。

追いかけボイス:「rack-queriesのときに話したのは、アプリケーションメトリクス的なものを吐き出させるときに便利じゃないかな?って話でしたね:
単体SQLで数だけ返すようなURL作ってDatadogとかCloudWatchみたいなのに食わせて眺める的な」

🙇。

その他Rails


Ruby

Ruby 2.7で新記法.:が追加

# 同記事より
 # Math.sqrtで数値の平方根を求める

 [1, 4, 9].map(&Math.method(:sqrt))
 # =>  [1.0, 2.0, 3.0]

# 新記法だとこうなる
 [1, 4, 9].map(&Math.:(:sqrt))
 # => [1.0, 2.0, 3.0]

つっつきボイス:「ちょっとわかりにくいですが新記法だそうです」「あ、.:で短く書けるってことか!😳」「見慣れてないせいかもしれないけどちょいキモい😅」「これが欲しいのわかる気がする: method(:シンボル)みたいな書き方って、冗長なのもそうだけど、自分たちエンジニアからすると目がついついmethodの方に行ってしまって割と紛らわしいのよね🤢」「あーなるほど!」「本当にやりたいことにフォーカスするという意味では.:は面白い😋」

CRubyでRTL(Register Transfer Language)(Ruby Weeklyより)

# 同記事より
      getlocal_OP__WC__0 <b index>
      getlocal_OP__WC__0 <c index>
      opt_plus
      setlocal_OP__WC__0 <a index>

つっつきボイス:「RTLってそもそも何だっけのレベルです👶」「RTLはRubyのVM(Virtual Machine)で使われるインストラクションですね」「あ、そういうことか!」「この間も、RubyではVMインストラクションに直結する書き方は速いみたいな話ありましたね」

参考: RubyでRubyVMを実装してRubyVMの気持ちになってみる - Qiita


上のフィードバックをいただきました(ユーザー名にアンダースコア_があるため埋め込みが効きません)。ありがとうございます。至らない点は今後改善いたします🙇。

その他Ruby

  • 元記事: Rubyhack -- ソルトレークシティで開催だそうです(Ruby Weeklyより)


rubyhack.comより


つっつきボイス:「Rubyhackの直後ってそんなに間あかずにRubyKaigi 2019じゃないですか!」「Matzの仕事は旅から旅が多い🛤」「忙しそう...💼」


rubykaigi.orgより




前編は以上です。

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

週刊Railsウォッチ(20190212)EnvoyとIstioに大注目、SQLQLとは、buildkite.comのCI、さよならItanium、PWA vs Androidほか

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

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

Rails公式ニュース

Ruby Weekly

RubyFlow

160928_1638_XvIP4h


CONTACT

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