週刊Railsウォッチ(20181022)Railsの名前空間地獄とrequire_dependency、PostgreSQL 11がリリース、clean-rails.orgほか

こんにちは、hachi8833です。今日のGitHubは何だか不調ですね。

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

臨時ニュース

PostgreSQL 11が正式リリース

つっつきの後でニュースが飛び込んできました。PostgreSQL 10リリースをウォッチでお伝えしたのが2017/10/06なので、わずか1年という驚きのメジャーアップデートですね。

参考: PostgreSQL 11が正式リリース。ハッシュパーティショニングやJITコンパイルによる高速化、ストアドプロシージャでのトランザクションサポートなど - Publickey

Ruby 2.5.3が追加リリース(Ruby公式ニュースより)

2.5.2のパッケージングの問題(#15232)のみを2.5.3で修正したそうです。

TechRacho記事にも反映しました↓。

Ruby 2.5.2→2.5.3/2.4.5/2.3.8リリース(脆弱性修正)

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

今回公式の更新情報がなかったので直近のコミットから見繕いました。ドキュメント(Railsガイド)の更新が目立ちます。

ActiveRecord#respond_to?のアロケーションを削減

#34227と関連しています。

# activerecord/lib/active_record/attribute_methods.rb#L261
    def respond_to?(name, include_private = false)
      return false unless super

-     case name
-     when :to_partial_path
-       name = "to_partial_path"
-     when :to_model
-       name = "to_model"
-     else
-       name = name.to_s
-     end

      # If the result is true then check for the select case.
      # For queries selecting a subset of columns, return false for unselected columns.
      # We check defined?(@attributes) not to issue warnings if called on objects that
      # have been allocated but not yet initialized.
-     if defined?(@attributes) && self.class.column_names.include?(name)
-       return has_attribute?(name)
+     if defined?(@attributes)
+       if name = self.class.symbol_column_to_string(name.to_sym)
+         return has_attribute?(name)
+       end
      end
        true
    end

つっつきボイス:「Active Recordのrespond_to?ってそもそも使ったことないけど何に使うんだろか?🤔」「改修そのものは:to_partial_pathでStringをアロケーションしてたのをやめたという単純な内容で、アロケーションと速度が少し改善したと↓」

# 同PRより
Before: Total allocated: 752009 bytes (6527 objects)
After: Total allocated: 743921 bytes (6325 objects)
Diff: (752009 - 743921) / 752009.0 # => 1.05%

respond_to :to_partial_pathとかrespond_to :to_modelとか知らないし: ちょっと追ってみよう🐇(IDEを起動)」

参考: ActiveRecord::AttributeMethods::ClassMethods respond_to?(name, include_private = false)

name属性を持つPersonオブジェクトにはperson.respond_to?(:name)person.respond_to?(:name=)person.respond_to?(:name?)するとtrueを返す。また、属性メソッドが生成されていなければ定義する。
APIドキュメントより大意

class Person < ActiveRecord::Base
end

person = Person.new
person.respond_to?(:name)    # => true
person.respond_to?(:name=)   # => true
person.respond_to?(:name?)   # => true
person.respond_to?('age')    # => true
person.respond_to?('age=')   # => true
person.respond_to?('age?')   # => true
person.respond_to?(:nothing) # => false

「ふむぅ、APIドキュメント↑に書いてあることはわかるんだけど、to_partial_pathto_modelが何なのかだな〜🤓こいつらはメソッド呼び出しじゃないし」「↓このあたりの記事を見るとActiveModelに前からto_partial_pathがあるのか」「日本語記事にはto_partial_pathをオーバーライドできる↓って書いてるし」

# o.inchiki.jpより
class User < ApplicationRecord
  def to_partial_path
    if user.admin?
      "users/admin_user"
    elsif user.guest?
      "users/guest_user"
    else
      "users/user"
    end
  end
end

参考: ActiveRecordのto_partial_pathをオーバーライドしてパーシャルを文脈によって使い分ける [俺の備忘録]
参考: Rendering Collections in Rails — Thoughtbot blog

「このpartialはビューのパーシャルなんですよね?」「ですね: renderにActive Recordオブジェクトを直接渡すとパーシャルに展開してくれるメソッドがto_partial_path」「改修の話と離れてきたのでとりあえずこの辺で☺️」

参考: ActiveModel::Conversion to_partial_path()


「ところで、コントローラには?なしのrespond_toというとっても紛らわしい名前のメソッドがありますね😓」「あーそういえば!」「述語メソッドじゃないから?は付かないんだけどホント紛らわしい」

参考: ActionController::MimeResponds respond_to(*mimes)

def index
  @people = Person.all

  respond_to do |format|
    format.html
    format.js
    format.xml { render xml: @people }
  end
end

Active SupportのChars#reverseChars#grapheme_lengthをリファクタリング

# activesupport/lib/active_support/multibyte/chars.rb#L115
      def reverse
        chars(Unicode.unpack_graphemes(@wrapped_string).reverse.flatten.pack("U*"))
-       chars(@wrapped_string.scan(/\X/).reverse.join)
+     end
...
      def grapheme_length
-       Unicode.unpack_graphemes(@wrapped_string).length
+       @wrapped_string.scan(/\X/).length
      end

つっつきボイス:「これも含めて今回はUnicode/マルチリンガル関連の改修が目立ちました: Rubyのマルチリンガル機能がよくなってきたからActive SupportよりRubyのメソッドを使おうという流れみたいです」「あ〜、確かにそれは正しい方向☺️」「reverseはともかくgrapheme_length?ぐらふぇめ?」「graphemeは確か言語学方面の用語ですね↓日本語だと『書記素』…🤯」「今はscan(/\X/).lengthで取れるようになったと」

参考: 書記素 - Wikipedia

Ruby 2.5.3 + Rails 5.2.1のコンソールで試してみました。

'क्षि'.mb_chars.length            #=> 4
'क्षि'.mb_chars.grapheme_length   #=> 2
'क्षि'.scan(/\X/).length          #=> 2

以下のプは見た目ではわかりませんが半濁点が分離しています(は゜のように分離が見えるものとは異なります)。

a = "プ"
a.mb_chars.length            #=> 2
a.mb_chars.grapheme_length   #=> 1
a.mb_chars.scan(/\X/).length #=> 1

# normalizeするとmb_chars.lengthは1になる
b = ActiveSupport::Multibyte::Unicode.normalize(a, :c)
b.mb_chars.length            #=> 1
b.mb_chars.grapheme_length   #=> 1
b.mb_chars.scan(/\X/).length #=> 1

mb_chars.lengthはnormalizeで変わりましたが、mb_chars.grapheme_lengthは変わりませんでした。

参考: Unicodeのgrapheme cluster (書記素クラスタ) | hydroculのメモ

scaffoldでフィールドに:referencesを指定するとindexページやshowページでidではなくメモリアドレスが表示される問題を修正

#29200の修正です。

# railties/lib/rails/generators/erb/scaffold/templates/index.html.erb.tt#L18
<% attributes.reject(&:password_digest?).each do |attribute| -%>
-       <td><%%= <%= singular_table_name %>.<%= attribute.name %> %></td>
+       <td><%%= <%= singular_table_name %>.<%= attribute.column_name %> %></td>
<% end -%>

つっつきボイス:「これはもろバグ🐞」「attribute.column_nameにしたかったのにattribute.nameだとモデルオブジェクトを取っちゃう」「実はscaffoldで:referencesを指定する人がほとんどいなかったりして😆」

「ところで最後にscaffold使ったのっていつですか?」「scaffold、ほぼ使わないっすね🤓: rails gですらマイグレーションにしか使わないし」「やっぱり〜😆」「scaffoldって初心者アイテムなんでしょうか🔰?」「というより、ある程度以上複雑なRailsアプリを書いているとscaffoldでは追いつかないですね」

「コントローラだけならscaffoldでもよさそう?」「scaffoldだと余分なものが作られがちなのであまりしないけど、先にモデルを作ったときなんかにscaffoldすることはたまにあるかな」「ボク全部手で書いてますけど🤣」「さすが職人!」「わかりみ: 書く量も大したことないですしね☺️」「むしろscaffoldのオプションを覚える方が面倒😅」

「ちなみにRubyMineではscaffoldも一応GUIでできて↓、アクション名とか名前空間も指定できる: ま使わないけど😆」

「Railsのscaffoldって名前空間があると急に使いづらくなってくるところがある」「名前空間を指定してscaffoldすることは可能みたいですね」「できるけど微妙にかゆいところに手が届かなくて、たとえばモデルだけ名前空間を変えたいみたいな指定ができなかった気がする: 何しろ普段scaffoldしないので」「そこで考えるぐらいだったらとっとと普通にファイル作って書いた方が早い😎」「scaffoldだとアクションのURLコメント↓を自動的に付けるけど、コードが変わったときに自動更新してくれるわけでもないからそんなに意味ないし🤣」

  # POST /users
  # POST /users.json
  def create
    ...

「そういえば最近のscaffoldはstrong parametersも自動的に付けるんじゃなかったかな↓」「strong parametersのテンプレ、いらね〜😓」「お、scaffoldのテンプレを開くとparams.fetchparams.requireが使い分けられてるし」「ちょうど今日のチームミーティングでfetchrequireをどう使い分けるかって話になりましたね」「この2つは要件に応じて使い分けるべき」

# rails/generators/rails/scaffold_controller/templates/controller.rb.tt
  ...
  private
    # Use callbacks to share common setup or constraints between actions.
    def set_<%= singular_table_name %>
      @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
    end

    # Only allow a trusted parameter "white list" through.
    def <%= "#{singular_table_name}_params" %>
      <%- if attributes_names.empty? -%>
      params.fetch(:<%= singular_table_name %>, {})
      <%- else -%>
      params.require(:<%= singular_table_name %>).permit(<%= attributes_names.map { |name| ":#{name}" }.join(', ') %>)
      <%- end -%>
    end
end
<% end -%>

「ところで上はscaffold用だからstrong parametersのテンプレがあるけど、通常のコントローラをgenerateするときのテンプレートにはstrong parametersはないはずですよね?🤔」「お、言われてみれば確かに↓たったこれだけしかない」「意外とシンプルっすね☺️」「そしてこっちにはURLコメントのテンプレは含まれてないな」

# rails/generators/rails/controller/templates/controller.rb.tt
<% if namespaced? -%>
require_dependency "<%= namespaced_path %>/application_controller"

<% end -%>
<% module_namespacing do -%>
class <%= class_name %>Controller < ApplicationController
<% actions.each do |action| -%>
  def <%= action %>
  end
<%= "\n" unless action == actions.last -%>
<% end -%>
end
<% end -%>

「定数読み込み順と名前空間」地獄とrequire_dependency

require_dependencyから、名前空間と定数読み込み順の話題に大きくシフトしました。

「お、上のテンプレでrequire_dependencyを使ってるし」「へー、名前空間があるとrequire_dependencyを付けるのかー」「普通のrequireとは違うみたいですけど、これって何のおまじないでしたっけ?」

「むむ…うろ覚えだけど、確かletter_openerっていう本番以外でメールを飛ばさないようにするgemでどハマったときに見たのを思い出してきたぞ😇」「自分も同じようなところでハマったことあり😇」

「たとえば以下のようにHogeモジュールの下にPiyoControllerがあるとするじゃないですか、するとHoge::ApplicationControllerと、トップレベルからの::ApplicationControllerという2とおりの探索パスが存在することになるんですよ」「この2つは、そのままだと先に見つかったものが使われるんです」「読み込み順は必ずしも一定じゃないので」

module Hoge
  class PiyoController < ApplicationController
end

# 上のようにネストで名前空間を作ると、以下の2つの探索パターンが形成される
# Hoge::ApplicationController
# ::ApplicationController

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

「ところがこれでは、Hoge::ApplicationControllerを指定して取りたくても、先にトップレベルの::ApplicationControllerが見つかるとHoge::ApplicationControllerが取れなくなってしまうんですよ👻」「それを回避するために、欲しい名前空間をrequire_dependencyで確定しておく必要があるという」「ほぁ〜🤯」

「RailsのModule#autoloadって定数の読み込みに「失敗したとき」(const_missing)でないと動き出さないので、定数が既に読み込まれちゃっていると動いてくれないんですよ😭」「あー、定数がもう埋まっちゃってるから発動しないのか」「letter_openerでも回避のためにrequire_dependencyを追加したコミットが確かあったと思います(たぶん#82)😤」

参考: 定数の自動読み込みと再読み込み — Module#autoloadが関与しない場合| Rails ガイド

「今の例はApplicationControllerだから動かなくなればすぐ気づけるだけまだマシな方: これが名前空間化されたモデルへのアクセス中に発現すると致命的な問題になる可能性があるのがコワイ☠️」「ぎょぎょ!」

「たとえば、コントローラのアクションでCustomer.find(params[:id])のようにモデルにアクセスするとして、モデルがAdminで名前空間化されていると::CustomerAdmin::Customerのどちらかが使われてしまうんですが、こういう場合えてしてAdmin::Customerの方がはるかに権限が強いので、::Customerのつもりで書いていたのがAdmin::Customerから取ってきてしまったり、逆にAdmin::Customerのつもりなのに::Customerから取ってきてしまうと(こっちの方がありそうかな)トンデモナイことになる😇」「何というお漏らし…」

「回避方法としてはrequire_dependencyもあるけど、やっぱり名前空間べた書きですかね」「そっ☺️: 自分もこれでハマって以来Customer.find(params[:id]じゃなくて必ず::Customer.find(params[:id]みたいに::を明示的に書くようになったし↓」「何だかすっごく不便…😅」

# require_dependency

module Admin
  class PiyoController < ApplicationController
    #   Admin::ApplicationController
    #   ::ApplicationController
    def show
      @customer = ::Customer.find(params[:id])
    # Admin::Customer 
    # ::Customer
    end
  end
end

「でなかったら、そもそもネストじゃない方法で名前空間を作ることでしょうね」「同意👍: 自分も名前空間はこうやってネストなしで書くようにしている↓」「これなら名前空間が揺れようがないし❤️」「階層も浅くなるし😋」

# morimorihoge like
class Admin::PiyoController < Admin::ApplicationController
  ...
end

「ただ、流派としてはどちらのやり方もあるみたい」「自分は今のclass Admin::PiyoControllerがいいと思います!」「自分も☺️」


「ちなみにこれはRailsエンジンを書くときにも注意しなければいけないハズ(某案件でそういう目に遭ったし)」「そうそう、エンジンなんかは特にそうだけど、想定外の名前空間掘り下げにはホント要注意⚠️: サードパーティのモジュール内で発生すると地獄を見る👹」

参考: Rails エンジン入門 | Rails ガイド

「現象としては、動いているはずのないモジュールのメソッドがなぜか呼ばれる」「しかもそのモジュールをいくら調べても原因がわからなくて、::を付けて呼び出すとなぜか解消するという」

「もうひとつの現象としては、たとえばdevelopmentモードだと正しく動くのにproductionだと動かなくなる(あるいはその逆)」「読み込み順が環境で変わることがあるのか…」「productionだと全部読み込んでから開始するけど、developmentだと使おうとして見つからないときに初めて読み込まれるから」「letter_openerはdevelopmentでしか使わないgemだから、まさにdevelopmentでだけ動かないという現象を引き起こしました😤」

「この問題を一度踏めば『二度と踏みたくない!』って気持ちになるから、無精しないで名前空間をきちんと書くようになりますよ😎」「踏んだことないと、何が起こったのかすらわかりませんからね…自分も踏んだのは今年になってからでしたし」

「今思えば、自分はずっと前からclass Admin::PiyoControllerみたいに名前空間べた書きにしてたんでこの問題を踏みようがなくて、逆に気づく機会がなかったんですよ」「それがたまたま誰かが他の書き方したか何かでネストになった箇所でrequire_dependencyし忘れたりして、それで初めて遭遇しました😩」

「ネストしない書き方ならこの問題は起きないんでしょうか?」「ネストしない書き方なら完全にグローバルスコープになるので起きませんね🧐: さっきの例で言うと、Customerと書いたときに必ずグローバルなCustmerだけを見に行くし、Admin::名前空間に入らないのでそもそも探索が発生しない」「なるほど!😃速度的にもよさそう」

「すごく詳しい説明!ありがとうございます😊」

ActiveSupportのUnicode関連メソッドが続々変更・deprecation

使われなくなったActiveSupport::Multibyte::Chars.consumes?がdeprecateになって今後はString#is_utf8?を使い、ActiveSupport::Multibyte::Unicode#downcase#upcase#swapcaseがdeprecateになって今後はStringのメソッドを直接使うようにとのことです。

また、Unicode#normalizeChars#normalizeもdeprecateになり、今後はRubyのメソッドを使うことになるようです。


つっつきボイス:「RubyでできるようになったからRubyでやろうぜってことみたいです」「なるほど、Rubyが変わるとこういう改修が入りますね😋: #upcase#downcaseもRubyでやれるようになったのか」「マルチリンガルでできるということでしょうね❤️」

[Rails5] Active Support::Inflectorの便利な活用形メソッド群

#capitalizeもRubyでやれるようになってるのかな…なってるし↓!」「Rubyはこういうのに対応しててエライな💪」

参考: instance method String#capitalize (Ruby 2.5.0)

なお、Rubyの#10085ではentrなどで言語を指定する形になっていましたが、現在はオプションなし以外は:ascii:turkic:lithuanian:foldのみとなっています。また、置き換え方法はエンコーディングにも依存します。大文字小文字の変換はマルチリンガルになると単純にはいかないので苦労が偲ばれます。

参考: instance method String#downcase (Ruby 2.5.0)

#normalizeって何だったかな」「えっと、ひらがなやかたかなの濁点や半濁点が本体の文字と泣き分かれになってるときなんかに使いますね: MacとWindowsでファイル交換するとファイル名でよく起きるヤツ」「あぁそれね☺️」「アルファベット語圏だと文字とアクサンが分離しているときにも使うみたいです」「normalizeってUnicodeだとそういう文脈なんだ〜: normalizeっていろんな意味があるし: 特にコンピュータサイエンスの文脈だと」「確かに」

参考: MacでPDFからコピペした時のNFD問題対策

UnicodeのnormalizeはNFD/NFC/NFKD/NFKCとありますが、分離したものも再合成されたものもそれぞれ正当です。さらにMacのファイル名正規化は独自仕様が含まれるらしいので困ったことです。処理対象に複数の正規化形式が入り混じってしまうと厄介なので変換の際はnormalizeを心がけたいですね。

参考: Unicode正規化 - Wikipedia

[Rails5] Active Support Core Extensionsのマルチバイト系メソッド: String#mb_charsとis_utf8?

モデルattribute accessorのメソッド名を改善

# activemodel/lib/active_model/attributes.rb#L29
      private

-       def define_method_attribute=(name)
-         safe_name = name.unpack1("h*")
-         ActiveModel::AttributeMethods::AttrNames.set_name_cache safe_name, name
-
-         generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
-           def __temp__#{safe_name}=(value)
-             name = ::ActiveModel::AttributeMethods::AttrNames::ATTR_#{safe_name}
-             write_attribute(name, value)
-           end
-           alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
-           undef_method :__temp__#{safe_name}=
-         STR
+         ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
+           generated_attribute_methods, name, writer: true,
+         ) do |temp_method_name, attr_name_expr|
+           generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
+             def #{temp_method_name}(value)
+               name = #{attr_name_expr}
+               write_attribute(name, value)
+             end
+           RUBY
+         end
        end

つっつきボイス:「リファクタリングかな?」「あー、属性のアクセサメソッドをメタプログラミングで動的に定義しちゃうとバックトレースで名前が出てこないことがあるから、attr_name.ascii_only? && DEF_SAFE_NAME.match?(attr_name)の条件がtrueならそのままの名前で定義するってことか」「上のコードの↓この部分で名前を明示的に宣言するように変わってる: それ以前はmodule_evalで無名メソッドを作ってからalias_methodしてたけど、その場合バックトレースにはエイリアスではなくオリジナルの__temp__が出てしまう」「確かに__temp__で塗りつぶされちゃうと追いにくいですね」

          ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
            generated_attribute_methods, name, writer: true,
          )

define_attribute_accessor_methodって、define_methodの属性アクセサ版みたいなものかしら」「わがんね🤣」「attribute_methods.rbに今回追加されたクラスメソッドがこのdefine_attribute_accessor_methodですね: 直接使うことはまずなさそうだけど😆」

# activemodel/lib/active_model/attribute_methods.rb#L499
        def self.define_attribute_accessor_method(mod, attr_name, writer: false)
          method_name = "#{attr_name}#{'=' if writer}"
          if attr_name.ascii_only? && DEF_SAFE_NAME.match?(attr_name)
            yield method_name, "'#{attr_name}'.freeze"
          else
            safe_name = attr_name.unpack1("h*")
            const_name = "ATTR_#{safe_name}"
            const_set(const_name, attr_name) unless const_defined?(const_name)
            temp_method_name = "__temp__#{safe_name}#{'=' if writer}"
            attr_name_expr = "::ActiveModel::AttributeMethods::AttrNames::#{const_name}"
            yield temp_method_name, attr_name_expr
            mod.send(:alias_method, method_name, temp_method_name)
            mod.send(:undef_method, temp_method_name)
          end
        end

参考: instance method Module#define_method (Ruby 2.5.0)

Rails

clean-rails.org: Railsのきれいなコードについて日本語で議論するコミュニティ


discourse.clean-rails.orgより

以下の記事で知りました。

参考: Rails開発で技術的負債を増やさないために知っておきたいこと - ログミーTech(テック)


つっつきボイス:「あーこれか: 以前誰かがこういうサイトやろうぜって話してた気がする」「上の記事に登場する前島真一さんが始めたみたいです」「知ってる顔が多いかも」「中級者以上が集う感じなので人数が多くないのは致し方ないかな」「中級者以上がそもそも少ないですし」

「実は中級者以上の設計上の悩みを説明するのってすごく大変なんですよ: 社内みたいにコンテキストがある程度共有されていればまだしも、そうでないと説明が恐ろしく長くなりがち」「確かに説明を書いているうちにダルくなりそう」「請負開発案件の悩みだったりすると、ソースコードをそのまま貼ったらNDAに違反するからソースを作り直さないといけないし」「ソースを貼らなくても前提条件の説明だけでNDAに抵触するかもしれないので、案件から切り離した前提を考えるのも大変」

「設計って最終的に好みが分かれるものだし、どこまでやるか/これはやりすぎかみたいなのも悩ましいし」「そういう感じで純粋に設計上の話をするならこのclean-railsはいい場なんじゃないかと思いますね❤️」

Rails 6.0の新機能をRailsコントリビュータがひたすら記録するサイト(Ruby Weeklyより)


つっつきボイス:「このbogdanvlvivさんはRailsのコントリビュータで、彼が収集したRails 6.0の新機能が上の記事にとってもきれいにまとまってます」「これまでウォッチで扱ってきたからだいたい見覚えある💪」「Changelogから抜いてきた感」「でもChangelogよりずっと見やすいですね😍」

「へー!#31944delegateprivate: trueオプションが追加↓ですって🤓」「えっナニソレ?」「これはウォッチで扱ってなかったか…」

# 31944より
class Foo
   # ...
   delegate :foo, :bar, to: :baz
   delegate :car, :dar, to: :daz, private: true
   # ...
end

foo = Foo.new
foo.foo
foo.bar
foo.car # => NoMethodError: private method `car' called for #<Foo:0x000000015e8b10>
foo.dar # => NoMethodError: private method `dar' called for #<Foo:0x000000015e8b10>

「ここしか見ずに言うと、delegateしたメソッドをあたかもdelegateしたかのように自分のクラスでだけ呼び出したいときに使うんだろうな: かつpublicにはしたくないと」

「そもそもRubyのprivateメソッドってdelegateできるものでしたっけ?あるいはdelegateはできるけどprivateにしかできないとか?🤔」「Rubyのprivateメソッドはdelegateできないはず」

「だから上のコードはcardarはそのメソッドをFooクラスの中でprivateにするという指定なんだろうと理解してる」「delegateはする側とされる側の関係がややこしい😭」

SQLインジェクションを防ぐ正規表現がRailsに導入?

#27947はcloseされているので改修ではありませんが。#32995も現在openです。


つっつきボイス:「最近はてブで話題になったkamipoさんの記事です」「そういえばこの記事読んだ」「Post.order(params[:order])みたいにorderの中に直接paramsを置くとアブナイよというヤツ: orderに限らず、ActiveRecordのwhereとかgroup_byとかに直接文字列を突っ込むのは基本的に怪しみを感じるべき⚠️」「😆」「シンボルとかハッシュで定義しないと怪しみが残る」

「で#27947を見に行ったらcloseされてました」「正規表現でチェックってw」「でもそうするしかないだろうな…」

「その#27947のホワイトリストは↓のようになってて、これ以外のものは突破できないようにするってことか: たいていこうなるから自分は別に構わないっすけど」「いやいや油断は禁物、MySQLはSELECTしてないカラムでorderingできるんですよ😎」「あーそうだったかも」「だから関数カラムでもorderingできるはず」

      # Regexp whitelist. Matches the following:
      #   "#{table_name}.#{column_name}"
      #   "#{table_name}.#{column_name} #{direction}"
      #   "#{column_name}"
      #   "#{column_name} #{direction}"

「たしか標準SQLではSELECTしたカラムでないとorderingできないことになってるので、関数を使ったカラムはいったんAS なんちゃらして、それに対してorderingする必要があった」「それがMySQLはそこんところが残念というかアグレッシブで、SELECTしてなくてもorderingできる: しかもこれがMySQLであまりにも普通に使われてるせいで、DBMSが変わると動かなくなる😇」

「あ、今はもしかするとMySQLもstrictレベル上がって修正されてるかもしれないけど?」「でも後方互換性を維持しているなら今でもできちゃいそう😎」「少なくとも以前のMySQLはそうでしたね」

#27947のこの正規表現↓って、スペースではない文字に+を付けてるってことかな…?」「正規表現の\wは語の境界を取るときに使いますね: だから\w+が1つの語を表す」「\wによる語の境界はスペースでも区切られるし、「:」とか「_」みたいな記号でも区切られるヤツ:日本語の漢字熟語の区切りは判定しようがありませんが😆」

COLUMN_NAME_ORDER_WHITELIST = /\A(?:\w+\.)?\w+(?:\s+asc|\s+desc)?\z/i

「ともあれ、この書き方はひたすら語をチェックしているだけで、それが実際のSELECTやカラム名かどうかまではチェックしてないってことですかね」「SQLインジェクションを防ぐのが目的だからそこまでは頑張ってなさそう」「だからMySQLでしか通らないクエリも通って幸せ🤣」「幸せっちゃ幸せですが🤣」「そしてPostgreSQLに投げるとエラーになると」

ちなみに(?:)はキャプチャを抑制する記法で、少々うざい代わりに高速化されます。普通に()でグループ化すると常にキャプチャされます。

参考: 正規表現 (Ruby 2.5.0)

「明らかに安全だと思うリテラルまでRailsに危険とみなされるとつらい人もいるってkamipoさん記事にありますね」「そうそう、この改修が入るとDiscourseが5.2にあげようとするとつらいって言ってますね」「Discourseってまさにさっきのclean-railsが置かれているサイトですよね」「まあそれは元の書き方がそもそもよくないから直せって思うけどなっ😎」「直せるなら、ね😆」

ActiveSupport::StringInquirerのマジック(RubyFlowより)

# 同記事より
class StringInquirer < String
  private
  def respond_to_missing?(method_name, include_private = false)
    (method_name[-1] == "?") || super
  end
  def method_missing(method_name, *arguments)
    if method_name[-1] == "?"
      self == method_name[0..-2]
    else
      super
    end
  end
end

つっつきボイス:「すとりんぐいんくわいやー、使ったことないなー」「自分もないけどRailsの内部で使ってるんですかね」「あ、string_inquirer.rbのAPIドキュメントにそのまんま書いてある: Rails.env == 'production'っていうダサい比較をしなくてもRails.env.production?で判定できるようになる、いつも使ってるヤツだ」「いかにもメタプロですね」

参考: ActiveSupport::StringInquirer
参考: ActiveSupport::StringInquirer を使って、ステータスを active? みたいに管理する

「これはRailsのconfigでもよくRails.configuration.なんちゃら?みたいに使われてるやつですね」「そして中身はrespond_to_missing?method_missingでやってる: そうするしかなさそうだし」「ハッシュに’staging’とかが入っててもメソッドが生えてくるのはこいつのおかげでしたか」

参考: Railsアプリを設定する | Rails ガイド

StringInquirerって、名前から想像が付きにくい機能😆」「英語圏では自然な名前なんだろか?」「inquireってaskやqueryに近いけどもっと堅苦しくて、広い意味では『調査』ですけど、手元の串刺し検索辞書で見ると警察などによる公の調査というニュアンスもあるみたいです👮‍♂️」「method_missingするまで捜査するみたいな」

映画「市民ケーン」では、主人公のケーンが世論操作に使う架空の新聞がその名も「The New York Inquirer」だそうです。皮肉も入っていると思いますが。

ActiveSupportのArray#extract!メソッド

webpack-serveはdeprecatedになった

「ついこの間Webpackerの記事を出したときに調べたら、本命だったはずのwebpack-serveがいつの間にかdeprecateになってて、元のwebpack-dev-serverを使ってくれとのことです」「マジでー?😩」「webpack-serveがメンテナー不在になったらしく」「ひどい🤮: webpack-serveとwebpack-dev-serverって実装は別だった気がするけど」「結局その間webpack-serveを使わなかった人が優勝✌️ってことじゃないすか🤣」「🤣」「もうコントか何かみたいに振り回されて」

「古い情報が半端に残ってると今後も踏み続ける人が出る予感」「はい、以前出した『Webpacker公式ドキュメントの歩き方』↓や『Rails 5: Webpacker公式README』の訳注で『今後はwebpack-serve』と書いていたので、もちろんこのことを追記しておきました」

【保存版】Rails 5 Webpacker公式ドキュメントの歩き方+追加情報

その他Rails


  • サイト: Rails Assets — Railsのアセットを一元管理するサイト
source 'https://rails-assets.org' do
  gem 'rails-assets-bootstrap'
  gem 'rails-assets-angular'
  gem 'rails-assets-leaflet'
end

つっつきボイス:「Railsのこういうアセット一元管理って一度はみんな考えそうかなと思って」「ああ昔こういうの流行りましたね: ここはrails-assets.orgにホスティングしてるようですが、もう今はWebpackerでやるし」「やらないとマサカリが飛んでくるし🔨」

Ruby trunkより

Ruby 2.5.2/2.4.5/2.3.8リリース

Ruby 2.5.2→2.5.3/2.4.5/2.3.8リリース(脆弱性修正)

記事そのままですが。

提案: OpenStructでハッシュオブジェクトを再帰的にOpenStructオブジェクトに変換する機能


つっつきボイス:「ちょうど先週のウォッチでもOpenStructが話題になりましたね」「OpenStruct使うつもりないし😆」

Ruby

Rubocopをパフォーマンス改善ツールとして使う(Ruby Weeklyより)

翻訳記事でもお世話になっているschneemsさんのブログです。

array = ["a", "b", "c"]
array.compact.flatten.map { |x| x.downcase } # compact, flatten, mapでarrayがアロケートされる

つっつきボイス:「duplicate array allocationのチェックにRubocopを使う、そういうルールを使えば確かにできるな」「もともとRubocopってそういうものだし☺️」

CLI版「12 Factor CLIアプリ」(Ruby Weeklyより)


つっつきボイス:「12 FactorといえばThe Twelve-Factor Appですけど、そのCLI版ってことみたいです」「どことなく自称感漂う😆」


The Twelve-Factor App (日本語訳)より

「あー、1.のヘルプの出し方といえばJavaが頭に来る😤」「というと?」「Javaだけ-help」「-が1個だけ!」「やめて欲しいわ〜」

「4.のストリームを重視、これはUNIXの基本ですし😎」

「6.の↓こういうメタな文字を入れるのはちょっとな〜、コピペすると化けたりするし」「Powerlineで頑張りすぎてそういう目に遭ってます😭」「こうやっていい感じにして、そして重くなるという🤣」「カラーリング頑張りすぎるのもね、たまにVT-100みたいなむちゃ古い端末でssh rootすると文字化けまくったりするし😇」

「RubyKaigiか何かでこんな感じにできるCLIのgemについて発表してた人がいた気がする」「んー、ググってもそれらしいのが見つからない…」

追記: piotrmurach/ttyかもしれないという情報をいただきました。ありがとうございます!😂

Rubyのメソッド探索、RubyVM.stat、グローバルステート(Ruby Weeklyより)


つっつきボイス:「Rubyのパフォーマンスチューニングでお馴染みのNoah Gibbsさんの記事です」「メソッドキャッシュがいつできるか、みたいなお話」「Global Constant Stateなんてのもあるのね」

Ruby 2.5.0はどれだけ高速化したか(翻訳)

Ruby 1.8〜2.5のStringの進化(Ruby Weeklyより)


つっつきボイス:「Rubyに限らず、Stringって一番使われるだけあって結構変遷があるんですよね」「2.1から2.5はそれほど変わってないのか」「バージョンごとのベンチも取ってますね↓」


同記事より

「何やかやで、RubyのStringは手厚くてよくできてるなって思う」「JavaScriptのはつらいな〜😢」「つかRubyのStringが強すぎる!ホント何でもできる💪」「万能感ありありですね」「Stringのメソッドめちゃめちゃ多いですし、正規表現使わなくてもかなりのことができますよね😋」「正規表現も必要ならさっと呼べるし、楽すぎる」

monotime: 単調増加クロックgem(Ruby Weeklyより)

# 同リポジトリより
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
do_something
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start

Rustにインスパイアされたそうです。


つっつきボイス:「名前でもろわかるように、ちゃんとCPUクロックから時間を取ってくるヤツですね」

EndohさんがRubyを「簡潔ビットベクトル」で高速化


つっつきボイス:「これも話題になってましたね」「簡潔ビットベクトル!」「英語で何て言うんだろう🤔」「TracePoint化での速度を改善するのか」「かなりコアな部分の改造っぽいですね」「めちゃニッチ😊」


同記事より

簡潔ビットベクトルは「succinct bit vector」だそうです。

参考: 簡潔データ構造 - Wikipedia
参考: 簡潔データ構造超入門III ~簡潔ビットベクトルで転置インデックスを効率的に実装する~ - EchizenBlog-Zwei

「こういうふうにテーブルから引っ張ってくるシチュエーションって、プログラムのあらゆるところに数えきれないぐらいありますね: それこそJavaScriptのイベントハンドラなんかもこんな感じでできているし、あとOSの割り込みベクタテーブルなんかもそうだし」

参考: JavaScriptのイベントハンドラ一覧|イベント|JavaScript/DOM|PHP & JavaScript Room
参考: 割り込み

Matz手ずからのRubyスタイルガイド

2004年です。


つっつきボイス:「とっても短いです」「おーシンプルなスタイルガイド」「kind_of?is_a?を使わずにダックタイピングする」「末尾が!のメソッドはなるべく作らない…今とだいぶ違うなー」

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

「これ↓って何?…あー、ライブラリファイルをそのまま呼び出すとサンプルプログラムとかが走るってことか」「あーなるほど!」「ライブラリとして使うと何も起きないのね」「$0は実行中のRubyスクリプト名を取れる特殊変数か」「スタイルというよりハックっぽいな〜😆」

ライブラリファイルの末尾には if __FILE__ == $0 で囲んで テストケース(かサンプルプログラム)を書いておくとよい。
rubyist.netより

[Ruby] Kernelの特殊変数をできるだけ$記号なしで書いてみる

「Matzもちゃんと書いてる: 未来の自分が混乱しそうと思ったことは躊躇せずコメントするって」「これは大事」

その他Ruby

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

ELBSecurityPolicyとは


つっつきボイス:「社内Slackに投下してもらったやつですが、ELBSecurityPolicyって何だったかなと思って」「このセキュリティポリシーは、SSLやTLSでサーバーとハンドシェイクをするときに、どういう暗号化方式を使える/使ってよいかという情報を最初に交換するんですよ」「サーバーはこれこれの暗号化方式を使ってよいというリストを渡して、クライアントはその中から暗号化方式を選べます」「あーなるほど!」「そのときに選べるのがこのELBSecurityPolicy↓」


同記事より

「あんまり新しい暗号化方式を選ぶと困ることとかあった気がする」「古代のガラケーなんかでは新しい暗号化方式に対応できなかったりするので、ポリシーを厳しくしすぎてたとえばAES256-*系を禁止したりするとTLS通信がコケることがある」「DES-CBC3もめちゃ古いな〜😆」

「ELBSecurityPolicyは基本的にデフォルトのポリシーを選びます(今は「2016-08」)が、実は今のデフォルトポリシーではTLSv1やTLSv1.1が許可されているのであまりよくない👎」「あー」「AES128やAES256も今は時間をかければ突破できますし」「通信でサポートする環境にもよりますが、デフォルトより強くしたければ、もっと強いポリシーを選択する必要がある」「ちなみにELBのLoad Balancer(確かClassic)だとセキュリティポリシーは自分で定義することもできるはず😎」「なるほど!」

参考: Classic Load Balancer での事前定義された SSL のセキュリティポリシー - Elastic Load Balancing

「GitHub Actions」とは


同サイトより

まだ本格稼働はしてない様子です。


つっつきボイス:「つい最近発表されたようです」「あーこれね☺️GitLab Pipelinesのいただきっぽいやつ」「あ、そっちに前からあるんですね😓」「↓こうやってactionで書けるところとかもGitLab Pipelinesみたい」「この辺はGitLabの方が進んでます😎」「とは言え、GitHubがあんまりこの辺を自前でやるとCircleCIとかがボヤくかもしれませんが😆」

参考: Introduction to pipelines and jobs | GitLab
参考: gitlab.com で いますぐCI してみよう - Qiita


gitlab.comより



circleci.comより

「GitLabといえば、morimorihogeさんが今年の夏書いたGitLab記事↓のビューが凄く伸びてるんですけど、もしかするとマイクロソフトがGitLabを獲得したのと関係あるのかなと思って」「そんなに伸びてる?みんなどういう部分を読んでるのかな🤔」「そのGitLab記事ってどんな内容ですか?」「GitLabサーバーを自前で立てる話です」「とするとタイミング的にはありそうですね😆」

GitLab自社運用のための注意点とノウハウ(2018/06版)

「記事にもありますけどBPSのGitLabサーバーはユーザー数が多いんですよ↓: これをGitHubでやろうとすると大変」

「GitHub Actionsの料金体系ってどうなるんだろう?」「まだベータだからかもしれませんが値段表が見当たらない感じです」

TwilioがSendGridを買収


つっつきボイス:「ちょうど先週のウォッチでTwilioのことをいろいろ教わったのでタイムリーだなと思って」「しかも今うちらもSendGrid使ってるし」「ちなみにSendGridには日本法人があるけどKDDIではなかったと思う」「構造計画研究所ですね↓」


sendgrid.kke.co.jpより

SQL

データベース接続を効果的に管理するには(Postgres Weeklyより)


つっつきボイス:「これは普通にコネクションプールを作ろうみたいな話かな?」「たぶんそんな感じ」「お、Minimum viable checkoutsなんてのがある」

「コネクションプールって好き派と嫌い派がいますよね」「オレは嫌い😆」「そういえばMySQLはあまりコネクションプールって作らないですよね、PostgreSQLは割とやるけど」

「MySQLってパッケージ機能があまり強くないからいいんですけど、Oracleみたいにパッケージ機能が強いと(PostgreSQLはパッケージじゃなくてプラグインだけど同じかどうかよくわからない)、コネクションプールを張ったときにコンパイル結果がコネクションごとに乗っかるじゃないですか」「そうそう」「すると何が起きるかというと、ヘビーに使われているJava+Oracleのサイトでパッケージも積極的に使っているところで、コネクションプールを張った状態でパッケージを更新すると、パッケージで何か呼ぶたびにfailしてリコンパイルが走る🤮、しかもそれがコネクションごとに起きるという現象です」「🤣」「🤣」「だから当時、コンパイルが失敗したというエラーメッセージを捕まえて再実行するという悲しいコードをよく書きましたもん😤」

「それは可哀想すぎる…コネクションプールを自分で管理するとかやりたくない〜😆」「とは言え、OracleとかPostgreSQLで大規模なことやるとコネクションプールを使わざるを得ないこともありますし」「あとTLS経由のコネクションって往復回数が多くなるからやりたくない: そういうときはコネクションプールをローカルに置ければ接続も早いしオーバーヘッドも小さくできるんじゃないかなと思うし」

「うほ、コネクションプールをさらに束ねるPgBouncerだって↓」「ここまでくるともうDBAの仕事😆」「大規模になって応答速度とかを改善しようとしたらDBAが管理していかないといけないでしょうね」「コネクションプールの状態を管理して、足りなかったら生成したりとか」


同記事より

参考: Postgres Plus ユーザーサイト - FAQ — PgBouncerについて

PostgreSQLの正規表現

# 同ドキュメントより
'abc' SIMILAR TO 'abc'      true
'abc' SIMILAR TO 'a'        false
'abc' SIMILAR TO '%(b|d)%'  true
'abc' SIMILAR TO '(b|c)%'   false

POSIX正規表現だそうです。


つっつきボイス:「PostgreSQLで正規表現を書けるというのをたまたま知ったので」「お?MySQLでもこういうの書けますよ」「やや、そうでしたか💦」「ポスグレはSIMILAR TOで、たしかMySQLはREGEXP、なお後者は演算子」「個人的にPOSIXなのが残念😢」

参考: MySQL :: MySQL 5.6 リファレンスマニュアル :: 12.5.2 正規表現

「そういえばそんなのがあった気がするけど、自分はこういうの超遅くなりそうなんで基本使わないです😓」「DBMSならパラレルで処理できるからそんなには遅くならないと思いますけどね」「集約関数で使ったらさすがにヤバいだろうけど、普通にカラム名を評価するぐらいなら大丈夫だろうし、事と次第によってはLIKEより速いこともあるんじゃないかなという気がしてきた」「文字数が確定する正規表現なら速くできそう」「+とか*を使わないのがポイントですね」「インデックス化もできますかね?」「さすがにそれは苦しいのでは😆」

はじめての正規表現とベストプラクティス#2: 正規表現とは何か/ワイルドカードとの違い

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

TLS 1.0/1.1無効化の動き

参考: Apple、Google、Microsoft、Mozillaが各社のブラウザでTLS 1.0/1.1のサポートを2020年前半に終了すると発表。 | AAPL Ch.


つっつきボイス:「さっきのELBSecurityPolicyは、このbabaさんが投下したこの記事の続きとしてSlackに貼ってたヤツですね」「ですです」「とは言え古いスマホもまだ残ってますけどね☺️」

言語よろずの間

Zig言語とは


ziglang.orgより


つっつきボイス:「究極だからZで始まる?」「C言語を置き換えるってw」「めちゃ鼻息荒いです」「D言語でやっとくれ😆」「そういえばあった!D言語って」

参考: D言語 - Wikipedia


dlang.orgより

PCRE2の仕様


pcre.orgより


つっつきボイス:「これもたまたま見つけました」「PCREに2がある?!いつの間に」「つかPCREにちゃんと仕様があるっていうのが驚き☺️Perlの正規表現ぐらいにしか思ってなかったけど」「実は2で初めて仕様を作ったりなんかして😆」

参考: Perl Compatible Regular Expressions - Wikipedia

その他言語


つっつきボイス:「😆」「😆」「このおかしみを知りたくて」「あ、malloc(sizeof(32))の流れを知らなかったんですね😆ついこの間はてブでも上がってたこの記事↓を受けたツイートです」「あ、そういうことか😳」「ちゃんとハロウィンらしく季節感出してるし」

参考: 侍エンジニア塾のC言語のサンプルがヤバすぎる。 - Qiita

「良い子のみんなはmalloc(sizeof(32))しちゃダメだよ😆: 32は数字じゃなくてシンボルとして解釈されるから」「32だと期待どおりにならないのはわかる: 普通はsizeof(int)とか書くヤツですよね」「数字でも書けるけどどれかのシンボルになっちゃう」「ついでに言うとC言語のsizeof演算子です: 関数じゃないからねっ」

参考: sizeof演算子



つっつきボイス:「Nintendo SwitchでLLD…ま使うよね動くんなら😋」

その他

オープンソースのISA「RISC-V」


つっつきボイス:「ISAってぱっと見で昔のISAバスかと思ったら、Instruction Set Architectureの方でした」「確かにISAって言いますね」「しかしRISCか😆」「今はLLVMがあれだけ流行ってるぐらいだし、中間言語で動くようになってしまうとエンドのCPUの種類ってあまり関係なくなってくるところがありますね🤓」

「このRISC-Vは実装ではないようなので、つまりARMみたいなものか」「というと?」「ARMはCPUではなくて命令セットでありライセンスなんですね: だからARMのCPUというものはなくて、ARM社は命令セットを持っていてそのライセンスで収入を得てるということ」「知らなかった〜☺️」

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

「このRISC-Vはオープンソースだから命令セットがオープンということか」「記事によると研究者がオープンな命令セットを長年欲していたらしいです」「確かにクローズドだと研究もままならないし」

番外

今から備えるか

各種業務への影響もでかそうな気が。


つっつきボイス:「そうそう、10連休で銀行系が青ざめてるらしいじゃないですか」「え、来年10連休?」「やべー」「やべー」「ガチのゴールデンウィークじゃないですか」「ガチすぎる😆」「厄介なのはたとえば給与支払日がここにかかっちゃうと労働基準法あたりの『月に1度支払うべし』とかに引っかかる可能性があることかな: あ、今は銀行が24時間365日で振込できるようになったからだいぶマシか」「ついこの間からでしたね」

参考: 9日から24時間365日「即時振込」のサービスが開始、それを可能にしたモアタイムシステムとは


今回は以上です。

おたより発掘

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

週刊Railsウォッチ(20181015)Rails初心者と一発でバレる書き方、次のVue.js構想、RubyのOpenStruct、Twilioほか

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

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

Ruby 公式ニュース

Rails公式ニュース

Ruby Weekly

RubyFlow

160928_1638_XvIP4h

Postgres Weekly

postgres_weekly_banner

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

この記事の著者

hachi8833

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

hachi8833の書いた記事

週刊Railsウォッチ

インフラ

ActiveSupport探訪シリーズ