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

週刊Railsウォッチ(20190415-1/2前編)Railsバージョンアップに便利なstill_life gem、Zeitwerkの改修進む、named_capture追加ほか

0
0

こんにちは、hachi8833です。RubyKaigi 2019はもう目前ですね。


つっつきボイス:「(つっつき時点→)RubyKaigiもう来週ですか!」「早いな〜📅」「改めて説明すると、RubyKaigiはRubyのコアな話を3日間にわたって聞ける年1回開催のカンファレンスです」「ここ数年は日本の地方都市を巡回していて、今年は博多で開催されます」「今回は参加しませんが、もし行ってたらコアな話についていけるかしら😅」「そこも楽しさのひとつだと思います❤: 『ここまでやる人がいるんだ』みたいな驚きがありますし」「個人の感想ですが😆、参加するたびにモチベが急上昇しました📈: 自分も何かやれるんじゃないかという気持ちが沸々とわいてくるというか」

RejectKaigi2019も東京博多の同時開催で盛り上がったようです。

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

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

今回はコミットログから見繕いました。

⚓モデルJOINのbelongs_toのautosave: trueで無効なレコードが保存される問題を修正

  • autosave: trueの循環を修正

インスタンス変数ではなく、activerecord/lib/active_record/autosave_association.rbsave_collection_associationメソッドでローカルな変数を使うようにする。

以前は、autosave: trueがいくつも循環していた場合に、has_many関連付けのコールバックが、同じ関連付けの同じコールバックの別のインスタンスが実行完了していないにもかかわらず、実行されてしまっていた。
コールバックの最初のインスタンスに制御が戻った時点でインスタンス変数が改変され、以後、関連するレコードが正しく保存されなかった。特に、has_manyに対応するbelongs_toのIDフィールドがnilになる。
同PRより大意


つっつきボイス:「autosave: trueってやったことない😅」「↓ここがポイントのようです」

# activerecord/lib/active_record/autosave_association.rb#L385
+         # インスタンス変数をローカル変数に保存することで
+         # コールバック全体をリエントラントにして#28080を修正する
+         new_record_before_save = @new_record_before_save
...
-         if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
+         if records = associated_records_to_validate_or_save(association, new_record_before_save, autosave)

「インスタンス変数をいったんローカル変数に入れる…?パッと見あまり意味がなさそうに見えるけど、マルチスレッドの文脈でインスタンス変数が共有されておかしくなってたのを修正したとかそんな感じかな?🤔」「インスタンス変数が他から変更されてたのに保存できちゃってたとか」「そういう感じですね」「その割にはdupしてないしな〜: インスタンス変数を引き回さないという意味でこの書き方は正しいんだけど、これでバグが直るのかどうかまだピンとこない😅」「とりあえず次行きましょう〜」

よく見たら2017年2月にオープンしたプルリクなので長い旅でしたね。久しぶりにy-yagiさんブログを覗いてみました。

参考: rails commit log流し読み(2019/04/10) - なるようになるブログ

autosave: trueが指定されている、かつ、参照が循環しているassociationがあった場合に、idの指定が無い不正なjoin recordが生成されてしまうバグがあったのを修正しています。
同ブログより

⚓==とハッシュメソッドのアロケーションを削減

# 同PRより
Warming up --------------------------------------
          Orginal ==    66.978k i/100ms
       Orginal #hash    38.182k i/100ms
              New ==   165.441k i/100ms
           New #hash   145.549k i/100ms
Calculating -------------------------------------
          Orginal ==    846.966k (± 4.4%) i/s -      4.287M in   5.073426s
       Orginal #hash    457.440k (± 7.2%) i/s -      2.291M in   5.038573s
              New ==      2.877M (± 7.3%) i/s -     14.393M in   5.032400s
           New #hash      2.366M (± 8.1%) i/s -     11.789M in   5.018887s

つっつきボイス:「3倍から5倍ぐらい速度改善してますね🚅」「ダブルイコール!」「改善前はattributes_for_hashでいっぱいオブジェクトが作られてたということかな?」

# activerecord/lib/active_record/connection_adapters/column.rb#L64
      def ==(other)
        other.is_a?(Column) &&
-         attributes_for_hash == other.attributes_for_hash
+         name == other.name &&
+         default == other.default &&
+         sql_type_metadata == other.sql_type_metadata &&
+         null == other.null &&
+         default_function == other.default_function &&
+         collation == other.collation
      end
      alias :eql? :==

      def hash
-       attributes_for_hash.hash
+       Column.hash ^
+         name.hash ^
+         default.hash ^
+         sql_type_metadata.hash ^
+         null.hash ^
+         default_function.hash ^
+         collation.hash
      end

+     protected
+
+       def attributes_for_hash
+         [self.class, name, default, sql_type_metadata, null, default_function, collation, comment]
+       end
    end

#==の修正後は&&で結んでますね」「個人的にはここで&&で結ぶのはあまり好きじゃなくて、attributes_for_hashで書きたい方ではある: 項目がどんどん増えたらここに足すのってあまり見た目が麗しくないというか」「それもそうですね」「これ以上項目が変わらないならともかくですが☺

#hashの方は^でつないでますね」「^ってなんだっけ…論理演算か」「XOR!」「attributes_for_hash.hashの右の#hashは、ハッシュ値のハッシュですね: 左はRubyのデータ構造としてのハッシュ」「右と左でhashの意味が違うってややこしい〜😭」「こわ〜い😅

参考: ハッシュ関数 - Wikipedia

attributes_for_hashが削られたということはコストが高かったんかな〜: attributes_for_hashって配列を返してたから、配列のコストが高かった?」「あーそうか!変更後みたいに直に比較しちゃえばアロケーションが発生しないと」「個人的にはmapでやってもいいんじゃね?と思ったりもするけどっ😆

「項目が固定しているから直接比較しようという、絵に描いたようなチューニングですね: 業務コードとかだとこう書きたくはない気持ち☺」「どうやらここではメモ化も効かなさそうか」

「これまで話を聞いてきてだんだん見えてきたんですけど、最初から最適化でやろうとしないのが大事なんですね」「そうそう😋: 最初から変に速度出そうとすると、ぴよコード🐣が量産されちゃって読むのも直すのもつらくなるから、最初は素直に書こうね💯

⚓ルーティングに named_captureを追加

# actionpack/lib/action_dispatch/journey/path/pattern.rb#L139
+         def named_captures
+           @names.zip(captures).to_h
+         end

つっつきボイス:「ビーバー大好きな@baweaverさんが足した、名前付きキャプチャだけ取り出す機能みたいです」「名前付きキャプチャって何だっけ😆」「正規表現でたまに使う、かっこ()内のマッチを$1とか\1みたいに登場順序の数字で参照する代わりに、たとえば(?<名前>正規表現)と書くと\k名前みたいに名前で参照できるヤツですね」「それか!」「名前付きキャプチャは使いすぎると読みづらくなるし、正規表現ごとに方言があってめんどくさいんで自分はあまり使いませんが😭

# actionpack/test/journey/path/pattern_test.rb#L284
+       def test_named_captures
+         path = Path::Pattern.from_string "/books(/:action(.:format))"
+
+         uri = "/books/list.rss"
+         match = path =~ uri
+         named_captures = { "action" => "list", "format" => "rss" }
+         assert_equal named_captures, match.named_captures
+       end

参考: 正規表現で名前付きキャプチャを使う - Qiita

「それを、正規表現の方で名前さえ付けておけばいきなりハッシュで取れるということね😋」「名前付きキャプチャをいちいち正規表現で取り出さなくても、最初からハッシュの形で取れるのがよさそう😍」「ショートハンド増やした感」「zipは引数を配列の配列に変換するのか」「それをto_hしてるだけ」

参考: instance method Array#zip (Ruby 2.6.0)

自身と引数に渡した配列の各要素からなる配列の配列を生成して返します。 生成される配列の要素数は self の要素数と同じです。
ブロック付きで呼び出した場合は、 self と引数に渡した配列の各要素を順番にブロックに渡します。
ruby-lang.orgより

# ruby-lang.orgより
p [1,2,3].zip([4,5,6], [7,8,9])
    # => [[1, 4, 7], [2, 5, 8], [3, 6, 9]]

⚓Zeitwerkの互換用ActiveSupport::Dependenciesでのautoloaded_constantsautoloaded?の実装をやめた

ZeitwerkのActiveSupport::Dependenciesの互換性インターフェイスでautoloaded_constantsautoloaded?を実装しなくなった(元々ドキュメントないし)。実験したところ、introspectionのユースケースはさして多くないし、トラブルシュートはログで行われる。この設計上のトレードオフにより、どの環境でもメモリ使用量が削減される。
Xavier Noria

# activesupport/lib/active_support/dependencies/zeitwerk_integration.rb#L24
-       def autoloaded_constants
-         cpaths = []
-         Rails.autoloaders.each do |autoloader|
-           cpaths.concat(autoloader.loaded_cpaths.to_a)
-         end
-         cpaths
-       end
-
-       def autoloaded?(object)
-         cpath = object.is_a?(Module) ? object.name : object.to_s
-         Rails.autoloaders.any? { |autoloader| autoloader.loaded?(cpath) }
+       def to_unload?(cpath)
+         Rails.autoloaders.main.to_unload?(cpath)
        end

つっつきボイス:「こういう改修するにはいいタイミングかも😋」「ZeitwerkはRails 6で新しく入るオートローダーです↓」「と週刊Railsウォッチに書いてあった😆

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

「morimorihogeさんの説明を記憶ベースで再現すると、現在のRailsでは名前空間とか定数読み込み順序で、たまにとてもわかりにくいバグが発生することがあって↓、Zeitwerkはそのあたりを解決するためのものだそうです」

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

「Zeitwerk(ツァイトヴェルク)という名前はスイスの時計メーカーから取ったようで、日本的な感覚で言うとセイコーみたいな感じでしょうか😆」「それ以前に文字から発音がわからないです😆」「ドイツ語のZ(ツェット)って英語のZ(ズィー、ゼット)よりずっと軽くて、逆にSの方がズみたいに濁る感じですね」「逆さま感🙃」「あとドイツ語のWは英語のVに近い感じで、Wolfもドイツ語っぽく言うとヴォルフみたいな音になりますね: そういう変換をかけるとドイツ語っぽくなるといえばなります😆

参考: Z -ツェット- - Wikipedia
参考: 誤用戦士ガンダム・荒野を走るニセドイツの列! | ドイツ情報満載 − Young Germany Japan by ドイツ大使館 — 名エッセイです❤

⚓カラムオブジェクトからtable_nameを削除して身軽に

# activerecord/lib/active_record/connection_adapters/column.rb#L5
  module ConnectionAdapters
    # An abstract definition of a column in a table.
    class Column
-     attr_reader :name, :default, :sql_type_metadata, :null, :table_name, :default_function, :collation, :comment
+     attr_reader :name, :default, :sql_type_metadata, :null, :default_function, :collation, :comment

つっつきボイス:「kamipoさんのconnection_adapter修正です」「リファクタリング?」「table_nameを削ってるから違うっぽい」「オブジェクトひとつ削ったけど他でできるからいいよね、みたいな」

「カラムはテーブル名と依存関係が元々あるから、カラムにいちいちtable_name置かなくてもわかるでしょというのはワカル」「たしかに〜」「カラムはテーブルに紐づくんだからカラム単独で何かするというのも考えにくいし、単独で使ったとしてもそこでtable_nameが欲しいとも考えにくいし」「今までカラムの数だけtable_name持ってたとしたら冗長ですね」「設計上はカラムでtable_name持つ可能性がないわけではないけど、実用上いらないんじゃね?という感じ☺

⚓Doc: t.integert.bigintに修正

# guides/source/association_basics.md#L394
    create_table :accounts do |t|
-     t.integer :supplier_id
+     t.bigint  :supplier_id
      t.string  :account_number
      t.timestamps
    end

つっつきボイス:「ドキュメントのt.integert.bigintに書き換えるという修正です: これ、ある時期に変わってましたよね?」「そうそう、変わってた↓」「うっかり真似されないようにしないと☺

そういえばこんなツイート↓を昔のウォッチに貼りました。

「integerだと基本的に桁数が足りなくなる可能性があるんですよ」「IDとかはbigintにしておかないと、最初のうちはよくても忘れた頃に足りなくなるみたいな」「なのでデータベースのIDでintegerは割とマズいですね🧐

「その意味で、UUIDって型できないかな〜🥺って思うし」「それ欲しいかも!」「特にRailsだとURLに出てくることが多いから、UUIDの方が適切な場合ってあるんですよね」「記事のページぐらいだったらURLに数字出てもいいけど、ユーザーIDとか生で出したくないし」

参考: PostgreSQL 10.5文書 8.12. UUID型
参考: UUID() — MySQL :: MySQL 5.6 リファレンスマニュアル :: 12.18 その他の関数
参考: Rails の UUIDプライマリキーを試す - Qiita

RailsのモデルIDにUUIDを使う(翻訳)

⚓Rails

⚓マイグレーションで本番データに触る方法(Ruby Weeklyより)

# 同記事より
class AddSlugsToPosts < ActiveRecord::Migration[5.2]
  # 最初に、データのマイグレーションに使うモデルを定義
  class Post < ActiveRecord::Base
    belongs_to :user
  end

  class User < ActiveRecord::Base
    has_many :posts
  end

  def up
    # 次にカラムを追加してNULL許容にする
    add_column :posts, :slug, :string, null: true

    # 次に新しいカラムがモデルに拾われるようにする
    Post.reset_column_information!

    Post.includes(:user).find_each do |post|
      # 次にslugをセットしてpostをsave
      post.slug = post_slug(post)
      post.save!
    end

    # 次にカラムをNull非許容にする
    change_colum_null :posts, :slug, false
  end

  def down
    remove_column :posts, :slug
  end

  def post_slug(post)
    # 重要: productionのモデルから、マイグレーションの書き込みに使った
    #      title-to-slugアルゴリズムをコピーすること
  end
end

つっつきボイス:「この記事の人は、マイグレーションの中でActive Recordモデルを作るというやり方をしてるみたいです」「あ〜こういうのをマイグレーションの中で使ってるのか: 何だかよくない気はするけど、やりたい気持ちはワカル気がする」

「記事の中でreset_column_information!ってのやってる😳: こんなのあったのね↓」

参考: migration の中で model を触ったら必ず reset_column_information する - onk.ninja

「どうやらこの人がやりたいのはデータマニピュレーションなのね」「スキーマではなく、ということなのでそうみたいです」「う〜ん、これはマイグレーションでやることと違う気がするな〜: 自分はRailsのマイグレーションにはDDL(データ定義言語)を書くべきであって、DML(データ操作言語)は別のところに書くべきだと思ってるし」「DDLやDMLって、データベーススペシャリストの試験問題に出てきましたね」「そうそう、SELECTやINSERTやUPDATEやDELETEがDMLに相当します🧐

参考: SQLの種類(DDL、DML、DCL) | 酒と涙とRubyとRailsと

「記事の人はpost_slug(post)を書いてるからマニピュレーションしたいんだと思うんですが、マイグレーションでこれをやるのはアブナイ気がする🤢」「こういう操作はやっぱりバッチを書くべきですか?」「自分だったらバッチ書くな〜: まあそういう運用のシステムがないとも言えませんけど😆

babaさんの以前の記事↓にも『Active Recordのモデルをマイグレーションで使わない方がいい』と書かれていたのを思い出しました。

[Rails 3] 失敗しないmigrationを書こう

「そうそう、記事にもあるけど、テーブルにカラムを追加したときってたいてい値が入ってて欲しいんですよ〜: こうやってみたい気持ちはワカル」「デフォルト値とか?」「でもデフォルト値では間に合わないこともあったりするんで、そういうときは2回マイグレーションしたりするんですよ」「あー、以前のウォッチ↓で、巨大なテーブルへのマイグレーションでいきなりデフォルト値とかの制約を付けると本番でその間テーブルがロックされるから作業を分割しよう、みたいな話がありましたけど、それですね」「そうそう☺

週刊Railsウォッチ(20190121)Rails 6.0.0 beta1リリース、Railsは2019年も「あり」か、Jetsでサーバーレス、ES2018の新機能、RSpecの心ほか

「たとえば最初はNOT NULL制約なしでカラムを追加して、別途バッチで値を入れて、それからNOT NULL制約を付ける、みたいにやるのが安全」「ですよね」「それをやりたくないとたぶんこの記事みたいなやり方になる😆」「😆」「いい悪いということじゃないんだけど、自分は作業を分割しないとコワい😆

⚓🌟still_life: テストの画面HTMLをRailsバージョンごとに保存(Ruby Weeklyより)🌟

@amatsudaさんがheavens_doorに続いてまた新しいgemを作りました。


つっつきボイス:「amatsudaさんの新作です: これヒットじゃないかと思うんですがどうでしょう?」「おぉ?」「Railsをバージョンアップするときにこれでテストを走らせて、そのバージョンで吐き出されるHTMLをバージョン名を付けて保存しておくというもののようです」「お〜なるほど!これはワカル〜😍

# 同リポジトリより
% STILL_LIFE=rails52 rails test:system test
% tree tmp/html
tmp/html
└── rails52
    └── test
        ├── controllers
        │   ├── users_controller_test.rb-14.html
        │   ├── users_controller_test.rb-20.html
        │   ├── users_controller_test.rb-27.html
        │   ├── users_controller_test.rb-32.html
        │   ├── users_controller_test.rb-37.html
        │   ├── users_controller_test.rb-43.html
        │   └── users_controller_test.rb-9.html
        └── system
            ├── users_test.rb-14.html
            ├── users_test.rb-18.html
            ├── users_test.rb-21.html
            ├── users_test.rb-25.html
            ├── users_test.rb-26.html
            ├── users_test.rb-29.html
            ├── users_test.rb-32.html
            ├── users_test.rb-36.html
            ├── users_test.rb-37.html
            └── users_test.rb-9.html

4 directories, 17 files

「たとえばRails 5.1のとき、Rails 5.2のとき、とこうやって画面HTMLが保存されていれば、差分がないかどうかをチェックすることで無事バージョンアップされたかどうかを確認できるということですね」「テストがあればの話だろうけどっ😆

「これならローカルでも気軽に動かせるし、結果が勝手にたまっていってくれるし、うん、これはいいですね~👍」「今までこれがなかったのが不思議」「画面出してくれるテストがあれば、それ以上みっちり書かれてなくてもやれますしね」「やってみたらバージョンアップでやんなるほど差分が出て目を覆いたくなったりして😆」「これは有能🥰」「標準で入っててもいいぐらいかも」

久しぶりに🌟を進呈いたします。ありがとうございます。

Beyond Compare↓とかWinMergeとかを使えばディレクトリごと差分をさっとビジュアルチェックできますね😋

もうひとつのGUI diffツール「Beyond Compare 4」

Rolling Stonesの昔のライブ盤のタイトルが「Still Life」(平穏な生活)でしたね。

「前回のheavens_doorという名前も音楽ネタっぽかったんですが、今回のももしかしてそうかなと思って: amatsudaさんの音楽の趣味についてはまったく知らないので憶測です😅」「heavens_doorはアニメ系でちょくちょく見るような気も😆: JOJOにもヘブンズドアー出てくるし」「あーそっちかも!」「でもJOJOの元ネタがだいたいもろその時代のロックだったりするし😆」「ですよね: 今やキングクリムゾンってJOJOの方が知られてますし😆

参考: 岸辺露伴のスタンド「ベブンズ・ドアー」は最強なのか!?能力考察まとめ
参考: キングクリムゾンのスタンド能力って結局なに?【ジョジョ5部ラスボスが強すぎる】 │ NazeNani JillTone

少し前に、本家King Crimsonのツイート↓にJOJOのそれが出てきて軽く話題になったのを思い出しました。

⚓react-rails: RailsでReactをスマートに使う(React Statusより)

gem 'webpacker'
gem 'react-rails'

つっつきボイス:「react-railsって初めて見たんですが、WebpackerにもSprokectsにも対応してて、TypeScriptやES6やCoffeeScriptやJSXとも共存できると言ってます」「マジか!じゃ今やってるヤツにこれ入れればReactさっと使える?😆」「今更gemかというのは置いといても😆、★5000超えてるし」

リポジトリをよく見ると、reactjs公式なんですね。最も古いコミットは2013年でした。

「後はどこまで使えるかですね☺」「どうなってもいいようなアプリで誰か試してみないかな😆」「どっかで使ってみよ」「React自体がアップグレードしたときにどうなるかも気になります😆」「それはある😆

参考: ReactをRailsと共に使う方法 - Qiita

「ところでJSXって何でしたっけ?😆」「MSXではない(キリッ」「前にウォッチでJSXを取り上げたとき、JSXが2つあることに当時気づかなかったのを思い出しました😅」「どっちだったか忘れたけど、ちょっとキモい書き方だった気がする👻ウォッチ)」「あ〜、タグの中にコード…イヤな予感😅」「ヤな予感しかしない😅」「ちっちゃく作る前提ならいいんでしょうけど☺

参考: Introducing JSX – React — ReactのJSX
参考: JSX - a faster, safer, easier JavaScript — DeNAが開発したJSX言語

⚓よく見るRails APIは?


追いかけボイス:「このエントリをつっつくのを忘れてました💦」「そうだったかも☺」「api.rubyonrails.orgでよく見るAPIって何かあります?」「そうねぇ、APIでというわけじゃないけどHashWithIndifferentAccessあたりはよくググるかも」「どもですっ♪」

参考: ActiveSupport::HashWithIndifferentAccess

⚓その他Rails

 group :test do
-  gem 'selenium-webdriver'
-  gem 'chromedriver-helper'
+  gem 'webdrivers'
   # other test-only dependencies ...
 end

つっつきボイス:「chromedriver-helperwebdriversに置き換わるというアナウンスが正式に出ました」「ドライバ周りは幸いあまりかかわりなかったし😆

⚓Ruby

⚓Rubyコードフォーマッター対決(Ruby Weeklyより)


つっつきボイス:「RuFoとplugin-rubyとrubyfmtあたりのフォーマッタを比較しています」「rubyfmtが2種類あるし」


つっつきボイス:「RuboCopやRipperの話なんかも混じえて…で結果は『ものによる』🤣」「結論じゃないし🤣」「判定を避ける🤣」「『個人的にはRuboCopかRuFo』だそうです😆

「rubyfmtはgemですらないスクリプトファイル、RuFoはもう少し洗練されてて、pritter-rubyは最近JSで人気のprettierで動くみたいです」

⚓Rubyにパッチを当ててテストを高速化(Ruby Weeklyより)

// https://bugs.ruby-lang.org/attachments/download/7717/ruby_gc_malloc_trim.patchより
diff --git a/configure.ac b/configure.ac
index d251da9915..5e2a67db1b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1825,6 +1825,7 @@ AC_CHECK_FUNCS(lstat)
 AC_CHECK_FUNCS(lutimes)
 AC_CHECK_FUNCS(malloc_usable_size)
 AC_CHECK_FUNCS(malloc_size)
+AC_CHECK_FUNCS(malloc_trim)
 AC_CHECK_FUNCS(mblen)
 AC_CHECK_FUNCS(memalign)
 AC_CHECK_FUNCS(memset_s)
diff --git a/gc.c b/gc.c
index 1331ef21dc..12caa162e7 100644
--- a/gc.c
+++ b/gc.c
@@ -6660,7 +6660,15 @@ gc_start(rb_objspace_t *objspace, int reason)

     gc_prof_timer_start(objspace);
     {
-   gc_marks(objspace, do_full_mark);
+        gc_marks(objspace, do_full_mark);
+#ifdef HAVE_MALLOC_TRIM
+        /* [Experimental] Explicitly free all eligible pages to the kernel.  See:
+         *
+         * - https://www.joyfulbikeshedding.com/blog/2019-03-14-what-causes-ruby-memory-bloat.html
+         * - https://bugs.ruby-lang.org/issues/15667
+         */
+        if (do_full_mark) malloc_trim(0);
+#endif
     }
     gc_prof_timer_stop(objspace);


つっつきボイス:「テスト高速化のためにRubyにパッチ当ててみたそうです」「いかにもメモリをゴニョゴニョしてる感」「malloc_trim()とか何だかコワいもの持ち出してるし😆」「テストだからと割り切ってザクザク切り刻んでるんでしょうね🍴」「わかる〜」「普段はこれでテストちっちゃく回して週一くらいは普通のを回すとか」「どのぐらい信頼できるんでしょう😅」「記事を見ると、メモリ方面の強い人たちといっぱい議論したっぽいです💣

参考: Man page of MALLOC_TRIM
参考: Cables vs. malloc_trim, or yet another Ruby memory usage benchmark - DEV Community 👩‍💻👨‍💻

⚓その他Ruby


つっつきボイス:「mrubyバイトコードハンドブックは技術書典に出る本らしいです」

参考: [DL版]mrubyバイトコードハンドブック - Kishima Craft Works - BOOTH



つっつきボイス:「いい先生ですよね🥰

⚓Ruby trunkより

⚓Rustのビルダーが欲しい


つっつきボイス:「RubyのC拡張みたいにRust拡張をビルドしたいということみたいです」「そのうち出てくるかも?」

⚓Unicode 12.1.0がfinalに


つっつきボイス:「Unicode 12.1.0は、Unicode Consorciumが令和年号のためだけにわざわざリリースすることになってたヤツですね」「お世話かけてます!」「お疲れさまです!」「さーせん!」「😆

参考: Unicode 12.1.0 — 現時点ではDRAFTです

「ちなみに年号はeraですね(Japanese era name)」「era知らないとリリースノート意味わからないし😆

⚓番外

⚓ブラックホールの撮影に世界で初めて成功

参考: First Image of a Black Hole — GoogleのDoodleにも早速取り入れられています


つっつきボイス:「ニュースでも話題になってましたが、撮影そのものは2年ぐらい前で、それをビジュアライズするための数式に2年かかってたどり着いたということだそうです」「おぉ〜なるほど♪」

「あと、一般相対性理論を外宇宙で確認できたという意義もあったみたいです」「相対性理論を覆すのはなかなか大変😆


今回は以上です。

おたより発掘

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

週刊Railsウォッチ(20190408-1/2前編)RubyKaigiの予習資料、Rails「今年ベストのプルリク」、numbered parametersの議論ほか

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

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

Rails公式ニュース

Ruby Weekly

React Status

react_status_banner


週刊Railsウォッチ(20190415-2/2後編)最近のRDBMS市場、Flutterがデスクトップにも向かう、書籍『失敗から学ぶRDBの正しい歩き方』ほか

0
0

こんにちは、hachi8833です。明日博多入りいたします🍜。唯一の心残りはクラフトワークの来日がRubyKaigi 2019の会期ともろにぶつかったので泣く泣く諦めたことです。

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

お知らせ🛎

来週の週刊RailsウォッチはRubyKaigiでつっつき会が開催されないためお休みいたします🙇

⚓はみだしRuby/Rails

つっつきの後で見つけたツイートなどをいくつか貼ります。

先週開催されたRejectKaigiのスライドが他にも続々上がっています↓。




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

⚓Googleが続々サービスを発表


つっつきボイス:「クラウド方面の速報はもうPublickeyの方にお任せします: Railsウォッチのペースだと到底かなわないので😆」「😆

「DockerコンテナをGoogleのクラウドで実行できるようになったのかー」「コンテナになっていれば何でも実行できるってことですよね、しかもサーバーレスで」「そういう時代か👴」「コンテナがでかくならないようにしないと」「public betaらしいので今のうちに遊んでみようかな😋」「容量どのぐらいまで置けるのかな😎

「もうひとつのニュースもちょっとびっくりですね😳」「Active Directoryを?Googleが?マネージドで?」「Microsoft SQL ServerならまだわかるけどActive Directoryもやるとは!」「しかもLDAPじゃなくて」

参考: Active Directory – Wikipedia
参考: Lightweight Directory Access Protocol – Wikipedia

参考: 第1回 誰も教えてくれないActive DirectoryとLDAPの「本当の関係」[前編]:知られざるActive Directory技術の「舞台裏」|gihyo.jp … 技術評論社

「LDAPって今もあるのかな?😆」「最近あまり聞かないけど、まだまだあるはずですよね🤔」「終わってはいないハズ」「使おうとしたというより、既に使われちゃってることの方がありそう😆」「LDAPって込み入っててわけわからなくなりがちなので触りたくない気持ち😭」「いつだったか、Railsの認証にLDAPが絡んでたのを見たことがあります😇

参考: RubyからLDAPのデータを扱うgem net-ldap | 酒と涙とRubyとRailsと

⚓AWS Innovateオンラインカンファレンスが開催中


aws.amazon.comより


つっつきボイス:「実はこのAWS Innovate Online Conferenceは既に開催が始まっていて、通常のセッション以外にも、期間中はAWS認定の有料トレーニングコース(「ソリューションアーキテクト – アソシエイト」)を無料で受けられるそうです」「へぇ〜」「メインのセッションは4/26(火)で営業時間中ですが😆、5/7(火)までやってるのでゴールデンウイークにもやれそうですね」「修了すれば証明書を発行とな」「ちょうど春の研修のタイミングだし、やってみようかな😋

参考: AWS資格勉強を始めようと思っている方!今がチャンスですよー 〜AWS Innovate試験対策セッションのご紹介〜 | DevelopersIO

「AWS Innovate、日本の昼間の時間帯にやるんですね」「あ、以前のAWS Innovateは海外で開催でしたっけ」「2018年からオンラインカンファレンスになってるのか」

⚓補足: AWS Summit Tokyo 2019

AWS Summit Tokyo 2019(6/12〜6/14)も募集開始されました。今年はRubyKaigiと会期がぶつかっていないのがうれしいですね😋

⚓Nuclio: オープンソースのサーバーレスプラットフォーム(Serverless Statusより)


つっつきボイス:「NuclioというオープンソースのサーバーレスプラットフォームをAWS Lambdaと比較する記事です」「今日のつっつきは惜しくもmorimorihogeさんが都合で出られないので、この辺りはあんまり深い話はできないと思います🙇」「Lambdaだと関数を書くときにいくつか制約があったりするという話が以前もありましたが(ウォッチ20190225)、Nuclioならできるみたいなものもあるようです」「今ならラムダって何だろうみたいな人も多いだろうから、今Nuclioがサーバーレスに参入するならチャンスあるのかも🤔


同記事より

同記事の中から、両者の比較部分のごく一部を抜粋しました。同記事の結論ではNuclioを持ち上げていますが、セカンドオピニオンが欲しいです😅

  • Lambdaのコンカレンシーモデルではアプリ開発が楽で呼び出しベースの課金がやりやすいが、関数がIO待ちのときにCPUサイクルをidleにできず、スケールするにはコールドスタートが必要。
  • Nuclioの「関数プロセッサ」(=コンテナに似ている)は関数ワーカーを複数保持でき、各ワーカーが1度に1つのメッセージを処理できる。同じ関数プロセッサ内でのコンカレンシーをサポートし、負荷に応じて関数プロセッサのレプリカ数を自動的にスケールする。
    同記事より抜粋・再構成

「ところでNuclioはどこに置いたらいいんでしょう😆

リポジトリを見ると、以下の4つのセットアップ例がありました。ラズパイでも動かせるようにする予定のようです。

⚓モバイル

⚓Flutterがデスクトップも目指している


flutter.devより


つっつきボイス:「BPS社内のFlutter部の方がSlackに投下してたので」

以下はFlutter部からの追伸を再構成したものです。

「FlutterでデスクトップOSアプリ、は実は以前より粛々と開発が続いていました↓」

「今は自分のアプリを試そうと思ったら例えば『exampleディレクトリのサンプルを自分のコードに入れ替える』ような形でやれます↓」

「冒頭のissueは、これをflutter開発のメインのコマンドであるflutterコマンド経由で
プロジェクトを作成、ビルド、実行できるように整備していく事に本腰入れはじめたissueのようです」

「なお、追加プロジェクト作成のissue達はこちらです↓」

参考: Support flutter create for macOS targets · Issue #30703 · flutter/flutter
参考: Support flutter create for Windows targets · Issue #30704 · flutter/flutter
参考: Support flutter create for Linux targets · Issue #30705 · flutter/flutter

⚓SQL

⚓書籍『失敗から学ぶRDBの正しい歩き方』


つっつきボイス:「この間のPHPerKaigiを見に行ったメンバーが以下のスライドでこの本を知って、今日のWebチーム内発表で触れていたのでチェックしました」「そうそう☺

第1章 データベースの迷宮
第2章 失われた事実
第3章 やり過ぎたJOIN
第4章 効かないINDEX
第5章 フラグの闇
第6章 ソートの依存
第7章 隠された状態
第8章 JSONの甘い罠
第9章 強過ぎる制約
第10章 転んだ後のバックアップ
第11章 見られないエラーログ
第12章 監視されないデータベース
第13章 知らないロック
第14章 ロックの功罪
第15章 簡単過ぎる不整合
第16章 キャッシュ中毒
第17章 複雑なクエリ
第18章 ノーチェンジ・コンフィグ
第19章 塩漬けのバージョン
第20章 フレームワーク依存症
amazon.co.jpより

「タイトルひとつひとつがとっても読んでみたい欲をそそります😋」「『効かないINDEX』🤣」「キャッチコピーとしてもいい感じ🤣」「『転んだ後のバックアップ』このままかるたになりそう🎴」「転ばないとバックアップしないとかあるある〜🤣」「『監視されないデータベース』に『知らないロック』とか、噛めば噛むほど味が出る感」「キャッシュ、中毒になるんですよこれが😅」「『ノーチェンジ・コンフィグ』って、素のままの設定で本番にぶちこんじゃうとか?」「『塩漬けのバージョン』もいろいろ思い出すし」

「この本、翻訳とかじゃなくて日本語で書かれてるし」「こういうのは翻訳だとガタピシしてしまいがちなので、これだけキマったタイトルにするのは至難の業です😭」「翻訳臭がないのはうれしいですね😋」「内容期待できそう😍

参考: そーだいなるらくがき帳 — 曽根さんのブログ

⚓PostgreSQLのcount(*)を高速化(Postgres Weeklyより)

-- 同記事より
SELECT count(*) FROM large_table;

つっつきボイス:「ぽすぐれのcount(*)を高速化する話だそうです」「うろ覚えですが、Oracleだとこういう書き方するとめちゃ遅くなるから何らかのカラム名を書けって言われたことあったな〜: 他のRDBMSではどうなのか知りませんが😆

「まずPostgreSQLはvisibility mapを使ってindex onlyスキャンを高速に行えるから、VACUUMしようみたいな話」「それから集約テーブル(aggregate table)」「集約テーブルって何でしたっけ😅

参考: 第3回 テーブル設計のグレーゾーン~毒と薬は紙一重 (2)列持ちテーブル :SQLアタマアカデミー|gihyo.jp … 技術評論社

このように出力の形に合わせたテーブルをフロントに持つ方法は,古くから利用されています。データを集計した結果を保持する集約テーブル(サマリーテーブル)も,このタイプのひとつに位置付けられるでしょう。
同記事より

count(*)は結局どこかのタイミングで数えるしかないんですが、WHEREみたいな条件がないとどこにインデックスをかけたらいいのか判定できなくてフルスキャンされたりすることがあるし」「あ〜」

「元記事には『count(*)がそもそも要るのか?』という話もありますね」「count(*)的なものが欲しいことはたくさんあるんですが、count(*)でなくても取れればそれでいいんだし😋」「かといって*使わないと、SELECTしてないidを数えるかどうかみたいなときにidが主キーじゃないととても面倒とか、とにかくいろいろ大変になるわけです」「ふむ〜」「なのでとりあえずcount(*)にしとけば文法的にも正しいし、という感じでやることもあるといえばある」「あとはRDBMSの実装次第かな、さすがに毎回フルスキャンはしないと思いたいけどっ😆

「あと元記事に『MySQLのMyISAMには”magical row count”があるけどPostgreSQLにはない』みたいな話もありますね」「あ〜、どのRDBMSだったか、行に勝手にidを振るやつがあるけどMySQLでしたか」「そこを見れば一発でカウント取れるという感じですね」「思いっきりRDBMS依存のコードになりますが😆

参考: MySQLでテーブルの行数を数える | b.l0g.jp

⚓最近のRDBMS市場(DB Weeklyより)


同PDF p32より


つっつきボイス:「最近のRDBMS市場のサーベイPDFです: 割と長いので目ぼしいグラフをちょっと覗いてみます👀」「Oracleは何位かな〜☺


同PDF p43より

「Oracleは安定のトップ」「MySQLって2位なんだ!」「3位がMicrosoft SQL Server」「ぽすぐれが意外にも4位でMongoDBとどっこいどっこいとは😳」「PostgreSQLの今年3月のスコアはMySQLの1/3ぐらい…」「ぽすぐれもっと評価されてるかと思ってた」

「自分も今の会社に来て初めてPostgreSQL触っていい子😍だってわかったけど、それがなかったらMySQL選んでたかも」「Microsoft Accessが7位につけてるのも何というか😆」「FileMakerもいらっしゃるし」「ここに入れていいんだろうか😆」「SQLite(10位)がAccessより下だし😳」「SQLiteもっと強いかと思ってた😅」「自分も〜」「クラウド系のDynamoDBもまだ下の方ですね」「きりがないのでとりあえず次へ👉

⚓その他SQL


⚓JavaScript

⚓WebAssemblyを20倍速にした話(JavaScript Weeklyより)


同記事より


つっつきボイス:「JSアプリをWebAssemblyにしてさらにチューニングして20倍速くしたそうです」「WebAssemblyがまだよくわかってないけどっ😆」「minifyしたとかじゃなくて?😆」「WebAssembly(wasm)は、すごく大雑把に言うとブラウザで実行できるバイナリですね」


webassembly.orgより

参考: WebAssembly - Wikipedia

「バイナリにしてどうするんだろ」「えっと、ちっちゃくなって速くなる: 自分が試しに以下の記事でmrubyで動かしてみたときはJavaScriptからwasmを起動してました」「Cのライブラリを呼べるとかそういうのとはまた違うのかな」「CやC++の他にRustやGoでもwasmバイナリを吐けるようになってますね: でもまだ新しいから最適化とかはこれからという感じで」「へぇ〜」

mrubyをWebAssemblyで動かす(翻訳)

「ブラウザ上で動くVMみたいなもの?」「wasmはスタックマシンだそうです」

# 同記事より
# Compile to WebAssembly
$ emcc seqtk.c \
    -o seqtk.js \
    -O2 \
    -lm \
    -s USE_ZLIB=1 \
    -s FORCE_FILESYSTEM=1

「記事の方はいろいろ工夫して、最終的にJSの21倍に高速化したそうです↓」「『いつもこううまくいくとは限らない』ってありますが😆」「そりゃそうだ😆


同記事より

⚓「shotgun surgery」とReactコンポーネント(React Statusより)


つっつきボイス:「記事は割と長いのでタイトルだけ見ると、shotgun surgeryで『できちゃった婚』を指すshotgun marriageを思い出しちゃいます😆: 『うちの娘と結婚するか今ここで死ぬか』をショットガンで迫るという」

参考: shotgun marriageの意味・使い方・読み方 | Weblio英和辞書

【由来】 妊娠させられた娘の父親が相手の男に shotgun をつきつけて結婚を強制したことから》
weblio.jpより

「と思ったら、このshotgun surgeryはどうやらアンチパターンの名前みたい😅: 1箇所変えると他のあちこちも変えないといけなくなることだそうです」「やっぱり銀の弾丸はないっ🔫

参考: Shotgun surgery - Wikipedia
参考: Shotgun Surgery


refactoring.guruより

「できちゃった婚になぞらえてshotgun surgeryを深読みすると、『全部作り直すか死ぬか2つに1つだ』みたいな意味にもなるかな、なんて🤣」「仕事でも実際に迫られるヤツだ🤣」「作り直すには工数が足りないんだけど、このままメンテするのもつらいような状況🤣」「作り直す方が早いことも多いのに😢」「よく起きる問題ですよね☺

⚓is-online: オンラインかどうかを確かめるライブラリ(JavaScript Weeklyより)

// 同リポジトリより
const isOnline = require('is-online');

(async () => {
    console.log(await isOnline());
    //=> true
})();

READMEを見ると、ブラウザ環境ならNavigator.onLineで確認できるけど、それができないブラウザ以外のJS環境で使うそうです。

参考: Navigator.onLine - Web APIs | MDN

⚓その他JS


同リポジトリより


つっつきボイス:「ギターのタブ譜を生成するJSなんですが、タブ譜(tablature)ってご存知です?」「うんにゃ😆」「ギターを始めて間もない人が割とよく使うギター専用の譜面の記法で、ギターのどこを押さえるかをこんなふうにビジュアル表示します: 私は楽器が違うんでタブ譜に用はないんですが🤣」「🤣

参考: タブラチュア - Wikipedia

「ギターの人で、普通の五線譜は読めないけどタブ譜なら読める、という人を割と見かけることがあって、ちょっと不思議だなと思ってました」「押さえる位置が物理的に示されているからわかりやすいのかも?」「ギター、音楽の授業でちょっとやったけど指痛くてやんなった😆」「最初痛いっすよねホント😆

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

⚓GSMArena.com: さまざまなスマホのスペックを同じフォーマットで見られるサイト

BPSのテスティングチームの人がこのサイトに喜びの声を上げていました🥰


つっつきボイス:「サイトを開いて、スマホのメーカーと機種をクリックすると、そのスマホのスペックが表示されるんですが、その表示が以下のようにどのメーカー/機種でも同じフォーマットに揃えられています↓」「そこ重要!」「これは欲しいヤツ❤」「よくぞ作ったと思います👍


gsmarena.comより

「いいっすね〜(しみじみ)、ノートパソコンでもこういうサイトが欲しいです💻」「ノートPCはさらに大変そうだけど😆」「この間ノートパソコンの液晶のサイズを調べたんですけど、メーカーごとにいろんな表記方法があってつらいことつらいこと😭」「公式サイトにもちゃんと載ってなかったりしますし😆

⚓その他フロント


つっつきボイス:「Edgeのpreview buildがChromiumエンジンを採用するという記事です」「それをEdgeと呼んでいいものかどうか😆」「エンジンがChromiumならコスメティックな部分は各社好きにやってくれていい気がします☺」「Edgeほとんど使ってませんけど😆

⚓言語・ツール

⚓謎のV言語

// 同サイトより
fn main() {
    types := ['game', 'web', 'tools', 'GUI']
    for typ in types {
        println('Hello, $typ developers!')
    }
}

つっつきボイス:「この間社内で教えてもらったV言語という新言語なんですが、登場して数日で★が2000超えてました」「バイナリが小さいらしくて、一説にはRustやGoのライバルと言われているみたいです」「ほ〜」「ただ、私の探し方が悪いのか、まだコンパイラが見当たらないんですよね😅」「リポジトリにあるのはV言語のサンプルのようですね☺」「サイトにはplaygroundがあって動くし、soon availableとか書いてあるのでそのうち出るのかもしれませんが🤔」「また一文字言語が現れたか😆」「CとかRみたいな😆

⚓Goはどうしてデカい?(Golang Weeklyより)


同記事より

  • リポジトリ: jondot/goweight — Goバイナリサイズの分析ツール


同リポジトリより


つっつきボイス:「Goのバイナリがデカい理由を探る記事です」「ワンバイナリでやってるからしゃーない気が😆」「goweightは記事とは別に、バイナリが何で太ってるのかを分析するツールです」

# 同リポジトリより
$ ./goweight
  3.0 MB runtime
  1.6 MB net
  1.4 MB reflect
  1.3 MB gopkg.in/alecthomas/kingpin.v2
  870 kB math/big
  668 kB github.com/alecthomas/template
  628 kB syscall
  626 kB text/template
  550 kB go/ast
  546 kB encoding/json
  509 kB text/template/parse
  495 kB github.com/alecthomas/template/parse
  424 kB time
  402 kB regexp/syntax
  395 kB golang_org/x/net/dns/dnsmessage
  388 kB fmt

⚓その他言語



つっつきボイス:「gistに詳しく書かれてますが、こんな姿でもシェルスクリプト↓で、しかもフィボナッチ数列を生成するんだそうです」「ちょ😆」「ヤメテー😆」「まさに難読化」「まるでbrainf*ck」

# 同gistより
! : "`/???/???/???${#?}???<<<_.`";_____=${_::-~$?}
____='__+=___,___=__-___,__<_[$($_____<<<$___>&$[-~${##}])]||____'
((__=-~$?,____))|&$_____#シェル芸

— yamaya (@yamaya) April 6, 2019

参考: Brainf*ck - Wikipedia

⚓その他

⚓その他のその他


つっつきボイス:「無料で使えるGopherくんイラストなんですけど、オリジナルの目の焦点が合ってないっぽいGopherくんよりカワイイ成分多めだったので🥰」「かわゆい〜🐹


同リポジトリより


今回は以上です。RubyKaigiでお会いしましょう!

おたより発掘

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

週刊Railsウォッチ(20190415-1/2前編)Railsバージョンアップに便利なstill_life gem、Zeitwerkの改修進む、named_capture追加ほか

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

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

Publickey

publickey_banner_captured

Postgres Weekly

postgres_weekly_banner

DB Weekly

db_weekly_banner

Serverless Status

serverless_status_banner

Frontend Focus

frontendfocus_banner_captured

React Status

react_status_banner

Golang Weekly

golangweekly_logo_captured

まもなく令和!各言語で「令和」変換してみよう

0
0

BPSの福岡拠点として一緒にお仕事させて頂いてます、株式会社ウイングドアのアリタです。

もうまもなく令和ですね!
システムの新元号対応は進んでいますか。
もう直前ではありますが、各言語での令和変換を試してみたいと思います。

今回、令和変換してみる言語は下記の通りです。

  1. Ruby on Rails
  2. PHP
  3. Python
  4. JavaScript
  5. Swift

「令和」変換してみよう

⚓1. Ruby on Rails

Ruby on Rails には、和暦を扱うGemとして wareki が存在します。
今回は、warekiのGemで変換を行なってみます。

既にwarekiのGemがインストールされている状態で「rails c」を実行します。

1. 実行

  • wareki (0.4.0)
irb(main):001:0> Date.parse("2019-05-01").strftime("%Je%Jg年")
=> "令和1年"

無事、令和1年が表示されました!

ちなみに、wareki (0.3.0)で確認すると平成31年のままでした。

  • wareki (0.3.0)
irb(main):001:0> Date.parse("2019-05-01").strftime("%Je%Jg年")
=> "平成31年"

wareki (0.4.0)から令和に対応されたようですね。

また、Ruby 2.6.3も「令和」対応されているようです。
参考:【速報】Ruby 2.6.3リリース: 「令和」対応のみ

⚓2. PHP

PHPで和暦・日本語曜日を使うという記事で紹介されている
DatetimeUtilityが汎用性が高そうでしたので、今回こちらを利用させていただきます。

1. DatetimeUtilityに「令和」を追加

/**
 * 日時用汎用クラス
 */
class DatetimeUtility {
    /** 元号用設定
     * 日付はウィキペディアを参照しました
     * http://ja.wikipedia.org/wiki/%E5%85%83%E5%8F%B7%E4%B8%80%E8%A6%A7_%28%E6%97%A5%E6%9C%AC%29
     */
    private static $gengoList = [
        ['name' => '令和', 'name_short' => 'R', 'timestamp' =>  1556636400],  // 2019-05-01,
        ['name' => '平成', 'name_short' => 'H', 'timestamp' =>  600188400],  // 1989-01-08,
        ['name' => '昭和', 'name_short' => 'S', 'timestamp' => -1357635600], // 1926-12-25'
        ['name' => '大正', 'name_short' => 'T', 'timestamp' => -1812186000], // 1912-07-30
        ['name' => '明治', 'name_short' => 'M', 'timestamp' => -3216790800], // 1868-01-25
    ];
(以下略)

2. 実行

日付操作のためのCarbon、DateTimeクラスの2パターンで実行のためのソースコードを書いてみます。

  • Carbonの場合のソースコード
require 'vendor/autoload.php';
use Carbon\Carbon;

$carbon = Carbon::parse('2019-04-30');
$format = 'Jk年';
echo DatetimeUtility::date($format, $carbon->timestamp)."\n";
$format = 'JK年';
echo DatetimeUtility::date($format, $carbon->timestamp)."\n";

$carbon = Carbon::parse('2019-05-01');
$format = 'Jk年';
echo DatetimeUtility::date($format, $carbon->timestamp)."\n";
$format = 'JK年';
echo DatetimeUtility::date($format, $carbon->timestamp)."\n";
  • 実行結果
平成31年
平成31年
令和1年
令和元年
  • DateTimeの場合のソースコード
$datetime = new DateTime('2019-04-30');
$format = 'Jk年';
echo DatetimeUtility::date($format, $datetime->format('U'))."\n";
$format = 'JK年';
echo DatetimeUtility::date($format, $datetime->format('U'))."\n";

$datetime = new DateTime('2019-05-01');
$format = 'Jk年';
echo DatetimeUtility::date($format, $datetime->format('U'))."\n";
$format = 'JK年';
echo DatetimeUtility::date($format, $datetime->format('U'))."\n";
  • 実行結果
平成31年
平成31年
令和1年
令和元年

いずれも無事「令和」に変換することができました!

またCarbonの場合、下記の書き方でも和暦変換出来るようなのですが、こちらはCライブラリに依存するようです。

$format = '%EC%Ey年';
echo $carbon->formatLocalized($format)."\n";

⚓3. Python

PHPと同じパターンですが・・pythonで和暦で紹介されている
JpEraを拡張させて令和変換してみたいと思います。

1. jp_era.pyに「令和」を追加

class JpEra:
    def __init__(self, shorthand, era_name, begin_date, end_date=datetime.date.max):
        self.shorthand = shorthand
        self.era_name = era_name
        self.begin_date = begin_date
        self.end_date = end_date

    def in_term(self, target_date):
        return self.begin_date <= target_date <= self.end_date

    def jp_year(self, year):
        return year - (self.begin_date.year - 1)


JP_ERA = (
    JpEra("M", "明治", datetime.date(1868,  9,  8), datetime.date(1912,  7, 29)),
    JpEra("T", "大正", datetime.date(1912,  7, 30), datetime.date(1926, 12, 24)),
    JpEra("S", "昭和", datetime.date(1926, 12, 25), datetime.date(1989,  1, 7)),
    JpEra("H", "平成", datetime.date(1989,  1,  8), datetime.date(2019,  4,  30)),
    JpEra("R", "令和", datetime.date(2019,  5,  1)),
)
(以下略)

2. 実行

  • ソースコード: jp_era_test.py
# coding:utf-8
import datetime
from jp_era import *

heisei_date = datetime.date(2019, 4, 30)
reiwa_date = datetime.date(2019, 5, 1)
heisei_jp = jp_era(heisei_date)
reiwa_jp = jp_era(reiwa_date)

heisei = '%s%d年' % (heisei_jp[1], heisei_jp[2])
reiwa = '%s%d年' % (reiwa_jp[1], reiwa_jp[2])

print(heisei_date)
print(heisei)
print(reiwa_date)
print(reiwa)
  • 実行結果
$ python jp_era_test.py

2019-04-30
平成31年
2019-05-01
令和1年

無事、令和変換できました!

⚓4. JavaScript

toLocaleDateStringを利用すれば和暦変換可能です。

var date = new Date('2019/5/1').toLocaleDateString("ja-JP-u-ca-japanese",{era:'long',year:'numeric'});
console.log(date);
  • 実行結果
平成31年

・・残念。平成31年になってしまいました。

その他の手段として、JavaScriptで日付をフォーマットする(和暦対応)にある
「UltraDate.js」を利用すると良さそうです。

  • ソースコード
console.log(dateFormatJp(new Date('2018/12/31'), 'gggee年'));
console.log(dateFormatJp(new Date('2019/1/1'), 'gggee年'));
console.log(dateFormatJp(new Date('2019/4/30'), 'gggee年'));
console.log(dateFormatJp(new Date('2019/5/1'), 'gggee年'));
  • 実行結果
平成30年
令和01年
令和01年
令和01年

・・・!?
どうも2019/1/1〜2019/4/30も「令和01年」として扱われているようです。悩ましいですね。

⚓5. Swift

DateFormatterのdateFormatで和暦変換が出来るので、こちらを試してみます。

  • ソースコード(Xcode 10.1 / swift4.2)
let components = DateComponents(calendar: Calendar.current, year:2019, month: 5, day: 1)
let date = components.date!

let dateFormatter = DateFormatter()
// Locale.currentの場合、24時間表示オフだとdateがnilになる
dateFormatter.locale = Locale(identifier: "ja_JP")
dateFormatter.calendar = Calendar(identifier: .japanese)
dateFormatter.dateFormat = "Gy年"
let formatedDate = dateFormatter.string(from: date)
print(formatedDate)
  • 実行結果
平成31年

・・残念。平成31年になってしまいました。

[Swift] [iOS] [令和] 和暦西暦変換は端末日付に依存するによると
iOS 12.3 beta2でCalendarコンポーネントの挙動が変わっているようですね。
Xcodeのバージョンが古いので早々にアップデートして試してみます・・。

おまけ:各方面の元号対応

新元号の表示について
・画面上の表示について
  2019年5月1日以降の日付を入力いただいた場合、「令和1年5月1日」といった
  和暦表示になるのは4月20日となります。

・社会保険・雇用保険の書類(紙)への表示について
  年金機構、ハローワークなどにより、新元号が印字された新しい書類フォーマットが
  公開されてから3ヶ月以内に新元号に対応いたします。移行期間中は次の対応とさせていただきます。
smarthr.jpより

今回紹介出来ませんでしたが、Androidでは Android 7.0(API レベル 24)から「ICU4J Android Framework API」を利用することで令和変換出来るようです。ICU – International Components for Unicode自体はC++などでも利用されているようですね。
(参考:【最終版】新元号に対応するために ICU を用いて和暦の動作確認をする)

まとめ

今回、幾つかの言語では無事に「令和」変換できました。
(無事変換出来なかったものについては後日の宿題ということで・・)

まもなく「令和」を迎えるので、急に元号対応がきた場合などの参考になれば幸いです。
それでは、良いゴールデンウイーク(もとい令和ライフ)をお過ごしください!


株式会社ウイングドアでは、Ruby on RailsやPHPを活用したwebサービス、webサイト制作を中心に、
スマホアプリや業務系システムなど様々なシステム開発を承っています。

関連記事

RubyMineからGitが使えて便利

Flutterアニメーション、時々PWA+TWAなど

RailsとPHP各フレームワークの「複数形」変換処理を比較してみる

0
0

こんにちは、BPSの福岡拠点として一緒にお仕事させて頂いてます、株式会社ウイングドアの坂本です。

普段日本国内で生活しているとなかなか使う機会がない英語。
しかしプログラミングにはドキュメントを読んだり、コードを書いたりと避けられないものですよね。
モデルには名詞の単数形を、テーブル名にはその複数形を、等英語のルールに則り命名することも多いかと思います。

そんな英語名詞の単数から複数形への変換処理。
各フレームワークでどんな処理になっているのか覗いてみると、
それぞれ実装の仕方や定義が違って面白かったのでぜひご紹介したいと思います。

調査対象

今回調査したのは以下のフレームワークです。

  • Ruby on Rails 5.2.3 (Ruby)
  • CakePHP 3.5.7 (PHP)
  • Symfony 4.3 (PHP)
  • Laravel 5.8 (PHP)

主に自分が利用している、したことあるものを中心にピックアップしてみました。
また、今回は英語の複数形の定義のみ調査しています。

英語の複数形について

実装の中身を見る前に、改めて複数形について確認しましょう。

複数形とは

ご存知かと思いますが英語などいくつかの言語の名詞には単数形(singular)と複数形(plural)があります。
対象が1つであるか、2つ以上であるかによってスペルが代わり、また意味も異なる場合もあるそうです。
また、すべての名詞が複数形になるわけではありません。
名詞が以下のような考えによって分類され、可算名詞のみが複数形の変換が必要になります

可算名詞(countable noun)
1つ、2つなど個数を数えられるようなもの
複数形の変換が必要なのはこれ
不可算名詞(uncountable nouns)
単数/複数の区別がない、個体の概念のないもの
単複同形名詞
単数/複数の区別ができるのに綴りは一緒

参考: 英語の中の「不可算名詞」の見分け方・数え方・考え方

単複同形名詞

ちょっと難しそうなのは単複同形名詞。
単複同形名詞されやすいものは以下のパターンに分類できるそうです。

基本的に「群れ単位で扱われる動物や魚」
例) 魚/羊などの群れで生息するもの
(※ニワトリ、ヤギなど多数派の動物は単数形・複数形が存在する)
発音上の区別がつきにくい語、発音が面倒くさくなる語
例) means / Japanese
比較的新しい外来語
例) sushi(寿司)/ tempura(天ぷら)
語尾に-craftまたは-wareのつく語
例) craft(船舶) / hardware(ハードウェア)

参考: 英語の「単数形と複数形の区別がない可算名詞」(単複同形名詞)

複数形のルール

複数形の変換は以下のようになります。

基本的には単数形に -s をつける
例)cat → cats
sh, ch など(歯擦音)で終わる場合は、単数形に -es をつける
例)bush→ bushes
e で終わる名詞は -s をつける
例)case→ cases
o で終わる一部の名詞は -es をつける
例)tomato→ tomatoes
y で終わる殆どの語は -ies をつける
例)lady→ ladies
f あるいは fe で終わる一部は -ves をつける
例)leaf→ leaves
sis あるいは xis で終わる殆どの語は -ses、-xes をつける
例) oasis → two oases
x で終わる語は -ces をつけることがある
例) matrix → matrices
us で終わる語はそれを -i に置き換えて複数形になることがある
例) cactus → cacti
um あるいは on で終わる語の一部はそれを -a に置き換えて複数形になる
例) forum → two fora

その他、例外も多数存在する

参考: Wikipedia「複数」より

上記を踏まえて、早速各フレームワークの実装を見てみましょう。

Ruby on Rails 5.2.3 (Ruby)

調査メソッド

RailsはActiveSupport.Inflectorモジュールのpluralizeメソッドを調査します。
参考: pluralize — ActiveSupport::Inflector
名前的にも処理的にも複数形の定義で問題なさそうです

pluralize('post') # => "posts"
pluralize('octopus') # => "octopi"

ソースコード

require "active_support/inflections"

    # 〜 略 〜

    def pluralize(word, locale = :en)
      apply_inflections(word, inflections(locale).plurals, locale)
    end

    # 〜 略 〜
    def apply_inflections(word, rules, locale = :en)
      result = word.to_s.dup

      if word.empty? || inflections(locale).uncountables.uncountable?(result)
        result
      else
        rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
        result
      end
    end

構造

大まかな流れは以下のようです。

  1. 複数形の変換ルールとlocaleを、メソッドapply_inflectionsに渡して変換
  2. apply_inflectionsメソッドで以下の処理実行
  3. 最初に不可算名詞かどうか判定し、不可算名詞の場合はそのまま返す。(空文字も同様)
  4. 不可算名詞でなければ渡されたのルールに最初に一致するものがあれば複数形に変換して返す、という形のようです。

変換のルール

では肝心な複数形の変換ルールはどう定義されているのか見てみましょう。

変換ルールは以下のファイルで定義されているようです。

こちらではinflect.plural({検索対象文字列}, {置換文字列}) という形で複数形の変換のルールを定義していました。
ちなみに定義数は21個です。

inflect.plural(/$/, "s")
inflect.plural(/s$/i, "s")
inflect.plural(/^(ax|test)is$/i, '\1es')
inflect.plural(/(octop|vir)us$/i, '\1i')
inflect.plural(/(octop|vir)i$/i, '\1i')
inflect.plural(/(alias|status)$/i, '\1es')
inflect.plural(/(bu)s$/i, '\1ses')
inflect.plural(/(buffal|tomat)o$/i, '\1oes')
inflect.plural(/([ti])um$/i, '\1a')
inflect.plural(/([ti])a$/i, '\1a')
inflect.plural(/sis$/i, "ses")
inflect.plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves')
inflect.plural(/(hive)$/i, '\1s')
inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies')
inflect.plural(/(x|ch|ss|sh)$/i, '\1es')
inflect.plural(/(matr|vert|ind)(?:ix|ex)$/i, '\1ices')
inflect.plural(/^(m|l)ouse$/i, '\1ice')
inflect.plural(/^(m|l)ice$/i, '\1ice')
inflect.plural(/^(ox)$/i, '\1en')
inflect.plural(/^(oxen)$/i, '\1')
inflect.plural(/(quiz)$/i, '\1zes')

特殊な変換

また、以下の項目は特殊な変換として、単数形/複数形のそれぞれの先頭に追加しています。
inflect.irregular({単数形}, {複数形}) という形で6個定義されています。

inflect.irregular("person", "people")
inflect.irregular("man", "men")
inflect.irregular("child", "children")
inflect.irregular("sex", "sexes")
inflect.irregular("move", "moves")
inflect.irregular("zombie", "zombies")

不可算名詞

不可算名詞の定義は以下の10個。
魚や羊がしっかり定義されています。

inflect.uncountable(%w(equipment information rice money species series fish sheep jeans police))

その他

複数形のルールはconfig/initializers/inflections.rbに定義を追加することもできるようです。

参考: rails/active_support_core_extensions.md at 5-2-stable · rails/rails

CakePHP 3.5.7 (PHP)

調査メソッド

続いてCakePHP3。
Inflector::pluralize($singular) を調査していきたいと思います。

ソースコード

ソースコードはこんな感じ。
Railsと違って、定義も同じファイル。
文字列をキャメルケースに変換するメソッドなどもこちらのクラスで一緒に実装されています。

class Inflector
{
    // 〜 略 〜

    public static function pluralize($word)
    {
        if (isset(static::$_cache['pluralize'][$word])) {
            return static::$_cache['pluralize'][$word];
        }
        if (!isset(static::$_cache['irregular']['pluralize'])) {
            static::$_cache['irregular']['pluralize'] = '(?:' . implode('|', array_keys(static::$_irregular)) . ')';
        }
        if (preg_match('/(.*?(?:\\b|_))(' . static::$_cache['irregular']['pluralize'] . ')$/i', $word, $regs)) {
            static::$_cache['pluralize'][$word] = $regs[1] . substr($regs[2], 0, 1) .
                substr(static::$_irregular[strtolower($regs[2])], 1);
            return static::$_cache['pluralize'][$word];
        }
        if (!isset(static::$_cache['uninflected'])) {
            static::$_cache['uninflected'] = '(?:' . implode('|', static::$_uninflected) . ')';
        }
        if (preg_match('/^(' . static::$_cache['uninflected'] . ')$/i', $word, $regs)) {
            static::$_cache['pluralize'][$word] = $word;
            return $word;
        }
        foreach (static::$_plural as $rule => $replacement) {
            if (preg_match($rule, $word)) {
                static::$_cache['pluralize'][$word] = preg_replace($rule, $replacement, $word);
                return static::$_cache['pluralize'][$word];
            }
        }
    }

// 〜 略 〜

構造

変換の流れは以下のようになってります。

  1. 既にキャッシュに定義がのこっていればその値を返す(通常の複数形の変換/特殊な文字列/単複同形名詞の順にチェック)
  2. 上記に該当しない場合、複数形の変換ルールを上から順にチェックし、最初に該当したものを置換して返す

変換のルール

Railsと同じように、{検索対象文字列} => {置換文字列} の形で定義。
定義数はRailsより若干多い23個です。

    protected static $_plural = [
        '/(s)tatus$/i' => '\1tatuses',
        '/(quiz)$/i' => '\1zes',
        '/^(ox)$/i' => '\1\2en',
        '/([m|l])ouse$/i' => '\1ice',
        '/(matr|vert|ind)(ix|ex)$/i' => '\1ices',
        '/(x|ch|ss|sh)$/i' => '\1es',
        '/([^aeiouy]|qu)y$/i' => '\1ies',
        '/(hive)$/i' => '\1s',
        '/(chef)$/i' => '\1s',
        '/(?:([^f])fe|([lre])f)$/i' => '\1\2ves',
        '/sis$/i' => 'ses',
        '/([ti])um$/i' => '\1a',
        '/(p)erson$/i' => '\1eople',
        '/(?<!u)(m)an$/i' => '\1en',
        '/(c)hild$/i' => '\1hildren',
        '/(buffal|tomat)o$/i' => '\1\2oes',
        '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin)us$/i' => '\1i',
        '/us$/i' => 'uses',
        '/(alias)$/i' => '\1es',
        '/(ax|cris|test)is$/i' => '\1es',
        '/s$/' => 's',
        '/^$/' => '',
        '/$/' => 's',
    ];

特殊な変換

こちらも同じように{単数形} => {複数形} で定義。合計で41個定義していました。

    protected static $_irregular = [
        'atlas' => 'atlases',
        'beef' => 'beefs',
        'brief' => 'briefs',
        'brother' => 'brothers',
        'cafe' => 'cafes',
        'child' => 'children',
        'cookie' => 'cookies',
        'corpus' => 'corpuses',
        'cow' => 'cows',
        'criterion' => 'criteria',
        'ganglion' => 'ganglions',
        'genie' => 'genies',
        'genus' => 'genera',
        'graffito' => 'graffiti',
        'hoof' => 'hoofs',
        'loaf' => 'loaves',
        'man' => 'men',
        'money' => 'monies',
        'mongoose' => 'mongooses',
        'move' => 'moves',
        'mythos' => 'mythoi',
        'niche' => 'niches',
        'numen' => 'numina',
        'occiput' => 'occiputs',
        'octopus' => 'octopuses',
        'opus' => 'opuses',
        'ox' => 'oxen',
        'penis' => 'penises',
        'person' => 'people',
        'sex' => 'sexes',
        'soliloquy' => 'soliloquies',
        'testis' => 'testes',
        'trilby' => 'trilbys',
        'turf' => 'turfs',
        'potato' => 'potatoes',
        'hero' => 'heroes',
        'tooth' => 'teeth',
        'goose' => 'geese',
        'foot' => 'feet',
        'foe' => 'foes',
        'sieve' => 'sieves'
    ];

不可算名詞

不可算名詞は以下。
定義は31項目ですが正規表現を利用しているので該当する名詞はもっとたくさんありそうです。

    protected static $_uninflected = [
        '.*[nrlm]ese', '.*data', '.*deer', '.*fish', '.*measles', '.*ois',
        '.*pox', '.*sheep', 'people', 'feedback', 'stadia', '.*?media',
        'chassis', 'clippers', 'debris', 'diabetes', 'equipment', 'gallows',
        'graffiti', 'headquarters', 'information', 'innings', 'news', 'nexus',
        'pokemon', 'proceedings', 'research', 'sea[- ]bass', 'series', 'species', 'weather'
    ];

その他

こちらも複数形のルールは追加可能。
config/bootstrap.phpに以下のように記載すれば追加できるそうです。

Inflector::rules('singular', ['/^(bil)er$/i' => '\1', '/^(inflec|contribu)tors$/i' => '\1ta']);
Inflector::rules('uninflected', ['singulars']);
Inflector::rules('irregular', ['phylum' => 'phyla']); // キーは単数形、値は複数形

Symfony 4.3(PHP)

調査メソッド

Symfony 4.3はInflector Componentのpluralizeメソッドで複数形へ変換ができるようです。
このメソッドの特徴として、複数形の結果が複数推測できる場合、文字列が配列で返ってきます。
利用する場合は配列かどうかの判定も必要です。

Inflector::pluralize('grandchild'); // 'grandchildren'
Inflector::pluralize('news');       // 'news'
Inflector::pluralize('bacterium');  // 'bacteria'
Inflector::pluralize('matrix'); // ['matricies', 'matrixes']
Inflector::pluralize('person'); // ['persons', 'people']

ソースコード

namespace Symfony\Component\Inflector;
/**
* Converts words between singular and plural forms.
*
* @author Bernhard Schussek
*/
final class Inflector
{
    /**
    * Map English singular to plural suffixes.
    *
    * @see http://english-zone.com/spelling/plurals.html
    */
    private static $singularMap = [
    // criterion (criteria)
    ['airetirc', 8, false, false, 'criterion'],
    // 〜 略 〜
    ];

    // 〜 略 〜
    public static function pluralize(string $singular)
    {
        $singularRev = strrev($singular);
        $lowerSingularRev = strtolower($singularRev);
        $singularLength = \strlen($lowerSingularRev);
        // Check if the word is one which is not inflected, return early if so
        if (\in_array($lowerSingularRev, self::$uninflected, true)) {
            return $singular;
        }
        // The outer loop iterates over the entries of the singular table
        // The inner loop $j iterates over the characters of the singular suffix
        // in the singular table to compare them with the characters of the actual
        // given singular suffix
        foreach (self::$singularMap as $map) {
            $suffix = $map[0];
            $suffixLength = $map[1];
            $j = 0;
            // Compare characters in the singular table and of the suffix of the
            // given plural one by one
            while ($suffix[$j] === $lowerSingularRev[$j]) {
                // Let $j point to the next character
                ++$j;
                // Successfully compared the last character
                // Add an entry with the plural suffix to the plural array
                if ($j === $suffixLength) {
                    // Is there any character preceding the suffix in the plural string?
                    if ($j &lt; $singularLength) {
                        $nextIsVocal = false !== strpos(&#039;aeiou&#039;, $lowerSingularRev[$j]);
                        if (!$map[2] &amp;&amp; $nextIsVocal) {
                            // suffix may not succeed a vocal but next char is one
                            break;
                        }
                        if (!$map[3] &amp;&amp; !$nextIsVocal) {
                            // suffix may not succeed a consonant but next char is one
                            break;
                        }
                    }
                    $newBase = substr($singular, 0, $singularLength - $suffixLength);
                    $newSuffix = $map[4];
                    // Check whether the first character in the singular suffix
                    // is uppercased. If yes, uppercase the first character in
                    // the singular suffix too
                    $firstUpper = ctype_upper($singularRev[$j - 1]);
                    if (\is_array($newSuffix)) {
                        $plurals = [];
                        foreach ($newSuffix as $newSuffixEntry) {
                            $plurals[] = $newBase.($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry);
                        }
                        return $plurals;
                    }
                    return $newBase.($firstUpper ? ucfirst($newSuffix) : $newSuffix);
                }
                // Suffix is longer than word
                if ($j === $singularLength) {
                    break;
                }
            }
        }
        // Assume that plural is singular with a trailing `s`
        return $singular.&#039;s&#039;;
    }

構造

こちらはRailsやCakePHPとも違い、接尾辞基準で定義されているようです。

大まかな手順としては以下のよります。

  1. 不可算名詞でならばそのまま返す
  2. $singularMapの配列を回して各定義毎に以下をチェックし、一致するものがあれば複数形に変換して返す
    1. 変換する文字の最後の文字が、接尾辞と一致しているかをチェック
    2. 変換する文字の接尾辞より前の文字の母音か子音かが一致しているかをチェック
    3. 上記問題なければ変換する文字から接尾辞を除いたものに、複数変換接尾辞をつけて返す
      ※ この時、{複数変換接尾辞}が配列で複数定義されている場合は返り値も配列になります
  3. 一致する定義がなければ、変換する文字にsをつけて返す

変換のルール(含む 特殊な変換)

判定する接尾辞と変換文字の他に、接尾辞の前の文字か母音か、子音かのフラグも一緒に配列で定義しています。
設定されてる配列の用途は、先頭から順に以下の通りです。
接尾辞(※反転済), 接尾辞文字数, 接尾辞前母音フラグ, 接尾辞前子音フラグ, 複数変換接尾辞(※配列で複数定義可能)

例えば['airetirc', 8, false, false, 'criterion']の場合
接尾辞:eci(反転前: ice), 接尾辞文字数:3, 接尾前母音フラグ: false, 接尾前子音フラグ:true, 複数変換接尾辞:ices
つまり『何らかの文字+(子音)+ice ->何らかの文字+(子音)ices にする』という定義になります
例) dice->dices

また、複数形の変換の定義と一緒にchildrenなどの特殊な変換の定義も行っているようです。
定義数は合計で49個でした。

// ※ コメントは省略して記載
    private static $singularMap = [
        ['airetirc', 8, false, false, 'criterion'],
        ['aluben', 6, false, false, 'nebulae'],
        ['dlihc', 5, true, true, 'children'],
        ['eci', 3, false, true, 'ices'],
        ['ecivres', 7, true, true, 'services'],
        ['efi', 3, false, true, 'ives'],
        ['eifles', 6, true, true, 'selfies'],
        ['eivom', 5, true, true, 'movies'],
        ['esuol', 5, false, true, 'lice'],
        ['esuom', 5, false, true, 'mice'],
        ['esoo', 4, false, true, 'eese'],
        ['es', 2, true, true, 'ses'],
        ['esoog', 5, true, true, 'geese'],
        ['ev', 2, true, true, 'ves'],
        ['evird', 5, false, true, 'drives'],
        ['evit', 4, true, true, 'tives'],
        ['evom', 4, true, true, 'moves'],
        ['ffats', 5, true, true, 'staves'],
        ['ff', 2, true, true, 'ffs'],
        ['f', 1, true, true, ['fs', 'ves']],
        ['hc', 2, true, true, 'ches'],
        ['hs', 2, true, true, 'shes'],
        ['htoot', 5, true, true, 'teeth'],
        ['mu', 2, true, true, 'a'],
        ['nam', 3, true, true, 'men'],
        ['nosrep', 6, true, true, ['persons', 'people']],
        ['noi', 3, true, true, 'ions'],
        ['no', 2, true, true, 'a'],
        ['ohce', 4, true, true, 'echoes'],
        ['oreh', 4, true, true, 'heroes'],
        ['salta', 5, true, true, 'atlases'],
        ['siri', 4, true, true, 'irises'],
        ['sis', 3, true, true, 'ses'],
        ['ss', 2, true, false, 'sses'],
        ['suballys', 8, true, true, 'syllabi'],
        ['sub', 3, true, true, 'buses'],
        ['suc', 3, true, true, 'cuses'],
        ['su', 2, true, true, 'i'],
        ['swen', 4, true, true, 'news'],
        ['toof', 4, true, true, 'feet'],
        ['uae', 3, false, true, ['eaus', 'eaux']],
        ['xo', 2, false, false, 'oxen'],
        ['xaoh', 4, true, false, 'hoaxes'],
        ['xedni', 5, false, true, ['indicies', 'indexes']],
        ['x', 1, true, false, ['cies', 'xes']],
        ['xi', 2, false, true, 'ices'],
        ['y', 1, false, true, 'ies'],
        ['ziuq', 4, true, false, 'quizzes'],
        ['z', 1, true, true, 'zes'],
    ];

不可算名詞

不可算名詞は以下で定義しています。

    private static $uninflected = [
        'atad',
        'reed',
        'kcabdeef',
        'hsif',
        'ofni',
        'esoom',
        'seires',
        'peehs',
    ];

こちらの定義は文字列を反転して設定していますので、実際に定義されるのは以下の8項目になります。
datadeerfeedbackfishinfomooseseriessheep

Laravel 5.8 (PHP)

調査メソッド

Laravel 5.8はIlluminate\Support\Str::plural()メソッドで複数形に変換できます。

ソースコード

Illuminate\Support\Str::pluralメソッドは違うメソッドの結果をそのままを返しています。

    public static function plural($value, $count = 2)
    {
        return Pluralizer::plural($value, $count);
    }

ということでIlluminate\Support\Pluralizerの中身を記載します

    public static function plural($value, $count = 2)
    {
        if ((int) abs($count) === 1 || static::uncountable($value)) {
            return $value;
        }
        $plural = Inflector::pluralize($value);
        return static::matchCase($plural, $value);
    }

構造

Doctrine\Common\Inflector\Inflector::pluralizeも途中で呼び出していますね。
DoctrineはSymfonyなどでも使われるORM。こちらの複数形の処理を利用し、不可算名詞の判定だけ追加しているようです。

  1. 「第2引数の$countが1」or「文字列が不可算名詞」ならばそのまま返す
  2. Doctrineのメッソドを利用し複数形変換
  3. 2.で変換した文字列を、元々の値にあわせて小文字/大文字を変換します。
    (すべて小文字にする、先頭だけ大文字にする、など)

という流れ。

それではInflectorクラスの複数化の処理も解析を……、と思ったのですがそうするとLaravelの複数形の定義からそれてしまいそうなので今回は除外します。

Inflectorクラスのコードは以下で確認できます。

複数形のルール24項目、特殊な変換の定義は62項目とかなり定義されています。
また、不可算名詞の定義は正規表現で10項目となっていました。

特殊な変換

Laravelの方で定義されているものはないようです。
(Inflectorの方で62個と細かく定義されているので必要もなさそうです。)

不可算名詞

こちらは細かく、41個ほど登録されています。
fishやsheepなど基本的なものはInflectorで定義されているのですが、ここでも改めて登録されているようですね。

    public static $uncountable = [
        'audio',
        'bison',
        'cattle',
        'chassis',
        'compensation',
        'coreopsis',
        'data',
        'deer',
        'education',
        'emoji',
        'equipment',
        'evidence',
        'feedback',
        'firmware',
        'fish',
        'furniture',
        'gold',
        'hardware',
        'information',
        'jedi',
        'kin',
        'knowledge',
        'love',
        'metadata',
        'money',
        'moose',
        'news',
        'nutrition',
        'offspring',
        'plankton',
        'pokemon',
        'police',
        'rain',
        'rice',
        'series',
        'sheep',
        'software',
        'species',
        'swine',
        'traffic',
        'wheat',
    ];

とみてみて…あれ?

pokemonが定義されてます! pokemonは不可算名詞なんですね!
(XYからは群れバトルが追加されましたし、群れで行動するという判定なのでしょうか?)

そしてよく見たらCakePHP3も不可算名詞の定義にポケモンがありました!
Laravelと違って改行がないので気が付きにくかったみたいです。

まとめ

Rails5 CakePHP3 Symfony4 Laravel5
変換方法 正規表現でパターンを定義して置換 正規表現でパターンを定義して置換 接尾辞の基準で接尾辞以前の文字に変化文字をつける
配列で結果が帰ってくる場合がある
基本的に外部のライブラリを利用
変換ルール定義数 21 23 49
(特殊な変換定義を含む)
0
(Inflector: 24)
特殊な変換定義数 6 41 0
(変換ルール定義数に含む)
0
(Inflector: 62)
不可算名詞定義数 10 31
(※正規表現で定義)
8 41
(Inflector: 10)

正規表現の定義の仕方などもあるので、簡単に定義数だけで比較はできませんが
Rails、CakePHPが比較的直感的で分かりやすい定義をしているのに対し、Symfonyは反転して定義したりと少し特殊。
Laravelは定義の多い優秀なライブラリを利用した上で、追加で定義したいものを追加するなど
フレームワーク毎にらしさがある実装のように見えます。

また、pokemonがしっかり定義されていたのは驚きました。
Laravel 5.8とCakePHP3は娯楽用途での利用が多いのでしょうか?
(業務系で『ポケモンを2匹追加』などと入力する場面が想像がつかないです)
複数形の定義だけでももっと細かく、また、いろんなフレームワークを見てみると更に多くの違いが見られそうですね!


株式会社ウイングドアでは、Ruby on RailsやPHPを活用したwebサービス、webサイト制作を中心に、
スマホアプリや業務系システムなど様々なシステム開発を承っています。

週刊Railsウォッチ(20190507-1/2前編)Rails 6.0.0rc1が4/24にリリース、Rails 6の新メソッド群、RubyリポジトリがCgitに移行ほか

0
0

こんにちは、hachi8833です。10日間の休みを挟むと少々調子狂いますね。

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

※今回のつっつきはGW前の4/25に行われました。

⚓お知らせ: “令和初の” 第10回公開つっつき会

今週木曜開催の公開つっつき会、引き続き募集しています。当日エントリでもOKですので、皆さまお気軽にご応募ください😋

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

ここにきて続々と新しいメソッドが追加されてます。

⚓ActiveRecord::Relation#cache_versionを追加

# activerecord/lib/active_record/collection_cache_key.rb #L54
      if timestamp
-       "#{key}-#{size}-#{timestamp.utc.to_s(cache_timestamp_format)}"
+       "#{size}-#{timestamp.utc.to_s(cache_timestamp_format)}"
      else
-       "#{key}-#{size}"
+       "#{size}"
  • ActiveRecord::Relation#cache_versionを追加。これはActiveSupport::Cacheでバージョニングされたエントリ経由のキャッシュキーを再利用可能にする。これによりActiveRecord::Relation#cache_keyが返すキーにmax timestampやcountが含まれなくなり、キーが安定する。
    注: この機能はデフォルトでオフであり、ActiveRecord::Base.collection_cache_versioning = trueを設定しない限りcache_keyは引き続きtimestamp含みとなる。
    これはRails 6.0以後のすべてのアプリで用いられる設定である。
    Lachlan Sylvester
    ChangeLogより大意

つっつきボイス:「rc1が出ているこのタイミングで他にも新メソッドが続々増えていて攻めてる感ありました😳」「timestampを指定してキャッシュを取れていたということか」

⚓dirty tracking関連の高速化とメソッド追加

# activemodel/lib/active_model/attribute_mutation_tracker.rb#L9
-   def initialize(attributes)
+   def initialize(attributes, forced_changes = Set.new)
      @attributes = attributes
-     @forced_changes = Set.new
+     @forced_changes = forced_changes
    end

    def changed_values
      attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
        if changed?(attr_name)
-         result[attr_name] = attributes[attr_name].original_value
+         result[attr_name] = original_value(attr_name)
        end
      end
    end

つっつきボイス:「あ〜Railsのdirty: どうしてこれがちゃんと動いているのか未だに不思議😅」「kamipoさんの高速化で2倍、ものによっては30倍ってスゲー🚀」「forced_changesが渡されたらそれを使い、渡されなければ初めてSet.newするのか: よく見つけるな〜こういうの」「その分メモリコピーが減って高速化するっぽいですね」「使い回せるSetがあればそれを使おうと」「そもそも今までもSet(集合)クラスを使ってたのね」

参考: class Set (Ruby 2.6.0)

「dirtyはRailsの中でものすごく使われているから、これを高速化する意義は大きいでしょうね☺

⚓after_save_commitを追加

DHH自らのコミットです。

# activerecord/lib/active_record/transactions.rb#L237
+     def after_save_commit(*args, &block)
+       set_options_for_callbacks!(args, on: [ :create, :update ])
+       set_callback(:commit, :after, *args, &block)
+     end

つっつきボイス:「そもそもafter_saveafter_commitって何が違うんだろか?🤔」「after_saveはsaveだからDELETEがないヤツ?」「あー、commitはDELETEの場合も含むのかなるほど」

after_commit :hook, on: [ :create, :update ]のショートカットってありますね」「after_save_commiton:でCREATEとUPDATEを行うのね」「after_commitだとDELETEも含まれるからそれをスキップすると」「DELETE要らない派が多いのかも😆

参考: after_commit — ActiveRecord::Transactions::ClassMethods

⚓register_tagsSourceAnnotationExtractor::Annotationに追加

# railties/lib/rails/commands/notes/notes_command.rb#L5
module Rails
  module Command
    class NotesCommand < Base # :nodoc:
-     class_option :annotations, aliases: "-a", desc: "Filter by specific annotations, e.g. Foobar TODO", type: :array, default: %w(OPTIMIZE FIXME TODO)
+     class_option :annotations, aliases: "-a", desc: "Filter by specific annotations, e.g. Foobar TODO", type: :array, default: Rails::SourceAnnotationExtractor::Annotation.tags

つっつきボイス:「ソースアノテーションが今までOPTIMIZE FIXME TODOみたいにタグが生書きだったのをちゃんとデータ構造に入れたのか」「お〜」「rake notesでは今までもカスタマイズ可能ではあったけど、SourceAnnotationExtractorに責務を分けてやれるように変えたのね」

「このタグってどこで使うタグでしたっけ?」「これはソースコードに付けるタグですね🧐: 以前からrails notesで表示できますよ」「まあrailsコマンドでやらなくてもという気はしないでもない😆、CIなんかでTODOがソースコードに残っていたらmergeさせないみたいな処理をやるには便利ですね」「なるほど!」

参考: rails notes — Rails のコマンドラインツール - Rails ガイド

⚓冪等なdb:prepareタスクを追加

# activerecord/lib/active_record/railties/databases.rake#L225
+ desc "Runs setup if database does not exist, or runs migrations if it does"
+ task prepare: :load_config do
+   ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).each do |db_config|
+     ActiveRecord::Base.establish_connection(db_config.config)
+     db_namespace["migrate"].invoke
+   rescue ActiveRecord::NoDatabaseError
+     db_namespace["setup"].invoke
+   end
+ end

つっつきボイス:「db:prepareって今までなかったっけ?」「見た覚えがあるような気がするんですがこのコミット番号は過去のウォッチにはありませんでした」「このdb:prepareはデータベースがなければ作成して、あればマイグレーションを実行するヤツですね」「データベースがあるかどうかを調べずに実行できると」「だから冪等なんですね」

参考: 情報工学における冪等 - Wikipedia

⚓Active Supportにdetach_fromを追加

# activesupport/lib/active_support/subscriber.rb#L47
      # Detach the subscriber from a namespace.
      def detach_from(namespace, notifier = ActiveSupport::Notifications)
        @namespace  = namespace
        @subscriber = find_attached_subscriber
        @notifier   = notifier

        return unless subscriber

        subscribers.delete(subscriber)

        # Remove event subscribers of all existing methods on the class.
        subscriber.public_methods(false).each do |event|
          remove_event_subscriber(event)
        end

        # Reset notifier so that event subscribers will not add for new methods added to the class.
        @notifier = nil
      end

つっつきボイス:「なるほど、サブスクライバからdetachすると」「detach自体は前からできてたと思うけど、これはnotifierのインスタンスがなくても名前空間を指定して一気にdetachできるっぽい」「コミットメッセージにあるこのコード↓がまさにそれか」「とりあえず便利メソッドとして使えるし、1つずつdetachするとマルチスレッドでタイミングの問題が生じそうなときにもよさそう」

# PRより
ActiveJob::Logging::LogSubscriber.detach_from :active_job
CustomActiveJobLogging::LogSubscriber.attach_to :active_job

「リリースが近づいているのにいろいろ追加されてますね」「まあ追加ですし、既存のコードが壊れるような仕様や変更でもないから大丈夫かと」「今のうちに駆け込みで入れちゃえみたいな😆

⚓bin/setupbin/updateを冪等にした

# railties/lib/rails/generators/rails/app/templates/bin/setup.tt#L29
  puts "\n== Preparing database =="
  system! 'bin/rails db:setup'
  system! 'bin/rails db:prepare'
<% end -%>

つっつきボイス:「お、さっきの冪等なdb:prepareに置き換わってるし」「今までのbin/setupはデータベースがあるとエラーだったのか」「地味にありがたい🙏

⚓Rails

⚓Rails 6がrc1に

GW中にもしや最終リリースと思いきや、順当に順延の流れですね。


つっつきボイス:「アバウト1000コミット!」「タイムスケジュールではrc1は3/1だけど」「ふた月遅れ」「予定通りに遅れ中🤣」「🤣」「平成とおさらばすると同時にリリースできたらよかったけど😆

参考: Timeline for the release of Rails 6.0 | Riding Rails

同プレスリリースで、以下の書籍がbeta状態とありました。同時リリースを狙ってるでしょうね。

また、4/30〜5/2にミネアポリスで開催されたRailsConf 2019は4/25の時点でソールドアウトでした。例によってスロットが7つもあるカンファレンスなので、現地では7分の1のセッションしか見られないこと確定でした😢

日本からRailsConfに参加した方が何人かいらっしゃいました。

⚓RubyCopの出力をPretter for Rubyっぽくした(Ruby Weeklyより)


同記事より


つっつきボイス:「Prettier for Rubyってのがあるのね」「Prettierって最近人気高いっぽいですね」「やっぱり名前がいいから?😆Rufoは名前がイマイチ感」


prettier/plugin-rubyより

「JavaScript方面ではPrettierめちゃ使われてるし、Rubyがプラグインになってるならこっちの方がよさそう😍」「Linterをいくつも入れなくていいのはうれしい😂」「宗教戦争を避けるのにもよさそうだし」

「記事はRuboCopの出力をPrettierっぽくしてみたということみたい」「最初からPrettier使えばいいような気もするけどそうもいかない事情があるんでしょうね☺」「なにしろRubyは書き方の自由度が高いし」「RubyCopをコードフォーマッターとみなしていいのかどうかは議論が分かれそうだけど😆」「rubocop -aはフォーマッターか否か😆」「さすがにちょっと乱暴な感じ😆」「記事にはRufoも一応出てますね」「RuboCopにこういうフォーマッタールール↓を入れるとできるっぽい」

⚓todo_or_die: TODOを放置すると…!


つっつきボイス:「RubyKaigi 2019@博多で発表されてたgemです」「yancyaさん大喜び😆

参考: The Selfish Programmer - RubyKaigi 2019

# 同リポジトリより
class UsersController < ApiController
  TodoOrDie("delete after JS app has propagated", by: Time.parse("2019-02-04"))
  def show
    redirect_to root_path
  end
end

「TODOを上のように書くと、期限切れしたときにTodoOrDie::OverdueErrorエラーで落ちるそうです😇」「TODOコメントがあると勝手に落ちてくれるというのは……いいのか?🤣」「どうなんでしょ🤣」「まあエンジニアもTODOコメントを書いている時点で何かがアブナイのはわかっているだろうし」「masterブランチにがんがんマージするようなやんちゃなプロジェクトなら、こういうgemで守るという手もあるといえばある?」「あんまりいい使い方じゃない気も😆

なお、このgemのリポジトリに貼られている画像はむか〜しの「Skate Or Die」というファミコンゲームのジャケットのようです。

⚓RailsのPostgreSQLデータベースで強制バリデーション


つっつきボイス:「このPaweł Urbanekさんの記事は何度かTechRachoで翻訳したことがあります」

Railsアプリと技術ブログにGDPRコンプライアンスを追加する(翻訳)

「なるほど、こうやってジョブを作ってエイヤで流してバリデーションしたと😆」「あんまりいい方法ではないことは承知でやってるみたいですね😅

# 同記事より
class ModelDataIntegrityCheckerJob
  include Sidekiq::Worker
  sidekiq_options retry: false

  def perform(klass_name)
    klass = klass_name.constantize
    invalid_objects = []

    klass.find_each do |object|
      unless object.valid?
        invalid_objects << [object.id, object.errors.full_messages]
      end
    end

    if invalid_objects.present?
      raise "Invalid state for #{klass} objects: #{invalid_objects}"
    end

    invalid_objects
  end
end

「DBのfalsenilの意味が違っててRuby側でハマる問題とかあるある😅」「それにしてもRailsのdata integrityってどこで行えばいいのか問題があるからな〜: ぽすぐれの層でやろうとするとテーブルをきれいに設計しておかないとトリガーで消せなくなるとか、トリガーにならないぐらいビジネスロジックが立て込んじゃうとかあるし」「うんうん」

「原則的にRDBMSの世界では、テーブルをきちんと設計しておけばdata integrityを全部RDBMSに任せられるんですが、これを原理主義的にやりすぎるとRailsでテーブル2個ぐらいで済むところをテーブルごと分けないといけないみたいなことになりかねないので、そこが悩ましいところ😭

⚓その他Rails


つっつきボイス:「あーなるほど、こうやってdylib(ダイナミックリンクライブラリ)を使えばプロセスをforkせずにやれるので速くなりそう」「頑張り味のある記事🥰

[dependencies]
mysql = "9.0.1"
libc = "0.2.0"

[lib]
name = "csv"
crate-type = ["dylib"]

こちらは5/6までなので期間終了です。皆さまは間に合いましたでしょうか?



つっつきボイス:「銀座Rails!」「4/24の銀座Railsに徳丸先生が登場して盛り上がったそうで、安川さんが早速記事にしてその中でTechRachoのセキュリティ記事も紹介いただきました🙇

参考: Railsエンジニアのためのウェブセキュリティ入門に参加 🔐 - YassLab 株式会社

⚓Ruby

RubyWeeklyにもいろいろ載っていたのですがきりがないので相当絞りました。

⚓リポジトリがSubversionからGitに移行(Ruby公式ニュースより)


つっつきボイス:「ついにRubyのリポジトリがSubersionからCgit管理に!」「 『not GitHub』ですって」「以下の記事にも書いた後で正式に発表されていました」 「Rubyに新しくコントリビュートする人がSubversionだとウッとなるという気持ちはワカル☺

キーワードで振り返るRubyKaigi 2019@博多(#1)


svn.apache.orgより

「今更ですが、Subversionってsvn checkoutのときにものすごく時間かかるんですよね😭」「そうそうっ😤」「一晩かかるとかざらにあるし😇」「コミットを1つ1つ追いかけるうえにsvnサーバーとのやりとりでネットワークトラフィックも凄いことになってホント重い」「そんなに重いんだ…」「リクエストとレスポンスを繰り返すからネットワーク帯域を有効に使えていないし」

「あの頃はsvn checkoutしたら『さて帰るか』でしたし😆」「ローカルのリポジトリをうっかり消すとまたやらないといけなかったりしたし😆

参考: svn checkout

「それがイヤだったので、ローカルでgitコマンドを使うために一時期git-svn使ってましたよ」「今回のRubyKaigiでも『cloneがめちゃ速くなった❤』って言ってましたね☺」「Gitだと平常運転😆」「歴史の長いプロジェクトだとどこかのタイミングで一度squashしないとみたいな話になったり、今までのRubyみたいにメインはSubversionでミラーはGitHubにみたいにやったりしてますよね」

参考: git-svn の使い方メモ

「その一方で、RubyがこれまでSubversionを使ってたのはそれはそれでよかったかもという話もワカル: RubyKaigiでも誰かが話していましたけど、Rubyにコミットする人たち以外はSubversionを使わないんだし、GitHubのミラーにあるコミットはSubversionとちゃんと同期していて、履歴を追いかけるとかならGitHubミラーでやれてたし」「たしかに〜」「それにRubyコミッター以外でもGitHubの方にコミットを投げればコミッターがSubversionの方で取り込めるようにはなってたので、コミッターがこの二度手間を面倒と思わなければ今回の移行はそれほど切実ではなかったという見方もできるといえばできますね☺」「時代の流れといえばそうかも」

「自分も一応バージョン管理システムはCVS->Subversion-Gitと使ってきましたけど、最近だとCVSどころかSubversionも知らない人とかいそうですよね」「学部生の頃にCVS使ってて、そのあたりでSubversionはいいぞみたいな話が出始めたし」「…自分はVSSから😇」「その頃の自分はもう働いてましたが😆、当時のSubversionは確かにヨカッタ」「CVSと比べるとSubversion速かったような気がする」「JavaやってたのでSubversionのディレクトリ移動のやりやすさがよかった覚えがありますね」

参考: Concurrent Versions System - Wikipedia
参考: Microsoft Visual SourceSafe - Wikipedia

「自分はCVSのままでもよかったかななんて😆」「でもまあCVSにはブランチという概念がなかったから」「Subversionはtrunkとbranchという概念を生み出しましたよね」「ブランチが全コピーだから恐ろしく効率低いけど🤣」「そうそうっ🤣」「ブランチを増やせば増やすほどサーバー容量も増える😆

「その点Gitは生ファイルをサーバーに置かずにGit独自のバイナリに保存するという発想でできていて、やはりあれは偉大な発明」「やっぱそうなるよねみたいな😆」「あれはGitより前にMercurialとかBazaarあたりからやってましたね」「Bazaar、一瞬使ってたナ」「Linus TorvaldsがGitを作るきっかけになったかつてのLinuxのバージョン管理システムってどれでしたっけ?」「えっとPerforceじゃなくてBitKeeperか」

参考: Mercurial - Wikipedia


Wikipediaより(GPLv2)

参考: Bazaar - Wikipedia


http://bazaar.canonical.comより

参考: BitKeeper - Wikipedia


bitkeeper.orgより

⚓RubyKaigi 2019の余韻


rubykaigi.orgより


つっつきボイス:「RubyKaigiはGW明けに社内勉強会で時間を取ることになってるので詳しくはそのときに☺」「ですね☺

キーワードで振り返るRubyKaigi 2019@博多(#1)

(撮った写真をいくつか眺めて)「今回は写真にがっつりキャプションを入れたので情報の価値が上がったなと思いました」「ロケーション情報と時刻が出るだけでもかなりありがたい」「博多で見つけた例の焼きラーメン(上記事)の店の場所も後でわかりました😆

「お、RubyKaigi会場横のメルチャリ乗ったな〜」「乗る暇なかった😢」「これ無料で乗れたんでしたっけ?」「有料ですけど最初はクーポンか何かで無料で乗れましたね😋

参考: メルチャリ


merchari.bikeより

⚓Ruby 2.7のパターンマッチング第一印象(Ruby Weeklyより)


つっつきボイス:「例のBrandon Weaverさんの発表でした」「Rubyの新機能『パターンマッチング』今回見られなかった😇」「私も」「半分ぐらい見た😆」「Erlangか何かの機能なんですよね」「Haskellにもあったかも」

参考: Erlang - Wikipedia
参考: Haskell個人メモ :: 3.関数の構文 - Qiita

「そうそう、case文でinが使えるようになる」「今でもcase文でarrayのカンマ区切りで書けますけどね☺」「発表ではJSONのネストが深いときにうれしいみたいな話をしてたかも」「単なる列挙なら今のwhenとか単純な正規表現マッチでもできるけど」「構造を持ってしまったときに欲しくなるヤツと言ってた」「lambda的なものが書けるようになったとか?」「lambdaは今でも書けるはず」

参考: Ruby の case 文で,値が配列に含まれているかを調べる - Qiita

# 同記事1より
case 0
in 0 | 1
  true
end
# => true

「このrange使いまくるヤツ↓はエグい😆」「そうそう、こういうバリデーションチェックっぽいのが書けるんだった😆」「パターンマッチングの方が評価順序的に速いとかある?」「まだそこまではわからない😅

# 同記事1より
case [0, 1, 2, 3, 4, 5]
in [0..1, 0...2, 0.., 0..., (...5), (..5)]
  true
end

「Weaverさん、よほどうれしかったのかパターンマッチング記事を立て続けに書いてますね」「こういうのでやりたかった人には待望の機能でしょうね☺

⚓その他Ruby

参考: RubyKaigi 2019 直前特集号

⚓Ruby trunkより

⚓なんぱら色々

他にもissueが上がっていますね。

# #15783より
->x:@2{}   # SEGVする

つっつきボイス:「ついなんぱらと略してしまいましたが😆、numbered parameterのissueがいろいろ上がっていたので」「これをtrace pointするとさらに凄いことになりそう😆」「hanachinさんのissueも上がってる」「RubyKaigiでもhanachinさんは髪の色で遠くからひと目でわかりました🌈

「numbered parameter、自分はitがいいかな〜なんて😆」「amatsudaさんもit推しでしたね」「ネストするとどうなるのかな😆」「itにする場合、itはRubyのキーワードになる?」「なるでしょうね」

「numbered parameterが@1だけというのがまだ腑に落ちてないかな〜」@2とかはありそうでないという😆「たぶん需要がないんじゃないかな: Rubyのブロックが1つしか渡せないのと同じような感じで」「あ〜そうかも」「Rubyではやってないけど、ブロックを複数渡せる言語ってありますし😎」「マジで?」「Rubyでもやろうと思えばできたはずだけど、現実的にほぼ誰も使わないでしょうし」「そういえばMatzがどこかで『Rubyで渡せるブロックを1つだけにしたのが自分的にヒットだった』と書いていた覚えが」「それと同じような感じでnumbered parameterは@1だけにしたんじゃないかなと思うのでそんなに不自然には感じない」「それならわかる気がする😋

追記(2019/05/09)

morimorihoge注)
※初出時、@2はないといった記述がありましたがミスでした、申し訳ありません。正確には numbered形式(itやthisなどの単語形式ではない)だと@2以降も参照できるが、実際にはそれほど@2以降には需要がないのでは?という話の流れのなかで「需要がない」を「存在しない」に混同したのだと思われます。
ご指摘いただいた@pink_bangbiさん、ありがとうございました。


今回は以上です。

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

週刊Railsウォッチ(20190415-1/2前編)Railsバージョンアップに便利なstill_life gem、Zeitwerkの改修進む、named_capture追加ほか

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

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

Ruby 公式ニュース

Rails公式ニュース

Ruby Weekly

RubyFlow

160928_1638_XvIP4h

週刊Railsウォッチ(20190508-2/2後編)サロゲートキーのコスト、Cloud RunとLambdaの違い、miniredis、CSS Subgridほか

0
0

こんにちは、hachi8833です。まだ令和が身体に沁み込んでない感じです。

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

⚓お知らせ: “令和初の” 第10回公開つっつき会

明日開催の公開つっつき会を引き続き募集しています。当日エントリでもOKですので、皆さまお気軽にご応募ください🙇

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

⚓AWSのオープンソースRDBMSを使う場合のポイント(DB Weeklyより)


つっつきボイス:「ざっと見た感じ、AWSのこのサービスでこういう場合はこうやろうみたいな話ですね☺: RDSのバックアップやセキュリティはこうやろうとか、High Availabilityも使えるよとか、Auroraの場合はこうとか」

  • AWS RDS for MySQL
  • AWS Aurora
  • AWS EC2に自前で立てる場合

「ちなみにAuroraの項で『Storage: 6 copies, 3 AZs』とあるのがミニマムなので、Auroraは結構高いです💰」「記事のタイトルはオープンソースRDBMSと言いつつMySQLの話がほとんどのような気はするけど😆

「最後に『AWS EC2に自前で立てる場合』ってありますけど、よっぽどケチりたい場合でもない限り今どき自前はメリットないと思うし🧐」「そういえばこんなツイートを見かけたことありました↓」「自前でやることがあるとすれば、RDSを使わずにケチりたいとか、あるいはRDSだと動かないMySQLのバイナリモジュールを使いたい場合ぐらいかな〜: いずれにしろやりたくないけど😆

⚓Google Cloud Runのメリットとデメリット(Serverless Statusより)


つっつきボイス:「最初Google Cloudでの実行かと思ったら、Google Cloud Runというサービス名でした😅」「タイトルの大文字小文字ね☺」「英語のtitle caseって固有名詞わかりにくくてキライ😢

「GKEコンテナを直接公開できるフルマネージドのステートレスHTTPコンテナという触れ込みなので、雰囲気としてはAWSのLambdaプラスAPI Gatewayっぽい」「だから記事でLambdaと比較してるんですね☺」「GoogleがAWSにサーバーレスで勝負かけに来た感じですか」

「駆け足で記事を眺めた限りでは、ポイントは『Cloud RunはFaaSではない』というあたりかな: まあたしかにCloud Runサービスの裏にいるのはKubernetesで動かせるコンテナそのもののはずだし、そういうコンテナをつなぐサービスなんだから、Lambdaと違ってCloud RunはGKEのインスタンス上でサーバー自体が動くものなのだろうと推測してみた」「お〜」「GKEはコンテナが動いた時間で課金されるんですが、Lambdaの課金はその辺がちょっと違っていて、Firecrackerあたりと相乗りしていたような気がする」「Firecracker?」「AWS謹製の、超多重化できるVMみたいなものですね」

参考: [速報]AWS、独自のセキュアなコンテナ実行用マイクロVM「Firecracker」、オープンソースで公開。AWS re:Invent 2018 - Publickey

「記事はそういった意味でCloud RunはLambdaのようなFaaSとは違うと言ってるんだと思う: ユーザーが自分でコンテナを明示的に上げないといけないし、Lambdaみたいにソースコードだけをzipでアップするのとは別物ということなんでしょうね」「Lambdaも最近のLambda Layerを使うとCloud Runとほぼ似たような感じになってしまうのでなかなか悩ましいですが😅

参考: AWS Lambda レイヤー - AWS Lambda

「Cloud Runの方はコンテナを使うので自由度が高い分、Lambdaで動いているものはおそらくCloud Runに移植できるけど、逆はたぶんできない」「両者は似ているようで違っているんですね」


「ところで元記事に出てくるこのliable↓って何でしょう?」「技術用語としては見覚えないな〜🤔」「法律用語?」

The point is that the code you put inside Lambda, the code that you are liable for, can be smaller and more focused because so much of the logic can be moved into the services themselves.

後で調べると、アセット(asset)はもともと会計用語で言う「資産」を指すので、その反意語である負債(liability)にかけたシャレのようです。10年ほど前に「Building Real Software: Source Code is an Asset, Not a Liability」という記事がバズっていたようです。

参考: 簿記の用語一覧(Glossary of bookkeeping terms)

⚓Azure Functionsでサーバーレスしてみた


つっつきボイス:「こちらはAzure Functions!」「マイクロソフトのサーバーレス」「Visual Basicが動くとうれしかったりするのかしら😆」「自分はやらないけどっ😆


microsoft.comより

そういえば今週のPublickeyでAzureにGitHubアカウントでサインインできるようになったと報じられていましたね。

参考: [速報]GitHubアカウントでAzureにサインイン可能に。GitHubとAzure Active Directoryとの同期サポートで企業ユーザーの管理が容易に。Microsoft Build 2019 - Publickey

「ところでExcelをデータベースとして使ってる人いますよね😆」「Excelってファイルサーバーに置いて共有する機能が一応ありますけど」「あまり信頼できないヤツ😆」「昔は共有したExcelブックで複数ユーザーが同じセルに触った途端に壊れるとか何度もありました😇」「今もそうですよ😆」「Excelでそっち方面には頑張りたくないな〜😅

「そういえばMicrosoft Accessって使われてるんでしょうか?」「Office365を見ると一応ありますね」「消えることはなさそう」「AccessにWebサーバーを内蔵するみたいな方向にはならなかったんでしょうか?」「そっちは見かけないけど、Access独自のフォームでユーザー追加とか、昔なつかしオフコンっぽい使い方はよく見かけましたね😆」「AccessはSharePointを使ってWeb共有できるっぽいです」「あーSharePoint使うのか」「こういうのはいったん作られると壊れるまでずっと使われ続ける宿命☺」「ファイルメーカーもある時期からWebサーバーを内蔵してますね: 遅かったけど😆

参考: Access デスクトップ データベースを共有する方法 - Access

重要 Microsoft では、SharePoint で Access Web App を作成および使用することはお勧めしなくなりました。代わりに、Microsoft PowerApps を使用して、Web およびモバイル デバイス用にコードなしのビジネス ソリューションを作成することを検討してください。
support.office.comより(強調は編集部)

⚓SQL

⚓無意味なサロゲートキーのコスト(DB Weeklyより)


同記事より


つっつきボイス:「サロゲートキーのコスト問題はよく言われるヤツで、DBA(DB管理者)はサロゲートキーを嫌がることが多いんですよ☺」「サロゲートキーは、その1カラムで一意性を識別するためだけに存在するプライマリキーのことで、カラムのデータ自体には意味がない」「ボクらは代理キーって言ってた😆」「なのでサロゲートキーはRDBのピュアな設計としては本来は不要なものだったりします」

参考: 代理キー - Wikipedia

「一方、RailsのActive RecordのようなORMではたいていサロゲートキーを使います: 複合主キーでやろうとするとWHERE文を足したりとどうしてもSQLクエリが複雑になってしまってテンプレート化しづらくなるんですが、サロゲートキーにすることでActive Recordだと必ずinteger型のidという名前でサロゲートキーが決まったりするのでORMが作りやすくなる」「ふむふむ」

「DBAの世界ではサロゲートキーが好まれない傾向にあるんですが、それももっともな話で、本来サロゲートキーは一種のバッドノウハウ的な手法という面があるので」「ふむふむ」「でこういう記事みたいにサロゲートキーをつぶそうとしたりする人も出てくると😆

「RailsでMySQLなりPostgreSQLなりを使うときだと、よくオートインクリメントで1から連番を振ったりしますけど、そもそも連番を作るコストが高い💰」「ふーむ」「サロゲートキーは、テーブルでユニークな連番を振るためにテーブルをロックしないといけないので、もうそれだけで重い⚖」「あー」

「なので最近の大規模システムだと、たとえばSnowflakeのように分散された環境でも一意性が保証されたid生成器を使ったりしますね」

参考: Twitter IDs (snowflake) — Twitter Developers

「サロゲートキーは使わないで済めばそれに越したことはないので、記事は見出しレベルでしか見てないけど、たぶんその辺の話をしてるんではないかと😆

「ただ、MySQLだとサロゲートキーを使う方が速いケースがありそうな気がするんですよ: MySQLは妙な最適化を行うことがあって、ユニークなプライマリキーに対してそういった最適化を行ってることがありそうなので😆」「MySQLって他で通用しない独自の知識が必要になることが多いのがつらい😭: PostgreSQLにはないかというとそんなことはないんですが、PostgreSQLの知識はたとえばMSSQLなんかでも基本使えますし☺

⚓Grakn.ai: ナレッジグラフを扱う知識指向システム



同サイトより


つっつきボイス:「ER図作る系かと思ったらそれだけではなさそうでした」「BIっぽい何かに見える」「インテリジェントデータベース」「リポジトリにはgraqlという独自のクエリ言語がありますね」「ナレッジスキーマを定義してそこにクエリを投げていろいろやれるとかそういう感じかな🤔」「これ系のは割といろいろあるといえばありますね☺

「ナレッジグラフはGoogleが作ったものだった↓」「固有名詞でしたか!」

参考: ナレッジグラフ - Wikipedia

「こんな記事もありました↓」「見た感じ、事前知識を(グラフ理論の)グラフの形にしたのがナレッジグラフということらしい」「データベースというかデータ表現でしょうか?」「たぶんデータだけじゃなくてクエリやトリガーの仕組みとかも含めてなんでしょうね🤔」「ニューラルネットワーク(NN)的な要素もあるっぽい」

参考: Knowledge Graph とNNから始まる(かもしれない)人工知能のブレイクスルー - Qiita
PDF: Embedding Logical Queries on Knowledge Graphs
参考: ニューラルネットワーク - Wikipedia

⚓miniredis: 単体テストで使うRedisサーバー

// 同リポジトリより
func TestSomething(t *testing.T) {
    s, err := miniredis.Run()
    if err != nil {
        panic(err)
    }
    defer s.Close()

    // コードで期待されるキーをいくつかオプションで追加
    s.Set("foo", "bar")
    s.HSet("some", "other", "key")

        // コードを動かして確認
        // github.com/gomodule/redigo/redisのredigoライブラリを使った例
    c, err := redis.Dial("tcp", s.Addr())
    _, err = c.Do("SET", "foo", "bar")

    // オプションで値をredisでチェック
    if got, err := s.Get("foo"); err != nil || got != "bar" {
        t.Error("'foo' has the wrong value")
    }
    // ヘルパーでもやれる
    s.CheckGet(t, "foo", "bar")

    // TTLと期限:
    s.Set("foo", "bar")
    s.SetTTL("foo", 10*time.Second)
    s.FastForward(11 * time.Second)
    if s.Exists("foo") {
        t.Fatal("'foo' should not have existed anymore")
    }
}

つっつきボイス:「Goで書かれたRedisサーバー?」「単体テスト用のRedisサーバーとあるので用途絞られてる感」「なるほど、miniredisというだけあって、saveみたいな永続化機能あたりをばっさり切り落として、テスト中にRedisに期待されるレスポンスを返すためのライブラリみたい」「本物のRedisにはファイル書き出しなどの永続化機能がいろいろあります🧐」「たとえばRedisのpub/subを使った結合テストをやるのに、わざわざRedisを立ち上げるのってつらいじゃないですか😆」「なるほど、Redisを立ち上げずにテストできるんですね😋

「そういえばlocalstack↓にそういう機能ってなかったかな…お、Redisはないか😳」「ちなみにlocalstackは、AWSのサービスを使わないと動かないシステムをローカル環境でテストするために、AWSのスタックをエミュレーションするサービスです🧐」「お〜」

参考: localstack/localstack: 💻 A fully functional local AWS cloud stack. Develop and test your cloud & Serverless apps offline!

「localstackを使うと、以下のサービスがこのポート番号でローカルで立ち上がって、AWSと同じようなレスポンスを返してくれるんですよ😋」「確かにないと困るヤツだ」「localstackってAWSが提供してもおかしくない感じですよね」「まあAWSとしては『AWS使え』と言いたいでしょうし🤣」「🤣」「localstackにはAWSのElastiCacheがないので、ElastiCacheをテストしたい場合なんかにminiredisを使えるかも」

同リポジトリより

⚓JavaScript

⚓Node.js 12のLTSが登場(Publickeyより)


つっつきボイス:「12出ましたかっ」「つい最近anyenvしたときはまだなかったな」「babaさんの書き込みによると今回出たのは12のLTS版なんですね」

「『デフォルトのヒープサイズが700MBから1400MBへ引き上げられ』とあるけど、環境によってはつらいことが起きるかも😇」「あ〜IoT機器とか?」「Nodeを動かすようなIoTならさすがにもっとメモリは積んでいそうですが😆」「どちらかというとAWS Lambdaとかコンテナみたいな、こんなにメモリを使うことを想定してないような環境が影響受けるかも」「たぶんオプションで変えられると思うけど☺


nodejs.orgより

⚓Svelte 3: UIコンポーネントフレームワーク(JSer.infoより)


同サイトより

★がやたら多いです。


つっつきボイス:「Svelteというのを初めて見たので」「JS界はもういろんなものがありすぎて😆」「ReactとかVueみたいなコンポーネントフレームワークですって」

「SvelteはVirtual DOMが不要ってありますね」「そういえばFacebookがReactを出したときに、その設計を見た人たちが『Virtual DOMって重いんじゃないの?』みたいなことを言ってたのに、FacebookがVirtual DOMのものすごく速い実装を出してきて、今では誰もVirtual DOMが遅いとか言わなくなりましたね😆」「そういう流れでしたか😳」「だって設計だけ見れば裏でわざわざVirtual DOMで差分検出してとか、普通に考えれば重くなりそうじゃないですか🤣」「もし仮にReactがめちゃ遅かったら、SvelteみたいにVirtual DOMを使わない設計が速いのは納得だけど、実際はReact爆速で何だか納得できん!みたいな🤣」「🤣

参考: VirtualDOMの仕事ってなに?(Reactの表示速度がはやい理由) - Qiita

「Virtual DOMってやっぱりJavaScriptで書かれているんでしょうか?」「Reactのはそうです」「表にも裏にもDOMの枝があって、そのdiffを取るのが遅いって言われてたし、設計を見たときにも枝が深くなったら遅くなるんじゃね?と思ってたら、実装の速さがみんなの想像の上を行ってたという😆」「ちょうどGoogleのV8エンジンが登場したときに、それまでの『JavaScript遅い』という概念が完全に覆されたのと似た感じありましたね😆

参考: React - Wikipedia
参考: Google V8 JavaScript Engine - Wikipedia

「もしReactのVirtual DOMが遅い世界線上だったら、Svelteのような設計が席巻していたかも☺」「速さは正義」「Virtual DOM使わない方が速そうですけどね」「元記事にもSvelteの詳しいパフォーマンス比較が載ってないっぽいし😆

「そういえばFacebookは以前Reactのライセンスに特許条項付けてましたね」「それを嫌った人たちがSvelteとか使ってたのかも?」

参考: 【速報:2017/09/23】Reactのライセンスから特許条項が外れて真のオープンソース・ライセンスになる - Qiita

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

⚓Vueアプリを5分でPWA readyにする


つっつきボイス:「Vueアプリを5分でPWAにっ」「やっぱりNuxt.js使ってますね」「Nuxt.jsはお便利ツール詰め合わせみたいなヤツでしたっけ」「@nuxtjs/pwaモジュールを入れれば即PWA化できると」「ほんまに?😆

npm i @nuxtjs/pwa

参考: はじめに - Nuxt.js

⚓CSS Subgridとは


つっつきボイス:「Gridを覚えたと思ったら今度はSubgridですって」「この辺の絵がわかりやすかったです↓」「おー、Gridでこういうイレギュラーな並びができるようになったと」「CSS 3が使えない状況でこれが使えるといいかも」「Flexboxでもできるのかな?ここまでは無理かな?🤔


同記事より

⚓その他フロントエンド

⚓その他

⚓CPU不足?


つっつきボイス:「なぜかTBSラジオのサイトですが😆」「CPU不足、 ちょっと前から言われ始めてますね」「そうみたいです」「インテルがなかなか新しいCPUを出さないせいだとか」

⚓その他のその他


つっつきボイス:「少し前からこの辺が気になっているんですけど、プログラミングを知らない人からは、プログラマーは頭の中で全部作り上げてからそれを一気に入力していると思われてるんじゃないかって」「『プログラムは上から下に順に書くものだ』という思い込み😆」「プログラムを書いてる人からすれば『そんなふうに書いてるわけないだろ』って思いますよね」「たま〜にそういう人いますけど😆

「作曲家が曲を作るのも、自分みたいなエンジニアからはそう見えがちかも」「作曲もまさにそうですよね: ヘッドアレンジで一気に全部作っちゃう作曲家って実際にはモーツァルトぐらいしかいなくて😆、ベートーヴェンみたいにモチーフをああでもないこうでもないと組み合わせたり使いまわしたりしながらじわじわ大きく育てていく方が現実のプログラミングに近そう」

「大昔の、それこそラインプリンタしかなくてディスプレイもカーソルという概念もない時代だったら、あらかじめ全部作っておいてから一気に入力してた人はいたかもですね☺」「出力された紙に書き込んでデバッグとかはしてたかもですけど😆」「今でもごくまれにしょぼいターミナルでirbのカーソルキーが効かなくなったりすると、必死で脳内で組み立てて入力したし😆」「縛りプレイ😆

参考: テレタイプ端末 - Wikipedia

テレタイプはこんな装置でした↓。

「たしか卜部さんも『漫画をそういうふうに端から順に描く人はいないと思う』とどこかで回答してました」「それこそ作文でも上から下に書き下ろすものだと思いこんでたりとか😆」「ありそう〜」「文章の流れを作るのが苦手な人ってよくそう思い込んでたりしますよね」「見出しから書いて構成決めてから書く方が普通楽ですし☺」「できあがったものだけを見てるとそう思っちゃうのかも」

「そういうノウハウも、もしかすると紙に文章を書いていた世代までで、自由自在に挿入できるワープロやエディタで作文を始めた世代だとまた違ってきてるかも😆」「赤ペンで校正とかも😆」「たまには紙とペンで縛りプレイで文章書いてみるとわかりみあるかも☺



つっつきボイス:「言われてみれば」「たしかに英語圏だとお高い教科書が公開されてることありますね」「出版事情が違いそうだけど、契約はむしろ英語圏の方が厳しそうだし🤔」「文化の違い?」「日本だと囲い込む傾向あるかも」「ありがちなのは、英語圏ではオープンになっているのに日本語版はとっくに絶版になってて、もう英語で読むしか選択肢がないパターン🤣」「で仕方なく学生が輪講で訳したような日本語版が上がってて、しかも間違ってたりする🤣」「🤣」「コンピュータサイエンス系の教科書でそういうのが割とよくあります😅」「『Refactoring Ruby』も日本版は絶版ですね😢

⚓番外

⚓タイタンの湖(メタン)

参考: 土星最大の衛星タイタンで深さ100メートル超の湖が発見される | ワールド | 最新記事 | ニューズウィーク日本版 オフィシャルサイト


今回は以上です。

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

週刊Railsウォッチ(20190415-1/2前編)Railsバージョンアップに便利なstill_life gem、Zeitwerkの改修進む、named_capture追加ほか

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

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

Publickey

publickey_banner_captured

DB Weekly

db_weekly_banner

Serverless Status

serverless_status_banner

JSer.info

jser.info_logo_captured

Railsで学ぶSOLID(5)依存関係逆転の原則(翻訳)

0
0

概要

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

Railsで学ぶSOLID(5)依存関係逆転の原則

「SOLIDの原則シリーズ」へようこそ。このシリーズ記事では、SOLIDの原則をひとつずつ詳しく説明し、分析します。シリーズの最後にはいくつかのヒントや考察を含む総括記事をお送りしますのでどうぞご期待ください。

それでは始めましょう。「SOLIDの原則」とはそもそも何なのでしょうか?SOLIDとは、オブジェクト指向プログラミング設計における一般的な原則であり、ソフトウェアをより理解しやすくし、拡張性やメンテナンス性やテストのしやすさを向上させることを目的としています。

  1. 単一責任の原則SRP: Single responsibility principle)
  2. オープン/クローズの原則OCP: Open/closed principle)
  3. リスコフの置換原則LSP: Liskov Substitution Principle)
  4. インターフェイス分離の原則ISP: Interface Segregation Principle) — 本記事についてはBPS社内で議論の末翻訳はしないこととしました原文
  5. 依存関係逆転の原則DIP: Dependency Inversion Principle)(本記事)

今回は、5番目の「依存関係逆転の原則」を見ていきましょう。

依存関係逆転の原則(DIP)

この原則は2文からなります。

  • 高レベルのモジュールは低レベルのモジュールに依存すべきではない。両者とも抽象に依存すべきである。
  • 抽象は(実装の)詳細に依存すべきではない。逆に詳細は抽象に依存すべきである。

この原則については、素直に例で考えるのがベストだと思います。

以下のコードは、DIP違反の例です(Gist[solid-5-violation.rb))。

class ReportGeneratorManager
  def initialize(data)
    @data = data
  end

  def call
    generate_xml_report
    additional_actions
  end

  private

  attr_reader :data

  def generate_xml_report
    XmlRaportGenerator.new(data).generate
  end

  def additional_actions
    ...
  end
end

このコードのどこがまずいのでしょうか?まず、ReportGenaratorManageという高レベルのクラスと、XmlReportGeneratorという低レベルのクラスが癒着しています。次に、別の種類のレポートジェネレータを追加する必要が生じた場合に、高レベルのクラスの修正も要求されます。つまり、低レベルのクラスが変更されることで高レベルのクラスまで修正を余儀なくされるということです。

ここでは、依存関係を逆転させるのが正解です。詳細は、個別の実装にではなく抽象に依存させます。Rubyは動的型付け言語なので、ダックタイピングの手法を使えます。Ruby世界には「抽象クラス」も「抽象インターフェイス」もないので、そうしたものを作成する必要はありません。

この他にDI(Dependency Injection)パターンを使って実現する方法もありますが、その場合1つ注意しなければならない点があります。

  • 依存関係逆転の法則 ≠ DI

DIは、単に依存関係逆転の原則を満たすのに使えるテクニックであり、原則そのものではありません(Gist)。

class ReportGeneratorManager
  def initialize(data, generator = XmlRaportGenerator)
    @data = data
    @generator = generator
  end

  def call
    generate_report
    additional_actions
  end

  private

  attr_reader :data, :generator

  def generate_report
    generator.new(data).generate
  end

  def additional_actions
    ...
  end
end

上のコードでは、特定のジェネレータクラスをこのマネージャにコンストラクタ経由で注入できるようになりました(かつデフォルトのジェネレータも提供しています)。これで、この高レベルクラスの操作は、具体的なジェネレータクラスすべてに共通する一般的なインターフェイスでのみ行われるようになります。実装クラスの差し替えは簡単です(ReportGeneratorManagerクラスを別の場所にある異なる実装で使うなど)。

上述のソリューションは柔軟性が遥かに高まり、テストもずっと容易になります。

単体テスト

上述のソリューションでの単体テストは、簡単かつ快適です。既に依存をコンストラクタで注入するようになっているので、doubleへの依存を作成して実物の代わりに注入するのも簡単です(Gist)。次のようなことをする必要はありません。

any_instance_of(ClassToMock) ...

# または

ClassToMock.new.stub(:method_to_stub)

この方法は一部で「ダメなテスト」と呼ばれています。

これは、まさに単体テストの主要な目的です。つまり、一切の依存性を考慮する必要のない、独立した単独のユニットをテストすることです。上のソリューションを使うことで、この目的を容易に達成できます。

ここからさらに一歩進めると、クラスの依存性をモック化しづらい場合、融通の利かない依存性がそこに頑固にこびりついていると言えます。これに限らず、テストを書くことで「コードの臭い」を見つけたり、何かが複雑になりすぎていることがわかるようになります。

概念を取り違えないこと

上述のとおり、「依存関係逆転の原則(DIP)」と「DI(Dependency Injection)」を混ぜこぜにしないようにすべきです。しかし、前述の2つと紛らわしい用語がもうひとつあります。それは「制御の反転(IoC: Inversion of Control)」と呼ばれているものです。IoCは本記事の本来の目的ではないので、Martin FowlerのDIP in the Wildという良記事のリンクをご紹介するにとどめます。その要点が1行にまとまったものを以下に引用します。

DIは「接続方法」であり、IoCは「指針」であり、DIPは「かたち」である
Martin Fowler

全シリーズのまとめ

これまでのシリーズのどこかで申し上げたように、これらの原則(に限らずあらゆるパターン)は、そこにメリットがあるから適用するのであり、「プロならそうする」とばかりに単純に適用するものではありません。目に見えるはっきりしたメリットがなければ、時間の無駄でしかありません。原則を満たすだけのためにルールなりパターンなりを適用すると、ほとんどの場合さまざまな問題を誘発し、コードベースが手に負えないほど複雑になるという無残な結果に終わります。これらの原則は皆さんを手助けする(コードを改善する)ために書かれたものであることを肝に銘じ、必要以上にコードを複雑にしないよう心がけましょう。銀の弾丸はないのです。とにかく、あらゆる作業において常識を働かせましょう。これらの原則の本来の意図を常に思い出し、よいガイダンスとして扱うことです。

最後に率直に申し上げておきたいことがあります。さまざまな規則、原則、パターンをすべて読みとおすだけでその日から完璧なコードが書ける、ということはありえません。コードの改善は終わりのないプロセスであり、必要なのはコードを書いて書いて書きまくることです。

関連記事

Railsで学ぶSOLID(1): 単一責任の原則(翻訳)

Railsで学ぶSOLID(2)オープン/クローズの原則(翻訳)

Railsで学ぶSOLID(3)リスコフの置換原則(翻訳)

週刊Railsウォッチ(20190513-1/2前半)6.0の地味に嬉しい機能、ActiveModelエラーの扱いが変更、Railsのリクエスト/レスポンスをビジュアル表示ほか

0
0

こんにちは、hachi8833です。

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

5/9の令和初の公開つっつきはおかげさまで盛況となりました。お集まりいただいた皆さまありがとうございます!🙇

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

今回はGW前の公式ニュースから見繕いました。最近のコミットリストを見るとドキュメントの更新が増えていて、リリースが遠くないことを感じさせます。

⚓モデルのエラーをオブジェクト化、APIを変更

単一のエラーをカプセル化するActiveModel::Errorを追加。メッセージ生成や詳細へのアクセスを扱う。これをErrorsクラスで使い、ハッシュベースのインターフェイスからErrorオブジェクトの配列に変更する。whereなどのより柔軟なクエリメソッド群を追加。
PRより大意

# activemodel/lib/active_model/error.rb#L3
module ActiveModel
+ # == Active \Model \Error
+ #
+ # Represents one single error
+ class Error
+   CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict]
+   MESSAGE_OPTIONS = [:message]
+
+   def initialize(base, attribute, type = :invalid, **options)
+     @base = base
+     @attribute = attribute
+     @raw_type = type
+     @type = type || :invalid
+     @options = options
+   end
...
+   attr_reader :base, :attribute, :type, :raw_type, :options
+
+   def message
+     case raw_type
+     when Symbol
+       base.errors.generate_message(attribute, raw_type, options.except(*CALLBACKS_OPTIONS))
+     else
+       raw_type
+     end
+   end
...
+   protected
+
+     def attributes_for_hash
+       [@base, @attribute, @raw_type, @options]
+     end
+ end
+end

つっつきボイス:「おー、ActiveModel::Errorクラスを作ったと」「変更追加の行数がかなり多いからがっつり再実装してるっぽい」「Active Modelのエラーをwhereとかで絞り込めるようになったように見える」「ほんとだ、プルリクの解説にも書いてありますね↓」「これは確かにありがたい!🙏

model.errors.where(:name, :foo, bar: 3).first

「今まではできなかったんですね」「従来のハッシュだと気合いでeachするしかなかったし😆: 今までのだと、Active Modelにバリデーションエラーを追加するときにキーがかぶると困ったんですよ😭」「arrayに変わったことで、そのあたりをあまり気にせずにやれるようになった😋

「これかなりよさそうなのでPRの👍押しとこうっと: でも既存のgemへの影響とかそこそこありそうな気も🤔」「言われてみればテストにもassert_deprecatedがいっぱい付いてますね」「割と大きな変更だと思う」

「Active Modelをどの程度までオブジェクト指向に作るかという設計の話の気もしますね: ハッシュの方が何も考えずに使えるという考え方もあるだろうし、オブジェクト指向的にきれいにやろうとするなら今回みたいにする方があるべき姿だろうし」「ふーむ」「自分は今回の変更の方が好き❤

⚓API変更を抜粋しました。

[]
変更なし、非推奨化(今後はmessages_for
as_json, blank?, clear, count, empty?, add
変更なし
added?
ほぼ変更なし(1箇所変更: “Testing is more precise and flexible”を参照)
delete
拡張された(エラーの種類やオプションなどの詳しい条件を指定できるようになった)
each
each{|attr,msgs|}は従来どおり(非推奨化)
each{|error|}Errorの配列をループで回す
full_message
非推奨化(不要になった)
変更されなかったメッセージはErrorで生成される
full_messages, full_messages_for, include?, size, to_hash, to_xml, to_a, messages, details
変更なし
messages_for
追加(非推奨の[]を置き換える)
where
追加(エラーオブジェクトへのクエリ)
generate_message, has_key?, key?, keys, values
非推奨化
import
追加(ネステッドエラーとしてエラーを1つインポート)(Form Objectやnested attributesでのエラーのインポートに便利)

⚓zeitwerk:checkタスクを実装

アプリケーションをRails 6にアップグレードするときに、従来のオートローダーを用いているプロジェクト構造が:zeitwerkモードと互換性があるかどうかを以下のコマンドでチェックできる。
PRより大意

bin/rails zeitwerk:check

つっつきボイス:「以前のオートローダーとzeitwerkモードで互換性があるかどうかをチェックするんだそうです」「そんなの動かしてみないとわからなそうだけど😆: 従来のオートローダーは実行時解決だから」「あー」「コード読まないとわかんないけど、空のRailsランナーを動かして通るかどうかをチェックしてるとかそういうノリ?」「コンパチブルチェックと言いつつ、zeitwerkに変えて動くかどうかぐらいのレベルのように思えるけど😆」「ここはひとつRails 6を触っている人たちにぜひチャレンジしていただきたい😆

⚓Active Jobの古いジョブのリトライ

# activejob/lib/active_job/exceptions.rb#L50
      def retry_on(*exceptions, wait: 3.seconds, attempts: 5, queue: nil, priority: nil)
        rescue_from(*exceptions) do |error|
-         # Guard against jobs that were persisted before we started having individual executions counters per retry_on
-         self.exception_executions ||= {}
-         self.exception_executions[exceptions.to_s] = (exception_executions[exceptions.to_s] || 0) + 1
+         executions = executions_for(exceptions)

-         if exception_executions[exceptions.to_s] < attempts
-           retry_job wait: determine_delay(seconds_or_duration_or_algorithm: wait, executions: exception_executions[exceptions.to_s]), queue: queue, priority: priority, error: error
+         if executions < attempts
+           retry_job wait: determine_delay(seconds_or_duration_or_algorithm: wait, executions: executions), queue: queue, priority: priority, error: error
          else
            if block_given?
              instrument :retry_stopped, error: error do
                yield self, error
              end
            else
              instrument :retry_stopped, error: error
              raise error
            end
          end
        end

つっつきボイス:「ジョブのリトライか〜😅」「プルリクメッセージを見ると、Rails 6にアップグレードしたときに、Rails 5.2のときの古いジョブをリトライできるようにしたとある」「ジョブのフォーマットが5.2と6で変わっていたということなのかな」

「たしかにこれやっておかないといけないヤツ: Rails 6から始まったプロジェクトならいいけど、Rails 5.2でジョブキューがたまっている状態で6にアップグレードするとジョブキューが動かなくなってたという話なんでしょうね」「これ現場で起きたらめちゃめちゃ悲惨ですよね😅」「これ踏んだらヤバい😇: リトライできないジョブをどうやって復旧させるかを考えるだけで🤣」「Sidekiqの生のジョブキューとにらめっこして復元方法を考えるとかやりたくない〜😭」「果たしてロールバックできるんしょうか😭

⚓Active Record高速化2連発

# activerecord/lib/active_record/attribute_methods/before_type_cast.rb#L48
      def read_attribute_before_type_cast(attr_name)
-       sync_with_transaction_state
+       sync_with_transaction_state if @transaction_state&.finalized?
        @attributes[attr_name.to_s].value_before_type_cast
      end
# activerecord/lib/active_record/attribute_methods.rb#L467
      def pk_attribute?(name)
-       name == self.class.primary_key
+       name == @primary_key
      end

つっつきボイス:「またしても@kamipoさんの高速化です」「やっぱり@kamipoさんはすごい人💪」「こういう地道な改修がホント大事🙏」「Active Recordのコード生成系の処理にこういうのが残ってたのか」「Active Recordのソースコードをここまで読み込んでる人って他にいなさそう」「@kamipoさんはこういうのをどうやって見つけるんでしょう?」「わがんね🤣: frozenでないものとかを地道に見つけて修正してるのかも」

「以下の差分の-って何でしたっけ?」「これはfreezeしたstringを返すヤツですね」「そうでした😅」「3回ぐらい見かけたのでさすがに覚えた😆

# activerecord/lib/active_record/attribute_methods/primary_key.rb#L43
          def primary_key=(value)
-           @primary_key        = value && value.to_s
+           @primary_key        = value && -value.to_s
            @quoted_primary_key = nil
            @attributes_builder = nil
          end

参考: instance method String#-@ (Ruby 2.6.0)

⚓番外: Orphan Recordを検索するメソッド

# activerecord/lib/active_record/relation/query_methods.rb#L
+     def missing(*args)
+       args.each do |arg|
+         reflection = @scope.klass._reflect_on_association(arg)
+         opts = { reflection.table_name => { reflection.association_primary_key => nil } }
+         @scope.left_outer_joins!(arg)
+         @scope.where!(opts)
+       end
+
+       @scope
+     end

つっつきボイス:「まだマージされていませんが、Gobyの@st0012さんが『これよさそう』って教えてくれたPRです」「missingはあれば使うかも😋: has_many関係で、manyされている側で参照元がなくなっているものを探せるヤツですね」「お〜」「この間ぐぐってみたところ、このorphan recordはデータベース用語として一般的に使われているもののようです🧐」「orphanって孤児とかみなしごという意味でしたっけ」「家なき子というか」「たまにデータベースの掃除をしたいときなんかにmissingが便利そう🥰

Post.where.missing(:author)

{他動} : 〜を孤児にする、親を失わせる、独りぼっちにさせる
{名・形-1} : 孤児(の)、親のない(人)、親をなくした(人)
{名・形-2} : オーファン(の)、孤立行(の)◆ページの末尾に残された、段落の最初の1行。2行目以降は次のページに送られている。

参考: What is an Orphaned Record? | Database.Guide

⚓その他改修

つっつきボイス:「HashWithIndifferentAccessってHWIAと略されることありますよね: 最初見たときわかんなかった😆」「HABTMなんて略語もありましたね」「はぶたむはもういいや😆: Railsにはもう存在しないし」

参考: ActiveSupport::HashWithIndifferentAccess
参考: HABTMリレーションシップは悪であるという論争 | A-Listers


つっつきボイス:「はー、where.notの動作がRails 6.1でNORからNANDに変わると」「あんまり使ってる人いなかったのかな?」「where.notって個人的には、気持ち悪いとまではいかなくても、ドットも含めてワンセットになっているのが何となく使いづらい気がするんですけど😅」「使うは使うけど、他にもメソッドがチェインされているとto_sqlしてどんなSQLが出てくるのか調べないと落ち着かない😆」「not_whereみたいな名前ならよかったかも☺

つっつきボイス:「ActiveModel::Attributesattribute_namesってなかったんだ!」「ハッシュのkeysに相当するものを付けた感じ」


つっつきボイス:「join_type…だと?」「あ、InnerJoinOuterJoinを指定できるようになったのか」「これはあっていい気がする、というかこの方がどういうSQLができるか見当がつきやすいし😆」「自分はORM登場前からSQL使ってたのもあって、メソッドチェインが深くなるとto_sqlしてSQLを確認しないと不安になるところがあるんですが、こういうやり方なら多少気持ちの安寧を得られるかなと😆」「このあたりORMネイティブ世代だとどう思うか知りたいところではありますね☺

⚓Rails

⚓Rails 6の地味に嬉しい機能たち(Ruby Weeklyより)


つっつきボイス:「これ系の記事は定期的に出てきますね☺」「TechRachoの翻訳記事でお世話になってるEvil Martiansさんの記事です」「お、Evil Martiansの人たちはRubyKaigi 2019にも来てましたね、つーか発表してた↓」

「記事の方ですが、Railsウォッチを読んでいる方にはだいたいお馴染みのものが多そうでした」「Action MailboxにAction Textにマルチデータベースにパラレルテストあたりはだいたい言い尽くされている感ありますね」

「おー、Active Storageのダイレクトアップロード機能: ちょうど以下の図↓みたいに、Railsサーバーを経由せずにS3みたいなクラウドストレージにアップロードする機能ということだと思います」「お〜」「普通に実装するとまずRailsサーバーにアップロードしてからS3にアップロードするみたいになるんですが、ダイレクトアップロードではS3の使い捨てトークンを使って直接S3に投げられる🧐

参考: Active Storage meets GraphQL: Direct Uploads - DEV Community 👩‍💻👨‍💻


dev.to/evilmartiansより

「ダイレクトアップロードみたいな方式がありがたいのは、ファイルのアップロードのためだけにRailsのワーカーを保持しないで済むところ😋」「ある程度の規模のアプリになったらこういうのを真面目に実装しておかないと、管理者が管理画面で巨大ファイルをアップロードするとユーザーのフロント側が詰まるなんてことが起きかねないので😇」「あ〜なるほど」「こういうのを自前で実装するのは大変なので、Railsの機能にお任せでやれるのはありがたい🙏

参考: ダイレクトアップロード — Active Storage の概要 - Rails ガイド

insert_allとかupsert_allあたりはウォッチでやったかな」「dirty周りでは、PostgreSQLのJSONBでもchanged?的なことができるようになってるらしい」「optimizer hintsもあったな〜」

destroy_byとかtouch_allあたりは使いそうだけどそのときに忘れてそうな気も🤣」「人が使ってるのを見て思い出したりして😆

⚓最近のService Object

社内SlackでService Objectの話題が出たので。Service Objectには「Facadeパターン」か「Commandパターン」の2つの方法があるという流れでした。

Railsで重要なパターンpart 1: Service Object(翻訳)


つっつきボイス:「そうそう、最近Service ObjectがBPS社内でも話題になってたタイミングでwillnetさんがとてもいい記事を書いてくれてた❤

「記事は以下のように、publicなメソッドはcallだけにしておいてあとは全部privateにするタイプの、単機能を使い捨てで実行するだけのService Objectの話ですね」

# https://tech.medpeer.co.jp/entry/2019/05/08/180000 より
class HereMentionCreator
  delegate :channel, :creator, to: :message

  def self.call(message:)
    new(message: message).call
  end

  def initialize(message:)
    @message = message
  end

  def call
    members.each do |member|
      message.mentions.create!(to: member, chennel: channel)
    end
  end

  private

  attr_reader :message

  def members
    @members ||= channel.members.active - [creator]
  end
end

「そしてはてブを追いかけてたらちょうどMastodonのソースコードでも同じような感じでやってるのを見つけたりしました」

「Mastodonのもcallだけがpublicで、後は全部private↓」「こういうのはわかりやすくていいですね😋

# https://github.com/tootsuite/mastodon/blob/master/app/services/notify_service.rb
class NotifyService < BaseService
  def call(recipient, activity)
    @recipient    = recipient
    @activity     = activity
    @notification = Notification.new(account: @recipient, activity: @activity)

    return if recipient.user.nil? || blocked?

    create_notification!
    push_notification! if @notification.browserable?
    push_to_conversation! if direct_message?
    send_email! if email_enabled?
  rescue ActiveRecord::RecordInvalid
    return
  end

  private

  def blocked_mention?
    FeedManager.instance.filter?(:mentions, @notification.mention.status, @recipient.id)
  end
...

「ゲストの皆さんにお伺いしますけど、こういうService Objectって使います?」「まさに今そういうのを使ってます☺」「お〜やはり」

「ついでに伺いますけど、呼び出しのメソッド名は何にしてます?callとかexecuteとかperformとか流派があるようですけど」「クラス名を踏襲するという意味でcall派です😎」「意味のある名前を付ける派の人もいますけど、統一する方がわかりやすそうな気はしますね☺」「たしかJavaだとperformを使ったりします」

runはだめでしょうか?」「runはService Objectだとちょっとニュアンスが違うかな〜: マルチスレッドで実行するイメージがあるので」「あ〜なるほど!」「Thread.run的な印象が強いかも」

「Service Objectは未だに統一見解がない感じですね😆」「Rubyだとcallが多いような印象ある」「後は上みたいにパラメータが増え過ぎたらParameter Object作ったりする、なんてのもありますね」

⚓RailsでFacadeパターンを使う

# 同記事より
# app/facades/users_facade.rb
class UsersFacade
  attr_reader :current_user, :vip_presenter

  def initialize(current_user, vip_presenter=VipUsersPresenter)
    @current_user = current_user
    @vip_presenter = vip_presenter
  end

  def new_user
    User.new
  end

  def last_active_users
    @last_active_users ||= active_users.order(created_at: :desc).limit(10)
  end

  def vip_users
    @vip_users ||= vip_presenter.new(active_users.vip).users
  end

  def messages
    @messages ||= current_user.messages
  end

  private
  def active_users
    User.active
  end
end

つっつきボイス:「Service ObjectでもCommand的に書くのとFacade的に書くのがあるという話があったのでこういうのを探してみました」「Facadeって正直デザインパターンで名前付けるほどのものだろうかという気持ちはある🤣」「app/の下にservices/じゃなくてfacades/を置いて名前にFacadeと入っているところぐらいしか違わないっぽいですね」「utils/の下に置く何とかUtilityクラスをこうやってFacadeで書いているのは見たことあったかも」

⚓Rails Trace: Railsのリクエスト/レスポンスをビジュアル表示(Ruby Weeklyより)


同サイトより


つっつきボイス:「左下の『Show Key』を押すと凡例が表示されます」「ほほう、コールスタックを追う的な?」「Metal呼んだりRack呼んだりしてる」「お、右にスクロールするし!結構長い」「Metal呼んだだけじゃまだまだ終わらないので☺

「Railsのリクエスト/レスポンスを旅するみたいな」「チャートの色をクリックすると機能とメソッドが表示されるし」「横に広がっている紫色がRackの層なのか: Rackのコンテキストがどこからどこまであるのかなんてのがわかるし」

「こういうリクエスト/レスポンスのイメージってどこかで意識しておくといいでしょうね☺」「学習用にもよさそうですね🥰

後で気が付きましたが、ビジュアル表示方法の解説記事とソースコードへのリンクも同サイトにありました。ReactでSVGを生成しているそうです。

⚓その他Rails

つっつきボイス:「記事は普通っぽいんですが、オライリーのサイトというのが面白かったので拾ってみました☺」「これは普通にアップグレード記事ですね: Railsに限りませんが、デュアルブートでやるのはベストプラクティスのひとつ」



つっつきボイス:「これははてブに上がってた記事ですね」「2018年の記事?」「あ、気が付かなかった😅」「大バズりした記事を定期的に広告としてはてブに浮かび上がらせてるからでしょうね」「見事釣られました🎣

「無駄に長いコメントがだいたいよろしくないというのは確かに」「ところで皆さんはソースコードにコメントを書きたくない派ですか?」「いや〜ははは😅」「記事の『検討したけどやらなかったことをコメントにする』は、どこかでMatzが『Why notはソースコードでは表せない』と言ってたのにも通じていて、why notを表すのは大事

「ソースコードを読めばわかることはコメントに書く必要はないと思うけど、なぜこういうコードになったのかという理由はコメントで表すしかなさそうですよね☺」「『普通ならpluckでやるけどなぜかmapじゃないと動かなかったのでこう書きました』とか😆」「『ここはActive Recordのバグらしいので回避のためにこう書きました』とか😆」「あと正規表現を究極にこじらせたときなんかも『こういうことをする正規表現です』ぐらいのコメントは欲しい😆

⚓Ruby

⚓Rubyで関数合成

Thoughtbotの記事です。


thoughtbot.comより

# 同記事より
BENEFIT_COST_IN_CENTS = 1000 * 100
TAX_RATE = 0.4

subtract_benefit = ->(salary) { salary - BENEFIT_COST_IN_CENTS }
pay_tax = ->(salary) { salary * (1 - TAX_RATE) }

calculate_take_home_pay = subtract_benefit >> pay_tax
calculate_take_home_pay.call(SALARY_IN_CENTS) # => 840000.0

つっつきボイス:「Rubyでやらんでもええやん🤣」「compositionってどっち方面の言葉かなと思ったらやっぱり数学で言う合成関数みたいでした😆

参考: 写像の合成 - Wikipedia

⚓Procodile: Ruby製プロセスマネージャ


同サイトより

$ procodile start --foreground

16:13:41 system  | Procodile supervisor started with PID 24532
16:13:41 system  | Application root is /Users/swagman/Development/my-app
16:13:41 system  | Listening at /Users/swagman/Development/my-app/pids/procodile.sock
16:13:41 system  | Reloading configuration
16:13:41 web.1   | Started with PID 24548
16:13:41 worker.1| Started with PID 24549
16:13:41 worker.2| Started with PID 24550
16:13:41 cron.1  | Started with PID 24551
16:13:41 web.1   | => Puma starting in cluster mode...
16:13:42 web.1   | => * Version 3.12.0 (ruby 2.5.3), codename: Llamas in Pajamas
16:13:42 web.1   | => * Min threads: 5, max threads: 5
16:13:46 cron.1  | => Starting clock for 2 events: [ daily hourly ]
16:13:46 worker.1| => Starting worker... waiting for jobs
16:13:46 worker.2| => Starting worker... waiting for jobs
16:13:50 web.1   | => ::1 - - [26/Mar/2019:16:13:50 +0000] "GET /style.css HTTP/1.0" 302 - 0.2817
16:20:00 worker.1| => [3871] Beginning execution of job 3871 with DownloadLatestTweets
16:20:00 worker.1| => [3871] Downloaded 322 new tweets from 3 Twitter accounts
16:20:00 worker.1| => [3871] Finished processing

A brief introduction to Procodile from aTech Media on Vimeo.


つっつきボイス:「Procodileという名前の由来はワニの絵で丸わかりですね😆」「ぱっと見た感じforemanみたいなヤツ?」「ですです、リポジトリにそう書いてあって、バックグラウンドでもフォアグラウンドでも動くそうです」

「今さらですが、プロセスマネージャってどういうときに使うんでしょう?」「複数のプロセスを立ち上げないといけないものをまとめるときですね: サーバー以外にRedisなんかも含めて全部1個のコンテナの中で立ち上げたいときとか」

「ワーカーのプロセスを立ち上げ忘れたせいで非同期系の処理が実行されていなかった😇、というような事態を避けるためにプロセスマネージャをよく使います」「プロセスマネージャはワーカーを立ち上げることはもちろん、スーパバイザーとしても動作するので、ワーカーがout of memoryで死んだときに自動で再起動してくれたりと面倒を見てくれる」「なるほど!」「要するにスーパバイザーですね🧐

参考: スーパーバイザー - Wikipedia

「Rubyだとforemanが定番なんでしょうか?」「Rubyならforemanだったり、Dockerだとsupervisorだったり↓」

Dockerでsupervisorを使う時によくハマる点まとめ

「あとは古き良きUnixではMonitなんてのもありますね」

「これまた昔にGODというRuby製のプロセス監視ツールもありましたね」「名前が強い!⛩」「プロセスマネージャはしょっちゅう使うものではありませんが、こんな感じでいろんなものがあります☺

⚓scientist: GitHub製のクリティカルパスリファクタリング用ライブラリ(Ruby Weeklyより)

★が5000近くあります。

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

class MyWidget
  def allows?(user)
    experiment = Scientist::Default.new "widget-permissions"
    experiment.use { model.check_user?(user).valid? } # 変更前のコード
    experiment.try { user.can?(:read, model) }        # 変更後のコード

    experiment.run
  end
end

つっつきボイス:「さいえんてぃすと?」「これもスゴい名前😆」「usetryを使って振る舞いやパフォーマンスを比較できるみたいです」「developmentモードとかtestモードで使う感じ」「コミットには入れないんでしょうね」「README、長っ😆

「あ、末尾にいろんな言語のAlternativeがずらっとある!」「scientistっていう一般的なフレームワークがあるのかな?」「PHP版すらあるし」「今気づきましたが、これGitHub自身がやってるリポジトリなんですね😳


同リポジトリより

「たぶん具体的には、リファクタリングの前と後で動作が変わってないかどうかをチェックするのかも?」「あ〜、それならtryとかrunも含めてしっくりくる😋」「compareなんてのもあるし」

「以下で言うとuseに既存のコード、tryにリファクタ後のコードを入れて、結果がcontrolcandidateに入って、それをcompareで比較するみたいな感じでやれるようだ」「リファクタリングテストのフレームワークと理解するとよさそう」「出力データの例がなぜかREADMEにないけど、だいたいそんな感じかなと☺

class MyWidget
  include Scientist

  def users
    science "users" do |e|
      e.use { User.all }         # Userインスタンスを返す
      e.try { UserService.list } # UserService::Userインスタンスを返す

      e.compare do |control, candidate|
        control.map(&:login) == candidate.map(&:login)
      end
    end
  end
end

後で気が付きましたが、他の言語のはRuby版のscientistから移植されたようです。以下のGitHub技術ブログに解説とともに整形済みグラフや出力サンプルっぽいものもありました。「副作用のあるコードへの利用は想定されていない」そうです。



github.blogより

# github.blogより
{
  context:
    repo: 3
    user: 1
  name: "repository.pullable-by"
  execution_order: ["candidate", "control"]
  candidate:
    duration: 0.0015689999999999999
    exception: nil
    value: true
  control:
    duration: 0.000735
    exception: nil
    value: false
}

⚓その他Ruby


同サイトより

つっつきボイス:「前にも似たようなサイトを紹介したことがあったような気がするんですが、今後開催されるRuby関連のカンファレンスが開催期日順にずらっとリストアップされています(ほとんどは日本以外ですが😆)」「Balkan Rubyは毎年やってた気がする」 「RubyConf Colombiaって南米のコロンビアかな?」「大小さまざま☺



⚓Ruby trunkより

⚓リクエスト: Module#name_components

Foo::Bar.name.split('::').last   #=> "Bar"
# 上と同等の以下が欲しい
Foo::Bar.name_components         #=> "Bar"

つっつきボイス:「@mrknさんのリクエストですね」「名前空間を切り落としたコンポーネント名をシンボルで取れるメソッドがあるといいよね、みたいな」「コードを検査するようなコードを書くときに欲しいのは何となくわかる」


前編は以上です。

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

週刊Railsウォッチ(20190508-2/2後編)サロゲートキーのコスト、Cloud RunとLambdaの違い、miniredis、CSS Subgridほか

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

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

Rails公式ニュース

Ruby Weekly

RubyFlow

160928_1638_XvIP4h


キーワードで振り返るRubyKaigi 2019@博多(#2)Rails、JIT、コントリビューション

0
0

こんにちは、hachi8833です。GWと年号変わりを経て記憶が薄まらないうちに、引き続きRubyKaigi 2019@博多をキーワードで振り返ります。BPS社内勉強会でのRubyKaigi 2019報告会でBPS社内の他の参加者から得た情報も取り入れました。


rubykaigi.orgより

なお、RubyKaigi 2019(と2018)のツイートタイムラインを追える便利なリンク集(@fuji_syan作)を見つけました❤

2019年度も、見たいセッションが横に並ぶことが多く、泣く泣くエイヤで決めたことも何度かありました😢Martin先生も同じように残念そうにしていました。

当初は公式サイトへのリンクに留めようと思いましたが、現時点では公式サイトにまだスライドや動画がまとまっていないので、以下のリンクを参考に貼りつつ、見つけられた範囲でのスライド・資料も貼ってみました。

参考: RubyKaigi2019 スライドまとめ - Qiita

誤りや追加情報などがありましたら@hachi8833までお知らせください。

「Rails」

2019年度は、例年よりRails向けの実用的な話題が若干多かった印象があります。


Zeitwerkを作った@fxnさんのセッションはおおむねこれまでの情報に沿った内容だと思いました。既にnanocという静的サイトジェネレータgemでもZeitwerkが使われていて、大量のrequire行を削減できたそうです。

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



@searlsさんの「Selfishプログラマー」は、「unambitions」「ungrateful」「ungenerous」「delusional」「narcissistic」といったネガティブなキーワードをわざと用いて開発を説明しているのがユニークでした。


@mrknさんのセッションで扱われていたApache Arrowは以前のRailsウォッチでも取り上げましたが、ここではActive RecordのクエリをArrowで高速化を試みていました。メモリ使用量の増大と引き換えに、速度とパラレル実行が向上したそうです。@mrknさんがRailsdm最終回で発表したRubyData and Railsで試みていたArrowの実験では速度が変わらずメモリ消費が減っていましたが、実は当時のコードが壊れていたので今回再実験したとのことです。


OpenAPI 3(Swagger)のセッションはmorimorihogeさんが見たそうです。フロントとバックエンドに分かれて開発する際の共通プロトコルとしてはOpenAPI 3が現時点で最もこなれているようで、今後の動向を追いかける価値がありそうとの見解でした。

参考: 【連載】Swagger 3.0 入門 [1] Swagger(OAS) 3.0の登場|開発ソフトウェア|IT製品の事例・解説記事


Railsそのものではありませんが、Crystalball gemは、RSpec実行時にcode dependencyまで追いかけて「修正したコードに関するテスト」だけを実行できます。セッションを見たmorimorihogeさんが「これは実用性高そう」と評価していました。Crystalballは@tenderloveさんの記事「Predicting Test Failures」からヒントを得たそうです。



また、Matzのキーノートで触れられていたMJITの進捗報告では、Ruby 2.6ではoptcarrotでのCPUボトルネックは倍以上に改善されたものの、Railsではまだ遅いということでした。

「JIT」


ここ数年RubyKaigiで目にするJITの話題といえば@k0kubunさんと@ymakarovさんです。

@k0kubunさんのセッションでは、上述の「2.6ではRailsが遅い」問題に2.7で取り組んでいる内容を発表していました。メソッド呼び出しとオブジェクトアロケーションが遅いことを突き止め、予測最適化やインライン化などを追い込んでいて、毎度のことながら強烈な印象でした。「Rubyプログラマーが無理な最適化をやらずに済むようにしたい」と言っていたのを思い出します。

参考: skaes/railsbench: benchmarking tool for rails applications

なお今回の発表とは関係ありませんが、LLVMの記事を追っていたらk0kubunさんが以前試していたLLVMベースのJITコンパイラ「LLRB」にたまたまたどり着きました。

参考: CRuby向けのLLVMベースのJITコンパイラを書いている話 - k0kubun’s blog


k0kubunさんと並んでJITを追求し続ける@ymakarovさんのセッションも同じくコアな内容でした。2016年の京都でのRubyKaigiではトリをつとめていましたね。GCCの最適化をずっとやりつづけてきた方だけに、MIR(ロシア語で「平和」を表す)という独自の中間表現形式にコンパイルすることで、実験レベルとはいえGCCの100倍前後の高速化を達成していました。MIRプロジェクトではRuby以外にも将来的にLLVMやJavaバイトコードやWASM(Web Assembly)なども扱う野心的な計画を立てていますが、C->MIRコンパイラはRuby 3には間に合わないそうです。

「コントリビューション」

大喜利の質問コーナーで「メンテナーが来てくれるとありがたい分野は?」で出され、「Windows対応やれる人がいるとうれしい」「GVLのスレッドロック周りをやってくれる人が欲しい(normalpersonさんが最近多忙なので)」という回答がありました。

他に、「Dateは今後Timeに寄せる」「Date.parseはカオス」「curryは後から作る人が出ないように先んじて実装してやった」といった話も出ました。

参考: class Date (Ruby 2.6.0)
参考: sugi/wareki: ruby 向け和暦ライブラリ。和暦と標準Dateの双方向変換をサポート。全元号対応。Date.parseにパッチを当ててます
参考: instance method Method#curry (Ruby 2.6.0)

また「(Rubyやライブラリに足りない)ドキュメントをやってくれる人も欲しい」「最近のRuby標準ライブラリはgemにするようにしているので、C言語がわからなくても貢献できますよ」といった回答もありました。私もお邪魔したDay2「コード懇親会」で早速CSVライブラリのドキュメントに貢献いただいた方がいらっしゃったそうです。たぶん以下がそれかと思います。

関連記事

キーワードで振り返るRubyKaigi 2019@博多(#1)

Rails 6のB面に隠れている地味にうれしい機能たち(翻訳)

0
0

概要

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


evilmartians.comより

Rails 6のB面に隠れている地味にうれしい機能たち(翻訳)

概要: 次回のメジャーアップグレードの中から、運用実績のある成熟したアプリケーションでも使いたくなるような、あまり知られていない機能を発掘したいと思います。昔の音楽に例えれば、「ヒットチャート上位」に顔を出すような売れ線の機能ではなく、LPレコードのB面やレアコレクションに隠れている名曲のような、新しいリリースの「地味だけど絶妙に役立つ」機能に目を向けてみたいと思います。

Rails 6で最も喧伝されているAction MailboxAction Textのような機能につい目を奪われがちですが、アップグレードするだけで利用できるWYSIWYGテキストエディタが、ある程度の期間運用されている現実のRailsアプリで今すぐに重宝するとは考えにくいでしょう。

一方、マルチデータベースのサポートパラレルテストのような、それほど前面に出ていない機能がただちに生産性向上に役立つようなこともあります。Rails 6には、そうした一見の価値のある機能が目白押しです。

私は数年前にAction Cableの改良に携わって以来、Railsフレームワークの開発を追いかけており、無数のプルリクに目を通しています。Rails 6 RC版リリースの数か月前には、既にある中規模クラスのRails 4アプリをRails 6向けに書き直す権限を与えられました。

私はAnyCableを手がけていることもあって、なんとかCableのたぐいは私の専門分野と言えます。

私は熱狂的な音楽ファンでもあるので、Railsという大規模フレームワークのリリースを目撃するのは、さながら(音楽の)レコード発売日に居合わせるような心持ちです。ヘビロテされる大ヒット曲もあれば、B面に埋もれたままになったり、ファンが発掘してレア物としてありがたがる曲もあります。

本記事では、このたびリリースされるRailsの舞台裏で息を潜めているgemを拾い上げてみたいと思います。新しいgemもあれば、数年の時を耐え忍んでRails 6にマージされたgemもありますし、プルリク一発のコードもあれば、Rails 6.xまでおあずけのgemもあります。

Action Cableのテスト

Rails 5のメジャーな機能であるAction Cableは、WebSocketsをすぐに利用でき、JavaScriptライブラリも同梱されていました。Action CableはRails wayの「設定より規約」に沿っていて構文も親しみやすいのですが、テスト駆動アプローチのサポートが抜け落ちていました。つまり、チャンネルのテストを書くための公式な方法が提供されていなかったのです。

Rails 6では、Action CableのJavaScript部分がついにCoffeeScriptとおさらばし、#34177でES6に書き直されました。

ある日、私は#23211を再オープンして修正する機会がありました。このプルリクはRails 5に取り込まれることになっていたのですが、最終的な曲目からは漏れてしまいました。その代り、シングル盤レコード(つまりaction-cable-testing gemのことです)をリリースし、3年越しでついに#33659でRails 6にマージされたのです。

というわけで、新しいRails 6プロジェクトで(--skip-action-cableを付けずに)rails newを実行すれば、app/channelsフォルダに加えてtest/channelsフォルダも作成されるようになりました。

サンプルではRSpecが使われています。Action Cableの統合はaction-cable-testing gemで実装されていますが、RSpec 4でマージされる予定です(#2113)。

さて、Action Cableではどこをテストすればよいのでしょうか?現実のアプリで使われている事例を見てみましょう。

Action Cableの接続周りであれば、次のように認証に関連するロジックをテストしたいでしょう。

# spec/channels/application_cable/connection_spec.rb
require "rails_helper"

# `type: :channel`でAction Cableテスティングヘルパーを追加する
# 現時点ではaction-testing-cable gemを使うが、RSpec 4に同梱されるはず
RSpec.describe ApplicationCable::Connection, type: :channel do
  let(:user) { create(:user) }

  it "cookieでの接続に成功する" do
    # "virtual"リクエストcookieをセット
    cookies.signed[:user] = user.id

    # `connect`メソッドはサーバーへのwebsocketクライアント接続を表す
    connect "/websocket"

    # idが正しく設定されたことをチェックできるようになった
    expect(connection.current_user).to eq user
  end

  it "cookieなしの接続は拒否する" do
    # cookieが渡されない場合は接続を拒否することをテストする
    expect { connect "/websocket" }.to have_rejected_connection
  end

  it "存在しないユーザーからの接続は拒否する" do
    cookies.signed[:user] = -1

    expect { connect "/websocket" }.to have_rejected_connection
  end
end

その他のAction Cableプリミティブである「チャネル」のテストもさらに興味深いものになりました。チャネルはWebSocketのコントローラとみなすことができます。

以下のテストで使っているPresenceChannelクラスは、実際のアプリでもさまざまなページに渡るユーザーのアクティビティを正確にトラッキングするのに使われています。#subscribe向けに以下のテストシナリオがあります。

  • ユーザーがそのチャンネルに接続するときは、プレゼンストラッキングシステムに登録されていなければならない
  • ユーザーがそのチャンネルに接続するときは、対応するストリームでサブスクライブされなければならない(通知を受け取るため)
  • ユーザーがそのチャンネルに接続するときは、「ユーザーが参加しました」という通知をストリームに送信しなければならない

真新しいAction Cableテストユーティリティを用いて、次のテストを書きます。

require "rails_helper"

RSpec.describe PresenceChannel, type: :channel do
  # `let_it_be`ヘルパーは`test-prof` gemが提供
  let_it_be(:projectschool) { create(:project) }
  let_it_be(:user) { create(:user, project: project) }

  before do
    # `stub_connection`は、渡されたidで
    # Connectionインスタンスを初期化する 
    stub_connection current_user: user
  end

  describe "#subscribe" do
    subject do
      # `subscribe`ヘルパーは、記載されているチャンネルへ
      # のサブスクライブアクションを実行する
      subscribe
      # `subscription`はサブスクライブされたチャネルのインスタンス
      subscription
    end

    it "プレゼンスストリームにサブスクライブする" do
      expect(subject).to be_confirmed
      expect(subject).to have_stream_for(project)
    end

    it "現在のユーザーをオンラインリストに登録する" do
      subject
      # Presence::OnlineUsersはこのアプリ特有のバックエンド実装
      # (プレゼンスのデータを保存する)
      expect(Presence::OnlineUsers.for(project)).to match_array([user])
    end

    it "オンライン通知を送信する" do
      expect { subject }
        .to have_broadcasted_to(project)
        .with(
          type: "presence",
          event: "user-presence-changed",
          user_id: user.id,
          status: "online"
        )
    end
  end

  # `unsubscribe`アクションもほぼ同一のシナリオ
  describe "#subscribe" do
    before do
      # unsbscribeを呼ぶ前に最初にsubscribeweしなければならない
      subscribe
    end

    it "現在のユーザーをオンラインリストから削除する" do
      expect(Presence::OnlineUsers.for(project)).to match_array([user])

      unsubscribe
      expect(Presence::OnlineUsers.for(project)).to eq([])
    end

    it "オフライン通知を送信する" do
      expect { unsubscribe }
        .to have_broadcasted_to(project)
        .with(
          type: "presence",
          event: "user-presence-changed",
          user_id: user.id,
          status: "offline"
        )
    end
  end
end

皆さんのアプリケーションに足りなかったAction Cableテストをご自由に追加してみてください!

Active Storageの将来を垣間見る

Active Storageは、Rails 5.2で新たに加わった新しいフレームワークの一員です。

私がActive Storageを使い始めたのはRails 6 beta 1からです。Active Storageには、すぐ使えるダイレクトアップロードのようなおいしい機能もいくつかありますが、まだ荒削りな部分がたくさん残っています。

グッドニュース: Active Storageは急速に進化を遂げており、改良方法が絶え間なく提案されています。

バッドニュース: 私たちが最も待ち望んでいるプルリクは、惜しくもRails 6の最初の安定版リリースには間に合いませんでした。しかし、それまでは提案されている変更を以下のような別実装で使ってみることもできます。

  • 添付ファイルのサイズやcontent typeのバリデーション(#35390)。現時点ではactive_storage_validations gemで実装されています。
  • 添付ファイルごとに複数の異なるサービスを使い分ける(#34935)。このプルリクがマージされれば、添付ファイルの種類ごとに異なるサービスを使い分けられるようになります(モデルごとに異なるS3バケットを使うなど)。
class User < ActiveRecord::Base
  has_one_attached :avatar, service: :s3
  has_one_attached :contract, service: :super_encrypted_s3
end
  • 添付ファイルを、現在のようにリダイレクトを経由せず、プロキシ経由で送信する(#34477)。これにより、CDNを簡単にセットアップして、最終的にユーザーがアップロードするアセットをより高速に送信できるようになります。別の#34581プルリクでは、同じ目的のpublic_service_urが提案されています。
  • 画像のvariantに名前を付ける機能(#35290)。現在は、添付ファイルのvariant(訳注: サイズ違いの画像)を作成するときにuser.avatar.variant(resize_to_limit: "50x50")のように正確なオプションを指定しなければなりません。named variants機能によってuser.avatar.variant(:thumb)のように書けるようになります。

named variants機能はRailsへのマージ待ち状態ですが、私たちが独自に実装したものもありますので、どうぞご覧ください。

Active Recordのinsert_all

#35077でActive RecordのバルクINSERTがサポートされます。

この機能がこれまで提案されていなかったことも驚きですが、私たちは既にいくつかのgem(activerecord-importがその道では最も有名です)を使っていました。

多数のレコードを一括でINSERTする方が、レコードを1つずつ保存するよりも明らかに効率が高くなります。

  • 必要なSQLクエリが1つで済む
  • モデルオブジェクトをインスタンス化する必要がない(メモリ使用量のコストはかかる)

ただし大きなトレードオフが1つあります。#insert_allメソッドではコールバックやバリデーションが一切呼び出されません。ご利用は計画的に!

この機能のおまけとして、すぐ使えるUPSERTステートメントがサポートされます。UPSERTPostgreSQLなどほとんどのリレーショナルデータベースでサポートされています。UPSERTINSERTUPDATEとして使った場合を考えてみましょう。INSERTしようとしているレコードがuniqueness制約に引っかかると、例外を出さずに既存レコードのUPDATE操作にフォールバックします。

PostgreSQLのINSERTのドキュメントに書かれていないxmax ='0'という裏技を使うと、どのレコードが実際に(UPDATEではなく)INSERTされたかをトラッキングできます。詳しくはStack Overflowをご覧ください。

今私が手がけているプロジェクトのコードを少しお目にかけましょう。このプロジェクトには「ユーザーの一括招待」機能があり、その背後にはInvitation(user_id, event_id, rsvp:bool, disposable:bool)モデルがあります。あるユーザーがイベントに他のユーザーを大勢招待すると、未招待のユーザーごとにinvitationのレコードを1件作成します。ユーザーが招待済みの場合は、invitationプロパティを更新したいと考えています(ここでUPSERTが活躍します)。

Invitation.pg_batch_insert(
  columns, # INSERTするカラムのリスト
  values, # 値のリスト(配列の配列)
  on_conflict:
    # (user_id, event_id)ペアでuniqueness制約をかけている
    "(user_id, event_id) DO UPDATE "\
    "SET disposable = (events.disposable AND EXCLUDED.disposable), "\
    "rsvp = (events.rsvp OR EXCLUDED.rsvp)",
  returning: "user_id, (xmax = '0') as inserted"
)

このコードでは少し前に書いたコードをmixinしてあり、とても役に立っています。

以上でおしまいです。後はRails 6がinsert_allメソッドやupsert_allメソッドを提供してくれます。

# 上と同じ機能
Invitation.upsert_all(
  # 注意: Railsではハッシュの配列が入力として期待されている
  values.map { |v| columns.zip(v).to_h },
  unique_by: %i[event_id user_id],
  update_sql: "disposable = (events.disposable AND EXCLUDED.disposable), "\
              "rsvp = (events.rsvp OR EXCLUDED.rsvp)",
  returning: "user_id, (xmax = '0') as inserted"
)

なお、上の例のupdate_sqlオプションやreturningオプションのプルリクは現時点ではマージされていない(#35636)ので、今後の更新情報をフォローしてください。

「dirty」ストアアクセサ

Action Cableテストのプルリクはお披露目されるまでに3年も待ち続けていましたが、この記録の上をいくのは間違いなく#19333の「ストアアクセサのdirtyトラッキングメソッド群」でしょう。何しろ提案が出されたのは2015年です。

このプルリクがめでたくRails 6にマージされたことで、Store属性を「素の」Active Record属性と同じように変更できるようになりました。

class Account < ApplicationRecord
  store_accessor :settings, :color
end

acc = Account.new
acc.color_changed? #=> false

acc.color = "red-n-white"
acc.color_changed? #=> true

この機能ができるまでの歴史を簡単に振り返ってみましょう。

Railsでは、いわゆる“dirty”属性(saveされていない変更)をトラッキングする方法が提供されています。dirtyは2008年のRails 2.1で導入されました。Rails 3.2では、単一カラムにシリアライズ保存されたデータ(多くはJSON)への読み書きメソッドを作成する方法として、いわゆるストアアクセサが追加されました

しかし、ストアアクセサが真の力を発揮したのは、PostgreSQLでJSONBデータ型がサポートされた後のことです。JSONBは、構造化されていないデータを効率よくコンパクトに、かつインデックス化可能な方法で保存する方法を提供します。

従来、ストアアクセサの変更トラッキングは次のような感じで行われていました(この例は実際に使われているコードベースから引用しました)。

class RangeQuestion < ActiveRecord::Base
  after_commit :recalculate_answers_scores, on: :update, if: :answer_was_changed?
  # RangeQuestionでは`min`値と`max`値に収まる正しい回答を期待する
  store_accessor :options, :min, :max
  # なおストアアクセサは通常の属性と同じ方法でバリデーションできる
  validates :min, :max, presence: true, numericality: { only_integer: true }
end

従来のanswer_was_changed?メソッドでは、options属性の変更全体をトラッキングしなければならず、以下のように扱いが面倒でした。

def answer_was_changed?
  # 詳しくはActiveRecord::AttributeMethods::Dirtyを参照
  return false if saved_change_to_attribute?("options")

  prev_options = saved_change_to_attribute("options").first

  prev_options.dig("min") != min || prev_options.dig("max") != max
end

これと同じようなことをコードのあちこちで行わなければならなかったので、私はあるときストアアクセサで*_changed?をextendすることを思いつきました。Rails 6からは以下のように書くだけで済みます。

def answer_was_changed?
  saved_change_to_min? || saved_change_to_max?
end

随分良くなったと思いませんか?こんなシンプルな機能のマージに時間がかかった理由は、主に当時コアコントリビュータだったSean Griffinが、ストアアクセサをあまり知られていない Attributes APIを用いる機能をフル装備した属性に昇格させることを望んでいたことです。残念なことにこの構想は実現せず、当面その見通しもなさそうです。なおSeanは最近Railsコアチームからリタイアしました

Active Recordのその他の素敵な小物たち

  • optimizer hintsのサポート(#35615)。これは、指定のクエリの最大実行時間に上限を設定するMySQLの機能です。
User.optimizer_hints("MAX_EXECUTION_TIME(5000)").all
#=> SELECT /*+ MAX_EXECUTION_TIME(5000) */ `users`.* FROM `users`

ちなみに、Active Recordは実行タイムアウトエラーを認識してStatementTimeout例外をraiseするようになりました(#31129)。これで例外を好きなだけキャッチできます。

  • クエリにannotateでコメントを追加できるようになった。
Post.for_user(user).annotate("fetching posts for user ##{user.id}").to_sql
#=> SELECT "posts".* FROM "posts" WHERE ... /* fetching posts for user #123 */
  • enumのネガティブスコープが自動で生成されるようになった(35381)。
class User < ApplicationRecord
  enum role: {
    member: "member",
    manager: "manager",
    admin: "admin"
  }
end

User.not_member == User.where.not(role: :member) #=> true
  • 以下のさまざまなショートカットが追加された:
  • seedの冒頭でModel.delete_allを実行する必要がなくなった。(railsコマンドの)db:truncate_allですべてのテーブルをDROPせずに内容をクリアできます。
# データベース内の全テーブルをtruncateする
# メモ: `be`は`bundle exec`のエイリアスを設定したものです(よろしければ皆さんもどうぞ)
$ be rails db:truncate_all

# truncateとdb:seedを以下のコマンド一発で実行できます
$ be rails db:seed:replant

この機能は、ステージングやレビュー用アプリで、データベースをDROPせずにseedを再実行したい場合に特に便利です(さすがにproductionのDBでseedを再実行するべきではありませんよね?)。

環境ごとのcredential

Rails 5.2から導入されたcredentialによって、評判のよろしくない.envファイルで重要なデータを管理せずに済むよう、新たなRails wayに則って重要なデータを扱えるようになっています。credentialを用いることで、サードパーティサービスの暗号化済みキーをバージョン管理システムに直接登録できるようになりました。

ただし現在のRailsでは、すべての環境で同一の暗号化済みファイルを使うようになっていたため、development環境とproduction環境で異なるキーを使おうとすると少々トリッキーになります。Rails 6ではこの点が環境ごとのcredential(#33521)によって最終的に解決されました。

私の作ったanyway_configでもcredentialをサポートしました。このgemを使うことで、アプリの設定データをさまざまなデータソース(credentialやYAMLファイルや環境変数)に直接アクセスせずに透過的に利用できます。

テーブル形式でないルーティング表示

アプリケーションのルーティングで頭を抱えたことのある方は、私の友人であるBenoitの新作rails routes --expandedをぜひお試しください(#32130)。

これであの邪魔っけなテーブル形式とおさらばです!

$ rails routes -g direct_uploads --expanded

Prefix            | rails_direct_uploads
Verb              | POST
URI               | /rails/active_storage/direct_uploads(.:format)
Controller#Action | active_storage/direct_uploads#create

Active Jobの小ネタ

Active Jobにもさまざまな改良が行われていて私の目を惹きつけました。

  • timezoneメタデータをジョブに追加することで、キューに入ったのと同じタイムゾーンでジョブを実行できるようになった(#32085)。ところで、ジョブ実行中は現在のロケールも保持されることをご存知ですか?
  • ジョブがキューに入った時刻を示すenqueued_atフィールドが新たにタイムスタンプとして追加された。これにより(私たちの作ったYabeda gemなどを用いて)パフォーマンス上きわめて重要な特性(ジョブがキューに入ってから実行されるまでの待ち時間)を測定できます。

Actionable Errorsによるエラー画面の操作性向上

最後は、私のもうひとりの友人であるGenadi Samokovarovが作った新機能で締めくくりたいと思います(#34788)。Genadiはweb-consoleの作者であり、Rails開発者をActionable Errors APIで幸せにしようとしています。

ブラウザ上でボタンをクリックするだけで、実行し忘れていたマイグレーションを実行できます

ブラウザ上でボタンをクリックするだけで、実行し忘れていたマイグレーションを実行できます

この機能は通常のRails例外ページにボタンを追加し、ブラウザのエラーページでマイグレーションを実行してActiveRecord::PendingMigrationErrorエラーを解決できるようにします。

カスタム例外にアクションを追加して機能を拡張するのも自由自在です!


ご覧のとおり、Rails 6には素敵な機能が山ほど盛り込まれています。こうした機能はさほどアナウンスされていませんが、名もないプルリクたちによって、皆さんが待ちに待った機能が実装されたり、あるいは既に愛用している有名な機能が強化されたりすることで、productionのRailsアプリが見違えるほど変わることもあります。

私見では、今回のRailsアップグレードは、特に長年に渡って成熟したプロジェクトから関心を寄せられると思います。正直に申し上げると、Rails 5が登場したときは、大量のRails 4コードベースをアップグレードするに足る理由が見当たりませんでした。Rails 6への移行は最終的に正しいものになると感じています。

お知らせ

スタートアップ企業をワープ速度まで加速すべく飛来した外宇宙のエンジニアたちに告ぐ: Evil Martiansのフォームまで連絡を乞う。

関連記事

Rails 5.2新機能を先行チェック!Active Storage/ダイレクトアップロード/Early Hintsほか(翻訳)

週刊Railsウォッチ(20190520-1/2前編)Evil Martians愛用の便利gemたち、render_asyncでRails表示を高速化、split gemでA/Bテストほか

0
0

こんにちは、hachi8833です。今回は何となくEvil Martians成分が多めになりました。

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

⚓お知らせ: 第11回公開つっつき会(無料)

第11回目を迎えた公開つっつき会は6月6日(木)19:30にBPS会議スペースにて開催されます。皆さまのお気軽なご参加をお待ちしております🙇

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

コミットリストから見繕いました。ドキュメントのタイポ修正が増えてきています。

⚓uniquenessバリデータでミスマッチするコレーション比較をdeprecate

これと次のPRは先週より前のものですが、後述のkamipoさん記事で取り上げられていたものです。

# activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb#L456
+     def default_uniqueness_comparison(attribute, value, klass) # :nodoc:
+       column = column_for_attribute(attribute)
+
+       if column.collation && !column.case_sensitive?
+         ActiveSupport::Deprecation.warn(<<~MSG.squish)
+           Uniqueness validator will no longer enforce case sensitive comparison in Rails 6.1.
+           To continue case sensitive comparison on the :#{attribute.name} attribute in #{klass} model,
+           pass `case_sensitive: true` option explicitly to the uniqueness validator.
+         MSG
+       end
+
+       super
+     end

Active Recordのuniqueness validatorはデフォルトでcase sensitiveな比較をするんですが、これが、文字列のデフォルトのcollationがcase insensitiveなMySQLと相性が悪く、DB上のUNIQUE制約と一致しない振る舞いだったりINDEXが効率よく使えずDBが死ぬみたいな問題を引き起こしていました。
Rails 6.0でDeprecatedになるActive Recordの振る舞い3つ - かみぽわーるより


つっつきボイス:「あー、uniquenessのバリデーションをRDBのコレーション設定に合わせるように変えるという話か」「Rails 6.1から実施されるんですね」「今まではRDBのコレーション設定と無関係にバリデーションしてたのか」「Rubyの世界でやると常にcase sensitive(大文字小文字を区別する)になるけど、コレーションによってはcase insensitive(大文字小文字を区別しない)なものがあるので」「@kamipoさんの記事で明示的にcase_sensitive: trueしてくださいとあるのはそういうことでしたか」「case sensitiveでやりたい人はね☺

「コレーションは元々RDB側の設定ですね」「collationって日本語が一応あるんですけど何て言うんでしたっけ…照合順序!」「あーそんな用語ありましたね、使わないけど😆

参考: MySQLのCollationを理解するためにまとめてみた。 - 6VOX

⚓レシーバのスコープがリークしている場合のクラスレベルのクエリメソッドの利用をdeprecate

# activerecord/lib/active_record/relation.rb#L69
    def new(attributes = nil, &block)
-     block = klass.current_scope_restoring_block(&block)
+     block = _deprecated_scope_block("new", &block)
      scoping { klass.new(attributes, &block) }
    end

つっつきボイス:「これも@kamipoさんのブログで言及されていました」「そっちを読む方が早い☺」「これ使ってたかも😅

(中略)scopeのメソッドチェインは条件が累積したrelationを伝搬させるのにクラスグローバルな状態を汚染する実装方法を現状とっており、scope定義内はその汚染されたクラスグローバルな状態の影響を受けるためです。
Rails 6.0でDeprecatedになるActive Recordの振る舞い3つ - かみぽわーるより

「今はunscopeしないとちゃんと動かないのか」「unscopeは使うけど、こういうケースで使ったことがあったかどうか🤔

⚓Actionable Errorsを追加

エラーをactionableにするには、ActiveSupport::ActionableErrorモジュールをincludeしてactionクラスマクロを呼び、そのアクションを定義する。アクションには名前と実行したいプロシージャが必要。名前はエラーページのボタン名として表示され、クリックするとそのプロシージャが呼び出される。
同PRより

先週公開したEvil Martiansの翻訳記事↓でも言及されていました。

Rails 6のB面に隠れている地味にうれしい機能たち(翻訳)


つっつきボイス:「あ〜なるほど、Actionable ErrorsはActive Supportに定義されているので実はActive Modelに限らず使えるのか: これは意外と便利かも😋」「Webのエラー画面が出たら[サポートに知らせる]ボタンを出すなんて使い方もありそう」「おや、erbとか使ってるし」「とするとActive SupportだけどRailsに依存するのかな?🤔

# actionpack/lib/action_dispatch/middleware/actionable_exceptions.rb#L3
+require "erb"
+require "action_dispatch/http/request"
+require "active_support/actionable_error"
+
+module ActionDispatch
+  class ActionableExceptions # :nodoc:
+    cattr_accessor :endpoint, default: "/rails/actions"
+
+   def initialize(app)
+     @app = app
+   end

⚓monotonic_subscribeを追加

# activesupport/lib/active_support/notifications.rb#L234
      def subscribe(*args, &block)
-       notifier.subscribe(*args, &block)
+       pattern, callback = *args
+       notifier.subscribe(pattern, callback, false, &block)
+     end
+
+     def monotonic_subscribe(*args, &block)
+       pattern, callback = *args
+       notifier.subscribe(pattern, callback, true, &block)
      end

-     def subscribed(callback, *args, &block)
-       subscriber = subscribe(*args, &callback)
+     def subscribed(callback, pattern, monotonic: false, &block)
+       subscriber = notifier.subscribe(pattern, callback, monotonic)
        yield
      ensure
        unsubscribe(subscriber)
      end

つっつきボイス:「ActiveSupport::Notificationsに追加されてる」「サブスクライブ系はタイミングをしっかりやりたいだろうから、そういう意図で追加したんでしょうね」「へー、こんなブロックになるのか↓」「何だかいっぱい付いてくる😆」「ぱっと見ブロック変数が多いかなと思ったけど、必要なものはこれで全部取れるのはいいですね☺

# activesupport/lib/active_support/notifications.rb#L44
  ActiveSupport::Notifications.monotonic_subscribe('render') do |name, start, finish, id, payload|
    name    # => 文字列(上の`above`のようなイベント名)
    start   # => monotonicな時刻(instrumentationされたブロックの実行開始時刻)
    finish  # => monotonicな時刻(instrumentationされたブロックの実行終了時刻)
    id      # => 文字列(発火したイベントのinstrumenterのユニークID)
    payload # => ハッシュ(ペイロード)
  end

ActiveSupport::Notificationsってそもそもどういうときに使うんでしょうか?」「ロガーとかinstrumentation(計測)みたいなpub/subで使われますね🧐」「あ、『Rails自身が持ってるpub/sub機能』だって以前教わったヤツでした😅」「それそれ🎯

参考: Active Support の Instrumentation 機能 - Rails ガイド
参考: 出版-購読型モデル - Wikipedia

「pub/subで便利そうな機能😋」「終わったら自動で消えてくれるかな?たぶんやってくれるだろう」

⚓非数値キーがある場合のstrong parametersを修正

Changelogにst0012さんの名前がありました。

# actionpack/lib/action_controller/metal/strong_parameters.rb#L226
+   class << self
+     def nested_attribute?(key, value) # :nodoc:
+       key =~ /\A-?\d+\z/ && (value.is_a?(Hash) || value.is_a?(Parameters))
+     end
+   end
...
-     def fields_for_style?
-       @parameters.all? { |k, v| k =~ /\A-?\d+\z/ && (v.is_a?(Hash) || v.is_a?(Parameters)) }
+     def nested_attributes?
+       @parameters.any? { |k, v| Parameters.nested_attribute?(k, v) }
+     end
+
+     def each_nested_attribute
+       hash = self.class.new
+       self.each { |k, v| hash[k] = yield v if Parameters.nested_attribute?(k, v) }
+       hash
      end
# actionpack/lib/action_controller/metal/strong_parameters.rb#L
-     def each_element(object)
+     def each_element(object, &block)
        case object
        when Array
          object.grep(Parameters).map { |el| yield el }.compact
        when Parameters
-         if object.fields_for_style?
-           hash = object.class.new
-           object.each { |k, v| hash[k] = yield v }
-           hash
+         if object.nested_attributes?
+           object.each_nested_attribute(&block)
          else
            yield object
          end
        end
      end

つっつきボイス:「普通にstrong parametersのバグ?」「あ〜、ネステッドでキーがnon-numericだった場合の挙動を修正したのか!」

参考: strong parameters — Action Controller の概要 - Rails ガイド

「これこれ、こういうパラメータ形式↓のときが問題だったと」「ははぁ、これか〜」「これいやらしいパラメータですよね😆: ハッシュのキーが012ときて全部数値になるかと思ったらnew_recordという数値でないヤツが入ってくる😇」「これはこういうパラメータを投げるフォームを書いた人がそもそも悪いのでは😅

# actionpack/test/controller/parameters/nested_parameters_permit_test.rb#L154
    params = ActionController::Parameters.new(
      book: {
        authors_attributes: {
          '0': { name: "William Shakespeare", age_of_death: "52" },
          '1': { name: "Unattributed Assistant" },
          '2': "Not a hash",
          'new_record': { name: "Some name" }
        }
      })

「こういう予想の斜め上を行くパラメータを投げられるとバグるというのはわかるな〜😭」「各行はハッシュの形式だけどキーに0とか来たときにRailsがarrayとして解釈していたのに、最後はどんでん返し😆」「結局パラメータを全部チェックしないとarrayにしていいかどうかというのは正確にはわからないというヤツなのでは😅」「なんといういやらしいパラメータ😤

「パラメータといえば、GW中にちょっと調べてBPS社内Slackにも書いたんですけど、こういうパラメータのフォーマットをCGIで一般的にどうやって解釈して展開するか、みたいなHTTP POST周りの仕様って、少なくとも自分が調べた限りではどうもなさそうなんですよね😇」「あ〜、それありそう!」「hoge[0]hoge[1]はたとえばarrayとして解釈する、みたいな仕様がありそうで見当たらない😢

「HTTPの仕様を追いかけた範囲では、キー(文字列)とバリュー(文字列)がアンパサンド&でつながる、ということしか規定されてなくて、ネストした配列やハッシュがプログラム側でどう展開されるべきか、という仕様は少なくとも見つからなかったんですよ…」

「Railsではこうやってる、ぐらいしか言えないとか?」「いえ、やってることはほぼすべてのCGIプログラムで基本同じなんですが、その仕様がないという🤪」「フレームワークで縛るしかないんでしょうか?」「それも不可能ですね: 誰がどんな形でPOSTしてくるかは縛りようがないので」「たしかに、上みたいなパラメータでもPOSTしていけないということはありませんし😆」「パラメータの解釈はプログラム側に委ねられていると😅: 極端に言えば開きかっこ(が閉じてなくたっていいわけですし🤣」「たしかに🤣」「『文字列だ文句あるか』と🤣」「後でパーサーがエラー吐くかもしれませんが☺

⚓Rails

⚓kamipoさん記事


つっつきボイス:「kamipoさんがこういう記事を書くのは珍しいかも」「3つのうち2つは上で見たので、3つ目はwhere.notがNORからNANDになる件」「ちょうど昨日BPSのSlackで、where.notのこれとはまた別の側面の挙動が話題になってましたね」

「ああ、where.notの項で言うネステッドスコープはTopic.toplevel.where(id: Topic.children.select(:parent_id))みたいなヤツで、そしてリレーションにクラスグローバルなステートがあるのか!」「つまり、スコープで評価されたその瞬間のステートがクラスグローバルになるから、unscopeしないと正しく取れない…」

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

# Works as expected.
Topic.toplevel.where(id: Topic.children.select(:parent_id))

# Doesn't work due to leaking `toplevel` to `Topic.children`.
Topic.toplevel.has_children

⚓anyway_config: さまざまな設定を透過的に扱うgem

# 同リポジトリより
# load data from config/my_app.yml, secrets.my_app (if using Rails), ENV["MY_APP_*"]
# MY_APP_VALUE=42
config = Anyway::Config.for(:my_app)
config["value"] #=> 42

# you can specify the config file path or env prefix
config = Anyway::Config.for(:my_app, config_path: "my_config.yml", env_prefix: "MYAPP")

これも以下の記事で紹介されていました。

Rails 6のB面に隠れている地味にうれしい機能たち(翻訳)


つっつきボイス:「このanyway_configもRailsのcredentialに対応したそうです」「秘密キーみたいな情報をcredentialやyamlや環境変数から透過的にアクセスできるそうです」「なるほど、ただこの種のツールは扱いに気をつけないとセキュリティの穴になる可能性があったりしますね⚠」「あー」「そうかも☺」「思わぬところから上書き差し込みされることがないとは言えないので、十分理解してから使わないとハマるかもしれません🧐

「たとえばですが、環境変数の設定を最優先するようになっている場合、仮に環境変数に外からインジェクトできるような脆弱性がもし発生すれば、もう何されてもおかしくないし😇」「うんうん」「configファイルに外から触れなくても、そうやって環境変数から攻撃される可能性があることは知っておくべき」「設定の優先順位も理解しておかないといけないんですね」

「でもデフォルトで環境変数を最優先するツールって割と一般的な気がするんですよね😅」「たしかにそういうの多いかも」「環境変数って起動時に読み込まれるものだから、起動するときに環境変数を優先するというのは直感的には理解できるんですけど、実は環境変数は割と書き換えることができてしまうので😆、攻撃の足がかりにされたり、そこまでいかなくても変数汚染されたりする可能性はあります」「自分は環境変数キライ: 汚染される可能性あるし😆

「環境変数がコンフリクトしないようにするためには、結局うんと長〜い名前を付けることになりますよね😆」「アンスコだらけの😆」「オレオレルールのプレフィックスとか😆」「名前空間という概念がないのでどうしてもしんどくなる😢

⚓Evil MartiansのレガシーRailsアプリ「テラフォーミング」

Evil Martiansの中の人がRailsConf 2019(ミネアポリス)で発表したスライドです。


つっつきボイス:「テラフォーミングと言っているのは、レガシーRailsアプリの単なるアップグレードにとどまらずに、この際やっておくべきことを全部やっておくという趣旨のようです」「なおTerraforming Marsというボドゲがあるらしいです👽」「なるほどEvil Martiansらしい👽

参考: テラフォーミング - Wikipedia

「目的は、さくっとgit cloneしてさくっとrails consoleできるようにしたいと☺」「わかるわかる☺」「『手順書でひたすら頑張る』オールドスクールなやり方から、docker-compose --buildするニュースクールなやり方に移行する」「今のBPS Webチームも新しいプロジェクトはdocker-composeに移行していますし😆」「理想は理想だけど、やれるかどうかは案件の複雑さにもよるんですよね😅

「そしてログイン情報の生書きをやめると↓😆」「この辺なかなか参考になりますね👍」「ログイン情報は環境変数から引っ張ってdocker-compose.yamlに設定するようにすれば、本番とまったく同じconfigが使えるようになる、と」「うんうん😋


「Railsのconfigもこんなふうに、本番かローカルかで挙動を変えるようにしておくと↓」「こんなふうに、本番環境と開発環境でconfigファイルを書き換えずに済むようにできるのは理想ですね🥰

「それから、環境変数やHerokuのconfigが設定がこんなふうに長くなる↓問題😇」「Envヘル!👹 」「🤣」「🤣」「そこでさっき登場したanyway_configを使うと、既にconfigファイルになってしまった設定であっても、特に頑張らずにdocker-compose.yamlのENVに渡せるようになるよ、という流れ」「ははぁ〜なるほど!」「anyway_configを作ったモチベーションはそこにあったのね: 気持ちはとてもよくワカル☺

「スライドはそこから先もSidekiqやらFactoryBotやらを改善したりテストを高速化したりしてますね」「TestProf gemが見えた↓」

TestProf: Ruby/Railsの遅いテストを診断するgem(翻訳)

「factoryのlinterですか」「こう書く方がいいよ、みたいなサジェスチョンでしょうか?」「あ、これはFactoryBotのバージョンが上がって古い書き方が使えなくなったというチェックだ」「なるほど、そっちですか」「これを自動でやってくれるなら悪くなさそう😋

「他にもbundler-auditを使ったりしてますね」「ぎょぎょ、.rubocop_strict.yml使ってるし😅」「聞くからにめちゃシバかれそう😣」「テストで使われているgemをトラックするGem Trackerですって」

「使われていないテンプレートを見つけられるTemplates Tracker!」「この辺かなり参考になるな〜❤」「あとはどこまで信用するかですが😆」「may be unusedですって😆」「まあ本当に使われていないのかどうかについては動かさないとわかりませんけどね☺」「Javaならはっきりわかるけど、Rubyですから☺

「ほほー、tracerouteは使われていないルーティングを見つけてくれるヤツか」「これamatsudaさんのgemだ!」

「学びが凝縮されたいいスライド!👍」「これはいいですね😍


後で気が付きましたが、発表で使われているツールなどは以下にすべてまとまっています。

⚓Postman: APIサーバーのデバッグ/自動テストツール


同サイトより


つっつきボイス:「APIを叩いたりテストを自動で回したり、ちょっと値を変えて試したり、みたいな、Chromeのツールとかにもあったりするヤツみたいですね」「そんな感じのようです」「こういうツールは、どれかひとつを使えていればいいのかなと思います☺

「こういうツールで重要なのは、作ったAPI定義ファイルを他の人と共有できること」「あ、たしかに」「それができていれば、最悪でもテスト用のconfigデータを共有すればまったく同じAPI POSTのテストができますし🧐」「このPostmanはオンラインでやるタイプのツールですね」

参考: APIの開発がむちゃくちゃ捗る「Postman」の使い方 - WPJ

⚓ポーリングをJSなしで行う

render_asyncでやれるそうです。

Rails: render_async gemでレンダリングを高速化(翻訳)


つっつきボイス:「まず、コントローラでrender partialして、そのパーシャルの中にpromiseでフェッチするコードを書くというのが普通のやり方であると」「ふむふむ」

# 同記事より
# app/controllers/movies_controller.rb
class MoviesController < ApplicationController
  # somewhere inside Movies controller
  def rating
    @rating = @movie.rating

    render partial: 'movie_rating'
  end
end
<!-- app/views/movies/show.html.erb -->

<div id="rating">Loading rating...</div>

<script>
  const checkRating = () => {
    fetch("<%= movie_rating_path(@movie) %>")
      .then(response => response.text())
      .then(response => {
        document.getElementById("rating").innerHTML = response;
      });
  };
  setInterval(checkRating, 2000);
</script>

「それがrender_asyncを使うとこう書けると↓: ああ、たしかに」

<!-- app/views/movies/show.html.erb -->

<%= render_async movie_rating_path(@movie), interval: 2000 %>

⚓render_asyncの使いどころ

「ちなみにrender_asyncはRubyのスクリプトでありながら、内部的にはJavaScriptで取ってきてそこに挿入するというようなことをやります」「上のpromiseとAjaxで取ってきてレンダリングするのと内容は同じ」「誰もがやりたくなるヤツですね☺

「render_asyncは一見ちょろいんですが、ものすごく重いサイトを手軽に軽くするのにとても便利😋」「render_asyncはもっと評価されるべき」

「もっと言うと、ページのDOM構造なんかをきちんと設計したうえでrender_asyncを使えば、HTMLを全部CDNに乗せることができます」「おぉ〜」「たとえば、ページの中でキャッシュ可能でない特定の部分をすべてrender_asyncで表示すれば、それ以外のキャッシュ可能なコンテンツは全部CDNに乗っていてもよくなります」「なるほど!」「つまり表示がすごく速くなるという圧倒的なメリットを得られる✈

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

「ただ最初からそういうふうに設計されていないと、CDNのキャッシュが効くかどうかドキドキしながらCloudFrontなんかを設定することになりますが😆」「😆」「render_asyncを後から入れるのは大変なんでしょうか?」「たとえCDNを使わないとしても、1ページあたりのレイアウトコンポーネント数が増えすぎたときにrender_asyncで表示を速くできるので、一定のメリットはあります🧐

「よくあるランキングページで考えると、たとえばページにある特定のコンポーネントの表示が非常に遅くて、レンダリング時間の半分がそのコンポーネントに取られているといった場合に、そのコンポーネントだけrender_asyncで表示すれば、そこだけ遅延読み込みになるので結果として速くなる」「ふむふむ」「なので後から入れる手もあります😋

「でもさっきも言ったように、render_asyncのパワーを最大限に発揮するにはページの設計時点から意識することですね: ユーザーごとに表示内容が違う動的な部分は全部render_asyncにするとか、フォームもrender_asyncにするならCSRFが必ずユーザーごとに分かれるようにするとか、やるならそこまでやらないと😎」「いいですね!😋」「そこまでやれれば、RailsアプリでありながらCDNにがっつり乗せて高速化できます❤

RailsのCSRF保護を詳しく調べてみた(翻訳)

⚓StimulusJSでネステッドフォームを作る(Ruby Weeklyより)

<!--同screencastより-->
  <h1>Tasks</h1>
  <div data-controller="nested-form">
    <template data-target='nested-form.template'>
      <%= form.fields_for :tasks, Task.new, child_index: 'TEMPLATE_RECORD' do |task| %>
        <%= render 'task_fields', form: task %>
      <% end %>
    </template>

    <%= form.fields_for :tasks do |task| %>
      <%= render 'task_fields', form: task %>
    <% end %>

    <div data-target="nested-form.add_item">
      <%= link_to "Add Task", "#", data: { action: "nested-form#add_association" } %>
    </div>
  </div>

つっつきボイス:「一部で流行っているという噂のStimulusJS😆」「DHHのいるBasecampが推しているのがこれですね」「へ〜」

Turbolinksとの相性もいいと謳っています。


同サイトより

⚓split: レポート機能付き、RackベースのA/Bテスト


つっつきボイス:「まあA/Bテストは何で動かしてもいいですけど😆」「ははぁ、テストをRackに挟んでおけて、かつRack変数をよしなに参照して切り替えられるという感じか」

<!--同リポジトリより: ビューの場合-->
<% ab_test(:login_button, "/images/button1.jpg", "/images/button2.jpg") do |button_file| %>
  <%= image_tag(button_file, alt: "Login!") %>
<% end %>
# 同リポジトリより: コントローラの場合
def register_new_user
  # 無料ポイントをいくらにすれば購入するユーザーが最大化されるかをチェック
  @starter_points = ab_test(:new_user_free_points, '100', '200', '300')
end

「ところでRackに入れるメリットって…?」「Rackの手前にA/Bテストを置けばどこでも使える、とか?」「ビューでA/Bテストするならクックパッドのchanko gemとか使えばできるし、と思ったけどchankoもコントローラに置けるし」「ミドルウェアで変数を参照したいとき、かな?🤔

「お〜、split gemの方はレポート機能があるのか↓」「これはエライ!😋」「エライ!😍」「ちゃんと実行計画を立ててイベントフックを書いておけば、実データでこういうレポートを生成できるのはありがたい🙏


splitrb/splitより

# 同リポジトリより: イベントフック
Split.configure do |config|
  config.on_trial  = :log_trial # すべてのトライアルで実行
  config.on_trial_choose   = :log_trial_choose # 新規ユーザーのみで実行
  config.on_trial_complete = :log_trial_complete
end

def log_trial(trial)
  logger.info "experiment=%s alternative=%s user=%s" %
    [ trial.experiment.name, trial.alternative, current_user.id ]
end

def log_trial_choose(trial)
  logger.info "[new user] experiment=%s alternative=%s user=%s" %
    [ trial.experiment.name, trial.alternative, current_user.id ]
end

def log_trial_complete(trial)
  logger.info "experiment=%s alternative=%s user=%s complete=true" %
    [ trial.experiment.name, trial.alternative, current_user.id ]
end

「A/Bテストをサーバーサイドでやりたいならsplit gemはよさそう👍」「まあサーバーサイドでやるのか、それともGoogleタグマネージャ的なツールを使うのかは悩ましいですけど😅」「サーバーサイドがからむA/Bテストはこういう形でないとできなさそうではあるし」

「ちなみに同じURLにGoogleのボットがアクセスしたときだけ別の画面を出す(クローキング)みたいなことをするとペナルティでランク下げられます😎」「でしょうね〜😆」「ボット相手に見た目を取り繕うのはアカンと😆

参考: ABテストでペナルティ?/Googleが警鐘
参考: クローキング - Search Console ヘルプ

⚓その他Rails


つっつきボイス:「PrometheusでやるヤツはRubyKaigi 2019でもやってましたね」

こちらも↓Evil Martiansでした😳

「2本目は、Active Recordを長らくやってたコアコミッターのSean Griffinさんが、ShopifyとRailsから同時に離れることになったという、ご本人からのお別れメッセージでした」「ありゃ〜」「ActiveRecord::Attributesの人だ」「おおそういえば!」「あの大人気のライブラリですね」

Rails5: ActiveRecord標準のattributes API(翻訳)

「そしてSeanさん、今度はRustでORMやるのか!」「その名もDiesel」

「このスクショも、OKを押してリポジトリのアクセス権を手放す前に記念で撮っておいたんでしょうね」「気持ちわかる」「もらい泣きしそう😭


同記事より

⚓Ruby

⚓「Rubyのunlessは使いたくない」


つっつきボイス:「『unlessifより文字数が多いから』使わないって言ってる?」「7文字の条件文がunlessだと10文字になると」「design mistakeとは言ってますが😆」「でも自分はunlessが不要とまでは思わないな〜」

「そもそもこの書き方↓は前からよくないって言われてるんだし、今更すぎる感😆」「うん、これはだめでしょ😆」「if !で書いてもわかりにくいし😆

destroy(thing) unless open

Ruby: 紛らわしい条件文を書かないこと(翻訳)

「『タイムマシンがあったらunlessを消し去りたい』と🕙」「上の書き方がよくないということについては同意するけど、unlessそのものがよくないとまでは思わないし☺」「unlessの設計上の弱点を問題にするというか」「そういう書き方ができてしまうという意味ではdesign mistakeなのかも🤔」「まあこれ をパーサーレベルで禁止するのは結構難しそうだし」「でしょうね〜」

「そういうのを叱ってくれるのがRubocopなんだし👮🏼‍♀️」「『条件が複数の場合はunlessを使わない』とかunless !はダメとか🤣」「怒られる前に自分がわけわからなくなる〜🤣」「unlesselseが付けられないなんてのもありましたね↓」「そこはちゃんとできないようになってるし☺」「あったらコワい🥶

Ruby: unless式にはelsifを書けない

⚓Rubyの同一性チェックとitselfHacklinesより)

# 同記事より
[3, 1, 2, 1, 5].group_by(&:itself)
# => {3=>[3], 1=>[1, 1], 2=>[2], 5=>[5]}

Ruby: Kernel#itselfの美学(翻訳)


つっつきボイス:「itselfっていうキーワードがあることを忘れてました😅」「itself好きな人いますよね☺」「自分itself好き〜❤: 上みたいなgroup_by(&:itself)とかよく使うし」

参考: instance method Object#itself (Ruby 2.6.0)

itselfってどういう使いどころがあるんでしたっけ?」「ん〜何かのメソッドに似てるんだけど…そうそう、tapと似てる!」「自分を返すメソッドをgroup_byと組み合わせるとそういう感じになって、上みたいに『この要素はいくつあるか』というカウントも取れる」

参考: instance method Object#tap (Ruby 2.6.0)

self を引数としてブロックを評価し、self を返します。
docs.ruby-lang.orgより

itselfってそれだけ見ると何に使うの?という感じなんだけど、組み合わせて使うと上みたいなのが一文で書けるんですよね😋」「そういえば英語記事の人も同じこと言ってますね」「自分も以前のウォッチでitselfを見たときにピンとこなかったけど、その後group_byと組み合わせるのを見てなるほど〜って思えたし😍」「itselfはメソッドチェーンを短く書けるというメリットはありますね: 自分は使いませんが😆」「😆」「まあ言われてみればitselfを使う方が美しいといえば美しいかも✨

itselfがツボにハマるところってそんなに多くはなさそう?🤔」「tapもそういうところあるかも😆」「自分はRailsコンソールで値をさっと見たいときにtap使ったりしてます😋」「tap、自分は使わないし😆: 別にtapじゃなくても書けるんじゃね?って」「覚えるとつい使いたくなる魔力があるのかも🧙🏼‍♂️」「yield_self↓もうれしくてつい使っちゃったり😆」「今はthenになったんでしたっけ?」「エイリアスだからどちらも使えます☺

Ruby 2.5の`yield_self`が想像以上に何だかスゴい件について(翻訳)

⚓Gem Check: gemを作るときのチェックリスト


同サイトより


つっつきボイス:「kazzさんが『gem作ろうかな』と言ってたので」「そうそう、アプリの外部APIへのアクセスをgemに切り出しとくといいよって話を聞きまして😆」「上のサイトにはチェックボックスも付いてます😋」「.gemspecって正しく書かれていて欲しいと常日頃思っていても、自分で正しく書こうとすると割と大変だし😅

st0012さんが以前『gemをGitHubに置くと.gemspecとGitHubの情報の一部が重複する』って言ってました」「そうそう、心配だから.gemspecに多めに書いておくとかぶっちゃったりとか😢

「それもあって、自分で.gemspec書くときはできるだけgem dependencyがないようにしますね😆」「でないとバージョン違いでgem dependencyがかぶって、それはもう悲惨なことに😇」「あ〜」「それはコワい😱」「なのでdependencyはできるだけ避けて、Rails用のgemならRailsにdependするとかしないとね」

⚓その他Ruby


つっつきボイス:「最初の2つはRubyKaigi 2019の報告で、1つはまたしてもEvil Martiansです😆

「3つ目は例のbootstrap-sassの件に関連して、フィンガープリントをチェックしようよという記事です」「GPGがツールでチェックされているときとか、sshのknown_hostsとかに初めて追加されるときなんかは見ますね: あれは使われたことのないキーが使われるときにチェックされるので」「ですね☺」「いつも使ってるサーバーなのにknown_hosts追加のメッセージが出たらさすがに緊張感走るし、見落としたらいけないヤツですし🧐

参考: GNU Privacy Guard - Wikipedia
参考: OpenSSH known_hosts ファイルの使用 | SSH Tectia® Client 6.0

セキュリティ: 3月26日のbootstrap-sass gem 3.2.0.3に危険なコードが含まれていた(影響を受けるサイトは少ない可能性)


つっつきボイス:「2006年のRailsConfのスケジュール表をたまたま拾って、ずいぶんぎっしりあるんだなと思ったので」「10年以上前か〜」「capistranoとか、全体にデプロイの話が多いっすね」「13年前だとDockerとか影も形もない頃だし」「それ以前にDockerが動くカーネルすらない時代🤣」「そっか🤣」「仮想化はあったんでしょうけど」「cgroupぐらいはあっただろうけど、コンテキストのスイッチまでできたかどうか」

参考: 第3回 Linuxカーネルのコンテナ機能[2] ─cgroupとは?(その1):LXCで学ぶコンテナ入門 -軽量仮想化環境を実現する技術|gihyo.jp … 技術評論社


つっつきボイス:「こういう人にスポットライトが当たるのがRuby界隈のいいところですね😍」「それはありますね☺」「こうやって裏で支えている人がいなかったら安定して使えなくなるわけですし」

⚓Ruby trunkより

⚓提案: OptionParser::ACは外してもよいのでは

参考: Class: OptionParser::AC (Ruby 2.6.1)


つっつきボイス:「optparseかと思ったら、optparseに含まれているACというライブラリは誰も使ってないしドキュメントもないから外そうということでした」「issueに書かれている--enable-fiber-coroutine=amd64というオプションがマニアック😆

「たまに素のRubyスクリプトを書くときにoptparseはやっぱり便利ですね: 一度使ってみるとよさがワカル👍」「以前のウォッチでもoptparseがいいという話ありましたね↓」「optparseはいいですよホント❤

週刊Railsウォッチ(20180518)Paperclip開発終了、RailsアプリをGDPR準拠に、optparseはやっぱりいい、Rubyの`super`ほか


前編は以上です。

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

週刊Railsウォッチ(20190513-1/2前編)6.0の地味に嬉しい機能、ActiveModelエラーの扱いが変更、Railsのリクエスト/レスポンスをビジュアル表示ほか

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

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

Rails公式ニュース

Ruby Weekly

RubyFlow

160928_1638_XvIP4h

Hacklines

Hacklines

週刊Railsウォッチ(20190527-1/2前編)RuboCopが3分割へ、Railsリクエストのライフサイクル、Opal 1.0、Railsベンチマーク、Rubyパターンマッチングの現状ほか

0
0

こんにちは、hachi8833です。Macbook Pro 2019をポチりそうになったのを危うく踏みとどまりました。

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

⚓お知らせ: 第11回公開つっつき会(無料)

第11回目を迎えた公開つっつき会は6月6日(木)19:30〜にBPS会議スペースにて開催されます。皆さまのお気軽なご参加をお待ちしております🙇

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

今回は公式の更新情報から見繕いました。

⚓システムテストにTrixエディタ入力ヘルパーを追加

# 同PRより
# <trix-editor id="message_content" ...></trix-editor>
fill_in_rich_text_area "message_content", with: "Hello <em>world!</em>"
# <trix-editor placeholder="Your message here" ...></trix-editor>
fill_in_rich_text_area "Your message here", with: "Hello <em>world!</em>"
# <trix-editor aria-label="Message content" ...></trix-editor>
fill_in_rich_text_area "Message content", with: "Hello <em>world!</em>"
# <input id="trix_input_1" name="message[content]" type="hidden">
# <trix-editor input="trix_input_1"></trix-editor>
fill_in_rich_text_area "message[content]", with: "Hello <em>world!</em>"

つっつきボイス:「fill_in_rich_text_areaが入った」「with:に任意のHTMLでテキストを渡せるようになったんですって」「Action TextというかTrixはまだ使ったことないけど、チャンスがあれば使うかなぐらいの気持ち☺

⚓ActiveRecord#respond_to?の文字列アロケーションをやめて1.5倍高速化

# 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 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
# activerecord/lib/active_record/model_schema.rb#L391
+     def symbol_column_to_string(name_symbol) # :nodoc:
+       @symbol_column_to_string_name_hash ||= column_names.index_by(&:to_sym)
+       @symbol_column_to_string_name_hash[name_symbol]
+     end

つっつきボイス:「高速化なので@kamipoさんかと思ったら@schneemsさんだった☺」「上のcase文を汎用化して不要にしたということか」「elseの後のname = name.to_sとかはsymbol_column_to_stringの方でやってるのと同じだし」「リファクタリングですね」

「ところで、この辺の書き方↓Rubocopに怒られそうではある👮」「あは、代入のシングルイコール=を条件で使ってるし😆

+      if name = self.class.symbol_column_to_string(name.to_sym)

「ダブルイコール==のつもりだったのを間違えたのかと一瞬思っちゃうし😆」「とはいえ、このイディオムは高速化のためには有効だったりしますね☺」「まあこのぐらいだったら許容範囲と思うし」「ビジネスロジックでは使って欲しくないけど、ライブラリコードの最適化ならしゃーない」

⚓S3に5GBより大きいファイルをアップロードできるようになった

# activestorage/lib/active_storage/service/s3_service.rb#L8
module ActiveStorage
  class Service::S3Service < Service
-   attr_reader :client, :bucket, :upload_options
+   attr_reader :client, :bucket
+   attr_reader :multipart_upload_threshold, :upload_options

    def initialize(bucket:, upload: {}, **options)
      @client = Aws::S3::Resource.new(**options)
      @bucket = @client.bucket(bucket)

 +    @multipart_upload_threshold = upload.fetch(:multipart_threshold, 100.megabytes)
      @upload_options = upload
    end

    def upload(key, io, checksum: nil, content_type: nil, **)
      instrument :upload, key: key, checksum: checksum do
-       object_for(key).put(upload_options.merge(body: io, content_md5: checksum, content_type: content_type))
-     rescue Aws::S3::Errors::BadDigest
-       raise ActiveStorage::IntegrityError
+       if io.size < multipart_upload_threshold
+         upload_with_single_part key, io, checksum: checksum, content_type: content_type
+       else
+         upload_with_multipart key, io, content_type: content_type
+       end
      end
    end

つっつきボイス:「ほほー、ついにマルチパートアップロードに対応!」「マルチパートがポイントなんですね」

「AWSのS3には元々マルチパートアップロードモードというのがあるんですが、何もしないとシングルモードになります」「シングルモードは文字通り1つのHTTP PUTで投げるんですけど、その場合の最大サイズが5GBです」「なるほど!」「それ以上のサイズでアップロードしたいときは複数のHTTP PUTで投げるマルチパートアップロードを使うことになるんですけど、この改修はそれに対応したということですね🧐」「コミットにもマルチパートって書いてありますね」

参考: マルチパートアップロードの概要 - Amazon Simple Storage Service

「100MB以上の場合は自動的にマルチパートモードになるんですね」「5GBとあるのはあくまでシングルモードの場合の上限ですし、それ以下の場合でもマルチパートモードの方が帯域を有効に使えます☺」「つか今まで対応してなかったんかい😆」「😆

「マルチパートアップロードはかなり大事: 実際、S3にでかいファイルをどっかん置くとか割とやりますし(ホームディレクトリをtarで固めて投げたりとか)、マルチパートアップロードができなかったらかなりつらいし、しかもアップロードでさんざん待たされた後で終わった頃にやっと失敗してたことがわかったり😇」「あるある〜🤣

「だからこの辺の制約は知っておくべき☺: AWS SDKとかAWS CLIのコマンドでやる場合は自動的にマルチパートアップロードになってくれるからいいんですけど、S3 APIに直接HTTP PUTするようなコードを自分で書くときなんかは注意が必要⚠」「たしかに〜」

⚓HashWithIndifferentAccess#initializeのパフォーマンスを改善

# activesupport/lib/active_support/hash_with_indifferent_access.rb#L52
    def initialize(constructor = {})
      if constructor.respond_to?(:to_hash)
        super()
        update(constructor)

-       hash = constructor.to_hash
+       hash = constructor.is_a?(Hash) ? constructor : constructor.to_hash
        self.default = hash.default if hash.default
        self.default_proc = hash.default_proc if hash.default_proc
      else
        super(constructor)
      end
    end

つっつきボイス:「HashWithIndifferentAccessの更新は久々に見た気がする」「is_a?(Hash)してる?」「あ〜to_hashがめちゃ重くなることがあるから、しなくていい場合はto_hashをやめたのか」「わかりみ〜😋」「中身の構造にもよるけどto_hashが重くなることあるし」

「プルリクに貼られているGist↓でもでかいハッシュ作って試してる👀」「HWIAにどでかいものを渡すことがどれぐらいの頻度であるのかにもよりそうですが😆、速くなるのはいいですね」

⚓mailbox_forをpublicに

# actionmailbox/lib/action_mailbox/router.rb#L23
    def route(inbound_email)
-     if mailbox = match_to_mailbox(inbound_email)
+     if mailbox = mailbox_for(inbound_email)
        mailbox.receive(inbound_email)
      else
        inbound_email.bounced!
        raise RoutingError
      end
    end

+   def mailbox_for(inbound_email)
+     routes.detect { |route| route.match?(inbound_email) }.try(:mailbox_class)
+   end

    private
      attr_reader :routes

+     def match_to_mailbox(inbound_email)
+       routes.detect { |route| route.match?(inbound_email) }.try(:mailbox_class)
+     end
  end
end

つっつきボイス:「いかにもAction Mailboxですね」「メソッド名も変更されてる」「メールのルーティングもroutesで扱うというのは言われてみればごもっとも☺

ApplicationMailboxの設定済みルーティングシステムへのエントリポイントとして現在公開されているのはrouteへの呼び出ししかなく、受信メールを完全にprocessする処理が大量に走ってしまう。テストなどで、そうした処理を行わずに、メールがどのメールボックスにルーティングされるかをチェックする手段があるとよさそう。
これにインラインドキュメントをもっと追記して、どういうメソッドをpublicにしてサポートするか、どういうメソッドではそれをやらないかという基準を示しておくとありがたみが増すと思われる。「その価値あり」と皆さんに賛成してもらえるなら、このPRか別のPRで自分がドキュメントを追加してもよい。
同PRより大意

⚓Rails

⚓RailsをAWS->GCPに移行


つっつきボイス:「この間BPSの社内Slackに貼っていただいたヤツです」「これかなり面白かった😋タイトルは釣り記事っぽかったけど読めば読むほど『こ、この会社…大変だったんだな』って苦労が偲ばれる😆」「何しろ元のRailsアプリを作った人もいなくなっててチームもほぼ解散状態で誰にも質問できないところからのスタート😇」「そこまで!😇」「はにゃーん😇」「CTOやRailsエンジニアが全員退職…😇」「『よくしらんRailsアプリ』ってそういう意味でしたか!」「『全社的な退職のビッグウェーブ』って🤣」「もはや災害レベル🤣


同記事より

「それにしてもよくここまで頑張ったと思うし」「マジで偉業!」「こういう究極の案件を1人で回して切り抜けるとめちゃめちゃ知見がたまります☺

⚓Railsリクエストのライフサイクル(Ruby Weeklyより)


つっつきボイス:「これは次のRailsConf 2019のセッションのスライドと記事だそうです」「なるほどなるほど、DNS lookupから始まってRackミドルウェアを通ったりしてRailsのリクエストをひととおり辿ると: これはとてもためになる👍」「採用面接で志望者にこれをひととおり説明してもらったりしますね😆」「この間のRails Traceにも通じそう」

「四六時中Railsのコードを追いかけている人ならこのスライドの内容は自明ですけど、Railsをしばらく触っていなかった人とか、Rails一応触っているけど中で何が起こっているか今ひとつわかってない人にはうってつけの教材😋」「エンジニア全員とまではいかなくても、Railsをメインで触っている人ならこのスライドで説明されていることぐらいは知ってて欲しいです☺」「同じ内容が元記事でも読めますし」「Railsチュートリアルをやったら次に学んでおきたい感じですね😃

⚓RailsConf 2019のセッション


同サイトより


つっつきボイス:「日本ではGW中にミネアポリスで開催されたRailsConf 2019のタイムテーブルです」「スロット7つで3日間!」「これをまともに追うのは大変すぎ😅」「6月の公開つっつきのときに、もう少し絞り込んでおこうと思います」

⚓Railsのシンプルなベンチマーク(Ruby Weeklyより)

TechRachoでパフォーマンス関連の記事を何度か翻訳させてもらっているNoah Gibbsさんの記事です🙇

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


つっつきボイス:「特にこういうPumaとかコンカレンシーとかのベンチマークって、やっぱり自分で書くしかないんですよね: ベンチマークソフトに丸投げしてストンと結果が出るなんてことはまずないので☺」「先週のウォッチの話にも通じますね」

「Pumaやコンカレンシーのベンチマークをやるということは、つまりスレッド周りとかGILとかをチェックすることになるし、そういう下のレイヤの中身を理解した上でコードを書かないといけないから、しんど〜😭

参考: グローバルインタプリタロック - Wikipedia


同記事より

「こんなにみっちり取ってる↑😳」「ああ、こういうのとても大事: 結構前にUnicornとかPumaはどういうパラメータが適切なのかというのが話題になりましたけど、こういうのはCPUの種類やカーネルのバージョンなどによっても変わってくることを知っておくべき」「うんうん」「シビアに考えるのであれば、ベンチマークはあくまで特定のスペックのサーバーでの結果であるということですね🧐

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

「ところで、こんな感じのベンチマークって取ったりします?」「バージョンごとのベンチマークまではやったことないです〜」「インスタンスタイプを変えて最適なものがどれかを試したりは?」「それもやってないかな〜😅」「自分はスループットチェックはたまにやってますけど、結局自分で取るしかないし、ダルいので自動化したりします😆

「記事ではRSBというベンチマークgemを新たに書き下ろしたそうです↓」「改めて思うんですけど、ちゃんとしたベンチマークを書くのって、OSを正しく理解しておかないといけなくてとても難しい😭

「こういう必要なパラメータ↓渡して一発でベンチマーク回せるようにしてるのって、すげっ!」「複数バージョンのRubyで一気に回せるようになってるし💪」「ウォームアップ時間もちゃんと指定できるようになってるし❤」「これらのパラメータはしっかり押さえてあるということですね」「ウォームアップはめちゃめちゃ重要」「ウォームアップしないベンチマークはマジで意味ないし」「ウォームアップに実際にかかった時間もチェックしておきたいし」「たしかに〜」「もっと言えば再起動から始めるとかありますし」

RSB_NUM_RUNS=10 RSB_RUBIES=”2.6.0 2.4.5 2.0.0-p0″ RSB_DURATION=180 RSB_WARMUP=20 RSB_FRAMEWORKS=rack RSB_APP_SERVER=puma RSB_PROCESSES=1 RSB_THREADS=1 ./runners/rvm_rubies.rb
同リポジトリより

「しかしよくこれだけ網羅してますね」「そこがベンチマークを自作したくない理由🤣: パラメータ入れ忘れそう」「🤣」「🤣

「Noah Gibbsさんはコンピュータサイエンスをきちんと押さえている人なので、彼のベンチマークコードはめちゃめちゃ勉強になります❤」「何度か翻訳させてもらいましたけどホント濃厚でした😅」「そこはどうしても濃厚にならざるを得ませんね〜: 下のレイヤを正しく理解してないと、まったく違う結果を出してしまうことすらあるので😇

⚓ route_translator: Railsのルーティングを多言語化(Ruby Weeklyより)

# 同リポジトリより
        Prefix Verb   URI Pattern                     Controller#Action
    admin_cars GET    /admin/cars(.:format)           admin/cars#index
               POST   /admin/cars(.:format)           admin/cars#create
 new_admin_car GET    /admin/cars/new(.:format)       admin/cars#new
edit_admin_car GET    /admin/cars/:id/edit(.:format)  admin/cars#edit
     admin_car GET    /admin/cars/:id(.:format)       admin/cars#show
               PATCH  /admin/cars/:id(.:format)       admin/cars#update
               PUT    /admin/cars/:id(.:format)       admin/cars#update
               DELETE /admin/cars/:id(.:format)       admin/cars#destroy
       cars_fr GET    /fr/voitures(.:format)          cars#index {:locale=>"fr"}
       cars_es GET    /es/coches(.:format)            cars#index {:locale=>"es"}
       cars_en GET    /cars(.:format)                 cars#index {:locale=>"en"}
               POST   /fr/voitures(.:format)          cars#create {:locale=>"fr"}
               POST   /es/coches(.:format)            cars#create {:locale=>"es"}
               POST   /cars(.:format)                 cars#create {:locale=>"en"}
    new_car_fr GET    /fr/voitures/nouveau(.:format)  cars#new {:locale=>"fr"}
    new_car_es GET    /es/coches/nuevo(.:format)      cars#new {:locale=>"es"}
    new_car_en GET    /cars/new(.:format)             cars#new {:locale=>"en"}
   edit_car_fr GET    /fr/voitures/:id/edit(.:format) cars#edit {:locale=>"fr"}
   edit_car_es GET    /es/coches/:id/edit(.:format)   cars#edit {:locale=>"es"}
   edit_car_en GET    /cars/:id/edit(.:format)        cars#edit {:locale=>"en"}
        car_fr GET    /fr/voitures/:id(.:format)      cars#show {:locale=>"fr"}
        car_es GET    /es/coches/:id(.:format)        cars#show {:locale=>"es"}
        car_en GET    /cars/:id(.:format)             cars#show {:locale=>"en"}
               PATCH  /fr/voitures/:id(.:format)      cars#update {:locale=>"fr"}
               PATCH  /es/coches/:id(.:format)        cars#update {:locale=>"es"}
               PATCH  /cars/:id(.:format)             cars#update {:locale=>"en"}
               PUT    /fr/voitures/:id(.:format)      cars#update {:locale=>"fr"}
               PUT    /es/coches/:id(.:format)        cars#update {:locale=>"es"}
               PUT    /cars/:id(.:format)             cars#update {:locale=>"en"}
               DELETE /fr/voitures/:id(.:format)      cars#destroy {:locale=>"fr"}
               DELETE /es/coches/:id(.:format)        cars#destroy {:locale=>"es"}
               DELETE /cars/:id(.:format)             cars#destroy {:locale=>"en"}
    pricing_fr GET    /fr/prix(.:format)              home#pricing {:locale=>"fr"}
    pricing_es GET    /es/precios(.:format)           home#pricing {:locale=>"es"}
    pricing_en GET    /pricing(.:format)              home#pricing {:locale=>"en"}

つっつきボイス:「↑こんな感じでルーティングを多言語化するそうです」「ははぁ、URLのSlugを多言語化するということか!」「Slugのローカライズって必要なんでしょうか?」「SEO的には欲しいヤツですね☺: CMSの代わりにRailsを使うとこういう感じになりそう」「Rails標準のi18nではここまでできないんでしょうか?」「Slugはi18nとは別の機能なので😆

参考: Slug (スラグ) - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN

「enが英語のcarでデフォルトか」「ということはvoituresがフランス語、cochesがスペイン語でそれぞれ自動車という意味なんでしょうね」「ヴォアチュ?」「ヨーロッパではこういう機能欲しいでしょうね」「ここまでやるか〜?とは思いますけど😆

「ということはSlugに日本語も置ける🤣」「やめて〜🤣

「そもそもこういうことするとめちゃルーティングが増える😇」「やりたい気持ちはワカル☺

⚓logidze: データベース変更ロガー(Ruby Weeklyより)

現時点ではPostgreSQL 9.5以降のみをサポートするそうです。


同リポジトリより


つっつきボイス:「Evil Martiansの人のgemで、logidzeという名前はグルジア(ジョージア)語由来だそうです」「じゃ読み方わからなくて当然😆」「Evil Martiansにはロシア系メンバーがいるからなのかな🤔」「なるほど、いわゆるaudit(監査)系のgem」

参考: 第1回 データベース監査の現場 [監査とは]|知って得する! Oracle知識|Oracleソリューションサービス|日立ソリューションズ

「paper_trailより速いそうです」「paper_trail、あれは重いよ〜😆」「やっぱり😆」「paper_trailは内部展開までやってるからそりゃ重い😆

参考: paper-trail-gem/paper_trail: Track changes to your rails models

「logidzeはこうやってバージョンをトラックしたりundoしたりとかもやれるのか↓」「ほほー」「保存先はデータベースでしょうか?」「rake db:migrateやってるからほぼ確実にそうでしょうね☺」「軽いpaper_trailとして使えそう」「auditツールは信頼性もチェックしときたいですけど☺

post = Post.create!(title: "first post") # v1
post.update!(title: "new title")         # v2
post.undo!(append: true)                 # v3 (with same attributes as v1)

⚓high_voltage: Railsで静的ページを扱う(Ruby Weeklyより)


thoughtbot.comより

# 同リポジトリより
# app/controllers/pages_controller.rb
class PagesController < ApplicationController
  include HighVoltage::StaticPage

  before_filter :authenticate
  layout :layout_for_page

  private

  def layout_for_page
    case params[:id]
    when 'home'
      'home'
    else
      'application'
    end
  end
end

つっつきボイス:「『高電圧注意』とか書いてあるけど別に危険な気はしませんね😆」「いわゆる静的ページを普通のRailsビューのように使えるという感じでしょうか?」「それっぽい」「Railsのキャッシュをいい感じに効かせてくれるようだ」

「静的ページを普通にpublic/の下に置くとできないことってあるんでしょうか?」「ビューのレイアウト機能なんかはpublic/ではできないでしょうね: ヘッダーを静的ページでも共通化してレンダリングしたい、というのはよくあるし」「レイアウトは共通化したいけど中身は固定ページ、みたいなときなんでしょうね」「『ご挨拶』ページとか😆

「わざわざgem入れる?とは思いますが😆、キャッシュのこととかを考えずに静的ページを楽に扱えるのはよさそうだし、Thoughtbotなら一応ちゃんとしてそう😋

⚓その他Rails

⚓Ruby

⚓ruby-regexp_trie: 文字列リストを正規表現に圧縮するgem

# 同リポジトリより
#!/usr/bin/env ruby
require 'regexp_trie'

# Regexp.union()のように使う
p RegexpTrie.union(%w(foobar fooxar foozap fooza)) # /foo(?:bar|xar|zap?)/
p RegexpTrie.union(%w(foobar fooxar foozap fooza), option: Regexp::IGNORECASE) # /foo(?:bar|xar|zap?)/i

# オブジェクト指向インターフェイスっぽくもやれる
rt = RegexpTrie.new
%w(foobar fooxar foozap fooza).each do |word|
  rt.add(word)
end
p rt.to_regexp # /foo(?:bar|xar|zap?)/

PerlのRegexp::List(小飼弾作)を元にしているそうです。Perl版はえらく短いコードです。


つっつきボイス:「ruby-regexp_trieは近々記事を出そうと思ってます: Trie(トライ木)っていうデータ構造をこれで知りました」「Trieは割と最近のヤツだったかな」「自然言語処理方面で辞書を作るのによく使われているみたいで、単なる二分木と得意不得意がちょっと違うようです」

参考: トライ木 - Wikipedia
参考: 簡潔データ構造 LOUDS の解説(全12回、練習問題付き) - アスペ日記
参考: 情報系修士にもわかるダブル配列 - アスペ日記

「PerlだけにやはりCPANに置いてある☺」「ところで小飼弾氏ってだいぶ前からコードも書く論客というイメージがあったんですが、やはりPerlの人だからなのか、つっつきでは名前出たことがなかったなと思って」「コードも書くけど今はどちらかというビジネスとか経営寄りな印象かな〜🤔」「元ライブドアのCTO」

参考: CPAN - Wikipedia
参考: 404 Blog Not Found — 小飼弾氏のブログ

⚓Opalが1.0になって高速化(Ruby Weeklyより)


同サイトより

  • プロトタイプチェーン探索がネイティブに
  • Module.prependの追加
  • c_lexerによる高速化
  • エラーメッセージやsource mapの改善
  • コンパイル時に見つからないrequireを実行時にのみエラーにできるようになった(MRIと同様)
  • Ruby 2.4や2.5の機能が多数使えるようになった
  • MRIと同じアルゴリズムの疑似乱数ジェネレータ、packunpack、Node.jsでFileDirクラスへのアクセスほか

コミットリスト: Comparing v0.11.4...v1.0.0 · opal/opal


つっつきボイス:「ついにOpalが1.0に!」「0.x系だと警戒して使わない人がいるからだったりして😆」「OpalってLambda系のサーバーレスアプリを作るときなんかに全部Rubyで書けるのがよさそうなんですよね〜🥰

⚓zxcvbn-ruby: パスワード強度チェッカー(Ruby Weeklyより)

# 同リポジトリより
>> pp Zxcvbn.test('@lfred2004', ['alfred'])
#<Zxcvbn::Score:0x00007f7f590610c8
 @calc_time=0.0055760000250302255,
 @crack_time=0.012,
 @crack_time_display="instant",
 @entropy=7.895,
 @feedback=
  #<Zxcvbn::Feedback:0x00007f7f59060150
   @suggestions=
    ["Add another word or two. Uncommon words are better.",
     "Predictable substitutions like '@' instead of 'a' don't help very much"],
   @warning=nil>,
 @match_sequence=
  [#<Zxcvbn::Match matched_word="alfred", token="@lfred", i=0, j=5, rank=1, pattern="dictionary", dictionary_name="user_inputs", l33t=true, sub={"@"=>"a"}, sub_display="@ -> a", base_entropy=0.0, uppercase_entropy=0.0, l33t_entropy=1, entropy=1.0>,
   #<Zxcvbn::Match i=6, j=9, token="2004", pattern="year", entropy=6.894817763307944>],
 @password="@lfred2004",
 @score=0>
=> #<Zxcvbn::Score:0x00007f7f59060150>

Dropboxがやっているzxcvbn.jsをRubyに移植したそうです。ダメパスワードリストもあります。


つっつきボイス:「zxcvbn発音できない😅」「Dropboxがこういうのやってるのか〜」「そのRuby版ですね☺

参考: Dropbox開発のzxcvbn(パスワード強度メーター)のブログ記事を全訳してみた - Qiita

USENIXで以下のプレゼンが2017年に行われていたようです。

The USENIX Association

usenix.orgより

⚓RuboCopを3つのgemに分割(Hacklinesより)


つっつきボイス:「RuboCopが以下の3つのgemに分けられるそうです」「ほほぉ〜」

「たしかにRuboCop Railsは分かれていて欲しいかも: 今のRuboCopは色々入れすぎてるから、gemとしてRails用だけ選択できたらうれしいし❤」「たしかに〜」「RuboCop Railsって単体で使えるかしら?」「まあRuboCop本体にdependするでしょうね☺」「RSpecみたく😆

「RuboCopをRailsでもっとデフォルトで使いたいからgemを分けて欲しいというのは気持ちちょっとワカル☺」「分けないとメンテが大変そうですし😅」「RuboCopは割とプルリクをガンガン受け付ける敷居の低さがあるんですけど、その分ちょっとやりすぎ感はありますし😆」「RuboCopのプルリクの数すごいですよ」

後で気づきましたが、従来のrubocop-railsがRuboCopチームに移管されるというアナウンスが昨年あったんですね。

⚓その他Ruby

つっつきボイス:「PySnooperにヒントを得ためっちゃexperimentalなデバッガーだそうです」「おー、この情報↓ってMac固有のものだったりするのかな?」「4番目の数値がRubyのメモリ使用量っぽいから、これが急激に増えていたら何かおかしいと」「GCすれば普通減りますけどね😆

Var......... a=["Bored", "Curious"]
Var......... b=["cat", "frog"]
18:38:15.188 line   test/support/test.rb:54        13770752           a << "Insane"
18:38:15.188 line   test/support/test.rb:55        13807616           shuffle(b)
Var......... arr=["cat", "frog"]
18:38:15.188 call   test/support/test.rb:45        13807616       def shuffle(arr)
18:38:15.189 line   test/support/test.rb:46        13807616         for n in 0...arr.size
Var......... n=0
18:38:15.189 line   test/support/test.rb:47        13811712           targ = n + rand(arr.size - n)
Var......... targ=0
18:38:15.189 line   test/support/test.rb:48        13811712           arr[n], arr[targ] = arr[targ], arr[n] if n != targ
Var......... n=1
18:38:15.189 line   test/support/test.rb:47        13811712           targ = n + rand(arr.size - n)
Var......... targ=1
18:38:15.189 line   test/support/test.rb:48        13811712           arr[n], arr[targ] = arr[targ], arr[n] if n != targ
18:38:15.189 return test/support/test.rb:50        13811712       end

短いtips記事です。

# 同記事より
land = 'Mordor'

verse = <<-'TEXT'
One Ring to rule them all,
One Ring to find them,
One Ring to bring them all,
and in the darkness bind them,
In the Land of #{land} where the Shadows lie.
TEXT

つっつきボイス:「ヒアドキュメントで式展開#{}を無効にしたい場合に、'TEXT'みたいにキーワードを一重引用符で囲むと展開されなくなるんだそうです」「へぇ〜😆」「これはマジ知る人ぞ知る😆」「シンタックスシュガーなんですけど、これで効くのがちょっと不思議」

「…このシンタックス、Bashだとメジャーなんですよね😆」「えぇぇ?!」「じゃそこから持ってきた文法なのかな」「そんな気がします: この文法キライなんですけど🤣」「Bashなんかだと使える記号にいろいろ制限があってそうなったんだろうけど、何もそれを踏襲しなくても😆」「Bashに強い人はいいんでしょうけど😆

↓たぶんこれだと思います。

参考: sh での変数とワイルドカードの落とし穴|てくめも@ecoop.net

# 同記事より
foo="SELECT * FROM table"
echo "$foo"
#=> SELECT * FROM table

⚓Ruby trunkより

⚓パターンマッチングの現状の仕様


つっつきボイス:「この間出した記事↓をまとめていて、パターンマッチングのうれしさがやっと少し見えてきたので、現時点のまとまった仕様と思われるものをこのissueから抜き書きしました」

キーワードで振り返るRubyKaigi 2019@博多(#4)最適化、パターンマッチング、mruby、ブランチメンテナンスほか

case obj
in pat [if|unless cond]
  ...
in pat [if|unless cond]
  ...
else
  ...
end

pat: var                                            # 変数渡しのパターン(任意の値にマッチ可能、その値の変数名にバインドされる)
   | literal                                        # 値渡しのパターン(pattern === objectのようにオブジェクトとマッチする)
   | Constant                                       # 同上
   | ^var                                           # 同上(Elixirのピン演算子^と同等)
   | pat | pat | ...                                # alternateパターン(いずれかのpatとマッチする)
   | pat => var                                     # asパターン(patにマッチする場合、その値の変数にバインドされる)
   | Constant(pat, ..., *var, pat, ...)             # arrayパターン(後述)
   | Constant[pat, ..., *var, pat, ...]             # 同上
   | [pat, ..., *var, pat, ...]                     # 同上(`BasicObject(pat, ...)`と同じ)(トップレベルでのみ丸かっこを省略可)
   | Constant(id:, id: pat, "id": pat, ..., **var)  # hashパターン(後述)
   | Constant[id:, id: pat, "id": pat, ..., **var]  # 同上
   | {id:, id: pat, "id": pat, ..., **var}          # 同上(`BasicObject(id:, ...)`と同じ)(トップレベルでのみ波かっこを省略可)

arrayパターンは以下のいずれかの場合にマッチする:
* Constant === objectがtrueを返す
* Arrayを返す#deconstructメソッドがオブジェクトにある
* ネストしたパターンをobject.deconstructに適用した結果がtrueになる

hashパターンは以下のいずれかの場合にマッチする:
* Constant === objectがtrueを返す
* Hashを返す#deconstructメソッドがオブジェクトにある
* ネストしたパターンをobject.deconstruct_keys(keys)に適用した結果がtrueになる

参考: Pattern matching - New feature in Ruby 2.7 - Speaker Deck

参考: パターンマッチング · Elixir School — ピン演算子^の解説


前半は以上です。

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

週刊Railsウォッチ(20190521-2/2後編)サーバーレスクラウドのベンチマーク比較サイト、VueJSパフォーマンス向上、GraalVM 19.0ほか

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

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

Rails公式ニュース

Ruby Weekly

RubyFlow

160928_1638_XvIP4h

Hacklines

Hacklines

週刊Railsウォッチ(20190603-1/2前編)Ruby 2.7.0-preview1リリース、RailsConf 2019を追う、pluckとincludesの組み合わせに注意、deep_transform_keys追加ほか

0
0

こんにちは、hachi8833です。

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

今回は私たちにとって珍しいゲストがいらっしゃいました。


つっつきボイス:「どうもお久しぶりです🙇」「タイミングが合ったので今回初めて参加します: TechRachoいつも読んでます☺」「お〜嬉しいお言葉!😂

⚓お知らせ: 第11回公開つっつき会(無料)

第11回目を迎えた公開つっつき会は6月6日(木)19:30〜にBPS会議スペースにて開催されます。皆さまのお気軽なご参加をお待ちしております🙇

⚓臨時ニュース: Ruby 2.7.0-preview1(Ruby公式ニュースより)

つっつきの後でリリースされていました。次回の公開つっつきでもう少し見ていこうと思います。まずは記念写真でインクリメンタルシンタックスハイライトしてみました📷。iTerm2に設定してあるハイライトと混じってしまいましたが😇

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

⚓ドキュメント: includespluckに与える影響

@st0012さんのコミットです。

Note: pluckは、relationオブジェクトにincludesの値が含まれると、以下のようにeager loadingがそのクエリで不要な場合であってもeager loadingをトリガすることも知っておくべき。
同PRより

# associationを再利用のため保存
assoc = Company.includes(:account)
assoc.pluck(:id)
# SELECT "companies"."id" FROM "companies" LEFT OUTER JOIN "accounts" ON "accounts"."id" = "companies"."account_id"

この挙動を避ける方法のひとつとして、次のようにそのincludesをunscopeする方法がある。
同PRより

assoc.unscope(:includes).pluck(:id)

つっつきボイス:「今日社内SlackのRails板に貼って盛り上がった、Raiilsガイドの更新です」「そうそう、includesを使ったときのpluckの挙動😇

「コミットメッセージの状況で単にpluckを使うとLEFT OUTER JOINしてしまう: すなわちpluck(:id)の結果に含まれるidの値が重複する可能性があるということだそうです」「はは〜なるほど😅」「問題はincludesと組み合わさった場合なんですよね」

参考: pluck — ActiveRecord::Calculations

「これはバグではなくて『こういう仕様である』みたいなものでしょうか?」「仕様ということですけど、これはORMの限界なのかも🤣」「ちょ🤣」「見ようによってはincludesがよしなにやってくれすぎるというか🤣

参考: includes — ActiveRecord::QueryMethods

「自分は元々pluckがuniqueとは思ってないし😆: Slackにも書いたように↑pluckメソッドの挙動を考えればunique保証されないので」「まあCompany.pluck(:id)みたいな形になっていればidpluckなんだから重複しませんけど😆、たしかにuniqueとは限らない」「uniqueにしたければ明示的にpluck.uniqueするとかDISTINCT付けるとかしないと: まあ自分がORMを真っ先にSQL寄りに考える癖が付いているのでそう思うんでしょうけど☺

なお、後で@st0012さんとチャットで話したところ「includesの値を含むAR relationオブジェクトは使い回さないことをおすすめする」そうです。

Rails: JOINすべきかどうか、それが問題だ — #includesの振舞いを理解する(翻訳)

⚓pluckのdatetimeの高速化

pluckといえば、@kamipoさんが以下でpluckのdatetimeを高速化しました」「今までは必要がないときにもdatetimeの小数以下を丸めていたのをやめたのか」「速度向上はわずかだけど」「速くなるのはいいこと😋

# activemodel/lib/active_model/type/helpers/time_value.rb#L24
-       def apply_seconds_precision(value)
-         return value unless precision && value.respond_to?(:usec)
+         number_of_insignificant_digits = 6 - precision
+         return value unless precision && value.respond_to?(:nsec)
+
+         number_of_insignificant_digits = 9 - precision
          round_power = 10**number_of_insignificant_digits
-         value.change(usec: value.usec - value.usec % round_power)
+         rounded_off_nsec = value.nsec % round_power
+
+         if rounded_off_nsec > 0
+           value.change(nsec: value.nsec - rounded_off_nsec)
+         else
+           value
+         end
+       end

「Ruby側で精度調整してるんですね😳」「お〜、DBからORMを通してRubyのタイムスタンプに変換すればどっちみち小数6桁目以下は使えないからここで切り落としている?と想像してみました🤔」「DBのタイムスタンプの方が分解能が高いんでしょうか?」「ことがある、ということでしょうね☺」「普段そこまで考えてませんけど😆


「ふと@kamipoさんのこのツイート↓が関係あるかな?と思ったけど別のコミットの話っぽいですね☺

⚓アダプタのキャストにフォールバックするようにした

# activerecord/lib/active_record/type_caster/connection.rb#L11
-     def type_cast_for_database(attribute_name, value)
+     def type_cast_for_database(attr_name, value)
        return value if value.is_a?(Arel::Nodes::BindParam)
-       column = column_for(attribute_name)
-       connection.type_cast_from_column(column, value)
+       type = type_for_attribute(attr_name)
+       type.serialize(value)
      end

つっつきボイス:「これも@kamipoさん」「やっぱりスゴい人」「前に似たようなものを見たような気がするけどどうだったかな〜」「情報が足りなかったっぽい」

「まあActiveRecordのコードっていきなり見てもマジわからないし😆」「データベースアダプタ本来の挙動を知っておかないと読み方わかんないです〜😇」「MySQLとかPostgreSQLのアダプタは基本的にコネクションを管理してつなぐだけのはず、と思いたい🤣」「Active Record自体にもMySQL専用のコードとかPostgreSQL専用のコードとかが結構な量ありますし😆: それぞれに通せるクエリがまるで違ったりしますので」「でしょうね〜😅

⚓explainをMySQLとSQLiteのDatabaseStatementsに移動

# activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb#L29
+       def explain(arel, binds = [])
+         sql     = "EXPLAIN #{to_sql(arel, binds)}"
+         start   = Concurrent.monotonic_time
+         result  = exec_query(sql, "EXPLAIN", binds)
+         elapsed = Concurrent.monotonic_time - start
+
+         MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)

つっつきボイス:「abstract_mysql_adapter.rbからmysql/database_statements.rbとかに移動してる」「リファクタリングですね」「最後の1つだそうです」「Railsみたいに巨大なフレームワークだと、行数爆発してきたときにこうやって小まめにリファクタリングしてくれるのはうれしいですね😍

⚓deep_transform_keysを追加

# actionpack/lib/action_controller/metal/strong_parameters.rb#L699
+   def deep_transform_keys(&block)
+     new_instance_with_inherited_permitted_status(
+       @parameters.deep_transform_keys(&block)
+     )
+   end
+
+   ...
+   def deep_transform_keys!(&block)
+     @parameters.deep_transform_keys!(&block)
+     self
+   end

つっつきボイス:「strong parametersでdeep_transform_keysが使えるようになった🎉」「キーと値を入れ替える?」「いえ、これはキーの変換ですね」「お、指定した方法でキーを一括変換できるヤツですか😋」「しかもdeepだから再帰的にぶん回せると」「最近このあたりをようやっと使いこなせるようになってきましたよ〜💪

参考: Hash#transform_keys — Rails API

「キャメルケースをスネークケースに変えたりできそうですね😋」「そうそう〜、ありがち😆」「文字列キーとシンボルキーが混じっているのをどっちかに揃えたいときとかもっ」「Railsにそんな機能があったとは😅」「今回deepでもできるようになったから、これでもう一網打尽🔫」「HashWithIndifferentAccessの下がHashWithIndifferentAccessとは限らないし😆

参考: ActiveSupport::HashWithIndifferentAccess

⚓scopeオプションに値がある場合にpartが落ちないようにした

# actionpack/lib/action_dispatch/journey/formatter.rb#L66
        def extract_parameterized_parts(route, options, recall, parameterize = nil)
          parameterized_parts = recall.merge(options)

          keys_to_keep = route.parts.reverse_each.drop_while { |part|
-           !options.key?(part) || (options[part] || recall[part]).nil?
+           !(options.key?(part) || route.scope_options.key?(part)) || (options[part] || recall[part]).nil?
          } | route.required_parts

          parameterized_parts.delete_if do |bad_key, _|
            !keys_to_keep.include?(bad_key)
          end
          if parameterize
            parameterized_parts.each do |k, v|
              parameterized_parts[k] = parameterize.call(k, v)
            end
          end
          parameterized_parts.keep_if { |_, v| v }
          parameterized_parts
        end

ルーティングがオプションのscope内で定義されていた場合、ルーティングにパラメータがないとパスヘルパーを用いるときにscopeが落ちてしまっていた。このコミットは、ルーティングがパラメータを取るかどうかにかかわらずscopeを維持する。
同PRより


つっつきボイス:「ルーティングのスコープですね」「どこかでパラメータが落ちてたのか」「自分はルーティングでnamespaceは使うけど、scopeってあまり使わないし😆」「どちらかを使えばオレの思うルーティングにできるかな?と思うと、実はどっちでもできなかったりすることありますけど🤣」「ルーティングのこの辺って毎回ググってます😅: でTechRachoが出てきたりします😆

参考: Rails のルーティング - Rails ガイド

「ルーティングはちょっと油断するとすぐ沼るから😆、悩むぐらいだったらベタにいっぱい書く方がマシ😆」「そうそうっ!ベタでいいと思うのっ😆」「resourcesぐらいは使いたいですけど☺」「頑張ってDRYに書こうとするとむしろ読みにくくなったりしますし😢

「その辺が極まってるのがDeviseスコープ🤣」「そうっ🤣」「あれは書いていて毎回すっごく不安になりますし😇」「rails routesしながら書かないと怖すぎるし😆」「Deviseは沼」「Deviseは沼」

[Rails] Devise Wiki日本語もくじ1「ワークフローのカスタマイズ」(概要・用途付き)

⚓Active Storageのdelegate_missing_to:allow_nilオプションを追加

  • ファイルが添付されていない場合に単一の添付ファイルのメソッド呼び出しでnilを返すようになった。
    従来は、たとえばUserモデルのuser.avatar.filenameは、アバターがない場合にModule::DelegationErrorになっていた。
class User < ApplicationRecord
  has_one_attached :avatar
end

上がnilを返すようになった。
changelogより

# activestorage/lib/active_storage/attached/one.rb#L3
module ActiveStorage
  # Representation of a single attachment to a model.
  class Attached::One < Attached
-   delegate_missing_to :attachment
+   delegate_missing_to :attachment, allow_nil: true
    ...

つっつきボイス:「ActiveStorageのdelegate_missing_toの修正か」「他のには入ってるみたいですね」「デフォルトは:allow_nil: trueだったんだろうけど、今までは:allow_nil: falseにできなかったということか」「ややこしい😆」「見方を変えれば存在保証ができなかったんですね☺

delegate_missing_toって何でしたっけ」「おやこんなところにTechRachoの記事が↓😆」「自分で訳しておいて忘れてました😅」「便利なブログですね〜☺

[Rails 5.1] 新機能: delegate_missing_toメソッド(翻訳)

⚓Rails


同サイトより

RailsConf 2019の1日目の出し物から、有用そうなものを見繕ってみました。

見てみると、セッションの他にワークショップの数もかなり多いことに気づきました。右の2つのスロットは基本的にワークショップのようです。しかも「コマンドライン入門」のような敷居の低いワークショップもあったりと、強者だけのカンファレンスではなさそうに思えました。

探すときには、以下のカテゴリを活用するのがポイントのようです。


railsconf.comより


つっつきボイス:「しばらくRailsウォッチでRailsConf 2019でよさげなセッションを追ってみようと思いました: その第1弾です」「7スロットかける3日間ですからそのままだと多すぎて大変☺」「RailsConfもう終わってるのか〜😇

「そういえばこのRailsConf 2019でRails 6を発表するという話があったような…?」「予定通り遅れてます😆」「過去に予定通りリリースされたことってどのぐらいあったかしら😆」「あったかもしれないけど記憶にないし🤪

⚓「GitHubで使っているRailsの歴史を晒す」、「2年半かかった移行」

スライドだけさっと見ても楽しめるセッションです。


つっつきボイス:「GitHubの中の人の発表で、GitHub自身のRailsアップグレードの話です」「今のGitHubのRailsは最新のはずですね」「スライド↓では2009年にRails 2.3をforkしてるんですけど『大失敗だった😇』だそうです」「でしょうね〜☺

「Rails 2ベースからのfork、そりゃたしかにキツイわ〜」「本家からのcherry-pickでしのいでたんでしょうね」「Rails 3.1ってかなり変更点多かったはずだし」「アセットパイプラインがない時代からある時代への移行は苦行😭」「あと誰が使ってたか知らないけどあの頃はRJSなんてのもありましたし🤣」「つ、使ってた覚えが😆」「RJS、個人的には悪くなかったんですけど☺

参考: アセットパイプライン - Rails ガイド
参考: RJSなら数行のRubyコードでAjaxアプリを作成できる (1/4):Ruby on RailsのRJSでかんたんAjax開発(後編) - @IT

「Rails 4のためにGemfile.lockが2本立てに↓😆」「イテテテ、痛みが伝わってくる〜😭」「GitHubこれでやってたってスゴいですね」「まあ大規模な移行になると動かしてみないとわからないことがよくあるので、こういうふうにするのは仕方ない😅

「大規模移行のベストプラクティスのひとつに、一気に移行しないで、最初は一部のページだけを新しいサーバーで扱って、様子を見ながらだんだんその割合を増やしていく、というのがありますね」「あぁ、リバースプロキシとか使って振り分けるヤツですね☺」「ベストプラクティスは他にもあるようですけど☺」「たまーにGitHubがおかしいときってこういうのをやってる最中だったのかも🤔」「そういうときって、こちらのブラウザ側でcookieをフラッシュすると切り替えが正常に戻ったりしますよね」「あるある〜」

「出た〜↓😱」「これがあるのはもう仕方ない」「ちゃんとやろうとするとこうなりますし☺」「そして2018年にようやっとforkが解消されて最新になった!」「GitHubぐらい大規模になるとホント大変」

「Railsをforkするとセキュリティパッチを当てられなくなるとかつらそう」「つかforkして後悔したことが山のように😇」「gemのサポートやプライベートRails APIもつらかったそうです」「プライベートRails API、しれっとリネームされたりしますよね😆

「GitHubは今はもう完全に最新のRailsなんでしょうか?」「GitHubだけに、独自の最適化は今もやってそうですけど🤔」「独自のパッチ当ててまた苦しむぐらいなら本家Railsにプルリク投げる方を選ぶかも😆

「いや〜想像するだけでゾッとします🥶」「体力ないととても無理😢」「まあ移行期間中に担当者が辞めないことが一番大事🤣」「たしかに🤣」「大規模な移行ってそのリスクも考えるとなるべく短期間に収めたいですし☺」「ちょっとずつでもバージョン上げていかないと」「ぼやぼやしてるとやってる間にまたバージョン上がったりしますし」「実際、移行中はどうしてもメンテナンスコストが最大になるので、短期決戦に持ち込むか、でなければさっきのようにリバースプロキシでURLを少しずつ新しいサーバーに振り分けるとかしないと」

参考: 以下の「The 30-Month Migration」も上とコンセプトが近そうです。

「こちらも大変そう…」「30か月だから2年半😇」「考えてみれば、Twitterなんかもまさにこういうことをやってきましたよね」「RailsからScalaベースになったり」「素のMySQLからCassandraになったり」

「そのキャッシュのコスト、いくらだと思う?」

参考: Tender Lovemaking | tenderlovemaking.com


つっつきボイス:「tenderloveことAaron Pattersonさんのキーノートで、前振りが割と長いんですが、Railsテンプレートの最適化の話でした」「ぱっと見、ERBをRubyに変換して高速化とかもやってるみたい」「そういえば今Rails 5のERBってどのgemで処理してるんでしたっけ?」「たしか前より軽いgemになってた気が🤔

後で調べると、#30945でerubisからJeremy Evansさんのerubiに移行していました(ウォッチ)。

「ところでビューレンダリングのファイル名のprediction(予測)って、いつも不安になる😅」「prediction?」「以下のスライド↓で言うと左の上と下で実際に呼ばれるファイルが異なる」「それそれっ😭」「context dependentってそのことでしたか!」「まあちゃんと/書けばいいんですけど😆」「プロジェクトでパーシャルを明示的にしてフルパスで書くこと、みたいな運用にしたことありますヨ😤

「そうそう、スライドをチェックしててこの一言が目に止まりました↓: テンプレートの実行よりキャッシュキーの算出の方が重かったと」「は〜なるほど」「スライドでもこの少し前で、キャッシュを変えてベンチマークを取ったらそうなってましたね」

「これはOSのファイルキャッシュも関係しているでしょうね: テンプレートがメモリに乗ればOSのファイルキャッシュにも乗るし」「ありそう!」「キャッシュを別ネットワークのRedisとかに乗せるより、ローカルOSのファイルキャッシュの方が速そうだし」

「プリコンパイルできるコードとできないコード↓」「なるほど、遅延評価が残るコードは当然できないし☺

「この辺は、可能なERBをプリコンパイルして高速化みたいなディープな話をしてるっぽい」「スライドではその辺を代わりにやってくれるっぽいactionview_precompilerというgem(Rails 6向け)↓を紹介していました↓」「それ事故ったりしないのかな?😆」「可能なものはプリコンパイルしてくれるっぽい」「組み合わせ爆発したらスゴいことになりそう😆」「最初のプリコンパイルが重かったらどうしましょう😆

「オチは『コンテキストに依存させるな』とこれ↓😆」「まあここまで言うとちょっと違う気もするかな〜: 『キャッシュの方が遠いこともある』ぐらいに考えとこう」「見出しの収まりの良さでこうしたのかもですね」「しかしキャッシュキーの生成だけならそっちの方が速そうなんだけどな〜🤔: キーを生成したキャッシュがネットワーク越しにもなっていればそっちの方が遅いのはわかるけど、スライドをざっと見た限りだとよくわかんない😆

Rails 6マルチDBとぽすぐれの落とし穴/パターン/パフォーマンス

抜き書き:

  • マルチDBになれば読み書きを分離できる
  • マルチDBはARクラスでどう認識されるか
  • 落とし穴: readレプリカでタイムラグが生じることがあった
    • ぽすぐれのパーティショニングで切り抜けた(詳しくはまた今度)
  • 落とし穴: シャーディングしたらDBでループが頻発
  • コネクションプールの考察
  • どのキャッシュが効いてるのか
  • ところでHerokuでPostgreSQL 11使えるようになったのでよろしく

つっつきボイス:「Herokuスポンサードのセッションで、Rails 6のマルチDBやってみた話でした」「マルチDB自体はRailsのプロジェクトやってればどこかで普通に出会いますけどね☺: switch_pointとか」「最後はHerokuの宣伝で締めてます😆

⚓キャッシュは重要


つっつきボイス:「Kingと言ってるのは『重要』ぐらいの意味かなと」「スライド多い〜」「こういうキャッシュの話よさそう😋: 速度が遅くなったときの対応方法は『簡単にある程度対応できる』『大変だけど改善度合いが大きい』みたいに複数の戦略を知っておかないとつらくなりますね」

「後は時間押してるので軽く流しま〜す」

⚓Railsのスケーラブルな監視

⚓Action Cableのしくみ

⚓その他Rails




jetbrains.comより

つっつきボイス:「RubyMineのIDEトレーニングプラグインだそうです」「ほほ~、ふぃーちゃーとれーなー😋」「RubyMineって使ったことないんですけど、便利ですか?」「そりゃもう便利ですよ〜❤」「ないと生きていけないレベル」「ワイも」「そんなに!?自分はAtomとかでちょろっとRailsやってるぐらい😅」「Railsをメインで書いてるのでなければそれでいいと思いま〜す☺

参考: IDE Features Trainer - Plugins | JetBrains

⚓Ruby

⚓IRBのインクリメンタルシンタックスハイライト


つっつきボイス:「IRBのインクリメンタルシンタックスハイライト、話題になってますね」「コンソールがぶっ壊れているときにインクリメンタルシンタックスハイライトが動くとスゴいことになりそうでコワい😆」「$PS1がおかしくなったときとか😆」「端末リセットしてもだめだったらCtrl-C押しまくってセッション切断するしかなくなったり」「デフォルトはオンとオフのどっちになるんでしょう?🤔

後で2.7.0-preview-1で試したところ、デフォルトでオンでした。

「k0kubunさんといえば、Hamlをむちゃくちゃ速くしたりとかいろいろエクストリームにやってる方ですよね😆」「あーHamlもでしたっけ」「Hamlもやってたやってた」「Hamlit…は別の人でしたか😅」「カッとなってJITやったり😆」「いろいろ凄すぎる💪

参考: Hamlを3倍速くした - k0kubun’s blog
* リポジトリ: haml/haml: HTML Abstraction Markup Language - A Markup Haiku
* サイト: Haml


haml.infoより

⚓ハッシュテーブルを学べるスライド

ちょうどRubyのハッシュテーブルがどうなってるか気になっていたのですが、そこにも言及していました❤


つっつきボイス:「この間BPS社内Slackに貼っていただいたヤツです」「これはいいスライド👍」「学びすごくありました😋」「ハッシュテーブルってあるから初心者向けの割とやさしい話かなと思いきや、シノニム処理とかリハッシュみたいな特濃の話: これはいいよ〜😍」「もろ内部実装の話😆」「しかも説明がめちゃくちゃ丁寧😋」「CuckooとかHopscotchなどのアルゴリズムをこれで初めて知りました↓🙇

Hopscotch {名} : 〔子どもの〕けんけん遊び◆英国や米国の小学校で一般的な、主として女の子の遊び。日本のけんけん(または、けんけんぱ)と同様に片足や足を広げてマスの中を跳ぶ

⚓Mongoid: Ruby向けのMongoDB ODマッパー


mongodb.comより


つっつきボイス:「Mongoidなつかし〜😂」「Mongoid、いつの間にかMongoDBのプロダクトに入ってるんですね」「公式になったみたい」「すっごく昔(2016年ぐらい?😆)に使いましたよ」「NoSQLが流行った頃ですか?」「ですです」「でデータ壊れたりしてつらくなったりして🤣」「図星: 一周回ってみて結局RDBどんだけ最高かと🤣

⚓httplog: HTTPリクエストのログを取る(Ruby Weeklyより)

# 同リポジトリより
[httplog] Connecting: localhost:80
[httplog] Sending: GET http://localhost:9292/index.html
[httplog] Status: 200
[httplog] Benchmark: 0.00057 seconds
[httplog] Response:
<html>
  <head>
    <title>Test Page</title>
  </head>
  <body>
    <h1>This is the test page.</h1>
  </body>
</html>

つっつきボイス:「これは…あ〜なるほどなるほど!」「モンキーパッチするヤツ?」「いえ、これはリクエストログを全部残しておけるヤツ」「お〜、それは便利そう!😋」「たとえばRailsサーバーから出ていくリクエストを記録するとか?」「そんな感じです、たぶんプロキシとして動くんじゃないかと思う☺」「あくまでgemなんですね」

「どうやらこの辺のおなじみgem↓をラップするっぽい」「Faradayはまだ一部のみサポート、と」「curlを直接使うツールだとラップできないかも(Faradayはどうなのか知りませんが😆)」


同リポジトリより

「Faraday昔使ってましたけど、皆さんも使ってます?」「すべてがRESTfulなUIならFaradayはいいですね😍: その代りRESTfulでないと途端に使いづらくなりますけど😇」「そうかも〜自分のプロジェクトはFaradayで十分だったのか😆」「相手によりけり😆

⚓その他Ruby

つっつきボイス:「この記事↓でRuboCop作者が勧めてたので」「Railsのspring的なツール?」「そんな感じです」「Vimみたいなエディタで保存するときにRuboCopを毎回起動すると遅いからこれでやる、とかなんでしょうね☺」「READMEでもnetcat使ってやってたりしてるし」「daemon化すれば素でRuboCop実行するよりは速いだろうけど、元々RuboCopがそんな速くないし😆」「ルールが小さければやれるかも」「RuboCop作者も遅いのは認めてますね☺

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

参考: Netcat - Wikipedia



⚓Ruby trunkより

⚓提案: frozenしたオブジェクトをエラーで表示して欲しい

# 同issueより
class A; end
A.freeze
def A.a; end
# can't modify frozen Class: A    ←こういうのを出したい
'a'.freeze << 'b'
# can't modify frozen String: "a" ←こういうのを出したい

つっつきボイス:「frozenされている文字列をいじってエラーになったときに、どの文字列でエラーが出たのか出して欲しいということだそうです」「あは確かに😆」「欲しいヤツだ〜」「文字列が複数あったらなおさらですね」「パスワード入ってたらどうしよう🤣」「パスワードfreezeしないっしょ普通🤣」「freezeがデフォルトになりつつあるし、はずみでログにぽろっと出たりしたらと思うと😆

⚓パターンマッチングの後置のifの挙動 — 却下

# 通常の後置のif
=> puts 1 if (puts 2; true)
2
1

# 質問
class A
  def deconstruct
    puts 'deconstruct called'
    [1]
  end
end

p case A.new
in A[1] if (puts 'if check'; true)
  'yes'
else
  'no'
end

# 以下が出力される(-- 仕様どおり)
# deconstruct called
# if check
# "yes"

「パターンマッチングの後置のifの挙動についての質問ですが、仕様どおりということでした」「一瞬考えちゃう😆」「後から追加する機能はチェックするところが多くて大変😅

通常の後置のifの挙動については以下で説明されています。

「そうそう、Rubyの後置のifは条件が偽の場合も代入されますね」「記事ではMatzに直接質問して回答いただいてます」「代入は構文解析の時点で行われるから、結果にかかわらずundefined variableとかにはならない☺

「この挙動はRuby試験で頻出しそう😆」「マジっすか!」


前半は以上です。

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

週刊Railsウォッチ(20190521-2/2後編)サーバーレスクラウドのベンチマーク比較サイト、VueJSパフォーマンス向上、GraalVM 19.0ほか

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

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

Ruby 公式ニュース

Rails公式ニュース

Ruby Weekly

RubyFlow

160928_1638_XvIP4h

週刊Railsウォッチ(20190604-2/2後編)Cloudflare Workers KVの可能性、PostgreSQL 12 Beta 1、Bootstrap 5でjQuery廃止ほか

0
0

こんにちは、hachi8833です。

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

つっつきボイス:「(ゲスト)へ〜、Railsウォッチのつっつきって、こういう感じでコメント付ける前のドラフト記事をみんなで見ながらワイワイやってるんですね😋」「ですです、それを後から文章化してます」「まあ雑談会です😆」「世の中にはいろいろなリリース情報とか出てますけど、そういうのだけ見てても『それで?』で終わりがちですし、『それがあるとどううれしいのか』みたいなところは、その分野に多少なりとも関心のある人の話を聞ける方がうれしいじゃないですか、そういうのを毎週つっつき会でやってます☺」「ああなるほど、知見のある人だったらどう思うかって知りたいですよね😋」「自分も詳しい人の話聞きたいですし☺

⚓お知らせ: 第11回公開つっつき会(無料)

おかげさまで第11回目を迎えた公開つっつき会は、あさって6月6日(木)19:30〜にBPS会議スペースにて開催されます。皆さまのお気軽なご参加をお待ちしております🙇

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

⚓高精度位置情報


つっつきボイス:「GNSSの誤差数cmってスゴいなと思って」「こういうのは昔から取り組みがありますけど、これは衛星ベースなのかな?」「衛星の他に基地局の情報も補正に使ってるっぽいですね」「基地局ベースの位置情報や衛星ベースの位置情報、他にもネットワーク系位置情報とかいろいろあります」「GLONASSはロシア系のGPSなのね」

参考: GLONASS - Wikipedia

「誤差数cmレベルのGPSは米国が前からやってると言われてますね」「つい軍事利用を思い浮かべちゃいます」「今のGPSは精度を落として使わせてもらってるという話もありますし☺」「このシステムも誤差数cmとはいえ、衛星を拾えていればの話でしょとは思いますけどっ😆」「トンネルに入ったらどうする問題😆

「ところでIPアドレスでふわっと位置情報を出していたりしますけどどこまで信じていいんでしょう?」「GeoIPの精度はそれなりにという程度ですね☺」「やはり😆

参考: GeoIP - FreeBSD入門

⚓HotFrameworks: フレームワークランキングサイト


同サイトより

GitHubスコアとStackOverflowから機械的に生成したランキングだそうです。


つっつきボイス:「この間の社内勉強会でちょっと引用したサイトなんですけど、あくまで参考値ということを別にすると、上位10フレームワークが最近になってぎゅっと横並びになっているのが妙に気になりました」「その謎の収束は案外APIがバグってたりして🤣」「何だかそんな気も😅

「Railsは一応Reactに続いて2位ですね」「Railsがそこまで使われてるとも思えないけどな〜?RubyとonとRailsで3単語のorで引っかかって件数増えてたりして😆: スコアの出し方にもよるでしょうけど、基本的には盲信しないようにしてますし☺」「ですね、あくまで参考ということで」

⚓Cloud RunでRailsを実行する(Ruby Weeklyより)


同記事より


つっつきボイス:「Cloud RunでRailsを実行、KubernetesのPodを構築するよりは楽かも😆」「Kubernetesは大変なんですね😅」「くばねてはその辺エグいですけど、Cloud Runは覚えることが少ないというのはよさそう😋

「あとCloud Runは、AWS Lambdaとかと違って立ち上げっぱなしにできます」「あ〜なるほど!」「単なるコンテナなので、立ち上げっぱなしにしたいサービスにはいいでしょうね☺」「ふむふむ」「ただCloud RunはChunked Transferが厳しいみたいなことも以下の記事にあるので何か制約があるのかも?🤔

参考: Cloud Runがすごく良い | # cd ~yaasita

「そのChunked Transferって何でしょう?😅」「大きなレスポンスを返すときに、1つのTCPセッションの中でデータを送ったり止めたりする形で制御することを指しますね(long pollingも含めて)」「なるほど!」「なおchunkedはHTTP 1.1の機能です🧐: 多重化ではなくて、あくまで1つのTCPセッションをつなぎっぱなしにしてデータが準備できるまで止めておいたりデータができたらまた流したり、というもので、Cometが比較的近いですね」「おお、ちょうどネスペの過去問題にCometが出てました」

参考: Transfer-Encoding - HTTP | MDN
参考: Push技術 - Wikipedia — long polling
参考: Comet - Wikipedia

⚓その他インフラ


つっつきボイス:「仕事のキャリアの話という意味では両方できた方がいいといえばいいけど、両方を中途半端にやるよりは片方を専門にやる方がキャリア上は絶対いいと思いますけど☺」「それもそうですね」

「どちらでも使えるようなサービスはどんどん誰でもできるように簡単になっているので、単にそれぞれのサービスをひととおり触ったことがある程度のキャリアよりは、成熟して枯れたサービスを徹底的に使いこなせて、例外的な問題にも対応できて、難しい要件をそこに落とし込める人の方が、少なくとも弊社ではニーズがありますね」「たしかに〜」「もちろん新しい機能にも対応して欲しいですけど☺

「それに、1つのクラウドでしっかり概念を押さえておけば、他ではどう違っているかという点も含めてむしろ理解しやすくなりますし☺」「たしかにそれぞれのクラウドのアーキテクチャはそれほどかけ離れてなさそうですよね」


「ついでにインフラ面で言うと、AWSとGCPを選択可能にするとすれば、単にロックインを避けるというよりはサービス継続リスクの回避と、あと法務上の問題があるでしょうね」「というと?」「米国とヨーロッパと日本とかでデータを扱うときの法律が違っている場合、その地域の法律に合致するクラウドを使うしかなくなるので、選択可能というよりそれしか選択できなくなる😆」「あーたしかに!」「某大手サービスもそのためにAWSとGCPを地域ごとに使い分けざるを得なかったという話がありますね」

⚓DB

⚓Cloudflare Workers KVが正式リリース(Publickeyより)


cloudflare.comより


つっつきボイス:「なるほど!Cloudflareのエッジにキーバーリューストアを置いてくれるのか: これはとても面白い😍!」「クライアントアプリをめっちゃ高速にできそう😋

「ソシャゲが使うかな?と思ったけど日本以外のトラフィックはそんなにないしな〜、それよりは全世界的なチャット/プッシュやpub/subサービスなんかを低遅延でやれそう」「お〜」「このサービスだとエッジ同士の通信がものすごく高速だと思うので、なまじサーバーに取りに行くよりエッジから取りに行く方が速くなるだろうし」「ふむふむ」

「おそらくですが、このCloudflare Workers KVで投げるデータはQOS上パケットの優先順位を上げるみたいなこともするでしょうね: 低遅延のAPIはお値段を変えるなんてこともするかもしれませんけど😆」「それはありそう😆」「全体に、Cloudflare Workers KVは低遅延のサービス構築でかなり効果がありそうな気がしています😋

⚓PostgreSQL 12 Beta 1リリース(DB Weeklyより)

参考: PostgreSQL 12β1が公開。Bツリーインデックス周りの性能向上や、インデックスの並列処理による再構成が可能に - PublickeyPublickeyより)


つっつきボイス:「Publickeyでも出ていましたが一応公式のを」「どれどれ」

⚓Inlined WITH queries

「ぽすぐれではこのWITHが使えるんですよ」「CTE(Common Table Expressions)!」「記事のクエリで言うと、冒頭のWITH cで定義したものを後の方で参照できたりします」「サブクエリをこういうふうにも書けるんですね😋

-- 同記事より
WITH c AS MATERIALIZED ( SELECT * FROM a WHERE a.x % 4 = 0 ) SELECT * FROM c JOIN d ON d.y = a.x;

参考: WITH問い合わせ(共通テーブル式)

⚓パフォーマンス

「次はINSERTとCOPYのパフォーマンスが向上して、ATTACH PARTITIONも改善された」

⚓JSONパスクエリ

「JSONパスクエリは、おぉ〜、あの謎のJSON expressionと別の書き方ができるようになってるし😍: 以下のJSONに'$.track.segments[*].location'とか'$.track.segments.size()'みたいにアクセスできるらしい」「ネストをこうやって取れるんですね😋」「JSON expressionは書くのも読むのも辛かったし😭

{ "track" :
  {
    "segments" : [ 
      { "location":   [ 47.763, 13.4034 ],
        "start time": "2018-10-14 10:05:14",
        "HR": 73
      },
      { "location":   [ 47.706, 13.2635 ],
        "start time": "2018-10-14 10:39:21",
        "HR": 130
      } ]
  }
}

参考: PostgreSQL: Documentation: devel: 9.15. JSON Functions, Operators, and Expressions

⚓コレーション

「ぽすぐれにもついにICU providedのコレーション(照合順序)が!: UTF-8的にイケてそう😋」「こうやってコレーションを作成できるのか↓」

CREATE COLLATION ndcoll (provider = icu, locale = 'und', deterministic = false);

参考: PostgreSQL: Documentation: devel: 23.2. Collation Support
参考: ICU - International Components for Unicode

「今までぽすぐれにコレーションがなかったんでしょうか?」「一応探してみるとPostgreSQL 10には既にICUコレーション入ってるみたい: 参考記事によると、それまではlibcでソートしていたのが10でICUをサポートしたとある」「お〜」

参考: PostgreSQL 10のICUコレーションを使うと日本語を普通にソートでき、更に文字順序までカスタマイズできる – yohgaki’s blog

「そしてPostgreSQL 12ではUnicode Technical Standard準拠のnondeterministicコレーションができるようになったらしい」「わかんないので後で見てみます😇

決定的(deterministic)コレーションでは、文字列がバイトシーケンスまで同じ場合にのみ等価とみなす。非決定的(nondeterministic)コレーションでは、バイトが違う場合でも同一とみなすことがある。よくあるのは、大文字小文字を区別しない比較、アクセント記号を無視した比較、Unicode正規化形式が異なる文字列同士の比較など。
PostgreSQL: Documentation: devel: 23.2. Collation Supportより大意

参考: UTS #10: Unicode Collation Algorithm
参考: Unicode正規化 - Wikipedia

⚓ Most-common Value Extended Statistics

「CREATE STATISTICSは最近のぽすぐれで使えますね」「12でパラメータとかが拡張される😋

参考: PostgreSQL: Documentation: devel: CREATE STATISTICS

⚓Generated Columns

「Generated Columnsは前にも話したので略(ウォッチ20190409)」

参考: PostgreSQL: Documentation: devel: 5.3. Generated Columns

⚓Pluggable Table Storage Interface

「使ったことないけど、table storageをプラガブルにできるとは😆

CREATE ACCESS METHOD heptree TYPE INDEX HANDLER heptree_handler;

参考: PostgreSQL: Documentation: devel: CREATE ACCESS METHOD

⚓Authentication & Connection Security

「PostgreSQL 12ではLDAPでdiscoveryできるようになった」「管理上ありがたい機能😋」「ぽすぐれのユーザー管理めちゃダルいけど😆


「あとはNoted Behavior Changesが非互換の変更」「JITコンパイルがデフォルトで有効になるんですね」

⚓JavaScript

木曜日のBPS社内勉強会でJavaScriptをテーマにした後、JSをガシガシ書いている勢からのコメントが印象的でした。

⚓Bootstrap 5からjQueryが消えることに

マージされたのは今年の2月でした。


つっつきボイス:「お、ドキュメント以外はだいぶ進んでる感: jQueryへの依存部分はそんなにないと思うけど、テストが大変なんでしょうね」「リアクションのほとんどは👍ですけど一部👎の人もいますね」「jQueryを使わなくなると別の部分がコンフリクトする可能性もあるからかも☺

「v4-without-jqueryというブランチがあったようなんですが今はなくなっているので、Bootstrap 4でjQueryをなくそうとしたけどできなかったのかなと」「一度作ったものをなくすのは大変ですし☺」「JSをだいぶ書き直したみたい」「一応jQueryなしでBootstrap 4を使えるようにするものをいくつか有志が作ってますね」


thednp.github.ioより

⚓その他JS


つっつきボイス:「へぇ〜、UnityでFlutterが動くのか😳: UnityはJSでも書けると聞いている割にはJSでUnityのアプリを出した話ってあまり聞かないかも🤔」「Unityのメイン言語って何でしたっけ?」「C#」「あっそうでした😅

参考: C Sharp - Wikipedia
* ドキュメント: C# のガイド | Microsoft Docs


Wikipedia-jaより

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

⚓W3CとWHATWGがついに合意(Publickeyより)


つっつきボイス:「ついに合意だそうです🎉」「むしろまだやってたんだと思いましたし😆」「恥ずかしながら、W3Cと別にWHATWGがあることを知ったのは割と最近でした😇」「WHATWGのドキュメントはめちゃ長いんですけど、それでも2つあるよりは遥かにマシ」


whatwg.orgより

「こういう仕様がもっとスクショとかビジュアルベースになったらいいなと思ったりします」「うーん、ブロックの配置の説明なんかは画像がある方がいいと思いますけど、仕様そのものは文章でないと解釈ができなくなっちゃいますね🧐」「あ、たしかに😅

⚓はてブのすべてのページでHTTPSが利用可能に


つっつきボイス:「これ、公式より先に増田で最初に見ましたよ🤣」「マジで🤣」「ちょっと前にはてなスターの挙動が怪しかった時期がありましたけど、今思えばあれば移行期だったのかも」

「実はこの移行はTechRacho的にとてもうれしいです😂: TechRachoが数年前にHTTPS化した後、はてなブックマークで同じ記事がHTTP時代のブクマとHTTPS時代のブクマが別エントリになってしまっていたんですけど、これでHTTP/HTTPSのどちらも同じブクマになってくれるので」「これについて以前はてなに相談したことがあったんですけど、そのときは有料で対応という回答でした😆」「そうでしたか〜、とにかくおめでとうございます🎉

その後、はてなのサービスやいにしえのソーシャルブックマークやTumblrの話題などで延々雑談が続きました☺

…「いろんなサービスが出たり消えたりする中で、はてなブックマークなんかは長持ちしてますよね」「何やかやで今もやってくれているのは良心的かなって思ったりしますし☺」「ブコメとかってえも言われぬ独特の文化がありますよね」…

…「実ははてブのカテゴリのうち『テクノロジー』はあんまり見ない」「え?、意外ですね」「『総合』とか他のカテゴリに上がる技術記事は割と見るんですけど、『テクノロジー』は玉石混交感あるし、よさそうな記事は知り合いがTwitterに流してたりするのでそっち見てる☺」「人間フィルタ最強😆」「『学び』とか割と面白いですよね」「そうそう☺、『家電』と『おすすめ』はまず見ないけど😆」…

⚓その他フロントエンド

つっつきボイス:「VS Codeにまた新しい何かが」「scaffoldっぽいというか、createapp.devあたりに通じそうですね」「ちょうどこの間のウォッチで扱いました(ウォッチ20190528)」

参考: Create App - your tool for starting a new webpack or Parcel project

「Web Template Studioは、JSやCSSのテンプレートに加えてこういう↓ビューのテーマ的なものも生成するっぽいですね」「テーマをストアみたいなところに置いてみんなで投稿できるとかもやりそう」「まぁどんなソースコードが出てくるかを見てからですけど 😆」「Wordの拡張HTML形式みたいなのが出てきませんように😆


同記事より



同記事より


つっつきボイス:「これはBPS社内Slackのテスト板で見かけたんですけど、自分がMacbook Pro(2013 Late)にダウンロードして再生しても違いがよくわからなかったので😅」「自分も60Hz以上のモニタ持ってないから調べられませんね😇」「あ、そういうことでしたか(そりゃ無理だ)😆」「ゲーム用のモニタとかって、144Hzぐらいからですよね?」「ですね」

「30Hzと60Hzの違いは人間にわかっても、60Hzと120Hzの違いって果たして人間にわかるかどうか😆」「この辺はゲームやってないとわからなさそうですし、そもそも120Hzの映像データってなかなかなさそう😆」「そもそも人間が識別できる下限が50Hzあたりって言いますから、それより上ならわからなさそう」「たしかVR(Virtual Reality)とかタッチパネルだと60Hzでは足りなくてタイムラグを感じてしまうなんて話をどこかで見ました: iPhoneとかのタッチパネルだとわかるかも」「VRが90Hzぐらいだったかな?」「iPhoneも120Hzとかそのぐらいな覚えが」

「ゲームやらないんでわからないんですけど、140Hzのモニタってどんな人が使うんでしょう?」「レーシングゲームやってる知人なんかはもう感覚が普通と違ってて、その辺を気にするみたいですよ」「へ〜」「レーシングゲームとか格ゲーとかフライトシミュレータあたりでしょうね」「この辺はゲームやりこんでいる人に聞きたいです😆

「そういえば音ゲーやってる人は液晶だと遅延があって嫌だって言ってましたね」「マジで?」「そうそう、液晶は元々遅延が大きいので」「その道ではブラウン管が最高だそうです😆」「でしょうね😆」「でなければプラズマディスプレイ😆」「音ゲーやってみるとまったく違うんですぐわかりますよ」「気持ち早めに叩いて補正するそうです😆

「そういえば昔のProphet 5↓というシンセもキーのトリガーが遅延するので有名だったそうで、心持ちツッコミ気味に演奏していた人がいっぱいいました😆」「ああ坂本龍一とか使ってたヤツでしたっけ」「当時プロはみんな使ってましたね😆

⚓言語・ツール

⚓ループは難しい


つっつきボイス:「Viscuitの原田先生の教育関連記事なんですけど、繰り返しや条件分岐より先に変数を教えないと意味がないのではということだそうです」「そりゃたしかに😆」「変数がないと無限ループぐらいしか書けなさそうですし😆」「なつかしのタートルグラフィックなら、ステートはあっても変数はなしでやれそうですね☺

参考: LOGO - Wikipedia

「小学生向けの教育方面で出てくるタートルグラフィックって自分は一度もやったことないんですけど、あれってどのぐらい有効なんだろう?🤔」「イケてそうなのってだいたいCodeCombat↓みたいにキャラクターを動かすようなタイプですし」「あーこれですね」「やったことないけど面白そう!」「CodeCombatはなかなかよくできてます😋: しかも前よりさらに進化してるナ」

「こういうのも要するにタートルグラフィックですけど、これをやることでプログラムが書けるようになるかというと、自分にはあんまりそう思えないんですよね🤣」「そこはまあ🤣」「組み込み制御系ならまだしも、ビジネスロジックとか文字列処理みたいな要素は皆無ですし😆」「ないない😆

「こういうのはプログラミングのきっかけになればそれでいいのかも🤔」「そうそう、結局きっかけですよね☺

⚓その他言語・ツール


jubat.usより

つっつきボイス:「はてブで見かけたので」「ユバタスってどこかで聞いたことあるかも…Preferred Networksのオープンソースですか」「それなら結構なレベルかも」

「機械学習方面は足が早いので大変😅」「最近はSparkとかRed Data Toolsとかが登場してて、学習モデルのデータが共通化される方向に進んでいますね」「あーなるほど」「エンジンはそれこそいろんなものがありますけど、Spark形式とかApache Arrow形式なんかがたいてい読み込めるようになっているので、ここ最近は自分の好きな言語やツールを好きに組み合わせてやれるようにだんだんなりつつあります☺」「この分野は知らないんですけど、最近はそういう潮流なんですね😳

Apache Arrowについてはウォッチ20190402をどうぞ。



github.comより

つっつきボイス:「GitHub Sponsorsでお金もらえるようになる日が来るか」「GitHubで有名人のページを見に行くと、もう『この人をスポンサーします』みたいなのが表示されてますね」「へ〜」

「GitHubは中間マージンを取らずに100%開発者に届くらしくて、そこがスゴいですよね」「たしかに〜」「それはそれでヨクナイことに使えちゃうかも😆」「個人間送金とか😆」「クレジットカードの現金化とか😆」「資金洗浄とか😆」「真っ先にその辺が気になる😆」「職業病ですね😆

「ところで最終的に受け取るお金はどういう形式になるんでしょう?」「はてなスターがもらえますとか?🤣」「金額分の鮨が届くとか?😆



つっつきボイス:「関数名の参照は、言語によるんじゃないかという予感が😆」「それもそうか😆」「最初はおお〜っと思う機能だけど、使いたいときには忘れてそう🤣」「脳内1次キャッシュには乗らない感じ😆」「Gitで拡張するよりJetBrainsのUpsourceとかで頑張りたい」


jetbrains.comより

「Upsourceって?」「これの何がスゴいって、コードジャンプにJetBrains IDEのコードパーサーを使っているので、たとえばRubyMineでできるジャンプは全部Upsourceでもできる」「あ〜それはスゴいですね🥰」「JetBrains IDEならWebStormでもどれでもやれます」

「そういえばGitHubにもちょっと似たようなのが前にありましたね」「Sourcegraphでしたっけ」「あれあんまりうまく動かなかった覚えが」「使ってみたけど重くなるのでやめました😆」「結局git cloneしちゃったり」「それも面倒なときにはいいんですけどね☺



つっつきボイス:「『動くコードが正義』派ですけど、OOPが理想とはまったく思いませんし🤣」「スパゲティコードが読めるエンジニアに一定の需要があるとは思いますけど☺」「こじれにこじれたjQueryコードを追いかけてデバッグできる人とか」「うぎゃ〜😇」「でもそういう人がいないとどうにもならないシチュエーションは、ある😭」「自分はパスタ苦手なんで、そういうのできる人はスゴいと思います😅」「やはり人の嫌がることがお金になる💰」「スパゲティコードが今後なくなることはないでしょうね〜🍝 」「パスタからは逃げられない」「名付けてプロフェッショナルパスタファリアン🤣」(以下延々)

参考: 空飛ぶスパゲッティ・モンスター教 - Wikipedia

⚓その他

⚓おめでとうございます


つっつきボイス:「ついに不起訴🎉」「よかったよかった☺」「これが通ってたらと思うと」「私も最近JS書いててalertループしちゃいましたし😆」「逮捕されちゃう〜😆



つっつきボイス:「おおっとこれは😆」「34だけ小数以下がないぞっと」

⚓番外

⚓一度は考える


つっつきボイス:「ピアノの鍵盤で文字入力するのって一度は考えるかなと思うんですけど、なかなか成功しませんね😆」「大昔誰かやってた気もする」「どうマッピングするかにセンスが表れそう😆」「よほどうまく設計しないと音が現代音楽になっちゃうかも😆」「(再生して)おお、でも慣れるとこの速度で文字入力できるのか」「こういうまったく違うインターフェイスにたとえば1か月ぐらいで慣れることができる、なんて研究もあったりしますね」「ピアノキーボードでかな入力はきつそう😆」「変換キーは〜?」「きっとペダルで😆」「Alt+Ctrl+Deleteは〜😆?」

「小学校の頃読んでたマンガ↓でそういうピアノ型キーボードを見たことあります」「そういえばパイプオルガンでロボットを操縦するアニメがあったような気がするんだけど、あれ何だったかな〜🤔」「うーん思い出せない😅

参考: コブラ速報 : 【コブラ】ピアノ型コンピューターのアイデアが秀逸だと思う・・・


今回は以上です。

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

週刊Railsウォッチ(20190603-1/2前編)Ruby 2.7.0-preview1リリース、RailsConf 2019を追う、pluckとincludesの組み合わせに注意、deep_transform_keys追加ほか

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

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

Ruby Weekly

Publickey

publickey_banner_captured

DB Weekly

db_weekly_banner

週刊Railsウォッチ(20190610-1/2前編)RailsConf 2019のスライドを追う、Railsのファイル添付gem、Railsの技術的負債を返す、RuboCop 1.0間近ほか

0
0

こんにちは、hachi8833です。iTunesが終わっても特に痛みはありません。

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

おかげさまで6/6の第11回公開つっつき会は無事終了いたしました。初参加の方も多く、回を重ねるごとに賑わいを増してきてうれしい限りです。
お忙しい中お集まりいただいた皆さま、どうもありがとうございました😂!

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

今回はコミットリストから見繕いました。

マルチプルデータベースのガイドも追加されていました。


つっつきボイス:「おー、ついにドキュメントが追いついた🎉」「6.0のマルチデータベースの実装はだいぶ進んでいるようですけど、ドキュメントができたということはここからはそれほど変わらないのかも」「6.0もrc1出ましたし☺️」「database.yamlがこんな感じ↓になるのはもう割と普通というか、migrations_paths:がある以外はswitch_pointっぽいですし😆」「switch_pointやったことある人なら割とすんなりやれるかも😋」

production:
  primary:
    database: my_primary_database
    user: root
    adapter: mysql
  primary_replica:
    database: my_primary_database
    user: root_readonly
    adapter: mysql
    replica: true
  animals:
    database: my_animals_database
    user: animals_root
    adapter: mysql
    migrations_paths: db/animals_migrate
  animals_replica:
    database: my_animals_database
    user: animals_readonly
    adapter: mysql
    replica: true

はみだし: マルチプルデータベースの話題

「ところで、参加者の方でこれまでマルチプルデータベースのRailsを開発したことのある方はいらっしゃいます?: お、割と少ないですね😆」「Octopusだとゴツすぎるので☺️、Active Recordのshard_forとかいうgemをカスタマイズして使ったりとか」「お〜なるほど!」

おそらくこれかと思います↓。

「Octopusは有名なgemですね: 自分はswitch_pointが好きです❤️」「私もOctopusです☺️」「なるほど、Octopusの方そこそこいますね」

「マルチデータベースは何をしたいかにもよりますよね: 自分の場合は別の基幹システムのデータを引っ張りたいというのがあって、そういうスキーマも違うデータに横断的にアクセスするのにswitch_pointを使ったりしてました」「ふむふむ」「他にもデータベースを2個だけ扱うシンプルなgemとか、とにかくいろんなのがあります」

クロスデータベースでのJOIN

「マルチプルデータベースから少し逸れますけど、複数データベースにまたがるJOINって、一見できなさそうで実はできたりします🧐: PostgreSQLでは実際にやりましたし、MySQLでもできたと思います」「マジで!?」「一見できなさそうですけど?」「もちろんパフォーマンスやデータのローカリティの劣化とかはあると思いますけど、最近だとできるようになってたりします☺️」「知らなかった〜😳」

参考1: 4 Methods for joining data from multiple PostgreSQL databases
参考2: sql - How do I construct a cross database query in MySQL? - Stack Overflow

「ちょうど検索で出てきたQiita記事↓で説明すると、ちゃんとGRANTされていればdb_A.user_entry.idみたいに外部のデータベースとJOINできますね」「おぉ〜」「クエリがちょっとだけ冗長になりますけど、できることを知っておくといざというときに役に立つことがあるかも😆」「できれば使わずに済む方がうれしいですけど🤣」

select db_A.user_entry.id, db_A.user_entry.created_at, db_B.users.email, db_B.users.name
from db_A.user_entry
left join db_B.users
on db_A.user_entry.user_id = db_B.users.id;

MySQLで異なるDBに存在するテーブルを結合して表示する方法 - Qiitaより

「マルチデータベースの他のシナリオでたまにあるのは、複数の会社がハイブリッドなシステム(Railsと別のフレームワークが混在しているなど)を共同開発しているときに、お互いに影響を与えないためにデータベースサーバーを別々に立てて、間違ってもGRANTで相手のスキーマを変更しないようにしておく、というのがあったりします: そういうときに、クロスデータベースのJOINできることを知っておくと助かったりするんですよ😅」

ワーカーが早期に終了するとパラレルテストが失敗するのを修正

# activesupport/lib/active_support/testing/parallelization.rb#L30
+       def length
+         @queue.length
+       end
...

      def shutdown
        @queue_size.times { @queue << nil }
        @pool.each { |pid| Process.waitpid pid }
+
+       if @queue.length > 0
+         raise "Queue not empty, but all workers have finished. This probably means that a worker crashed and #{@queue.length} tests were missed."
+       end
      end

つっつきボイス:「なるほど、パラレルテストでありそうなバグ😆」「終了時には@queue.lengthチェックしようよということで☺️」「まだキューが残ってるのに終わってはいかんと😆」「やっぱりパラレルは難しい😅」

saveのたびにarrayが作られるのを回避

# activerecord/lib/active_record/timestamp.rb#L62
-     private
-       def timestamp_attributes_for_create_in_model
-         timestamp_attributes_for_create.select { |c| column_names.include?(c) }
-       end
+     def timestamp_attributes_for_create_in_model
+       18:01stamp_attributes_for_create_in_model ||=
+         (timestamp_attributes_for_create & column_names).freeze
+     end

つっつきボイス:「先週見かけた@kamipoさんのツイート↑はどうやらこれのことかなと😅」「5つってありますし😆」「なるほど、selectをやめて結果をメモ化したと」「selectはRubyのメソッドの方でしたっけ?」「ですね☺️: selectの戻り値はarrayで、内部的にarrayを生成してるのか」

参考: instance method Enumerable#filter (Ruby 2.6.0)select

pluckのテストにorder(:id)を追加

# activerecord/test/cases/calculations_test.rb#L838
  def test_pluck_columns_with_same_name
    expected = [["The First Topic", "The Second Topic of the day"], ["The Third Topic of the day", "The Fourth Topic of the day"]]
-   actual = Topic.joins(:replies)
+   actual = Topic.joins(:replies).order(:id)
      .pluck("topics.title", "replies_topics.title")
    assert_equal expected, actual
  end

つっつきボイス:「pluckのテストが落ちることがあったのを修正したそうです」「ここだけ見てると、assert_equalでやるのがいいのか順序を問わないアサーションがいいのか、どっちだろうって思っちゃいますけど、どっちもありそう😆」

「そういえばORDERを付けなくても何となくid順に返してくれるのはMySQLぐらいでしたっけ: ぽすぐれは内部のアラインメントの絡みとかでORDERなしだと結構順序変わったりしますし😆」

マルチDBでActiveRecord::BaseApplicationRecordを”primary”に

# activerecord/lib/active_record/connection_handling.rb#L205
    def connection_specification_name
      if !defined?(@connection_specification_name) || @connection_specification_name.nil?
-       return self == Base ? "primary" : superclass.connection_specification_name
+       return primary_class? ? "primary" : superclass.connection_specification_name
      end
      @connection_specification_name
    end

+   def primary_class? # :nodoc:
+     self == Base || defined?(ApplicationRecord) && self == ApplicationRecord
+   end

マルチDBアプリをやっているとApplicationRecordは次のような感じになる。

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  connects_to database: { writing: :primary, reading: :replica }
end

上はActiveRecord::Baseへの実際の接続を1つだけにしたい場合でも接続が2つオープンされる。connection_specification_nameが異なると、RailsがApplicationRecordをチェックするときにこれがActiveRecord::Baseではなく新しい接続だと認識するのが原因。
このPRはApplicationRecordクラスを”primary”接続と同一とみなすようにする。
同PRより大意


つっつきボイス:「おお、これはマルチDBで起きそう: 今まではActiveRecord::Baseのコネクションはシングルトンだったので問題なかったのが、マルチDBだとコネクションになるからでしょうね」「今回はマルチDB系のプルリクが多かったような気がします」「そうかもですね☺️」

その他細かな改善

# activesupport/lib/active_support/evented_file_update_checker.rb#L
    private
      def boot!
        normalize_dirs!
-       Listen.to(*@dtw, &method(:changed)).start
+
+       unless @dtw.empty?
+         Listen.to(*@dtw, &method(:changed)).start
+       end
      end

つっつきボイス:「これはわかりやすい改善」「速度的に違うでしょうね☺️」

Rails

今回はRailsConf 2019のDay 2〜Day 3から見繕いました。RubyKaigi 2019と同じスライドもちらほら見かけました。

メンタルケアに関するセッションが複数あるのが日本と違う感じですね。


つっつきボイス:「RailsConf 2019のセッションをざざーっと見てみたところ、意外に強い人の話ばかりとは限らなくて、Railsやって2年目ぐらいの話なども含めてバラエティに富んでいる印象がありました」「そういう印象ありますね: めちゃ深い話があったりそうでもなかったり☺️」「雰囲気としてはRailsdmと少し似ているところがあるのかも?」

「なるべくスライドだけでわかるものを選んでみました」「喋りの背景に徹したスライドだと見ててわからないですよね😆」

技術的負債を返済する


つっつきボイス:「技術的負債を返済する話ですが、半分ぐらい自分で作ったgemも使っているそうです」「こういう『現場であったつらみ集』は親近感が持てていいですね☺️」「興味のある人は追いかけておくとよさそう👍」

以前のウォッチでも取り上げたShopifyの「Shitlist」開発にも言及していました。

分散トレーシングによるRailsマイクロサービスのトラブルシュート

日本勢です。


つっつきボイス:「こちらはWantedlyの方によるマイクロサービスの分散トレーシングのセッションです」「マイクロサービス、成長するとだいたいこうなりますよね↓😇」「😆」「マイクロサービスはよほどキレイに設計しないと、ね☺️」

「なるほど、こういうのをやりたかったと↓」「お〜」「AWSのApp Meshなんかもそうですけど、マイクロサービスは監視がしっかりしてないと話にならないというのはよく言われますね」


「App Meshでもこの辺のグラフをLambda関数チェインなんかも含めて生成できるようになった気がするけどどうだったかな?🤔」「監視と言えばIstioも」「以前ウォッチでEnvoyとともに話題になったヤツですね(ウォッチ20190212)」

参考: AWS App Mesh(マイクロサービスをモニタリングおよびコントロールする)| AWS
参考: Istio

「思い出した、AWS X-Rayなんかがそれ」「X-Ray!?」「おととしぐらいに始まったサービスで自分もまだ使ったことはないんですが😆、EC2なんかも含めてこういうサービスグラフ↓なんかを生成できる」「ふむふむ」「X-Ray使ったことある人っています?」「まだかな〜😅」

参考: AWS X-Ray とは何ですか。 - AWS X-Ray


aws.amazon.comより

「お〜、内部も含めて全部のログが辿れるようになってるみたいですね」「ですです: マイクロサービスの間しか取れないと、結局どのリクエストがfailしたのかがわからなくなるので😆、だいたいマイクロサービス系のバックエンドでは絶対必要になる機能です」「なるほど!」

バックグラウンドジョブをサーバーレスで


つっつきボイス:「サーバーレスで重いバックグラウンドジョブを回すみたいな話だそうです」「AWS LambdaやGCPなんかも使ってる」「Railsも」「やっぱりpub/sub使いますよね😆↓」

「時間のかかる処理の場合、AWSだとイベントをチェインしたりして最終的にS3に置く、みたいなピタゴラスイッチを組み立てられますね😆」「😆」「あと最近ちょっと使い始めているAWS Step Functionsでもやれますし」「お〜」

参考: AWS Step Functions(分散アプリケーションの調整)| AWS

AWS Step FunctionsとAWS Batch

「たとえばLambdaである処理が終わったら次はこの処理を実行したいというときに、Lambda関数の最後で次の関数を呼び出すことも可能ではあるんですが、関数が10個ぐらいチェインするとどんどんわけわからなくなってくるので、Step Functionでそうした部分を作れます」「なるほど!」「Step Functionsは分岐や待ち合わせなんかもできます😋」

「Step Functionsは最近Lambdaに加えてAWS Batchなんかにも接続できるようになってますので、たとえば最初はLambdaあたりで始めて、EC2インスタンスベースで作ったバッチをAWS Batchで一気に実行させて、それが終わったら別のLambdaを実行する、みたいなこともできます」「へぇ〜!」

AWS Batch – 簡単に使えて効率的なバッチコンピューティング機能 – AWS

「と言いつつ、Step Functionsを実際に使った人の話によると、ドキュメントサービスの紹介文には書かれていてもドキュメントを見ると『まだ使えない』と書かれている機能があったりするそうです🤣」「だっはっは🤣」「聞いた話では、成功/失敗部分の取得周りで欲しい機能がまだ足りないんですって🤣」「何というずっこけ🤣」「なのでそのあたりは自分が人柱になるか誰かに人柱になってもらってから導入を決める方がいいかも✞」「皆さまの貴重な人柱が明日を作ると😆」

「まあ最終的にはピタゴラスイッチを自分で作ればいいんですけどっ😆: その場合Step Functionでは管理できない部分になってしまいますけど、Terraformとかで管理する手もあるので、その辺はよしなにやってもらえれば☺️」「な〜るほど」「スライドをちら見してからだいぶ話が逸れましたが、そういう感じのスライドなんでしょう、きっと😆」

参考: Terraform by HashiCorp

PaperclipからActive Storageに移行したお


つっつきボイス:「thoughtbotのPaperclip、懐かし〜😭」「Paperclip、今は開発止まっちゃったんですよね: 移行手順が公式に一応示されてますけど↓」

Rails: PaperclipからActiveStorageへの移行ガイド by thoughtbot(翻訳)

「PaperclipってRails 3の初期ぐらいからあるとても古いgemですけど、ご存じない方はいらっしゃいます?」「名前しか聞いたことなくて😅: 自分はShrineでやってます」

参考: shrinerb/shrine: File Attachment toolkit for Ruby applications

Railsのファイル添付gemたち

「Railsでファイル添付やるときにどれ使います? Active Storage、Shrine、Paperclip、CarrierWaveと、あとひとつ何かあった気がするけど😅」

参考: carrierwaveuploader/carrierwave: Classier solution for file uploads for Rails, Sinatra and other Ruby web frameworks
参考: refile/refile: Ruby file uploads, take 3 — これかなと思いました

「CarrierWaveの人…そこそこいる」「PaperclipとActive Storage…少なっ😆」「人柱足りない😆」「Shrine…はもちろんいますね」「後は、まさかスクラッチで実装?😆…さすがにいないか」「可能ですけどね☺️」

「自分はShrineが好き❤️: ファイル添付系のgemとしてはかなりよくできていて、一番多機能で一番何でもできると思ってます」「へぇ〜!」「たしかAWS S3のマルチプルアップロードにも対応しているので、数十GBのファイルをアップロードしてもサーバーが落ちない限りたぶん処理できる」「何だかすげ〜」

「Active Storageは新しくRailsアプリを作るときには検討すると思いますけど、まだ発展途上っぽい分、既存のものから移行ってあんまりしないんじゃないかなって😆」「既存のものが動いているなら😆」

「ただActive Storageでひとついいなと思うのがクライアントからのダイレクトアップロード機能: これはS3などのオブジェクトストレージで一時的に使えるトークンを発行するなどして、サーバーを経由せずにブラウザからストレージに直接アップロードできるヤツですね」「なるほど!」「こういうのって自分で書きたくないですし(やれますけど)🤣、これができないと数十GBのファイルをアップロードしたときにサーバーのワーカーが止まっちゃうので😇、Active Storageのそういう機能はうらやましいですね」

参考: ActiveStorageでS3にダイレクトアップロードする - Rails Webook

approval: Railsの承認フローgem

# 同リポジトリより
staff = User.find_or_create_by(email: "staff@example.com")

record  = Book.new(name: "Ruby Way", price: 2980)
request = staff.request_for_create(record, reason: "something")
request.save # Created Approval::Request record.

records = 10.times.map {|n| Book.new(name: "my_book_#{n}", price: 300) }
request = staff.request_for_create(records, reason: "something")
request.save!

つっつきボイス:「Railsdmを主催してた平野さんのgemですね」「元カルパスさん🐩」「実は自分もこのgemを使ったことがあって、そのときは欲しい機能がちょっとだけなかったので自分でカスタマイズしました😆」「そうでしたか!」

「このgemは、たとえばデータの更新リクエストを作っておいて、approve何とかというメソッドを実行すると初めて更新が実行される、みたいなことができます」「ふむふむ」「更新内容をモデレートしたいときとか、adminでものすごく権限の強い操作があるときなんかに、approveしないと更新できないようにするのに使いますね」

「approve gemは必要なメソッドもひととおり揃っていますし、コードも短くてすぐ読めますし、発想もいいと思います☺️」「これはRailsのモデルレベルで行うんでしょうか?」「はい、モデルの拡張です🧐」

「このgemは、あくまで1個のテーブルに閉じていることが前提なので、1個の更新で複数テーブルを更新しないといけない場合はちょっと頑張らないといけない」「あ〜」「自分が使ったときは、複数テーブルに対応するために、たしか更新処理自体を抽象化したテーブルを作って、そこにイベントをJSONとかで埋め込んだような覚えがあります☺️: でapproveするとその中のコードを適当にパースして実行するみたいなことをやりました😋」「お〜なるほど!」

前任メンテナからのメッセージ

つっつきボイス:「この@kamipoさんのツイートは?」「前任のメンテナってたぶんあの帽子かぶった@sgrifさんのことかなと」

「ツイートの方は…あ〜そういうことか: id = ?で直接arrayを打ち込める場合の話」「おぉ?」「これは何の話かというと、データベース側のプレースホルダprepared statement(及びquery plan)のキャッシュにヒットさせたいということです」

訂正(2019/06/10)上の修正に合わせて以下のパラグラフを更新しました🙇。

「データベース側の?というプレースホルダは、あくまで?というプレースホルダの状態でprepared statementで持っているので、1つでも違うとprepared statementキャッシュにヒットしなくなります: 逆にprepared statementの内容がまったく同じであればprepared statementのキャッシュが効きます」「ふむふむ」「で、id IN (?,?,?,...)みたいにプレースホルダーの数が可変であってもid = ?で受け止められるのであればキャッシュのヒット率が上がるよね、ということですね」「なるほど〜」「この短いツイートだけでよくそこまで😳」「prepared statementキャッシュの話はたまに出ます☺️」

その他Rails

Ruby

Ruby 2.7.0-preview1(Ruby公式ニュースより)

先週つっつきの後に出てきたので、主な新機能をメモします。

  • コンパクションGCの導入(メモリ空間をデフラグできる)
  • パターンマッチング(experimental)
  • REPLの改良(複数行の編集やインタラクティブハイライトなど)
  • メソッド参照演算子.:(experimental)
  • 開始値なしのrange(ary[..3]など)(experimental)
  • Enumerable#tally(要素ごとの個数をハッシュで返す)
  • JITのパフォーマンス改善

その他の2.6以降の主な変更:

  • ブロック付きで呼ばれたメソッド内のブロックなしProc.newprocでwarningを表示
  • ブロック付きで呼ばれたメソッド内のブロックなしlambdaをエラーに
  • Unicodeと絵文字のバージョンを12.0.0に
  • Unicode 12.1.0の令和文字対応

参考: ガベージコレクション - Wikipedia

trace_location: ソースコードの位置をトレースするgem


つっつきボイス:「上のツイートがこのtrace_locationを指してるのかどうかわからなかったのですが😅、構わず拾ってみました」「ヒューマンリーダブルにコールチェインをログ出力してくれるのかな?🤔」「markdownCSVでも出せるみたいですね」

サンプルログ: trace_location/result.log at master · yhirano55/trace_location

Logged by TraceLocation gem at 2019-06-08 01:10:24 +0900
https://github.com/yhirano55/trace_location

[Tracing events] C: Call, R: Return

C vendor/bundle/gems/activerecord-5.2.3/lib/active_record/connection_handling.rb:49 [ActiveRecord::ConnectionHandling.establish_connection]
  C vendor/bundle/gems/activerecord-5.2.3/lib/active_record/core.rb:59 [ActiveRecord::Base.configurations]
  R vendor/bundle/gems/activerecord-5.2.3/lib/active_record/core.rb:61 [ActiveRecord::Base.configurations]
  C vendor/bundle/gems/activerecord-5.2.3/lib/active_record/connection_adapters/connection_specification.rb:121 [ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver#initialize]
  R vendor/bundle/gems/activerecord-5.2.3/lib/active_record/connection_adapters/connection_specification.rb:123 [ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver#initialize]
  C vendor/bundle/gems/activerecord-5.2.3/lib/active_record/connection_adapters/connection_specification.rb:141 [ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver#resolve]
    C vendor/bundle/gems/activerecord-5.2.3/lib/active_record/connection_adapters/connection_specification.rb:238 [ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver#resolve_connection]
      C vendor/bundle/gems/activerecord-5.2.3/lib/active_record/connection_adapters/connection_specification.rb:268 [ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver#resolve_hash_connection]
      R vendor/bundle/gems/activerecord-5.2.3/lib/active_record/connection_adapters/connection_specification.rb:274 [ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver#resolve_hash_connection]
    R vendor/bundle/gems/activerecord-5.2.3/lib/active_record/connection_adapters/connection_specification.rb:247 [ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver#resolve_connection]
  R vendor/bundle/gems/activerecord-5.2.3/lib/active_record/connection_adapters/connection_specification.rb:149 [ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver#resolve]
  C vendor/bundle/gems/activesupport-5.2.3/lib/active_support/core_ext/hash/keys.rb:56 [Hash#symbolize_keys]
  R vendor/bundle/gems/activesupport-5.2.3/lib/active_support/core_ext/hash/keys.rb:58 [Hash#symbolize_keys]
...

「これ、呼ばれてる場所を実行した順に追えるのが結構便利そうですね😋」「Railsってコードを見ててもどんな順序で動いてるのか割とわからないし😆」「ソースコードだとマジわからないし😆」

「trace_locationはRubyのどのバージョンから使えるんだろう?TracePointを使っているなら新しめのRubyじゃないと動かないだろうし」「それもそうか」「gemspecを見ると2.5.0以降ですね」「たぶん処理的には相当重いと思われ😆」

RuboCop入門(銀座Rails#9)

BPSのWebチームで銀座Railsを見に行った方がいたので。以下はSlackのメモから。

もうすぐ1.0が出るよ!
– 1.0に盛り込まれる目玉的なものはスライド71ページ
koicさん「デフォルトが厳しすぎるからカスタマイズして使っていいと個人的には思ってる。」


つっつきボイス:「RuboCop 1.0がもうすぐ出るそうです」「あ、まだ出てないのか」「例のRuboCopを分割する話とかも含めてやるんでしょうね」

「ちなみにRuboCopを使ってる人は?」「やはり多い😆」「じゃデフォルトのrubocop.yamlを使ってる人は?さすがにいないか😆」「RuboCopのデフォルト厳しいっすよね😇」「何をやってもABCサイズがどうとかline lengthがこうとか言われますし🤣」「koicさんもそうおっしゃってくれていますし😆」

Ruby trunkより

CGI.escapeHTMLの最適化


つっつきボイス:「mattnさんがRubyにコミットしたと聞いて」「CGIライブラリって今使ってる人どのぐらいいるんだろ😆」「CGIってあのCGI?」「ですね、RailsとかではないRubyのライブラリ」「しかもC言語のコードだし😳」「じゃ速そう😆」

参考: library cgi (Ruby 2.6.0)


前編は以上です。

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

週刊Railsウォッチ(20190604-2/2後編)Cloudflare Workers KVの可能性、PostgreSQL 12 Beta 1、Bootstrap 5でjQuery廃止ほか

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

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

Ruby 公式ニュース

Rails公式ニュース

Ruby Weekly

RubyFlow

160928_1638_XvIP4h


Rails: テキストフィールドのリンクを無効にしてスパムを防ごう(翻訳)

0
0

概要

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

Rails: テキストフィールドのリンクを無効にしてスパムを防ごう(翻訳)

多くのアプリケーションでは、招待メールや通知メール、パスワード忘れのリマインダーメールの形でメールを送信します(必ずしもメインの機能とは限りません)。

ユーザーが生成したコンテンツを送信メールで許可してしまうと、たちまちアプリケーションはメールスパム業者の格好の標的になってしまいます。スパム業者はろくでなしの人間のこともあれば彼らの操るボット軍団のこともあり、あなたが正式なユーザーに許可してしまった自由文入力機能を悪用します。

AppleメールやGmailのようなメールクライアントは、メールのWebアドレス的な文字列を自動的にハイライト表示します。つまりハッカーたちはテキストフィールドにそれらしいWebアドレスを入力するだけで、HTMLの注入すら行うことなく、ユーザーをろくでもないWebサイトに導くことができてしまいます。

決して次のように書かないこと

アプリでスパム業者たちがメールを送信できるように書いてしまう。

<%= form_for :invitee do |f| %>
  Email: <%= f.text_field :email %>
  Message : <%= f.text_field :message %>
  <%= f.submit "Send Email" %>
<% end %>

常に次のように書くこと

バリデーションを用いて、テキストフィールドへのWebアドレスの挿入を阻止する。

<%= form_for :invitee do |f| %>
  Email: <%= f.text_field :email %>
  Message : <%= f.text_field :message %>
  <%= f.submit "Send Email" %>
<% end %>
  • validators/no_urls_validator.rb
class NoUrlsValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    return if value.blank?

    if value.match?(%r{(http|\w+\.\w+\/?)})
      record.errors[attribute] << (options[:message] || "Webアドレスが含まれているようです")
    end
  end
end
  • app/models/invitee.rb
class Invitee
  # ...
  validates :message, no_urls: true
  # ...
end

そうすべき理由

これは気づかれにくい課題ですが、スパム業者たちに知られていて悪用される攻撃方法であることは間違いありません。ユーザーが入力した自由文を含むメール送信をアプリケーションで許可したら最後、テキストがどれほど短くても確実に標的にされてしまいます。

最も顕著なのは、支払いを受け取る前にメール送信をアプリケーションで許可している場合です。もしメール送信前に正しいクレジットカードの入力を義務付けていなければ、スパム業者が「無料お試し」にサインアップするスクリプトを自動で実行し、アプリからリンクを送信する可能性があります。

アプリから大量のスパムメールが送信されると、アプリのメール配信全体はもちろん、ビジネスドメインにまで悪影響が生じる可能性があります。そんな事態を望む人はいません。

ユーザーが生成したHTMLやJavaScriptからブラウザを保護する機能はRailsの初期から組み込まれていますが、Webサイトらしく見える非HTMLテキストを自動的にリンク化するメールクライアントまでRailsアプリケーションが面倒を見ることはできません。

ユーザーが入力したテキストをバリデーションすることで、このスパム手法の標的になる可能性を減らせます。

そうしない理由があるとすれば

私がバリデーターで使っていた正規表現は出来が今ひとつで、たまにfalse positive(偽陽性)になることがありました。httpという文字列を含んでいたり、like.thisのようにドットでつながった文字があるだけで却下していたのです。

そうした正規表現は、マッチするパターンを狭めるよう変更しましょう。

あるいは、バリデーションで防いだりユーザーにエラーメッセージを表示する代わりに、コールバックでテキストを修正するという手もあるといえばあります(あらゆるドット.の後ろにスペースを追加するなど)。

"like.this".tr(".", ". ")
#=> "like. this"

他にも、もし可能なら、ユーザーがお金を払うまではテキスト入力などの機能を制限する方法を検討する手もあります。正しいクレジットカードの入力を義務付けることで、スパム業者を効果的に防げることがよくあります。

関連記事

Railsアプリで実際にあった5つのセキュリティ問題と修正方法(翻訳)

週刊Railsウォッチ(20190617-1/2前編)マルチプルデータベースガイドが追加、mmcと「Ruby 3の型解析に向けた計画」、Ruby 2.6のCSVライブラリはいいほか

0
0

こんにちは、hachi8833です。Macbook Pro late 2013の次をどうするか心が揺れてます。


つっつきボイス:「WSL2の出来はかなり気になるヤツですね(今Windowsだし❤)」「結局POSIX APIを頑張ってラップしてどうにかするのを諦めてLinuxカーネルを内包する流れになったという😆」「WindowsのPOSIX APIはつくづく👎だったけど、あれはまあ別物ですし」「今macOSのbashとParallels DesktopのUbuntu VMのbashの二重生活なんですけど😢、WSL2でもさすがに1つにはならないんでしょうか?」「ハイパーバイザでLinuxを動かすならまあDocker for Windowsを使ってるのと大差ないですね😆」「やっぱり〜😆

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

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

今回はコミットリストから見繕いました。ドキュメント更新がだいぶ増えています。


つっつきボイス:「そろそろRC2が出そうかなと」「これだけかかるとRC2必要でしょうね😆

⚓MessageEncryptoron_rotationがコンストラクタで渡されるようにした

  • メッセージの暗号解除/検証時に使われるon_rotation procをコンストラクタのレベルで渡されるようにした
    Before:

    crypt = ActiveSupport::MessageEncryptor.new('long_secret')
    crypt.decrypt_and_verify(encrypted_message, on_rotation: proc { ... })
    crypt.decrypt_and_verify(another_encrypted_message, on_rotation: proc { ... })

    After:

    crypt = ActiveSupport::MessageEncryptor.new('long_secret', on_rotation: proc { ... })
    crypt.decrypt_and_verify(encrypted_message)
    crypt.decrypt_and_verify(another_encrypted_message)

つっつきボイス:「暗号化で言うローテーションでしょうか?」「on_rotationはどうやら鍵のフォールバックで使うものらしい」

MessageEncryptorは、encryptorのスタックへのフォールバックによって古い設定のローテーションをサポートする。rotateを呼んでビルドし、encryptorを追加すると、decrypt_and_verifyでもフォールバックを試行する。
ローテーションされたencryptorは、デフォルトでは他を指定されてない場合プライマリのencryptorを用いる。
api.rubyonrail.orgより大意

「ちなみに、鍵を変えないといけなくなる状況ってやっぱりあって、極端な場合は、間違えてsecretをコミットしちゃったとかお漏らししちゃったとか😅」「あ〜」「そこまでいかなくてもsecretを知っている人が退職したときなんかもそうですね: 正直面倒ですし、NDAで縛るという手もありますが、やはり鍵はちゃんと変えるべき」「ですね😅

on_rotationは新しいらしく、6.0↓には入ってますがRails 5.xのAPIにはありませんでした。

⚓Action Viewのテストをパラレル実行

# actionview/test/abstract_unit.rb#L194
class ActiveSupport::TestCase
+ parallelize
+
  include ActiveSupport::Testing::MethodCallAssertions

  private
    # Skips the current run on Rubinius using Minitest::Assertions#skip
    def rubinius_skip(message = "")
      skip message if RUBY_ENGINE == "rbx"
    end
    # Skips the current run on JRuby using Minitest::Assertions#skip
    def jruby_skip(message = "")
      skip message if defined?(JRUBY_VERSION)
    end
end

つっつきボイス:「parallelizeって書くとパラレルになってくれるんだ!」「せっかく作ったのに書き漏れてたから足したっぽい」「パラレライズって何となく麻酔が効きそうな響き(それはparalyze)😆

      def parallelize(workers: :number_of_processors, with: :processes)
        workers = Concurrent.physical_processor_count if workers == :number_of_processors
        workers = ENV["PARALLEL_WORKERS"].to_i if ENV["PARALLEL_WORKERS"]

        return if workers <= 1

        executor = case with
                   when :processes
                     Testing::Parallelization.new(workers)
                   when :threads
                     Minitest::Parallel::Executor.new(workers)
                   else
                     raise ArgumentError, "#{with} is not a supported parallelization executor."
        end

        self.lock_threads = false if defined?(self.lock_threads) && with == :threads

        Minitest.parallel_executor = executor

        parallelize_me!
      end

parallelize_me!ですって。

⚓値をnumericで返すアダプタに統一

# activerecord/test/cases/calculations_test.rb#L591
  def test_should_sum_expression
-   if current_adapter?(:SQLite3Adapter, :Mysql2Adapter, :PostgreSQLAdapter, :OracleAdapter)
-     assert_equal 636, Account.sum("2 * credit_limit")
-   else
-     assert_equal 636, Account.sum("2 * credit_limit").to_i
-   end
+   assert_equal 636, Account.sum("2 * credit_limit")
  end

つっつきボイス:「前は一部のアダプタのテストでto_iしてたのが要らなくなった?」「SQLite3とMySQL2とPostgreSQLとOracleは前からnumeric値を返してたのね」「こんな違いがあったなんて知らんかった〜」「この4つ以外にRailsがサポートしているアダプタって何があるんだろか😆」「わかりません😆」「よくぞ見つけたという感じ」

「numericでなかった場合は何が返ってきたんでしょう?」「文字列ではないかと☺」「あ〜」「BigIntかな?と思ったけどそれなら問題ないはずだし」

⚓safe SQL文字列関連


つっつきボイス:「@kamipoさんがsafe SQL関連のコミットをいくつか投げてたので」「え、そもそもNULLS FIRSTと書ける↓なんて知らなかった!😳」「ぽすぐれとOracleの構文なのか」

# activerecord/test/cases/relations_test.rb#L338
  def test_reverse_order_with_nulls_first_or_last
    assert_raises(ActiveRecord::IrreversibleOrderError) do
-     Topic.order(Arel.sql("title NULLS FIRST")).reverse_order
+     Topic.order("title NULLS FIRST").reverse_order
    end
    assert_raises(ActiveRecord::IrreversibleOrderError) do
-     Topic.order(Arel.sql("title  NULLS  FIRST")).reverse_order
+     Topic.order("title  NULLS  FIRST").reverse_order
    end
    assert_raises(ActiveRecord::IrreversibleOrderError) do
-     Topic.order(Arel.sql("title nulls last")).reverse_order
+     Topic.order("title nulls last").reverse_order
    end
    assert_raises(ActiveRecord::IrreversibleOrderError) do
-     Topic.order(Arel.sql("title NULLS FIRST, author_name")).reverse_order
+     Topic.order("title NULLS FIRST, author_name").reverse_order
    end
    assert_raises(ActiveRecord::IrreversibleOrderError) do
-     Topic.order(Arel.sql("author_name, title nulls last")).reverse_order
+     Topic.order("author_name, title nulls last").reverse_order
    end
- end
+ end if current_adapter?(:PostgreSQLAdapter, :OracleAdapter)

NULLS FIRSTってわかる: ORDER BYしたときにnullが最初に来るか末尾に来るかが問題になることあるし」「あ〜そういうことですか!」「知らなかったわこれ〜: たしかにこれ制御したいことってあるし😋」「もちろんnullはnullなので本来的にはどうかというのはあるんですけど😆、nullを含む表示を制御したいときはあるので」「改修よりそっちの方がすげ〜感😋

「改修は、Arel.sql使わなくてもTopic.order("title NULLS FIRST")と書けばSQLになってくれるぞと」「サニタイズしなくてもよくなったということでしょうか?」「詳しくは知らないけど、前はArel.sqlなしで書くとwarningとか出てたのが、SQLインジェクション対策が強化されたりしたことでArel.sql使わなくてもチェックされるようになったんじゃないかなと想像してます☺

「他の2つもsafe SQL関連みたいです」「使っていいリストに追加したんですね: お〜AS、これは書きたいヤツ😍

# activerecord/lib/active_record/connection_adapters/abstract/quoting.rb#L162
      COLUMN_NAME = /
        \A
        (
          (?:\w+\.)?\w+
+         (?:(?:\s+AS)?\s+\w+)?
        )
        (?:\s*,\s*\g<1>)*
        \z
      /ix

「カラム名に関数を含む場合↓、これも使っていいリストに追加したと」「こういうのを汎用的に書くのって大変ですよね😭

# activerecord/lib/active_record/connection_adapters/abstract/quoting.rb#L158
      COLUMN_NAME = /
        \A
        (
-         (?:\w+\.)?\w+
+         (?:
+           # table_name.column_name | function(one or no argument)
+           ((?:\w+\.)?\w+) | \w+\((?:|\g<2>)\)
+         )
          (?:(?:\s+AS)?\s+\w+)?
        )
        (?:\s*,\s*\g<1>)*
        \z
      /ix

「こうやっていろいろよくなってくるとRails 6.0に期待しちゃいますね😋

⚓番外: privateの次の空白行を削除

# ctioncable/lib/action_cable/server/worker.rb#L68
      private
-
        def logger
          ActionCable.server.logger
        end
    end

つっつきボイス:「修正はちょろいんですが、更新ファイル数がめちゃ多かったので😆」「RuboCopのLayout/EmptyLinesAroundAccessModifierが追加されたと」「アクセス指定子(private)の前は空行あり、次は空行なしがRuboCopのデフォルトだったかな」

「このprivateから先のインデントを1つ下げるかどうかという議論もありますよね😆」「privateの先はインデントしてない(privateと同じインデントにする)の方が普通に見かける気がするんだけど?🤔」「私もそんな気がします」「インデントするのもたまに見かけますけど☺

privateの下はインデントする派?」「ぼくはしませんね〜☺」「やっぱそうですよね😆」「自分もしたくない派😆」「上の変更は前からインデントしてるけど、そういう文化なのかな?☺

⚓Rails

⚓Rails 6アップグレードガイドの「オートローダー」


つっつきボイス:「この間Rails 6アップグレードガイドを更新翻訳したんですけど、やはりメジャーアップグレードだけに注意書きが普段より多めだなと思ったので」「メジャーアップグレードならありそう☺

「特にオートローダーがZeitwerkに変わったあたりの記述が、どちらかというと従来のオートローダー周りの謎の挙動がいろいろわかってきたという感じでした😆」「オートローダーって何か困ったことがあるまで調べませんし🤣」「6.0ではclassicモードも使えると」

⚓Rails 6ガイドの「マルチプルデータベース」


最後に、データベースをまたがるJOINは利用できません。Rails 6.1ではhas_manyリレーションシップの利用と、JOINではなく「2つのクエリ作成」をサポートする予定ですが、Rails 6.0ではJOINを手動で2つのSELECTに分ける必要があります。
同PRより

つっつきボイス:「こちらもRails 6で追加されたマルチプルデータベースガイドなんですけど、Rails 6はデータベースをまたがるJOINをサポートしないんだそうです↑」「前回話しましたけど(ウォッチ20190610)、MySQLやPostgreSQLではデータベースをまたがるJOINはサポートされています: ここで言っているのはRailsのArelではデータベースをまたがるJOINはサポートしないということでしょうね」「なるほど!」「たしかデータベースをまたがるJOINってSQL標準から少し外れてたと思いますし、サーバー側の設定にもよると思うのでサポートしないんじゃないかなと想像してます🤔

「まあクエリを2つ書けばやれますけどっ😆」「ガイドにもありますね」「どうしても欲しければ生SQL書けばいいんだし☺

なお、つっつきの後で更新情報出ました。

⚓light-service: Service Object gem


同リポジトリより


つっつきボイス:「また別のService Object gemですけど、他のと違うところはどこかなと思って」「記事を見ると、こういう感じで一連のService Objectたちを1個のService Objectにまとめてオーガナイズする、ライトサービスオーガナイザーというもののようだ」「あ、Serviceをまとめるヤツですか」「callするとwithのところに書かれたService Objectを順番に呼んでくれるんでしょうね: reduce使ってるのはちょいどうかと思いますけど😆

# 同記事より
class CreateOrder
  extend LightService::Organizer

  def self.call(user, subtotal)
    with(user: user, subtotal: subtotal).reduce(
        VerifyActiveUser,
        CalculateTotal,
        CreateOrderRecord,
        PayForOrder,
        SendConfirmationEmail,
        AddHistoryItemToUserHistory,
        NotifyServiceTeamOfSpecialtyOrder
      )
  end
end

「これは欲しいときありますね😍」「ちゃんと読まないとわかりませんが、この感じからして途中で失敗したときの復帰なんかもうまくやってくれそう😋」「お〜!」「gemなしで自分で書けるんでしょうか?」「まあ自分で書いてもいいんですけど、エラー処理のこととかを考えると共通ライブラリになっている方が使いやすそうではありますね☺

「README長い〜😅」「お、かわいい図が🐥」「オーガナイザがアクションの呼び出しをまとめているということでしょうね」「promisesってあるけど、非同期実行かと思ったらコンテキストのキーを設定するものみたい😅


同リポジトリより

「ちゃんと動くなら悪くなさそう😋」「試してみようかな🥰

⚓その他Rails


つっつきボイス:「へぇ〜、ary.any?って特化命令ないのか」「特化命令って何でしたっけ?😅」「Active Recordにそういうのがあるのかなと思ったらこれのことのようですね↓」

参考: YARV Maniacs 【第 9 回】 特化命令

Ruby では今までこれをそのままメソッド呼び出しとして実行しており、これが「Ruby は遅い」と言われる原因になってきました。とくに、よく評価に利用されるマイクロベンチマーク (つまり、すごーく単純な性能評価のためのプログラム) では、この「1 + 2」のような単純な計算のコストが実行時間全体に対して多くの比率を占めるので、ここが遅い Ruby は不利になっていました。これに対して、例えば JavaVM では 1 などの整数値を特別扱いすることで性能の向上をはかっています。
そこで YARV では「1 + 2」のような単純な計算を高速に実行する工夫をしています。それが特化命令です。
magazine.rubyist.netより

⚓Ruby

⚓mmc: もうひとつのType Profiler


つっつきボイス:「スライドは冒頭から『タイトルは釣りです』🤣」「なるほど、Typer Profilerね☺」「mmcってメモリマネジメントコントローラかと思た🤣」「遠藤さんがやってるのと似てる?」

「そうそう、この抽象実行↓: こういうふうにFixnumみたいな型だけを追いかける」「たぶんこのあたりはRubyKaigiの遠藤さんのスライドで追えばわかりそう☺」「mmcはそれといろいろ関連してそうですね🤔」「いろいろおもしろいスライド😋

「遠藤さん、RubyKaigiのときはまだまだ動かないものがあるとは言ってたけど😆」「やっぱハッシュテーブルの中も追いかけないといけないか〜」

後日


regional-gh.rubykaigi.orgより

つっつき後にmametterこと遠藤さんのとても新しいスライドを見つけました。この間の名古屋Ruby会議で発表されたそうです。

同スライドに以下の記述があることから、mmcがRuby 3の型解析に影響を与えつつあるということのようです。

もろもろ後で読んでみます😅

⚓Ruby 2.6のCSV最適化がスゴい


つっつきボイス:「こちらもRubyKaigi 2019の後追いですが、今日は忙しくて出られないkazzさんが『Ruby 2.6のCSVリファクタリングの話がとてもよかった: 最適化してめちゃ速くなったのにとてもきれいなコードで、最適化=汚くなるという通念を覆された』と激賞してたので集めてみました」「なるほどっ☺」「fastestCSVってどういう位置づけなんでしょう?🤔」「おそらくRubyに元からあったCSVモジュールが遅かったので、fasterCSVというgemがあって、それがその後RubyのCSVに取り込まれたときにfastestと銘打ったんでしょうね☺」「fastestの次はどういう名前にするんでしょう😆

参考: FasterCSV - Ruby

「RubyのCSVは以前から結構よくできてて、文字コード変換なんかもよしなにやってくれたりするんですよ❤」「クォートがある場合とかない場合とかでかなり細かく場合分けしてるんですね」

「出たな!KEN_ALL.csv🤣」「こういうベンチマークにはむしろうってつけですね☺」「KEN_ALL.csv、数百MBぐらいあるかと思ったら12MBぐらいでしたか😳」「KEN_ALL.csvってたしか2種類ぐらいあった気が」「しかもcp932混じってるし😇」「KEN_ALL.csvじゃしょうがない😆

参考: 郵便番号データダウンロード - 日本郵便
参考: KEN_ALL.CSV (郵便番号検索)の落とし穴 - Qiita
参考: 本当は怖くないCP932 - Qiita

StringScanner使うよねやっぱ😆」「ここにいる人はあまり使わないかな?」「以前もちょっと話した気はするんですが、StringScannerを使うと任意長の文字列に対してカーソル的にスキャンできる: 1文字ずつ読んだり、4文字進めたり」「へぇ〜!」「頑張れば改行を含んでたりしても処理できます: かなり地獄感ありますけど👹」「StringScannerは結構好き❤: デバッグは大変だけど😆

参考: class StringScanner (Ruby 2.6.0)

# docs.ruby-lang.orgより
require 'strscan'

s = StringScanner.new('This is an example string')
s.eos?            #=> false

p s.scan(/\w+/)   #=> "This"
p s.scan(/\w+/)   #=> nil
p s.scan(/\s+/)   #=> " "
p s.scan(/\s+/)   #=> nil
p s.scan(/\w+/)   #=> "is"
s.eos?            #=> false

p s.scan(/\s+/)   #=> " "
p s.scan(/\w+/)   #=> "an"
p s.scan(/\s+/)   #=> " "
p s.scan(/\w+/)   #=> "example"
p s.scan(/\s+/)   #=> " "
p s.scan(/\w+/)   #=> "string"
s.eos?            #=> true

p s.scan(/\s+/)   #=> nil
p s.scan(/\w+/)   #=> nil

⚓CSVあれこれ

「CSVといえば、AAAA,AAAA,AAAAみたいにダブルクォートなしのもあれば"AAAA","AAAA","AAAA"みたいにダブルクォートありのもありますし、あとダブルクォートをどうやってダブルクォートの中に入れるかなんて話もあったり、もうキリがない😇」「たしかExcelのCSVは、ダブルクォートの中に出てくるダブルクォートは2つ重ねないといけないんですよね🤣」「Excelローカルルール🤣」「私そのあたりはもう理解放棄しました😇

「そういえばCSVのRFCってあるんでしたっけ?」(ググる)「あぁ!一応あるし!」「RFC4180ですか」「2005年だけに番号若いし👶」「つまり2005年まではCSVの仕様なかった🤣

参考: CSVファイルの一般的書式 (RFC4180 日本語訳) - アルプス登山の玄関口・笠井家

ファイル末尾のレコードの終端には、改行はあってもなくてもよい。

「改行はあってもなくてもよいなんて書いてますし😆」「あってもなくても動かないといけないとは😭」「ひど〜い😢」「ヘッダ行が存在してもよい、とかも😆」「やめて〜😅」「もう現状追認まくり😇」「こんなものでもどこかにまとまってないとつらいばっかりですよね☺」「さっきのダブルクォートも仕様に入ってるし↓」

フィールドがダブルクォーテーションで囲まれている場合、フィールドの値に含まれるダブルクォーテーションは、その直前にひとつダブルクォーテーションを付加して、エスケープしなければならない。

「この仕様、一応ABNFになるんだ?!」「ABNFで表現できるなら一応マシか🤣」「お気持ちではなくなったと🤣

「つまり『CSVを扱って欲しい』とか言われたら『それはRFC4180に則ったCSVということでいいんですよね?』という確認ができるのでとっても話が早くなる❤」「それ重要!!」「CSVの仕様を個別に確認するとかしたくないですし😇」「区分がInformationalなので標準とまではいかないにしても🤣」「それでもあるだけうれしい😂」「そもそもCSV使いたくないけどっ🤣

参考: ABNF - Wikipedia


「一説によると、CSVは『comma separated values』ではなくてタブ文字も含めて『character separated values』じゃないかって😆」「どっちだ😆」「軽くググった感じではcommaの方が多いみたいですね」「タブ区切り好きなのでcharacterを推したいです😍

参考: Comma-Separated Values - Wikipedia

⚓スペーシングが奇妙

# kaminari-core/test/helpers/action_view_extension_test.rb#L546
      test 'the last page' do
        users = User.page(3).per(1)
-       assert_equal'/users?page=2', view.path_to_prev_page(users, params: {controller: 'users', action: 'index'})
+       assert_equal '/users?page=2', view.path_to_prev_page(users, params: {controller: 'users', action: 'index'})
      end

つっつきボイス:「kaminari gemなんですけど、コミットのタイトルが個人的に刺さったので♫」「↑おおこれ😆: よく見つけたし」「RufoとかPrettierとかで退治したいヤツ」

「Space OddityはDavid Bowieの大昔のヒット曲で、実は一昨年のRubyKaigi@広島のRubyカラオケでこれ歌いました(外人部屋で)🎤」「ちょ😆

参考: スペイス・オディティ (曲) - Wikipedia

⚓Rubyのスネークケース

つっつきボイス:「こういうMatzが直に回答してくれるQ&Aが読めるってさりげにうれしいです😋」「キャメルケースをうるさいと感じる人ってたまにいますよね」「そうかも」「スネークケースは文字数が増えるからヤダという人もいたりとか」

⚓BASIC言語よもやま

「そういえばむかーしのBASIC言語なんかだと変数名が長すぎるとメモリ食うからダメ、なんてのがありましたね😆」「私の頃のBASICはもっと古くて、変数名は何文字であっても最初の1文字しか認識されなかったので、変数は最大で26個しか使えない仕様でした🤣」「それ知らない🤣」「インタプリタってそういう縛りありがちですね🤣」「なので逆に2文字目以降は好きに名前つけられちゃいました🤣

参考: BASIC - Wikipedia

「ちなみにこちらの若い方はBASICとかやったことって?」「1ミリもありません」「やっぱり🤣」「🤣」「じゃ触ったことのある言語で古くからありそうなのは?」「うーん、C言語っすかね😅」「あ、そりゃそうか😆

⚓C言語よもやま

「じゃC言語は頑張ればunion(共用体)とかあっても読めたりします?」「たぶん無理じゃないかと😆」「unionは自分もほぼ読まないし😆」「unionはLinuxカーネルとかネットワークのプロトコルスタックとかだとめっちゃ出てきますけどっ😆」「union、自分ではまず書かなさそう😅」「アラインメント合わせるときぐらいでしょうし」

参考: 共用体(union) - Wikipedia

「そういえばLinuxカーネルあたりはC言語のマクロがやたらめったら使われてますよね😆」「C言語本来の部分がめちゃ少なかったり😆」「マクロがもはや謎の拡張言語と化してる感😆」「Linuxカーネルのソース、一度読んでみるとわかりますけど、なんでこんなことがC言語でできるんだ??って思うことだらけですよもう🤣」「そんなに😆

参考: C言語のマクロの基本

「いやもうめっちゃありますって: スレッドの構造体から、その親のスレッドが所属するプロセスを取りに行くのをマクロでやってたりしますし🤣」「なぜマクロで🤣」「スレッドの構造体はその親のスレッド構造体へのポインタとか持ってないのに、どうやってやってるんだと思って調べたらマクロでしたよ!マクロ!😇

「それってマクロの中で処理しちゃうんでしょうか?」「そこがまたよくできていて、構造体のアラインメントが決まってるから、そこからマイナス何バイトとかやると取れる🤣」「だっはっは🤣」「C++だとコンパイラによって結果変わるかもしれないけど似たようなことやったりしますよね: ここから何バイト遡れば親の構造体があるはずみたいな」

「あんましやっちゃいけなさそうですけど😆」「何だかバッファオーバーランと紙一重😆」「Cで書かれたカーネルだとそういうのが普通に出てきますし😆、そもそもOSのカーネルだからユーザーランドと違ってバッファオーバーランではないし」「あそれもそうか😅」「OSの特権🤴

参考: バッファオーバーラン - Wikipedia

⚓その他Ruby


つっつきボイス:「プロのRubyプログラマーは30万人で、Rubyを使ってるプログラマーは100万人ですって」「Rubyプログラマーの数といっても、Rubyが書ける人をどう数えているのかですよね😆


「k0kubunさんがBuildersconに応募してるのか」「まだ審査中のようですね」「Buildersconは毎年やってますね〜❤」「何だかとっても強そう」

参考: builderscon - 知らなかった、を聞く


builderscon.ioより

2019年度は8/29〜8/31に開催だそうです。

審査中のセッション一覧を見たら、ものすごいエントリ数…!行ってみたいです。

参考: セッション一覧 - builderscon tokyo 2019


つっつきボイス:「そうそう、@kamipoさんのこのツイートなるほどというかとてもわかりみ深い😋: ぴよコードに文句を言う前に歴史を調べようぜと」「ですね〜」「コードが変だったからプルリク投げるとかではなくて、こういう歴史上の理由があって変になっていたからこう直すのが設計的にも合っているはず、までやると」「実際こういうことって結構ありますね☺


前編は以上です。

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

週刊Railsウォッチ(20190611-2/2後編)Dockerfileベストプラクティス、DBの冗長化、jQueryとの付き合い方ほか

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

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

Rails公式ニュース

週刊Railsウォッチ(20190618-2/2後編)決済の分散トランザクション、フロントエンドのMicro Frontendアーキテクチャ、GitHubのコードジャンプほか

0
0

こんにちは、hachi8833です。この半年ほど自宅のネット接続がみるみる遅くなっています。

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

⚓お知らせ: 第12回公開つっつき会(無料)

開始以来ついに1年目を迎える第12回目公開つっつき会は、7月4日(木)19:30〜にBPS会議スペースにて開催されます。皆さまのお気軽なご参加をお待ちしております🙇

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

⚓🌟決済の分散トランザクション🌟


つっつきボイス:「そうそう、WebチームのSlackにも流しましたけど、メルカリさんのこの記事はマジで力作❤」「はてブでもバズってましたね😀」「実はこの記事は、マイクロサービスの話を抜きにしても成立します👍」「え、そうだったんですか?😳」「もちろんマイクロサービスと完全に無関係ではありませんが、マイクロサービスに限らない分散環境でのトランザクションはどこが難しいかという点を詳しく解説してくれています😋

「当たり前なんですけど、決済って分散するものなんですよ」「確かにそうですね!」「自分のところのシステムと、外部の決済代行システムって別物ですから☺

「この図↓といい記述といい、よくここまで徹底的に詳しく書いたなって感心するしかないです😂」「しびれる内容ですね🥰


同記事より

「といっても特に目新しい話はありませんけどっ😆、図もしっかり作られているし、しかも具体的なのが本当にありがたい🙏」「なるほど〜!」「トランザクション一般についての話なら教科書などにいくらでも載っているんですけど、教科書にあるような一般的な話と、業務の現場で実際に起こることってどうしても距離があるんですよ: その点この記事はステートマシンなども登場しつつ、とことん具体的で、大事なことをひととおり押さえてくれています😋

「しかもこの記事では用語を正しく使っているのも見逃せませんね: おかげで用語を安心してググれます😍」「偉大な仕事!」

「その分、この記事の流し読みはたぶん無理😆」「でしょうね〜😆」「ちゃんと理解するなら、うんうん唸りつつ図と本文を行ったり来たりしながらじっくり読むことになるでしょうね☺: しんどいけど、この記事の内容を解説するだけで軽く1時間以上話せるぐらい濃厚😆」「😆」「ちょろっと流し読んだだけだと『じゃここ説明して』『これが起きるとどういうことで困るの?』って聞かれてもお手上げです🤣」「🤣」「時間をかけて読むべき良記事👍」「声に出して読みたい気持ちです😅

🌟を進呈いたします。おめでとうございます!

⚓その他インフラ


つっつきボイス:「たしかに今どきIPアドレスを静的に設定する状況ってどんなときだろう?」「WiFiルーターとの接続を設定するときも、最近だと、えっと名前が出てこない…AOSS!」「使ったことないですけど😆」「自分も😆」「最近BPS社内の若手がネットワークスペシャリストの勉強会やってるからきっと押さえてると思いたい😆

参考: AirStation One-Touch Secure System (AOSS) - Wikipedia

「ところでここにいる若手に質問しますけど、IPアドレス手動で設定したことってあります?」「ないんじゃないかな〜って😆」「マジっすか!😆」「そんなにゆとってた?🤣」「🤣」「開発中にやったことぐらいはあるでしょ?」「それならあります☺」「やっぱり〜」「でないとどうやって開発するんだろって思っちゃいますし🤣

「私の周辺だとIP設定したことない人何人もいますね〜😆」「でもWebアプリ作ってたら、リバースプロキシがらみでIPアドレス設定せざるを得ないことってある気がするし☺」「末端で作業しているとその機会もなさそうですね」「あ、できあがったものの上で開発してたりするとそうかも😆」「まあIPアドレスが設定できるからといってどやぁ〜!とできるわけでもありませんし🤣」「🤣」「IP設定したことなくても困らないといえば困らない」「困りますよきっと〜😆」「困るまでは困らないと思うしっ😆

⚓DB

⚓PostgreSQLのBloom filterインデックスとは(Postgres Weeklyより)


つっつきボイス:「BloomってPostgreSQLのプロジェクト名か何かかな?と思ったら、本当にBloomフィルタなのか」「classical Bloom filterってあるのは以前ウォッチで扱ったものみたいですね(ウォッチ20180827)」「Bloomフィルタって空間効率のよいデータ構造で云々かんぬんみたいなやつでしたっけ」「ぽすぐれの9.6のドキュメントにもBloomフィルタあるから、前からあったっぽい」

参考: PostgreSQL: Documentation: 11: F.5. bloom
参考: ブルームフィルタ - Wikipedia

ブルームフィルタ(英語: Bloom filter)は、1970年に Burton H. Bloom が考案した空間効率の良い確率的データ構造であり、要素が集合のメンバーであるかどうかのテストに使われる。偽陽性(false positive)による誤検出の可能性があるが、偽陰性(false negative)はない。要素を集合に追加することができるが、削除することはできない(counting filter を使えば削除できる)。集合に要素が追加されればされるほど、偽陽性の可能性が高くなる。
ja.wikipedia.orgより

「偽陽性はあっても偽陰性はないと」「bloomって予約語があるのね」「using bloomか〜 」「まさにインデックス作成部分ですね」

demo=# create index flights_bi_bloom on flights_bi
using bloom(airport_code, airport_utc_offset, flight_no, flight_type, aircraft_code, seat_no, fare_conditions, passenger_id, passenger_name)
with (length=96, col1=7, col2=7, col3=7, col4=7, col5=7, col6=7, col7=7, col8=7, col9=7);

「この記事↓に載ってそうかと思ったけど惜しい、Hashインデックスの話だった😇」「NTTデータにはぽすぐれに超詳しい人がいるらしいという噂ありますね💪」「強い人の解説求む😆

参考: 第2回「Hashインデックス」 | NTTデータ先端技術株式会社

「やっぱり公式ドキュメントかな😅

参考: PostgreSQL 9.6.5文書 — F.4. bloom

ブルームフィルターは、空間効率の良いデータ構造で、ある要素が集合のメンバーかどうかをテストするのに用いられます。 インデックスのアクセスメソッドとして使用する場合、インデックス作成時に大きさが決まるシグネチャーを使って、条件を満たさないタプルを高速に除外することができます。
(中略)
この種のインデックスは、テーブルに多数の属性があり、その任意の組み合わせを検索する問い合わせを実行するときにもっとも有効です。 伝統的なbtreeインデックスはブルームインデックスよりも高速ですが、可能なすべての問い合わせをサポートするためには多数のbtreeインデックスが必要なのに対し、ブルームインデックスでは、たった一つのブルームインデックスだけで事足ります。 しかし、ブルームインデックスでは等価検索だけをサポートすることに注意してください。 btreeインデックスでは、等価だけでなく、範囲検索も実行できます。
postgresql.jpより

「ぽすぐれっていろんなインデックスがあるんですね」「だいたいはB-Treeあたりで足りますけど、特定のユースケースでは別のインデックスの方がいいことがあったりしますね☺」「データの性質とかが違う場合ですね😋」「ですね: 順序が重要なデータとかみたいな用途に応じてインデックスを選ぶ必要があるでしょうね」

⚓JavaScript

⚓書籍『最新JavaScript開発』


つっつきボイス:「この間BPS社内でJavaScript勉強会をやった後に見つけた本なんですけど、JavaScriptが中途半端な今の自分的には、JSの大まかなところを押さえるのにとても助かりました」「あーなるほど、JavaScriptのアット・ア・グランス的な感じの本ですか☺」「Node.jsの非同期部分の概要とか、ちょうど自分が知りたい取っ掛かり部分だったので」「…と、2017年のレビューを見ると『ミスがある』とかありますけど😆」「買ったのはつい最近ですけどこれといったエラーは見当たらなかったし、2019年に改訂されてるのでよくなってると思いたい🙏」「おそらくJavaScriptを一応知っている人がターゲットの本なんでしょうね☺: たぶんJavaScriptが初めての人が読む本ではなさそう」「あ〜そうかもです😅

「本ってどういうタイミングで読むかも結構大事なんですよね: この本だとある程度JavaScriptを書いてきた人が、ES2017とは何ぞや的なところをおさらいするのにはいいかも」「たしかに〜: 今の自分には情報を整理するうえで身の丈に合ってた感はあります😀

「この本で今頃知ったんですが、Node.jsってRubyみたいに毎年10月に必ずLTS版をリリースするというポリシーがあるそうです」

参考: リリース一覧 | Node.js

⚓その他JS


つっつきボイス:「ユーザー名に__が入っているのでWordPressのMarkdown機能が誤認識してツイートを埋め込めませんでした😇」「JSの人気投票というか😆」「ま〜好きなの使えばいいんじゃないでしょうか🤣」「🤣

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

⚓「Micro Frontend」アーキテクチャとは


つっつきボイス:「ソフトウェア設計でおなじみMartin Fowlerさんのサイトのフロント設計関連の記事ですが…と思ったら書いてる人はまた別でした😅Cam Jackson)」「Micro Frontendsの定義が載ってる↓」「記事を眺めた感じではよくまとまってる雰囲気ありますね😋

「Micro Frontendとはアーキテクチャのスタイルの一種であり、個別に配信可能なフロントエンドアプリケーションをより大きな全体として組み上げたもの」
同記事より大意

「フロントエンドのコンポーネントのマイクロサービス化というかマイクロコード化というか」「インクリメンタルな更新など、サーバーサイドでやるのと同じような利点をフロントエンドでも得られると」「これはちゃんと読んでみるとよさそうな感じ😍

「この絵↓どっかで見たことあるナ」「Railsdmのスライドだったかな?🤔」「概念図ではあるけれど、この図の左ってまさにReactとかAngularとかVueでもやっているような感じに、それぞれがMicro Frontendとして独立したコンポーネントになれば別々に開発・管理できるようになるよね、ということなのかも」


同記事より

サーバーサイドの分割なのでちょっと苦しいですが、Railsdmの「Railsの正体」のこのスライド↓が少しだけ似ているかなと思いました。「Micro Frontends」というキーワードもちょっぴり登場しています。

「基本的に図だけ見て言ってますが😆、フロントエンドはチーム分けもこんなふうに↓スタイリングも含めてMicro Frontendごとに垂直分割しようぜということか」「右に赤い字で『スタイリングのチームをhorizontalにするのを避けよ』とあるのは?」「スタイリングを別チームに押し付けると後で死ぬよってことなんでしょうね☺」「なるほど😆」「CSSの記述形式とかも含めてマイクロサービス的にちゃんと分離できるという主張かも🤔


同記事より

「そしてMicro Frontendをこんなふうに組み立てると↓」「ふむふむ」「”Browse”は独立したコンポーネントで、サーバー側ではテンプレートを組み立てる」


同記事より

「appサーバーはテンプレートのコンテナになってて、フロントに対応したBrowseとかOrderなどのマイクロフロントエンドサーバーがあるという感じ↓」


同記事より

「普通にアプリを書けばおおよそこういうアーキテクチャになる気はしますね😆: Reactとかのコンポーネントの概念をより一般化して、Reactじゃなくてもやれるとかそういう話のようだ」「そういえば本文にもありますね↓」「記事は特定の言語やフレームワークに絞っていないようだし」「それにMicro Frontendという名前を付けたということなんでしょうね」

この例は、Micro Frontendが必ずしも新しい技法ではないことと、必ずしも複雑になるわけではないことを示している。設計上の決定がコードベースやチームの自立性にどう影響するかに注意を払う限りは、用いるテクノロジーを問わず同じメリットを達成できる。
同記事より大意

「ちゃんと読んでみるとよさそうな記事🥰

⚓UIのお作法28


同記事より


つっつきボイス:「これもはてブからです」「あ〜なるほど、こういうアプローチ」「いわゆるUniversal Designにも通じますね😋」「〜感という言い方がうまい」「電話かかる感😆

参考: ユニバーサルデザインを意識したアプリ作りについて考えてみました | 日本VTR実験室

「こういうのはAppleのヒューマンインターフェイスガイドラインが先駆的ですね🧐」「そういえばMacは最初期からこういうガイドラインを定めてましたね🍎

参考: Human Interface Guidelines - Design - Apple Developer
PDF: Macintosh Human Interface Guidelines — 1985年のガイドライン


同PDFより

「ダウンロード感といえば、最近中国語しか書かれていないサイトでダウンロードのリンクがどこにあるかさっぱりわからなくてつらかったのを思い出しました😇」「ダウンロード感は漢字じゃないもので出してくれと😆」「フロッピーディスクアイコン出さないと💾」「🤣

⚓その他フロントエンド


つっつきボイス:「前から2件までだった気もするけど😆」「こういうのって検索条件にもよりそう」「記事にも関連性が高い場合は3件以上出すことがあるそうです」「それできないと困るし😆」「もし『週刊Railsウォッチ』で検索して2件しか出てこなかったらオイっ!てなるし🤣」「🤣

⚓言語・ツール

⚓GitHubのコードジャンプ機能

飛び先がリポジトリ内であればツールチップを出してジャンプできるようです。


つっつきボイス:「GitHubにたまたま表示されてて気づきました」「まだベータ版」「これは誰もが欲しいヤツですよね〜」「特にウォッチの『先週の改修』でこれ出てくれたらめちゃうれしい😂」「それな: Railsの最新ソースでコードジャンプしようと思うと今だとローカルにチェックアウトしないといけないし、ファイル数多すぎてうっぷってなるし😇」「リポジトリ内という制約はあるみたいですけどそれでもありがたいです🙏

⚓その他言語


つっつきボイス:「最近のJavaはもうわかりましぇ〜ん😆」「同じく〜😆」「オレの知ってるJavaはJava 5だし😆」「WebチームのkazzさんもLambda式が出てくるあたりでJavaから離れたって言ってました」「ジェネリクスが導入されたあたりまでは覚えてる😆

参考: Java 5.0はJava.comで入手できなくなりました
参考: ジェネリックプログラミング - Wikipedia

「や〜今記事眺めてて、昔のJavaに比べてJava 9だとこんなに短くなる↓ってありますけど、自分だったら絶対上で書いちゃうと思いますし🤣」「新しい方、List.of()なんて書き方増えてる〜」

// Java 1.4
int[] array = {3, 1, -4, 1, -5, 9, -2, 6, 5, 3, 5};
List numbers = new ArrayList(array.length);

for (int i = 0; i < array.length; i++) {
    numbers.add(Integer.valueOf(array[i]));
}

Comparator comparator = new Comparator() {
    public int compare(Object o1, Object o2) {
        return ((Integer) o2).intValue() - ((Integer) o1).intValue();
    }
};

List sorted = new ArrayList(numbers);
Collections.sort(sorted, comparator);

System.out.println(numbers);
System.out.println(sorted);
// Java 9
List<Integer> numbers = List.of(3, 1, -4, 1, -5, 9, -2, 6, 5, 3, 5);
List<Integer> sorted = numbers.stream()
        .sorted((n1, n2) -> n2 - n1)
        .collect(Collectors.toList());
System.out.println(numbers);
System.out.println(sorted);

⚓Javaよもやま

「あの頃のJavaといえば、ほら、誰もが修行で一度は通るBufferedReaderReader🤣」「それそれっ🤣」「ファイルから読み込むだけなのに何でこんなにたくさん書かないといけないの?って思いましたよ当時😆」「printf()的なヤツ?(わかってない)」「BufferedReaderreader渡して、readerに何渡すんでしたっけ?」「んーと、InputStreamReaderと、FileReaderと…」「FileReaderBufferedReaderに渡すと行出力されたり」「当時はどのJava入門本にも書かれてたし😆

参考: BufferedReader (Java Platform SE 6)

「Railsの若手の人に聞いちゃいますけどJavaやったことは?」「まったく(キリッ」「おお、ちょっと珍しいかも?」「ということは最初からRubyとRails一筋で?」「ええ、なのでボロボロです😆」「んなこた〜ない😆

「Javaって大学や企業の研修とかでたしなみレベルでやったりする印象あるから一応みんな知ってるかと思ってたけど」「今だとPythonで教えそうですよね☺」「何を教えるかにもよりそうですよね: オブジェクト指向の概念を教えようとするとPythonだと苦しそうな気がしますけど😆」「Javaだと教えやすいかというとそうでもないという😆」「ただ縛りはかけやすいですよね☺

「あの頃はBufferedReaderひとつとってもネスト多いしコード量多いし、『Javaはタイプ量多くてつらいです』ってなるのがよくあるパターン😆」「大学とかで教えてると入力ミスりまくって嫌になる学生続出したり😆」「で『オレはプログラミングできない』って嫌いになるという😆」「もう黄金のパターン🥇

参考: ファイル読み込みは何がベストなのか(Java) - Qiita

「そう思うと、今のJavaはもう別の言語になったと言ってもいいぐらい変わりましたね〜」「マジでそう思います」「JavaもRubyやPythonみたいに『書きやすいよ!』アピールしないといけなくなってきたというか☺」「Javaで関数や変数の頭にいちいちpublicとか型とか書くのが邪魔くさいと思ってました😅

「とはいえ自分はJavaって決してキライじゃなかったな〜: というのも、1.4ぐらいまでのJavaって言語仕様がはるかに小さくてジェネリクスすらなかったんで、記法上覚えることがとても少なかったんですよ」「えぇ〜知らなかった!😳」「クラスライブラリは山ほど使いますけどね😆

「初期のJavaは、記法がほんとシンプルだったんで覚えやすい代わりに、やんなるほど冗長になったり標準ライブラリが深すぎたりというのはしんどかった😭」「そういえば今日の勉強会でも『Javaでちょろいコードを書いただけなのにクラスが100個ぐらいできちゃう』って言ってましたね😅

「それがJava 5のあたりでジェネリクスとかアノテーションコメントが入って、自分が追いかけてたのはその辺まででしたけど、7とか8のあたりでRubyやPythonみたいにショートハンドで楽に書けるようにしようぜって変わりつつあるという印象はありますね」

参考: ジェネリクス
参考: Javaのアノテーション便利すぎワロタwwをしたいので総復習したけどワロエナイ | KentaKomai Blog

「自分も初期のカッチリしたJavaってキライじゃなかったんですよ」「そうそう、当時のJavaでは同じことをやるとコードの書き方がかっちり揃うというのはそれはそれで読みやすかったですし☺」「それそれ😋」「同じことをやるのにいろんな書き方があると結局調べないといけなくなったりするし、それぞれで微妙に書き方の癖が違ったりするとそれが元でハマったりするんですよ😭」「あ〜」

「Rubyでもそういうのありますよね: CSVからファイルを読むときに、いったんファイルからreadしたものをそのままCSVクラスに突っ込む流儀と、CSVに.eachで突っ込むのとでは微妙に挙動違うんですよ😇」「うんうん」「たしか文字コード指定オプションがどちらか片方でしか使えないとかもあったり😭

「まあRubyは『書いていて楽しい』方に寄せればいいと思うんですけど、JavaでBufferedReaderを繰り返し繰り返しカタにはめて書くというのは(自分はもうやりたくないけどっ😆)、プログラマーが数百人もいるようなプロジェクトだとこのブレなさが逆によかったりしますよね☺

「実際Javaも慣れちゃえばそうやって書けちゃいますよね、Eclipse様の仰せの通りにしてさえいれば🤣」「EclipseってLintもやるんですね」「Eclipseは何でもやりますよ😎」「究極の拘束感😆」「そしてEclipseがあまりに何でもやりすぎるんでどんどん重くなっていったという🤣」「でもEclipseのimport行を自動で書く機能、あれがなかったらこれっぽっちもJava書く気になりませんし🤣」「あの機能があってこそのJavaですよね🤣


eclipse.orgより

「ついJava 1.4みたいな古代の話になってしまいました👴」「もう20年ぐらい前😆」「マジか〜❕

⚓その他

⚓VimとNeovimで任意コード実行の脆弱性

日報で知りました。


つっつきボイス:「おぉ?細工されたコードをVimとNeovimで開くと任意コードが実行される…」「古いVimは使うなと😇」「しかもつい最近の情報!」「やゔぁいじゃん!☢」「早速SlackのVim板に貼っとこう😆

参考: Vim or NeoVimを使っている人は、すぐにアップデートしましょう [CVE-2019-12735] - Qiita

なお本記事執筆時点のnvd.nist.govでは、詳細調査のため再解析待ち状態だそうです。

参考: NVD - CVE-2019-12735

⚓その他のその他

つっつきボイス:「この製品は未踏プロジェクトから始まったと聞いたので取り上げてみました」「名前はダジャレ由来感😆」「見ようによっては健常者が使っても面白そう😋」「ベンチャーがクラウドファンディングでやるのに向いていそうな製品だけど、『全国のろう学校に無償で体験版を提供』みたいなボリューム感を出せるのは大手ならではかも🤔

参考: 未踏事業ポータルページ:IPA 独立行政法人 情報処理推進機構
参考: 髪の毛で音を感じる装置『Ontenna』開発者が語る”次世代的発想力”/富士通・本多達也氏 - エンジニアtype | 転職type



同記事より

「フォントが単なる読みやすさとかではない影響を与えるってありそうだなって前から思ってたので」「記事にもちょっとありますけど、明朝体についてる細かいウロコみたいなのが、識字障害のある人にとってうるさく感じて目が疲れるらしいというのは聞いたことがありますね☺

「いわゆるユニバーサルデザインの狙いのひとつに、健常者以外の人にとってのハンデを取り払うというのもありますよね: その意味では、こういうフォントの違いは健常者からはわからなくてもいいんです😆」「たしかに、ハンディキャップを抱えている人にとっては改善になって、健常者には気づかれないぐらいでもいいかも!😍」「もちろん健常者にとっても改善になればなおいいですけど☺

「ついでに私、相当いい年なのに実は昔から活字本のザラザラした書体がキライで😆、老眼のせいもあって電子書籍の方が圧倒的に好きです❤」「そういえば紙の本、マジで読まなくなったな〜」

参考: 活字 - Wikipedia

⚓番外

⚓人工の青空


つっつきボイス:「これもはてブですが『床に設置したら認知が狂いそう』ってブコメがありました😆」「この照明、重量めちゃ大きいらしいけど😆」「ありゃ」「300kgとか書いてるし」「普通のオフィスだと、重さを十分支えられる柱とかないと設置難しいかな?」「天井にそんな重たいものあったら地震コワい😇」「まあ天井と柱をちゃんと設計すればいいと思いますし、展示会場みたいなところなら天井しっかり作ってそうですし」「あ〜そうかも」


後編は以上です。

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

週刊Railsウォッチ(20190611-2/2後編)Dockerfileベストプラクティス、DBの冗長化、jQueryとの付き合い方ほか

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

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

Postgres Weekly

postgres_weekly_banner

Rails: APIdockがいつの間にかRails 5.2.3とRuby 2.6.3まで更新された

0
0

訂正(2019/06/20)apidock.comをサービス名の「APIdock」に修正しました🙇

APIdockが更新された

以前のTechRacho記事で、RailsのAPI検索サイトとして根強い人気のある(?)apidock.comがRails 4.2.7から更新されずじまいであることを書きました。

しかし今日ふと開いてみると、いつの間にかRailsが最新の5.2.3、Rubyが最新の2.6.3まで更新されていました😂。しかも以前http://だったのがいつの間にかhttps://に変わっていました(Let’s Encrypt証明書を使用)。


apidock.comより

has_many_attachedのような5.2.3のメソッドも検索できています。has_manyがトップに来るのは相変わらずですが😅


apidock.comより

現状

RailsのAPIドキュメントのステータスは以下のとおりです。更新されたのはつい1か月前です。

RubyのAPIドキュメントのステータスは以下のとおりです。こちらも1か月前に更新されています。

なおRSpecはずっと前から更新がありません😇

どうなっているのか?

APIdockの更新はありがたいことですが、更新された理由やこれまで滞っていた理由がわからず、今後も安定して更新されるかどうか不安が残ります😅

ログイン機能がまったく使えない

ログインするとAPIにコメントを付けられるようなのですが、この機能はだいぶ前から死んでいるようで、ユーザー登録できません。自分がユーザー登録してたかどうかを確かめようとパスワード再設定しようとすると、明らかにRailsとわかるSomething went wrongエラーが表示されました。

コメントを付けると同時にTwitterにも投稿されていたようですが、2013年から止まったままです↓。

まあログインしなくても使えるのでいいのですが。

運営側の様子がわからない

APIdockにはブログへのリンクがありますが、リンク先のブログはドメインそのものがなくなっています。

また、本来直接の関係はないはずのRailsのGitHubリポジトリに、APIdockの更新はどうなってるのかというissueが以前から立っています(closedですが)。


issue#27663より

どうやら、APIdockの運営会社の創業者は@livedoという方で、未だ連絡が取れないようです。業を煮やして自分でAPIサーバーを立ててみた方や、つい1か月前にドキュメントが更新されていて驚いた方もいますね。

別記事でも書いたように、Railsには公式(と思われる)api.rubyonrails.orgがあるのですが、バージョンを串刺しで探せませんし、SEO系タグが皆無なので検索でもさっぱり上がってきません。

Rails APIの有効なバージョン範囲がひと目でわかるサイトが他にないだけに、ご無事をお祈りいたします🙏

関連記事

Rails: api.rubyonrails.orgで過去バージョンのAPIドキュメントを参照できるURLリスト

週刊Railsウォッチ(20190624-1/2前編)6.1でActionView::Componentが入る、RuboCopのRuby/Railsスタイルガイドサイト、RailsガイドProプラン/Teamプランほか

0
0

こんにちは、hachi8833です。先週金曜日夜は銀座Rails#10でのmorimorihogeさんの発表を写真撮影しておりました。

RDBMSのVIEWを使ってRailsのデータアクセスをいい感じにする【銀座Rails#10】

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

お知らせ: 第12回公開つっつき会(無料)

開始以来ついに1年目を迎える第12回目公開つっつき会は、7月4日(木)19:30〜にBPS会議スペースにて開催されます。皆さまのお気軽なご参加をお待ちしております🙇。

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

主に公式情報から見繕いました。

6.1にActionView::Componentがやってくる

# actionview/lib/action_view/helpers/rendering_helper.rb#L27
      def render(options = {}, locals = {}, &block)
        case options
        when Hash
          in_rendering_context(options) do |renderer|
            if block_given?
              view_renderer.render_partial(self, options.merge(partial: options[:layout]), &block)
            else
              view_renderer.render(self, options)
            end
          end
        else
-         view_renderer.render_partial(self, partial: options, locals: locals, &block)
+         if options.respond_to?(:render_in)
+           options.render_in(self, &block)
+         else
+           view_renderer.render_partial(self, partial: options, locals: locals, &block)
+         end
        end
      end

なぜか公式の更新情報には入ってませんでしたが、プルリクのスレを見るとみんな超大喜び&大盛り上がりしているようです。実装はまだ始まったばかりのようですが、PRメッセージにがっつり構想が書かれています。Rails 6.1の真打ちの予感です(6はさすがに間に合わないそうです😅)。

プルリクの主はGitHubの中の人の@joelhawksleyさんです。

参考: Joel Hawksley | Boulder, Colorado


つっつきボイス:「今日社内Slackで盛り上がったActionView::Component、やっぱり6.1からだそうです」「やはり😆まだ遠いかな〜」

「RailsConf 2019の以下の発表が先行していたようですが、このスライドは喋りの演出に最適化されてて単独だと読みづらかったのでウォッチではスキップしてました😆」

「Reactのコンポーネントみたいなものを目指してるのかな?」「お〜、こんな感じに書けるようになるっぽい↓😋」

「どうやらまたRails Wayに寄せてきた感あるかも😆」「フロントエンジニアに何言われるかと思うと🤣」「Reactとかの方もそれはそれで将来もっと書きやすくなるかもしれないし🤔」「ここはとりあえず見守っておこう🤣」「🤣」

ActionView::Componentは「新奇なアイデア」からは程遠いものである。Rubyにも以下のようなビューコンポーネントの有名な実装はあるが、この他にもある。
同PRより

「上のgemたちがActionView::Componentと同類ということみたいです」「コンポーネントっぽくしたいならこのあたりに似てくるでしょうね☺️」

「PRのスレを見てると、コンポーネント内のファイル配置↓をどれにしようかな〜ってみんなキャッキャウフフしてますね❤️」「遠足の前の日ですか😆」「Railsだけでコンポーネントやれるというのはいいかも😋」「採用するかどうかは、まプロジェクト次第ということで😆」

app/views/components/
  - my_component/
    - component.js
    - component.css
    - component.rb
    - template.html.erb
    - template.html+mobile.erb
app/views/components/
  - my_component/
    - component.rb
    - template.html.erb
    - template.html+mobile.erb
app/views/components/
  - my_component.rb
  - my_component.html.erb
  - my_component.html+mobile.erb

「前回のウォッチで話したMicro Frontend(ウォッチ20190618)にも通じるんでしょうか?」「まそんな感じですね☺️」

「そういえば、モノは違うけどRails 1.2にこんなのあったよね↓とPRに書いてる人がいました」「あ〜なるほど、Webフレームワークってこういうコンポーネントみたいなものって昔からあって、自分もPHPのSymfonyでそういうの使いましたよ」「そうでしたか!」


symfony.comより

Symfony 1.xのコンポーネント

「ただSymfonyのコンポーネントは、たしかレイアウトがあって、その中にコンポーネントを配置できて、コンポーネントの中にはさらにスロットを配置するという、いわゆるビューに階層を作れるアーキテクチャでしたね🧐」「お〜」「たとえばSideMenuコンポーネントの中にRankingスロットみたいなのをいろいろ配置できるヤツで、自分が見たSymfonyのコンポーネントはそういう趣き: 今はどうなってるか知りませんが😆」「😆」

参考: Symfony - Wikipedia

「軽くググってみるとSymfony 1.x系みたいだから今はなさそうかな」「どうもそんな感じですね」「あの頃Symfony 1系の中のコードまで読んでたな〜」

参考: 第7章 - ビューレイヤーの内側 (1_4)

「そうそう、Symfony 1.xのスロットはこんな感じだった↓: 全体テンプレート、ここではHeadlines SidebarというHeadlines部分テンプレート(partial)、さらにスロットを置けるようなつくりになってます」「う、ややこしい…😅」



symfony.com/legacyより


「Rails 6.1に入るActionView::Componentは、これらよりはフロントエンドに寄せた感じというか、Web Componentsレイヤーのコンポーネント的な位置づけなんでしょうね☺️」「RailsでWeb Componentsすることになるんだろうか😆」

参考: Web Components | MDN

ダイレクトアップロードでmirrorサポート

  • mirrorサービスでダイレクトアップロードをサポート。
    新しいファイルはprimaryサービスに直接アップロードされる。ダイレクトアップロードされたファイルはレコードにアタッチされ、バックグラウンドジョブがキューに入って各セカンダリサービスにコピーする。
    ジョブをミラーリングする処理で使われるキューはconfig.active_storage.queues.mirrorで設定する。デフォルトは:active_storage_mirror
    同Changelogより
# activestorage/lib/active_storage/engine.rb#L19
module ActiveStorage
  class Engine < Rails::Engine # :nodoc:
    isolate_namespace ActiveStorage
    config.active_storage = ActiveSupport::OrderedOptions.new
    config.active_storage.previewers = [ ActiveStorage::Previewer::PopplerPDFPreviewer, ActiveStorage::Previewer::MuPDFPreviewer, ActiveStorage::Previewer::VideoPreviewer ]
    config.active_storage.analyzers = [ ActiveStorage::Analyzer::ImageAnalyzer, ActiveStorage::Analyzer::VideoAnalyzer ]
    config.active_storage.paths = ActiveSupport::OrderedOptions.new
    config.active_storage.queues = ActiveSupport::OrderedOptions.new
    config.active_storage.queues = ActiveSupport::InheritableOptions.new(mirror: :active_storage_mirror)

つっつきボイス:「なるほど、そっちの意味のmirrorね☺️: ストレージサービスを複数(GoogleドライブとS3とか)使って、どちらにでもアップロードできるようにするということのようだ」「たしかにこれは欲しいヤツ😋」「マルチリージョンでアップロードできるようになったらうれしい😍」

ActiveRecord::Relationのプリロードを修正

# activerecord/lib/active_record/associations/preloader/association.rb#L29
        def records_by_owner
-         @records_by_owner ||= preloaded_records.each_with_object({}) do |record, result|
+         # owners can be duplicated when a relation has a collection association join
+         # #compare_by_identity makes such owners different hash keys
+         @records_by_owner ||= preloaded_records.each_with_object({}.compare_by_identity) do |record, result|
            owners_by_key[convert_key(record[association_key_name])].each do |owner|
              (result[owner] ||= []) << record
            end
          end
        end

つっつきボイス:「❤️が山盛りなのは『Rubyエライ!』ということかしら😆」「compare_by_identity?」「お〜、Hashインターフェイスにcompare_by_identityというのがあるのか!」


同PRより

参考: instance method Hash#compare_by_identity (Ruby 2.6.0)

ハッシュのキーの一致判定をオブジェクトの同一性で判定するように変更します。
デフォルトでは、キーのオブジェクトによっては内容が同じならキーが一致しているとみなされますが、より厳密に Object#object_idが一致しているかどうかを条件とするようにselfを変更します。
selfが変化する破壊的メソッドです。
docs.ruby-lang.orgより

「ドキュメントにあるように、ハッシュのキーの一致判定をオブジェクトの同一性で判定するように変更するメソッドなのね」「おぉ?」「普通だとハッシュのキーはvalueで判定されるんですが、compare_by_identityを呼ぶとハッシュのステータスが変わって、オブジェクトが同一でないと一致しないようになるという」「compare_by_identityっていう名前だから同一性で比較するメソッドかと思ったら、同一性比較のためにハッシュを改変するってちょっとコワい😅」

「このプルリクは元々#36396↑の修正のためということで…なるほど、関連付けがプリロードされると同じレコードがインメモリでコピーされて重複することがあったから、それを扱えるようにcompare_by_identityを使ったということらしい🧐」「おぉ」「レコードが複製された場合は違うものとみなされないと困るからでしょうね」「先週もマルチDB絡みで似たような話があった気がするので、その流れなのかも?」「よくぞ見つけた感👍」

Active Recordのスキーマキャッシュのdeduplicate

# activerecord/lib/active_record/connection_adapters/deduplicable.rb#L3
+module ActiveRecord
+  module ConnectionAdapters # :nodoc:
+    module Deduplicable
+      extend ActiveSupport::Concern
+
+      module ClassMethods
+        def registry
+          @registry ||= {}
+        end
+
+        def new(*)
+          super.deduplicate
+        end
+      end
+
+      def deduplicate
+        self.class.registry[self] ||= deduplicated
+      end
+      alias :-@ :deduplicate
+
+      private
+        def deduplicated
+          freeze
+        end
+    end
+  end
+end

つっつきボイス:「これももしかするとマルチDB絡み?」「どちらかというとメモリ削減のリファクタリングっぽい: まあこれまでシングルトンだったのがマルチになってくるといろいろやらないといけないでしょうけど」

Deduplicableっていうモジュールが増えてますね」「レオナルド・デュカプリオみたい😆」「早口言葉😆」「dereferenceとかも😆」

小品: エラー画面をレスポンシブに


同PRより


つっつきボイス:「あは、なるほど😆」「こういう修正はやっぱりいいっすね~😍」「地味だけどうれしい修正」「意外に今までやってなかったんですね☺️」「しかも修正はviewport入れてCSSをちょいちょいっと直しただけ😆」「それでやれる修正ですし☺️」「グッジョブ!👍」「誰かがやってくれるとみんなが喜ぶヤツ😂」

# actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb#L2
<html lang="en">
<head>
  <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Action Controller: Exception caught</title>
  <style>
    body {

Rails

RubyスタイルガイドのリニューアルとRailsスタイルガイド(Ruby Weeklyより)


rails.rubystyle.guideより


rubystyle.guideより


つっつきボイス:「RuboCopの人たちがRubyとRailsのスタイルガイドサイトを立ち上げたそうです」「へぇ〜」「しかも私全然気が付かなかったんですが、どちらもしっかり日本語化されてます🇯🇵」「こういうのはコントリビュートしやすい分野ですよね☺️」

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

「以前TechRachoで出していたRubyスタイルガイドの翻訳プラスアルファ記事↑があってPV稼いでくれてますけど、今後は上のサイトがメインになるのかな…😢」「上のサイトの方が新しい?」「まだちゃんと見てませんが何だか編成も変わってるみたいで、RuboCopのLayout部署とかその辺も最近変わってるからかも」

「日本語版の『ヴァリデーション・ファイル』って表記珍しい😆」「私はこの中黒使わないスタイルでやってるので割と気になります😆」「『ユーザー・フレンドリー』とかも😆」「まあ統一されていればいいんですけど、点があったりなかったりしたらちょっと😅」「英単語が切れてたらそこが中黒になるんでしょうけど☺️」「その割には『ヘルパーメソッド』に点がないし😆」

「ともあれ、日本語じゃないと読みたくない人たちにはありがたいと思いますね☺️」「あと困ったときに『とりあえずここ読め』という一次情報があるのもありがたいです😂」

「私も翻訳で常日頃悩んでるんですが、今後原文の差分を訳文でどう追いかけるのかなと😆」「ほんにそれ😆」「『とりあえず原文で読め』と🤣」「それが結局コスト一番低いし🤣」「翻訳版で地雷踏んだことがあるとだいたいそうなる🤣」「ちゃんとメンテされているとわかっている日本語ドキュメントならさらっと読むのに使ったりするけど、訳が間違ってるとか古いとかでいろいろハマった身としては、ね☺️」

Bounded Queueでアプリを短時間で復帰(Hacklinesより)


つっつきボイス:「Bounded Queues?」「お、これは中で詰まってるパターン↓ですね: キューがあふれてタイムアウト🕙」

Starting…
[0] 200 - 4.13 seconds elapsed
[1] 200 - 9.13 seconds elapsed
[2] 200 - 14.13 seconds elapsed
[3] 200 - 19.13 seconds elapsed
[4] 200 - 24.14 seconds elapsed
[5] 200 - 29.14 seconds elapsed
[6] 504 - 29.70 seconds elapsed
[7] 504 - 29.80 seconds elapsed
[8] 504 - 29.90 seconds elapsed
[9] 504 - 30 seconds elapsed
Took 30.0102 total, avg of 21.9242 in thread time, max 30, min 4.13

「こういうふうに時系列をちゃんと書いてくれているのはエライ↓👍」「たしかに〜」「教科書みたい」「できれば図にして欲しいけどっ😆」「TCPバッファリングの話もある」

00:00 — [A] Client sends request A to Server
00:01 — [A] NGINX buffers the HTTP request into memory and sends it to Puma
00:01 — [A] Puma accepts the connection, reads the request into memory and queues it
00:03 — [B] Client times out, sends request B to Server
00:04 — [B] NGINX buffers the HTTP request into memory and sends it to Puma
00:04 — [B] Puma accepts the connection, reads the request into memory and queues it
00:05 — [A] Puma starts processing the request, and responds with it to NGINX
00:06 — [A] NGINX throws the response away since the client closed the connection for A
00:06 — [B] Puma starts processing the request, responds with it to NGINX
00:07 — [B] Client receives the response for B

「NGINXをフロントにしてPumaを挟むとNGINXの方でいい感じにしてくれるという話」「ふむふむ」「Pumaでフロー制御までやるのは大変なので、NGINXのフロー制御周りの機能を使って、アプリケーションストリームだけPumaの方に流すとかそんな感じ☺️」

「そしてmax_conns=3にかかったら即座にエラーを返すようにしたということみたい」「なるほど!」「スレッドプールがフルになってたらどうせさばけないので😆」「諦めが肝心😆」「おそらくNGINXでPumaのスレッドの残数とかを参照して、フロントのNGINXがPumaのステータスに応じて空気を読むことで、Pumaがあふれていたら手前のNGINXのところでリクエストを止めようという話でしょうね」「Pumaの顔色がやばかったら助けてあげる感じですか🤢」

Starting…
[0] 200 - 4.13
[1] 200 - 9.14
[2] 200 - 14.14
[3] 502 - 0.01
[4] 502 - 0
[5] 502 - 0
[6] 502 - 0
[7] 502 - 0
[8] 502 - 0
[9] 502 - 0
Took 14.14 total, avg of 2.74 in thread time, max 14.14, min 0

参考: Module ngx_http_upstream_module

「ステータス502って何だったかな: Bad Gatewayか↓」「502でいいのかというのはあるけど😆、クリティカルなものでなければこれでやれるでしょうね☺️」「案件によっては実際こうしないとキューが無限にたまり続けちゃいますけど、こうしておけば、さばくべきものはちゃんとさばけるようになりますし」「さばけないものはさばけないなりに早めに諦めると😆」

参考: 502 Bad Gateway - HTTP | MDN

Railsガイド「Proプラン」「Teamプラン」


railsguides.jpより


つっつきボイス:「お、安川さんだ!」「私も翻訳で関わっているRailsガイドのProプラン(個人向け)とTeamプラン(法人向けアカウント管理機能と決済一元化など)で、通常のガイドはもちろん誰でも見られるんですが、有料だとこうやって例のAlgolia検索ショートカットキーが使えます😋」「お〜なるほど!」

インタビュー: 超高速リアルタイム検索APIサービス「Algolia」の作者が語る高速化の秘訣(翻訳)

「こういうのはいいですね〜❤️」「私も少し試す機会がありましたが、ありがたいです🙏」「自分はそんなに頻繁には使わないかもですけど😆、BPS社内のWebチームでみんなが使いたいということであれば試しに契約してもよさそうだし😋」「今度Webチームミーティングで軽く聞いてみましょう😀」

「今の自分はRailsを長く使っていて検索キーワードがだいたい身体に沁み込んでるので、どちらかというと今は高速なAPI検索が欲しいかも😋」「APIdockの記事↓にも書きましたけど確かに欲しいですね😋」「その意味では、RailsガイドのProプラン/TeamプランはRails始めて日が浅い人とか久しぶりにやる人に特に向いている感じですね👍」

Rails: APIdockがいつの間にかRails 5.2.3とRuby 2.6.3まで更新された

「APIのメソッドってときどき別のクラスやモジュールに引っ越したりしますけど、それを追うのって大変ですよね😢」「となると今だとそれを追えるのはAPIdockかな〜」

なお、本日以下の記事も公開されていますね。

参考: 🚀Railsガイド『Teamプラン』をリリース - YassLab 株式会社

truemail: メールアドレスのバリデータgem(RubyFlowより)

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

Truemail.configure do |config|
  config.verifier_email = 'verifier@example.com'
  config.whitelisted_domains = ['white-domain.com', 'somedomain.com']
  config.blacklisted_domains = ['black-domain.com', 'somedomain.com']
  config.validation_type_for = { 'somedomain.com' => :mx }
end

つっつきボイス:「メールアドレスをバリデーションしてくれるgemだそうです」「お〜なるほど!」「自分で書かない方がよさそうなヤツですよね😆」

「どうやらtruemailはメール送信時にも使えるようだ」「あ、そうみたい」「実際に到達可能なメールアドレスかどうかをMXとかに問い合わせてやってくれそう😋」「MXバリデーションってありますね」「メールが届きません疑惑ってよくあるから😆、そういうときに使えるかも」

その他Rails

つっつきボイス:「Stellerっていう認証方法をこれで初めて知りました」「名前はどこかで聞いたことあるかな🤔」「使ったことないけど😆」「ブロックチェーンでanonymousに認証するみたい」「それが真のanonymousかどうかだけど😆、ブロックチェーンでやる以上はノードが分散している限りはやれるということなんでしょうね☺️」

参考: StellarAuth - Authenticate using your Stellar account - Galactic Talk
参考: ブロックチェーン - Wikipedia



つっつきボイス:「@kamipoさんのこの話、まさによくあるヤツ☺️」「これはマイグレーションの手順の問題: めちゃデカいテーブルのマイグレーションって一瞬では終わらないので、ALTER TABLEでロックされないようにこういう手順にしたりしますね」「ふむふむ」「でも②を先にやっておかないとコケることがあるよという話」

「まず、①で足すカラムはNULL許容でなければいけません」「それから②の新規レコードをデフォルト値fillで先にデプロイして、その後で③をやらないといけない: でないとデフォルト値fillしている最中にまた新規レコードが作られるとNULLカラムが残っちゃう、すると④のときにコケる😇」

「レースコンディション(競合状態)を発生させないためには、③の前に②をやっておくことが重要: ③の後に②をやることも可能は可能なんですが、それならもう一回③をやってから④をやらないといけない」

参考: 競合状態 - Wikipedia

「まあ例えるなら、昔のLinuxシステムの電源を落とす前にsyncコマンドを3回入力するみたいなものです🤣」「あ〜あの習慣ですか🤣」「自分もsyncコマンド3回的に念のためもう一度やっておくことありますし: 何しろマイグレーションでコケると結構ショックでかいので😇」

参考: sync; sync; sync; haltの伝承 - Qiita

Ruby

injectable: Ruby向けDIライブラリ(RubyFlowより)

# 同リポジトリより
class MyClass
  include Injectable

  dependency :collaborator

  def call
    collaborator.submit!
  end
end
# 同リポジトリより
class PdfGenerator
  include Injectable

  dependency :wicked_pdf

  argument :html
  argument :render_footer, default: false

  def call
    wicked_pdf.pdf_from_string(html, options)
  end

  private

  def options
    return {} unless render_footer

    {
      footer: {
        left: footer,
      }
    }
  end

  def footer
    "Copyright ® #{Time.current.year}"
  end
end

# でこんなふうに使ったり
PdfGenerator.call(html: '<some html here>')
# wicked_pdf依存をオーバーライドしたり
PdfGenerator.new(wicked_pdf: wicked_pdf_replacement).call(html: '<some html>')

つっつきボイス:「以前も似たようなDI gemを見たことある気がするけど?🤔」「これは初めてでしたが、似たようなのいっぱいありそうではありますね😅」「dependency :collaboratorとかするといい感じにやってくれると」「gemでやるほどのものだろうかという気はしますけどっ😆」「initializeメソッドとか生やしてくれるんなら、まあとても使いたい人が使えばいいのかなと☺️」

参考: 依存性の注入 - Wikipedia

「以下もbehavesも趣旨が近いのかなと思ったんですが」「これはまた別のものっぽいですね😆」「やっぱり😅」

# 同リポジトリより
class Predator
  extend Behaves

  implements :hunt
end

class Prey
  extend Behaves

  implements :run, :hide
end

class Shark
  extend Behaves

  # Sharkは`Predator`でもあり`Prey`でもある
  behaves_like Predator
  behaves_like Prey
end

implementsとかあるあたりにJavaの匂いを感じる😆」「injectもできるところは上と似ていなくもないけど」「これを使って効果的に読みやすくなるかどうかはまた別かな〜: 既にこれでいっぱい書かれていて後の人はコピペすればできる、ぐらいになってればいいけど、新たにこれを入れようとはあまり思わないし😆」

Rubyのパイプライン演算子


つっつきボイス:「演算子、はじかれたっぽい😆」「話があちこちで進んでるっぽくて、今どうなってるのか時系列を追うのつらいです😭」「Matzの『積極的に読みにくさに貢献してますね』というコメント🤣」「『必要性が高くない』も🤣」「まあパイプライン演算子が入ったところで使う人は少なそうですけど☺️」

「パイプライン演算子は順番通りに書き下しっぽく書けるから、たとえばコマンドラインなんかでマルチラインで入力して、最後にCtrl-Dと入力すると実行されるみたいな使い方のときにいいかも」「あ〜それあるかも」「手元でスクリプトのRubyをcat -とかで開いて、そこにコピペして最後にCtrl-Dとか、そういうふうにできるんだったらあってもいいかも😋」「pryとかで使ったりとか」「まぁ使うかというと使わないかな〜😆」

「そしてmametterさんのパイプライン演算子の歴史、日英両方で書かれてましたね」「偉大な仕事!」

参考: パイプライン演算子の歴史 - まめめも
参考: A Brief History of Pipeline Operator – mamememo

「パイプライン演算子だとこういうふうに実行順どおりに書けますからね↓」「うんうん」「エディタでなら普通に書きますけど、シェルだとReturn押したら戻ってこれないし🤣」「そうそう🤣」「だからパイプラインでこうやってシェルで入力できるなら意外にいいかも?」「まあドット.で書いてもできますけどっ😆」「😆」

# 同記事より
astonishing_argument_x
|> fantastic_process_f
|> marvelous_process_g
|> wonderful_process_h

「外してるかもしれませんが、yield_selfことthenにこの|>記号当てたらだめかなって」「パイプライン演算子はメソッドをチェーンしたいという目的があるからyield_selfだとまた違うかな🤔」「やはり〜😅」「左から右に処理を書けるのはいいかも」

Ruby 2.5の`yield_self`が想像以上に何だかスゴい件について(翻訳)

OmniAuth::Apple(Ruby Weeklyより)


つっつきボイス:「さっそくAppleの匿名認証に乗っかってきた😆」

参考: アップル「匿名認証機能」がもたらす巨大衝撃 | スマホ・ガジェット | 東洋経済オンライン | 経済ニュースの新基準

その他Ruby

# 同回答より
class C
  private
  def private_method
  end
end

こういう質問への一次回答を読めてとてもうれしいです😂。


つっつきボイス:「endがないのはアクセス修飾子だから?、と思ったらC++に似せたのか!」「インデントも浅くて済むし」「そういえば先週privateの後のインデントってどうよって話になりましたけど(ウォッチ20190617)、Matzの回答に沿うならprivateの後の行はインデントを変えないのが正当?😆」「Matzはインデントしてない、やったね😋」「じゃこれで行きましょう😆」

Ruby trunkより

0.nonzero?nilを返す(Ruby Weeklyより)


つっつきボイス:「上のissueはRuboCop作者の以下の記事で知ったんですが、下の方が読みやすいです」「0.nonzero?がnilを返す😆」「1.nonzero?1を返す🤣」

# 同記事より
0.zero?
# => true
0.nonzero?
#=> nil
1.zero?
#=> false
1.nonzero?
#=> 1

「たぶんif文の中でif a = 1.nonzero?みたいに代入する形にするヤツで、1.nonzero?があれば自分自身を返して、なければnilを返すみたいな、Railsで言うpresence的な使い方なのかな、たぶんね😆」「実際Rubyのbooleanとして使う分には問題ないし☺️」「Rubyではnilfalse以外はtrueでしたね」

参考: Object.presence
参考: Railsのpresenceメソッドが便利


前編は以上です。

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

週刊Railsウォッチ(20190618-2/2後編)決済の分散トランザクション、フロントエンドのMicro Frontendアーキテクチャ、GitHubのコードジャンプほか

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

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

Rails公式ニュース

Ruby Weekly

RubyFlow

160928_1638_XvIP4h

Hacklines

Hacklines

Viewing all 1203 articles
Browse latest View live


Latest Images