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

週刊Railsウォッチ(20190708-1/2前編)ActiveRecord::FixtureSetがめちゃ強くなってた、MacだとRubyが遅い理由、Puma 4登場ほか

こんにちは、hachi8833です。「?‍?」という絵文字をSlackに貼ったらこんなふうにぶっ壊れたことで合字だということを知りました。


つっつきボイス:「Bald?」「人間の顔の絵文字にズラのコンポーネントをかぶせてたことが判明しました?」「?」

参考: ?‍? Man: Bald Emoji
参考: ? Emoji Component Bald Emoji

「そうそう?、Unicodeってこんなふうに複数の文字を組み合わせて合字が作れるんですよね☺️」「4人家族もパパとママと子ども2人を悪魔合体っぽく作ったりしてますね?‍?‍?‍?」「こういうのに長けたUnicode職人がいるんですよきっと?」「こういう字は1文字でもバイト数めっちゃ多かったりするので、データベースにぶちこむと溢れたりすることがまれによくあるという?」

参考: 合字 - Wikipedia
参考: 絵文字一覧(家族:family)? | Let’s EMOJI

「そういえば最近のRubyだとなんとかlengthみたいなので取れますね」

後でやってみたらscan(/\X/).lengthで取れました。\Xがgraphemeの境界にマッチする正規表現だそうです。

# Ruby 2.6.3
» str = "?‍?‍?‍?"
» str.each_char do |i|
»   p i
» end
"?"
"‍"
"?"
"‍"
"?"
"‍"
"?"
#» "?‍?‍?‍?"
» str.length
#» 7
» str.bytesize
#» 25
» str.scan(/\X/).length
#» 1

参考: Ruby: 書記素クラスターを考慮して文字数を求める - Sarabande.jp

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

今回は7/4(木)に開催された「公開つっつき会#12」を元にお送りいたします。おかげさまで1周年にして満員御礼となりました。お集まりいただいた皆さま、ありがとうございます!?

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

今回は公式更新情報コミットリストの両方からみつくろいました。公式情報が夏っぽいですね☀️??‍♀️。

Active Recordスキーマキャッシュのcolumns_hashのシリアライズとパースを止めた

#35891の続き。
columns_hashは、名前でインデックス化されたものを単純にcolumnsしているので、単にカラムのarrayからカラムを再インデックスした場合よりもシリアライズとデシリアライズがずっと遅い。
同PRより大意


つっつきボイス:「とりあえずコード見るのが早そう↓」

# activerecord/lib/active_record/connection_adapters/schema_cache.rb#L28
      def encode_with(coder)
        coder["columns"]          = @columns
-       coder["columns_hash"]     = @columns_hash
        coder["primary_keys"]     = @primary_keys
        coder["data_sources"]     = @data_sources
        coder["indexes"]          = @indexes
        coder["version"]          = connection.migration_context.current_version
        coder["database_version"] = database_version
      end

      def init_with(coder)
        @columns          = coder["columns"]
-       @columns_hash     = coder["columns_hash"]
+       @columns_hash     = {}
        @primary_keys     = coder["primary_keys"]
        @data_sources     = coder["data_sources"]
        @indexes          = coder["indexes"] || {}
        @version          = coder["version"]
        @database_version = coder["database_version"]
      end
...
      def columns_hash(table_name)
-       @columns_hash[table_name] ||= Hash[columns(table_name).map { |col|
-         [col.name, col]
-       }]
+       @columns_hash[table_name] ||= columns(table_name).index_by(&:name)
      end

「こういうHash[columns(table_name).mapみたいなのって遅くなりそうな印象あるし」「高速化系の改修ですね☺️」

time.getutcの呼び出しを削減


つっつきボイス:「まさにタイトル通り: タイムゾーンが既にUTCだったらtime.getutcを呼ばないと」「わかりやすい〜?」「時刻変換ってどれぐらい重いんだろう?」

# activemodel/lib/active_model/type/helpers/time_value.rb#L6
module ActiveModel
  module Type
    module Helpers # :nodoc: all
      module TimeValue
        def serialize(value)
          value = apply_seconds_precision(value)

          if value.acts_like?(:time)
-           zone_conversion_method = is_utc? ? :getutc : :getlocal
-
-           if value.respond_to?(zone_conversion_method)
-             value = value.send(zone_conversion_method)
+           if is_utc?
+             value = value.getutc if value.respond_to?(:getutc) && !value.utc?
+           else
+             value = value.getlocal if value.respond_to?(:getlocal)
            end
          end

          value
        end

「ベンチマークはついてないけど、実行命令数レベルでは減ってる?」「7行から5行に?」「Railsはこういう地道な修正も多いですね☺️」


同PRより

TranslationHelper#translateのハッシュデフォルト値の扱いを修正

translateメソッドがHashを1つ返すことを期待する場合がある(たとえばnumber.formatキーの場合)。
そういう人は次のようにデフォルトのHashを指定する必要があるかもしれない。
translate(:'some.format', default: { separator: '.', delimiter: ',' })
このやり方はI18n.translateメソッドで期待どおり動作するが、TranslationHelper#translateではデフォルト値にArray()を適用しているのでデフォルト値が最終的に[:separator, '.', :delimiter, ','](というarray)になってしまう。
同PRより大意

# actionview/lib/action_view/helpers/translation_helper.rb#L60
      def translate(key, options = {})
        options = options.dup
        if options.has_key?(:default)
-         remaining_defaults = Array(options.delete(:default)).compact
+         remaining_defaults = Array.wrap(options.delete(:default)).compact
          options[:default] = remaining_defaults unless remaining_defaults.first.kind_of?(Symbol)
        end

つっつきボイス:「Array.wrapってActive Supportのヤツ?」「…arrayだったら何もしない的なメソッドだったはず↓」「やっぱAPIdock見やすい〜?」


apidock.comより

# apidock.comより
Array.wrap(nil)       # => []
Array.wrap([1, 2, 3]) # => [1, 2, 3]
Array.wrap(0)         # => [0]

「あ〜なるほど、arrayでなければ必ずarrayでラップしてくれるのね❤️」

「エッジなバグでもあったのかな?」「…このテストケースに対応してる↓: 前はハッシュが単なるarrayになってた」「おかしな変換が残ってたのね☺️」

# 同PRより
  def test_hash_default
    default = { separator: ".", delimiter: "," }
    assert_equal default, translate(:'special.number.format', default: default)
  end

「(内職中ふと顔を上げて)ははぁ、Array()にハッシュを食べさせるとハッシュがarrayに分解されちゃうけど、Array.wrapすればハッシュのままarrayに入ってくれるのね?」「それだ」「arrayの中にハッシュが入ってるのが欲しかったと☺️」「translateするならそうあって欲しいし」「よく踏んだなこれ」

後でArray.wraprails cで試してみました↓。

development» hash = { separator: ":", delimiter: "," }
#» {:separator=>":", :delimiter=>","}
development» Array(hash)
#» [[:separator, ":"], [:delimiter, ","]]
development» Array.wrap(hash)
#» [{:separator=>":", :delimiter=>","}]

URL破損チェックを修正

# activerecord/lib/active_record/database_configurations.rb#L137
      def build_db_config_from_hash(env_name, spec_name, config)
        if config.has_key?("url")
          url = config["url"]
          config_without_url = config.dup
          config_without_url.delete "url"

          ActiveRecord::DatabaseConfigurations::UrlConfig.new(env_name, spec_name, url, config_without_url)
-       elsif config["database"] || (config.size == 1 && config.values.all? { |v| v.is_a? String })
+       elsif config["database"] || config["adapter"] || ENV["DATABASE_URL"]
          ActiveRecord::DatabaseConfigurations::HashConfig.new(env_name, spec_name, config)
        else
          config.each_pair.map do |sub_spec_name, sub_config|
            walk_configs(env_name, sub_spec_name, sub_config)
          end
        end
      end

つっつきボイス:「ほほぅ、URL形式のデータベースコンフィグが反映されてないことがあったと☺️」「database.ymlって実はいろんな形式を食わせることができるんですけど、たしか前もウォッチで話した気がする(ウォッチ20181105)」「そうでした!」「ODBCなんかでよく使うmysql://なんちゃらみたいなヤツもRailsでは食えるんですけど、あの手のオレオレURLみたいなのって何か名前あるんですかね??」「URLスキームっぽいといえばそうだけど、どうだろね〜、なさそう?」「たぶんRFCとかにはないんじゃないかなって?」「雰囲気URLスキーム?」「このプルリクは、そういうのをパースするあたりを修正したんでしょうね☺️」

参考: URL schemeの一覧


wiki.suikawiki.orgより

「そういえばRails 6の例のマルチプルデータベースで、複数DBをURL形式で指定した場合ってどうなるんだ?みたいな話をどっかで聞いたんですけど」「定義ごとにURL持つみたいな形になるだろうからどうってことなさそうだけど??」「自分はdatabase.ymlでURL形式で書くときに別途ユーザー名とかパスワードとか書かないんですよね?」「あ、Rails 6からdatabase.ymlが1階層増えるから、ユーザー名とかパスワードをバラして書かずにURLにまとめて書いたときに個別のユーザー名とかパスワードとかがどう扱われるかってことか?」「まあそんな感じで☺️」

「今の話を簡単に説明すると、Rails 6でマルチプルデータベースにしたときのdatabase.ymlでURL形式で書いたときにどうなるかという話です: 今Sublimeで記憶ベースで要点部分だけ書いてみますけど、普通はこんな感じ↓」

production:
  primary: 
    host: 'hoge'
    port: '3306'
    user: 'user1'
    password: 'pass1'
  replica:
    host: 'huga'
    port: '3306'
    user: 'user1'
    password: 'pass1'

「それをたとえば以下↓みたいに書いたときにどう扱われるか、みたいな話です」

production:
  primary: 'mysql://user1:pass1@hoge:3306/test'
  replica: 'mysql://user2:pass2@huga:3306/test'

「まあ移行するときに頑張れば済むんですけどー」「そうそう、書き換えれば頑張れる?」「以上雑談でした?」

オートロードガイドに追記

# guides/source/upgrading_ruby_on_rails.md#L273
+#### Having `app` in the autoload paths

+Some projects want something like `app/api/base.rb` to define `API::Base`, and add `app` to the autoload paths to accomplish that in `classic` mode. Since Rails adds all subdirectories of `app` to the autoload paths automatically, we have another situation in which there are nested root directories, so that setup no longer works. Similar principle we explained above with `concerns`.

+If you want to keep that structure, you'll need to delete the subdirectory from the autoload paths in an initializer:

+ActiveSupport::Dependencies.autoload_paths.delete("#{Rails.root}/app/api")

+In `classic` mode, if `app/models/foo.rb` defines `Bar`, you won't be able to autoload that file, but eager loading will work because it loads files recursively blindly. This can be a source of errors if you test things first eager loading, execution may fail later autoloading.

+In `zeitwerk` mode both loading modes are consistent, they fail and err in the same files.

つっつきボイス:「例のZeitwerkの記述がちょっと増えたようです」「そういやZeitwerkって、おれドイツ人じゃないから発音わかんないんでホットロードだったか何か別の名前になるんじゃないかってRubyKaigiあたりの発表で誰か言ってたような気が?」「マジで?」「まだ名前変わってないかな?」「ツァイトヴェルクってそんなに言いにくいかな〜?」

「ちなみにZeitwerkについて簡単に説明すると、Rails 6でオートローダー周りがZeitwerkというものに変わるんですね: 一応従来のローダーも使えるけど多少コンフィグが必要で、あとSTI使ってると場合によっては読み込み順を明示的に指定しないとエラーになることがある、みたいなことが移行ガイドに書いてますね」

参考: 「シングルテーブル継承 (STI)」Active Record の関連付け (アソシエーション) - Rails ガイド

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

そういえばKraftwerkもドイツ語ならクラフトヴェルク(=発電所)ですが、例のバンド名はみんなクラフトワークって英語風に呼んでるっぽいですね。

Rails

Puma 4が登場(Ruby Weeklyより)


つっつきボイス:「お〜Puma 4出ましたか!」「@schneemsさんの記事なんですが、ご本人のツイートのコラ動画が何だかかわいくて↓?」「字幕が『ずっと一緒だよ』って?」「そして3.42と4.0が泣き別れ?」「3系はサヨナラと?」「ズッ友って言ったのにって?」「Reactorとかいう部分が変更されてAPIがちょい変わったみたい」

RailsのWebサーバーあれこれ

「ついでにゲストの皆さんにお伺いしますけど、Railsで使ってるWebサーバーは何ですか?: じゃあなさそうなところから、Mongrelの人!」「え何それ??」「いないか?、じゃWEBrickの人」「これもいないかな?」「じゃPassengerの人!」「お〜1人いますね」「じゃUnicornの人!」「1人、いや2人?」「じゃPumaの人!」「お〜多い」「…Thinは?」「そういえばThinってありましたね?」「これもなつかしい?」「Thin使ったことあった?」

参考: WEBrick互換の軽量WebサーバMongrel - [Ruby on Rails/Ruby] ぺんたん info
参考: library webrick (Ruby 2.6.0)
参考: Passenger - Enterprise grade web app server for Ruby, Node.js, Python
参考: unicorn: Rack HTTP server for fast clients and Unix
参考: macournoyer/thin: A very fast & simple Ruby web server

「やっぱり今だとデフォルトのPumaですねよ: Pumaもマルチプロセス・マルチスレッドで設定すれば全然速いですし苦労しなくなったし?」「みんながPumaを使うようになったことでスレッドセーフに書くことを心がけるようになったであろうというか?」「みんなどこまでスレッドセーフって意識してるのかな、なんて?」

「ついでに、Railsアプリでマルチスレッド周りのバグを踏んだことのある方は?Railsそのものじゃなくてアプリケーションのコードを書いたときに」「お、意外にいない?」「オレはあるっ?: あれを一度でも踏むと、クラスインスタンス変数とかを見かけたときにコイツメ〜という気持ちになりますよもう?」「?」「?」

参考: 【まとめ】インスタンス変数、クラス変数、クラスインスタンス変数 - Qiita

「そういえばUnicornのgraceful restartがどうやってもグレースフルになってくれなかったことあるし?」「間違った方向に苦労してたりとか??」「手動でシグナル送ったりもういろいろやりましたよ〜?、Capistrano書き換えてstop/startしたり」「そういうときは念のため数秒待つ?」「そうそう?、そしてstopしたらpsでマスタープロセスがちゃんと死んだのを確認してからリスタートするコードとか書きましたし?」「Unicornだとちゃんと死んでくれないマスターっていたな〜?」「Pumaにしてからまったくそういう経験ないっすね?」「Pumaが突然死したんでUnicornに替えたことならあるといえばあるけど?、今ならインスタンス増やすとかすればいいんだろうし」「今はメモリだけ軽く監視しとけばいいかみたいな☺️」

Action TextをJSなしでテストする


つっつきボイス:「Action TextはRails 6で入ることになっているWYSIWYGエディタですね」「BasecampのTrixエディタを使ってるヤツ」「そのAction TextをJSなしでテストすると」「has_rich_text :content??」「そんな感じのDSLが入ってたかも」「Action Textまだ使ったことないし?」

参考: Trix: A rich text editor for everyday writing


trix-editor.orgより

週刊Railsウォッチ(20181009)Rails 6の新機能:WYSIWYGエディタ「Action Text」、Rails 6の青写真スライド、Apache POIはスゴイほか

「ところでRailsでWYSIWIGエディタやるときって皆さん何をお使いですか?」「redcarpetかな〜」「redcarpetはよく使われますよね☺️」「く、このリポジトリにデモがない…?」「デモがないあたりが昔のgemっぽい?」

例のRailscasts↓に動画チュートリアルがありました。

参考: #272 Markdown with Redcarpet - RailsCasts

「他のWYSIWIGエディタを使ってる方は?」「んーと、フロアーラみたいな名前のWYSIWIGエディタをこないだ社内で使ってたかな」「お、これですね↓」「そうそう、Froala Editor」「うん、ひととおりのリッチテキスト使えるっぽい?」



froala.comより

参考: イカすwysiwygエディタFroalaをRailsに5分で導入

「Floala、新し目でオシャレ感あるし?」「エディタでUndo使えるし!」「有能?」「ところでこの記事↑でもFloala使ってるけど、gem 'wysiwyg-rails'でインストールって?」「なんつう名前?」「どーなんだろう、他のWYSIWIGをもラップする神gemな匂いありそうだけど?」「この名前空間取るってどうよ?」「これは攻めてる⚡️」「攻めてる?」

「Floalaのリポジトリ、2 years agoみたいなのがそこそこ目につく?」「Floalaは一応商用がメインでライセンス料払うみたいな形式なんだけど、オープンの方の更新は微妙かも??」「ということはRails専用とかではないわけですね?」「まあnpmとか使わずに入れられるという感じなんだけど、今なら無理して使わなくてもいいんじゃね?って?」

「…CKEditorってのもあります?」「CKEditorなら使ってもいいかな〜: でもWYSIWIGって外部ライブラリを使うと結局サーバー側でアップロードとかの実装を自分でやらないといけないのが面倒なんですよね?」「昔はCKEditorぐらいしかまともなのがなかった?」


ckeditor.comより

「こういうライブラリはどこまでまともなHTMLを吐いてくれるかが気になりますし?」「自分の職場のiOSエンジニアがですね、世の中にはIMEで変換中にうまく動かないWYSIWIGエディタが多いってボヤいてましたよ」「うお〜それはキツそう?」「もうね、気の毒すぎてお疲れさま以上の言葉をかけられないっすよ?」「尊い犠牲?」「WYSIWIGって入れようとするとホント大変だし、家帰りたくなるし?」「何回車輪を再発明したら気が済むんだの世界?」

「Floala、割と良さそうな印象❤️」「wysiwyg-railsって名前を除けば?」「そうそう?」

hanmoto: 静的ページをhamlなどで書けるgem

// 同リポジトリより
// app/views/public_pages/404.html.haml:
- provide(:title, 'Not found')
%h1 Not found
%p This webpage is not found.
%p= link_to 'Home', root_path

つっつきボイス:「@junchitoさんの会社で使われてるhanmoto(版元)というgemだそうです」「お、こういうの以前にも見たような??」「昔からあるといえばある☺️」「hanmotoのリポジトリで、gakubuchi↓にヒントを得たってありますね?」「自分が覚えてたのはgakubuchiじゃなかったけど、こういうのはみんな一度はやりたくなるヤツ?」

「Railsのエラーページを動的にやろうとすると『Railsが死んだときにエラーページを出せない』という問題がありますよね?」「?」「なのでエラーページは静的にする必要があるんですが、でもエラーページでCSSとかJSとかも読み込もうとするとアセットのファイル名についてるシグネチャとかをちゃんと扱えないと困る: hanmoto gemはそこらへんをいい感じにやってくれるんでしょうね☺️」「Rails自身はそういう仕組みってないのかな…?」「Rackが死んだときに表示するエラーはRailsでは対応できませんね?」「Railsで404.erbみたいなのが欲しいってこと?」「ああ、エラーページのerbをプリコンパイルするのをRails自身が持ってればいいのにってことね: まああればあったでいいかも☺️」

後で探してみたら、hanmotoと少し似た感じのrambulanceというgemもありました。

参考: 【動的VS静的】Railsの404/500エラーページ 静的の勝利 - 珈琲駆動開発

「まあエラーページを何もカスタマイズしてないRailsアプリが本番でデフォルトのエラーページを表示すると、なかなかショッキングな色合いが目に飛び込みますし?」「赤い赤い真っ赤っ赤?」「そしてむっちゃ電話かかってくる?」「赤は刺激が強いかも?」「その意味で、必要以上にユーザーを驚かせないエラーページを作っておくというのは大事だと思います?」「それが身を助けますね☺️」「いつだったかAmazon Payでいつもの真っ赤な画面が出てましたし↓」「?」

「ユーザーがあの赤い画面を見ると『オレ、壊しちゃった??』って気持ちになりがちですし?」「『大丈夫だよ』がユーザーに伝わるエラー画面が重要?」(以下延々)

初心者向けRSpecの書き方


つっつきボイス:「メドピアさんの記事ですけど、例のRSpecえかきうたと合わせて読むとちょうどいいのかなと思って」

「『describe/context/itのフォーマットが統一されていない』のコード例は確かに悲しい?」

# 同記事より
# contextが日本語だったり英語だったり式だったり
describe 'valid?' do
  context '非公開のとき'
  context 'publish'
  context 'expired < Time.now'  
end

# itに条件が書かれている
describe 'valid?' do
  it '◯◯で△△のとき、回答が登録されること'
end

「『letで定義した変数名が、何を表しているかわからない』、自分はlet否定派だけど?」「なぬう?」「とはいえ変数名を考えるのってめっちゃ時間かかるし、件数が多くなるとどんどんつらくなってくるし?」

「『beforeブロック内でテストを行う』、これはやっちゃダメでしょ?」「書こうと思えば書けちゃうのか?」

「『テスト対象が同じ、複数のテストケースで、subjectが使われていない』、subjectを使うと読みやすくなるのかというと個人的には疑問ありますが?」「subjectがきれいにキマるといいんだけどね〜?」「キマるとかっこいいけど、今のsubjectってどれ?っていちいち上の方見に行かないといけないのってどうかな〜って?」「subjectが遠くなるとつらくなる?」「結局ネストするとつらくなってくるし?」「ネストとともに地獄感高まる?」

みんなでうたおう『RSpecえかきうた』

「うちのメンバーが書いてくれた『RSpecえかきうた』記事↓、自分もイチオシなので?読んだことのない方はぜひ一度どうぞ」

RSpecえかきうた

「前半は普通にitでテストケースを並べて、expect書いて、前提条件を書いて、ってやってくわけですね」「どきどき?」「で後半は『やっぱ離れてると読みにくいよね』って途中からitをまとめてって、contextitに合流させて、するとsubjectがないのにitって何だかヘンだからitやめてtestって名前にしましょって?」「するとRSpecがあっという間にかわいいアレのようになってしまうわけですよ?」

「こ、これは?」「mとiとnとiがつくテストに?」「えかきうたがいつの間にか替え歌に?」「そりゃもうアンチでしょ〜?」「自分は好きっ?」「業務のテストコードってdescribeとかきれいに書けないことの方が多いくらいだし、もうこれでいいんじゃね?って思うことは、 多々ある?」

「このRSpecえかきうた記事、Twitterでもはてブでも『最高です』というコメントだけが付いてました?」「もうアンチしかいない?」「みんなアンチ?」「RSpecで疲れた人にひとときの安らぎを☺️」「一服の清涼剤?」

とっても引用したい動画があったのですが、やめときます?。察してください。

「ちなみにRSpecでテスト書いてる人は?」「やっぱRSpec多い」「minitestの人は?」「お、いますね〜?」「他のテストフレームワーク使ってる人は?」「さすがにいない?」「ZenTestとかは?」「今は亡きZenTest?」「いないかやはり〜?」

ActiveRecord::FixtureSetがスゴくなってた

↑もともと上の記事を取り上げようとエントリしてたのですが、FixtureSetの話があまりによかったので組み替えました。


つっつきボイス:「そうそう、今日のミーティングで出た話なんですけど、テストデータをFactoryで管理するのとFixtureで管理するのとでそれぞれメリットとデメリットがあるよねって」

fixtureとfactoryについて

「fixtureとfactoryは皆さんご存知ということでいいでしょうか?」「fixtureは知らない人いるかも??」「fixtureはRailsが最初から持っている仕組みで、fixturesというフォルダの下にyamlファイルを置いておくとテスト時にそれがいい感じに1行ずつ展開されてデータベースに入ってくるヤツで、生データを置いているような感覚に近い」

factoryは、基本的な定義はfactoryに書くんですけど、実際のデータはコードで作ります: factory createとかtraitとかを使ったりすることもあります」

FactoryGirlでtraitを使うとintegration test書くのが捗るという話

「ついでに皆さんどっちをお使いでしょうか?、まずはfixtureの人」「ほいっ」「おお、fixtureなんですか?」「オレfixtureでしか書かないし?」「へ〜、以前factory_bot(当時はfactory_girl)の記事とか書いてたのに?」

「factory_botって、テストが増えてくるとすごく遅くなるのが問題なんですよ?」「あ〜」「一方fixtureを全部グローバルなfixtureでやれると、最初にガッと全部読み込んで後はトランザクションでやれるから速い?: その代り、そのガッと読み込みを実現するまでの作業はつらい?」

「整合性のあるfixtureを書くのってつらくないですか?」「そこはまあそんなに大変でもないんですけど、大変なのはグローバルfixtureだと全部入ってくるんで本番データでテストしているみたいな感じになってくるところで、User.firstとかUser.lastみたいなのが気軽に使えなかったりとか」「なるほど〜」

「fixtureでやるときは、考え方をfactoryのときとは変えた方がいいというのはありますね?: たとえばファイル名を変えてそのときだけロードするみたいな設計をある程度最初から考えとかないといかないとか」「このテストコードではこっちのfixture、あちらでは別のfixtureみたいな感じですね」

「でもfixtureって自由度高く書けるから、昔ほどアンチにすることもないかな〜って: 初期段階からやれるんならfixtureでやってみるといいんじゃないでしょうか?」

「続いてfactoryの方は?やっぱりいますね: 反論オーケーですよ?」「そうですね、fixtureだとどんなふうにデータが入ってくるかとかって、特にチームに初めて参加する人はfixtureを全部見ないとわかりにくいかなと思うんですよね」「たしかにfixtureだとidも生idが入ったりしますし?」

ActiveRecord::FixtureSetがいつの間にかスゴくなってた

「それがですね、ここだけの話、実はfixtureにはActiveRecord::FixtureSetといういいものがあるんですよ奥さん❤️」「ほほぅ?」「多くの人はfactory_girlにやられる前のfixtureしか知らないかもしれないんですけど、以下のサンプルコード↓を見るとだいたいわかります」

参考: ActiveRecord::FixtureSet

「え、ダイナミックfixtureとかある?」「ナウいfixutureにはあるんですよ〜?」

<!-- api.rubyonrails.org より -->
<%Q1.upto(1000) do |i| %Q<
fix_<%= i %>:
  id: <%= i %>
  name: guy_<%= 1 %>
<% end %>

「つかもうちょっと下のアドバンストfixtureの方がむしろ普段づかいするところで、たとえばidなんか自動生成してくれるし↓」

george:
  id: 1
  name: George the Monkey

reginald:
  id: 2
  name: Reginald the Pirate
george: # generated id: 503576764
  name: George the Monkey

reginald: # generated id: 324201669
  name: Reginald the Pirate

belongs_toなんかもこうやって扱えるし↓」

### in pirates.yml

reginald:
  id: 1
  name: Reginald the Pirate
  monkey_id: 1

### in monkeys.yml

george:
  id: 1
  name: George the Monkey
  pirate_id: 1

「おお、末尾のpirate: reginaldreginald:を参照してくれるのか?」「だからfactory_botとかでリレーション作るよりは楽ですね〜?」「たしかに〜?」

### in pirates.yml

reginald:
  name: Reginald the Pirate
  monkey: george

### in monkeys.yml

george:
  name: George the Monkey
  pirate: reginald

「結局factory_botの何がつらいって、複雑なリレーション入れ子になったデータを作るのがつらいんですよね?」「相互参照も地獄感あるし?」「それ、誰かが作ってくれてればいいんですけど?、自分でつくるのはホントしんどいし、あったらあったで今度はそれが要らなくなったときに困るんですよ?」「それを消すと?」「他のテストに影響出ますね〜?」「createすると思わぬところで使われたりとか?」

「fixtureの続きを見ると、ポリモーフィックbelongs_toもやれるし!」

### in fruit.rb

belongs_to :eater, :polymorphic => true
### in fruits.yml

apple:
  id: 1
  name: apple
  eater_id: 1
  eater_type: Monkey

「has_and_belongs_to_manyもやれますし?」

### in monkeys.yml

george:
  id: 1
  name: George the Monkey
  fruits: apple, orange, grape

### in fruits.yml

apple:
  name: apple

orange:
  name: orange

grape:
  name: grape

ラベルのinterpolation(式展開)もやれますし?: まあこのラベル名が何やら一意の整数値になってるみたいなんで、スペルミスするといつまでたっても関連付けられなくて他のが関連付けられちゃう、みたいなつらいこともありますけど?」「何だかマクロ的に展開される?」「まあそんな感じで」

geeksomnia:
  name: Geeksomnia's Account
  subdomain: $LABEL
george_reginald:
  monkey_id: <%= ActiveRecord::Fixtures.identify(:reginald) %>
  pirate_id: <%= ActiveRecord::Fixtures.identify(:george) %>

「YAMLのdefaultsも使えますよ〜?」「おぉっ!」「ただこれは大文字のDEFAULTSにしないと普通のラベルとして解釈されちゃうとか、arrayでしか使えないみたいなのがあって、fixture使っている人が少ないせいか、ドキュメントにそうやってポロポロ穴開いてますし?」「なるほど、DEFAULTSが予約語になってると」「これはもっと使ってフィードバックしないと!」

DEFAULTS: &DEFAULTS
  created_on: <%= 3.weeks.ago.to_s(:db) %>

first:
  name: Smurf
  *DEFAULTS

second:
  name: Fraggle
  *DEFAULTS

fixture model classもアンスコ付きの_fixture:でやれるし: これを使ってたとえばusersとerror_usersみたいにファイルを用意しておいて、specごとにfixtureコマンドで呼ぶことでテストケースを分けたりできますし」「は〜なるほど!、fixtureはデフォルトで実在のモデル名を使うけど、モデル名じゃないのを_fixture:で定義しておくと読み込めると」「読み込んで、さらにグローバルfixtureとは別にこっちを使ってねって指定できる」

_fixture:
  model_class: User
david:
  name: David

「いやだ何これスゴい」「やべ〜、こんな賢いfixtureが書けるなんてっ?」「fixtureでここまでできるとfactoryってあんまり要らなくて、むしろトランザクションだけでたいてのテストが書けるっつーか☺️」

「しかしいつの間にこんなにスゴくなったんですか?」「まあ少しずつ増えてたみたいで、Rails 5の頃にはだいたい今のような感じになってたかな〜?」「何だか『眼鏡を取ったらスゴい美人だった』みたいな?」

「そういうのを調べるにはやっぱりAPIdock↓に限る?」「なるほど、4.0.2から入ったと」「APIdockだとその機能がいつから入ったかがわかるのがとってもいい?」「ラベルのinterpolationとかERB直接扱えるようになったのは割と最近だった印象ですけど」


apidock.comより

「fixtureは、たとえばステージングより前の段階でQA用のサンプルデータを作ったりするのにもよかったりしますね❤️: fixtureってステージングでもdevelopment環境でも使えるんで、fixtureでテストケースをばばっと書き並べておくとか、本番からfixtureにダンプしてそこから削っていくとかみたいな用途ならfactory_botよりもいいかなって?」

「あとfixtureのデータの整合性の問題ですけど、いったん全部ぶっこんでからvalidかどうかで流せばいいだけなんで大したことないと思うし」「たしかに!整合性はたまにチェックすればいいぐらいかと?」「そうそう☺️」

「そういうわけで皆さん一度ActiveRecord::FixtureSetはチェックしてみるとよいかと?」「少なくともDHHはfixture派だろうし、そこは譲らないだろうし?」「今日からfixture派!?」「きっとCIが早く終わって地球に優しいっすよホレホレ?」「このAPI翻訳しようかな✨」

その他Rails

つっつきボイス:「Deviseの動きをトラックするgemみたいです」「どちらかというと証跡ログっぽいかな?」「Geocodingとかもあるから、こいつはどこからログインしたのかみたいなのをデータベースに入れてくみたいな」「上の方のHow It Worksあたりに書いてあるかな〜: ログインの成功/失敗とかIPアドレスとかを記録できるから、やっぱり証跡ログかな」「この手のログは提出を求められることがよくあるし、とりあえず入れとけ感あるかも☺️」「GeoIPとか入れとくと、やたら中国からアクセスあるなみたいなのも取れるし?」

参考: GeoIP2 Databases | MaxMind


つっつきボイス:「これは来週7/10に渋谷で開催されるTokyo Rubyist Meetupという英語話者を前提としたミートアップなんですけど、誘われたのでちょっくら行ってきます↓」「そういえば前からやってますね〜☺️」

scenicか久しぶりに聞いたな〜」「この間の銀座Rails↓でもscenicのこと話しましたし?」「そうそう?」「ミートアップのお題もdatabase viewsだから怖いくらい既視感ありますね?」「やっぱデータベースでやった方が速いことはデータベースでやろうよって?」

RDBMSのVIEWを使ってRailsのデータアクセスをいい感じにする【銀座Rails#10】


Ruby

impersonator: オブジェクトのやりとりを記録/再生(Ruby Weeklyより)

# 同リポジトリより
calculator = Impersonator.impersonate(:add, :divide) { Calculator.new }

つっつきボイス:「impersonateって『偽装する』でしたっけ」「なるほど、オブジェクトインタラクションを記録して再生すると」「なんかVCRって文字見えたし?」「VCRってVideo Cassette Recorderだからビデオデッキでしたっけ」「VCRってgemがあるんですよ↓」「そうそう、HTTPリクエストを記録して再生するヤツ」「HTTPSpyなんてのもあったような」「impersonateはそういうののメソッド版というか」

「しかし何でまたこういうの作ったんだろ?」「やりたかったからとか?」「VCRにインスパイアされたってあるけどどうしてこうなったというか?」「使いみち何かあるかな〜?」

MacだとRubyが遅い理由(Ruby Weeklyより)


つっつきボイス:「最近Mac使ってないからわかんないけど?Ruby遅いの?」「Discourseが出してたベンチマークでもMacが遅いって出てたの見たな〜: WindowsでVM使うより遅いって」「えぇそんなに遅い??」「お、記事にもまさにそのツイートが↓」

「リストの上の方がMacで独占されてるし?」「だっはっは?(マカーだけど)」「これはひどい?」「Win10のVMwareで動いてるLinuxにすら負けてるし?」「か、悲しい?」「どうしてこうなった?」

「下の速い方を見るとさすがに素のLinuxのRubyが速いですね」「しかしディストリで差が出ているのはなぜ?って思うし?」「デフォルトのカーネルパラメータとかが関係してそう」「果たしてMacのSSDが原因なのか、それとも何なのか」「上と下でダブルスコアぐらい違ってますし?」「@samsaffronさんの記事↓はタイトルではWindowsの話かと思ったらMacの方が遥かにやべーじゃねーかって?」「WindowsのHypervisorはベンチ入ってないのかな?」

参考: Why I stuck with Windows for 6 years while developing Discourse

「MacのRubyが遅いのは、果たしてMach-Oバイナリだからなのか、それともmacOSのHypervisor層が遅いのか、いろいろご意見が出そうではありますね?」「どうなんだろね〜?」「もしMach-Oが原因なら、Hypervisor層でLinux動かしてそこで動かせばもう少しマシになったりして」「どっちにしろ遅い?」「とほほ?」「それにしても負けすぎにもほどがあるというか?」

参考: Mach-O - Wikipedia

「というわけで皆さんWindowsマシンに乗り換えましょう」「?」「?」「おやこんなところにWindows機が?」

追記(2019/07/08)

上の測定結果はあくまで雑談の参考どまりとお考えください。

Rubyの「Direct Instruction」


同記事より

つっつきボイス:「@tenderloveことAaron Pattersonさんの記事ですが、いつもの自分のサイトではなくGitHubのブログに書いてますね」「こういうアニメーション↑作ってるのはすげー」「@tenderloveさん、最近GIFアニメに入れ込んでますね: 自分もTechRacho記事でこういうのやりたいなと思いつつ?」「どんなツール使ってるんだろ?」「聞いてみたら案外パワポかも?」「?」「案外その方が楽だったりして?」

「ASTツリーとかもみっちり書いてる↓」「Rubyは新しい技術をこうやって解説してくれる人がいるからいいですね〜?」「Linuxだと最新技術を学べる書籍がそもそも最近なくなってるし?」「Linuxの最新カーネルなんかだと、最近は大学の研究室なのか誰なのか結構みっちりドキュメント書いてたりするんですけど、どれを信用していいのかよくわからないという?」「?」


同記事より

その他Ruby


つっつきボイス:「最近流行りのRuby型チェッカー」「まだちゃんと読んでませんが、型チェックについて思うところを書き連ねてますね」

「Rubyの型チェッカーをJetBrainsのIDEとかが採用するようになるとまた世の中が変わってくるかなって」「JetBrainsのIDEなら、型チェッカーがない今でもめちゃうまく回ってるんじゃないかって?」「ですよね、今でもIDEがYardのドキュメントとかを参照して引数とか型のヒントとか出してくれますし?」「取りあえず押せば何か出てくる?」「Yardでちゃんと書かれてさえいればヒント出してくれるとかホント神がかってるし⛩」「Rubyの型チェッカーはむしろVSCodeとかの方がうれしいんじゃ?」



つっつきボイス:「@k0kubunさんのメモ書きだそうです」「そういえばRubyのVMを書くためにJVMを書いてみることにしたとかスゴいことをやってるらしき」「マジで?」

↓こちらのようです。

参考: セルフホストで学ぶJVM入門 - k0kubun’s blog

Quoraより


つっつきボイス:「最近QuoraでMatzの回答を読むのが楽しみで?」「Rubyのグルがこうやって一次回答を示してくれるのはいいですね〜☺️」「しかも日本語で??」「Quoraだと英語ロケールは自動翻訳されるのかな??」

「Rubyのブロック、そういえば他でほとんど見かけないですね?」「Rubyのブロックのよさは、何といってもブロックを1個しか取らないと決めたことでしょうね〜」「それはある!?」「おかげで記法が複雑にならずに済んだし?」


前編は以上です。

おたより発掘

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

週刊Railsウォッチ(20190701)RMagickのメモリ使用量が劇的に改善、インスタンス変数の定義順で速度が変わる?、GitLab CIランナーをローカルで回すほか

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

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

Rails公式ニュース

Ruby Weekly

CONTACT

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