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

Railsの技: Capybaraのテストで複数のセッションを扱う(翻訳)

$
0
0

概要

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

Railsの技: Capybaraのテストで複数のセッションを扱う(翻訳)

アプリケーション内の機能は、複数のユーザーにまたがって動作することもあります。自動化システムテストを書くときにCapybaraのusing_sessionヘルパーを使うと、複数ユーザーの間の行き来を簡単にシミュレートできます。

ユーザーごとにログインやログアウトを繰り返したり、アプリに対して別のユーザーが変更を加えているように見せかけなくても、同一のCapybaraテスト内で複数のセッションを利用できます。

using_sessionヘルパーは通知機能やチャット機能などのテストにも便利ですし、ワークフローでタスクを進めるために複数のユーザーが操作を行わなければならない場合のテストにも有用です。

利用法

Capybaraでセッションを制御する方法はいくつかあります。

以下のようにセッションを手動で設定することも一応可能です。

Capybara.session_name = "Test session #1"

しかし私はusing_sessionヘルパーにブロックを渡す方法が好みです。これによってブロック内のコードを独立したセッション内で実行し、ブロックが終了すると元のセッションに復帰します。

以下は、基本的なタスク管理システムのテストの例です。ユーザーはこのシステムで、完了すべきタスクを別のユーザーに割り当てることができます。

describe "Task Assignment", type: :system do

  it "allows users to assign tasks to other users" do
    login as: users(:kelly)

    visit "/tasks"

    click_on "Review deliverable"

    click_button "Assign to..."

    select "Sam", from: "Assignee"
    click_button "Save"

    expect(page).to have_content("Status: Pending")
    expect(page).to have_content("Assigned: Sam")

    using_session("Sam") do
      login as: users(:sam)

      visit "/tasks/me"
      expect(page).to have_content("Review deliverable")

      click_on "Review deliverable"
      click_on "Mark complete"
    end

    refresh

    expect(page).to have_content("Status: Completed")
  end
end

参考資料

関連記事

Railsの技: Active Recordオブジェクトはチェイン可能にして返そう(翻訳)

The post Railsの技: Capybaraのテストで複数のセッションを扱う(翻訳) first appeared on TechRacho.


週刊Railsウォッチ: GitHubによるdisable_joins解説、MemoWise gemでメモ化、RailsのDDoS攻撃対策ほか(20210719前編)

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

お知らせ: 今週木金は祝日のため、来週は週刊Railsウォッチの代わりに通常記事を公開します。

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

以下の公式更新情報から見繕いました。今回は小粒でわかりやすい改修が多い印象です。

🔗 config_forでyamlのsharedルート要素が配列を受け取れるよう修正

# railties/test/application/configuration_test.rb#2060
    test "config_for works with only a shared root array" do
      set_custom_config <<~RUBY
        shared:
          - foo
          - bar
      RUBY

      app "development"

      assert_equal %w( foo bar ), Rails.application.config.my_custom_config
    end

    test "config_for returns only the env array when shared is an array" do
      set_custom_config <<~RUBY
        development:
          - baz
        shared:
          - foo
          - bar
      RUBY

      app "development"

      assert_equal %w( baz ), Rails.application.config.my_custom_config
    end

つっつきボイス:「yamlファイル内の各環境設定のルート要素にハッシュではなく配列を指定するとエラーになる問題が修正されたようですね」「なるほど」「通常はハッシュを渡すと思うので、これを踏むことは少なそうかな」

# railties/lib/rails/application.rb#L249
        if shared
-         config = {} if config.nil?
-         if config.is_a?(Hash)
+         config = {} if config.nil? && shared.is_a?(Hash)
+         if config.is_a?(Hash) && shared.is_a?(Hash)
            config = shared.deep_merge(config)
+         elsif config.nil?
+           config = shared
          end
        end

参考: プログラマーのための YAML 入門 (初級編)

🔗 filepathにファイルがない場合のエラー出力を改善


つっつきボイス:「ActionView::TemplateRendererで出すエラーの種類を増やして、絶対パスでない場合に適切なエラーを出すようにしたんですね」

# actionview/lib/action_view/renderer/template_renderer.rb#L14
    private
      # Determine the template to be rendered using the given options.
      def determine_template(options)
        keys = options.has_key?(:locals) ? options[:locals].keys : []
        if options.key?(:body)
          Template::Text.new(options[:body])
        elsif options.key?(:plain)
          Template::Text.new(options[:plain])
        elsif options.key?(:html)
          Template::HTML.new(options[:html], formats.first)
        elsif options.key?(:file)
          if File.exist?(options[:file])
            Template::RawFile.new(options[:file])
          else
-           raise ArgumentError, "`render file:` should be given the absolute path to a file. '#{options[:file]}' was given instead"
+           if Pathname.new(options[:file]).absolute?
+             raise ArgumentError, "File #{options[:file]} does not exist"
+           else
+             raise ArgumentError, "`render file:` should be given the absolute path to a file. '#{options[:file]}' was given instead"
+           end
          end
        elsif options.key?(:inline)
          handler = Template.handler_for_extension(options[:type] || "erb")
          format = if handler.respond_to?(:default_format)
            handler.default_format
          else
            @lookup_context.formats.first
          end
          Template::Inline.new(options[:inline], "inline template", handler, locals: keys, format: format)
        elsif options.key?(:renderable)
          Template::Renderable.new(options[:renderable])
        elsif options.key?(:template)
          if options[:template].respond_to?(:render)
            options[:template]
          else
            @lookup_context.find_template(options[:template], options[:prefixes], false, keys, @details)
          end
        else
          raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :html or :body option."
        end
      end

🔗 credentialエディタ呼び出し時にファイルパスに含まれるスペースを正しく扱えるよう修正


つっつきボイス:「credentialへのパスがエスケープされていなかったのか: これは修正すべきでしょうね」「修正で追加されたShellwordsは、そういえば以前も取り上げましたね(ウォッチ20200225)」「ShellwordsはRubyのデフォルトgemで、Rubyで実行するsystemコマンドに渡す文字列に変数を埋め込む必要がある場合は必ずShellwordsでエスケープしないといけません: 今回はたまたまcredentialのパスにスペースが含まれているとcredentialエディタの起動でエラーになったことで見つかったんでしょうね」

# railties/lib/rails/commands/credentials/credentials_command.rb#L95
        def change_credentials_in_system_editor
          credentials.change do |tmp_path|
-           system("#{ENV["EDITOR"]} #{tmp_path}")
+           system("#{ENV["EDITOR"]} #{Shellwords.escape(tmp_path)}"
          end
        end

参考: Shellwords.#shellescape (Ruby 3.0.0 リファレンスマニュアル)
参考: Rubyから外部コマンドを実行するときはShellwordsモジュールが便利 - ブログのおんがえし

🔗 Action Cableのbroadcastでログ出力を300文字までに変更


つっつきボイス:「なるほど、ログが溢れないようにtruncate(300)を追加したんですね」「気持ちわかります」「Action Cableのやりとりに画像のような大きなファイルが含まれているとログが大量に発生するのはあるある」

# actioncable/lib/action_cable/server/broadcasting.rb#L42
          def broadcast(message)
-           server.logger.debug { "[ActionCable] Broadcasting to #{broadcasting}: #{message.inspect}" }
+           server.logger.debug { "[ActionCable] Broadcasting to #{broadcasting}: #{message.inspect.truncate(300)}" }

            payload = { broadcasting: broadcasting, message: message, coder: coder }
            ActiveSupport::Notifications.instrument("broadcast.action_cable", payload) do
              encoded = coder ? coder.encode(message) : message
              server.pubsub.broadcast broadcasting, encoded
            end
          end

🔗 fixtureの読み込み後に外部キーをverifyするよう変更


つっつきボイス:「Railsのfixture機能では参照の外部キーチェックを行わないんですよ: 以下で言うとpirate: redbeardはpirates.ymlに存在しないんですがバリデーションされないので通る」「ふむふむ」「この改修では外部キーのバリデーションを行うようにしたようですね」

# 同PRより
# test/fixtures/parrots.yml
george:
  name: "Curious George"
  pirate: redbeard

# test/fixtures/pirates.yml
blackbeard:
  name: "Blackbeard"

「修正後はバリデーションに失敗するとエラーをraiseするようになっている↓」

# activerecord/lib/active_record/fixtures.rb#L640
            if ActiveRecord.verify_foreign_keys_for_fixtures && !conn.all_foreign_keys_valid?
              raise "Foreign key violations found in your fixture data. Ensure you aren't referring to labels that don't exist on associations."
            end

Rails API: `ActiveRecord::FixtureSet`(翻訳)

🔗 fixtureとfactory_bot

「ただfixtureが外部キーでバリデーションエラーを出すようになると少し面倒になるんですよ: 複雑な依存関係を持つデータをfixtureで扱う場合、バリデーションエラーにならないためにfixtureの適切な読み込み順序なども考えないといけなくなってしまう」「あ〜そうか!」

「おそらくfixtureの使い所は基本的に単体モデルのデータのようなあまり複雑でないものだと思うので、それもあって従来はfixtureで外部キーをバリデーションしていなかったんじゃないかなと想像しています」「ふ〜む」「自分もこれまでfixtureは比較的シンプルなデータのセットアップに使うことが多くて、リレーションが複雑になってきたらfixtureをやめてfactory_botにしていましたね」

thoughtbot/factory_bot - GitHub

「このバリデーション機能はfixtureで基本的にやりたい人にはありがたいと思います: 個人的には上のように外部キーを使うデータはあまりfixture向きではないかなという気もしていますが」「なるほど」

🔗 存在しないミドルウェアをdeleteした場合にエラーを出すよう修正


つっつきボイス:「config.middleware.deleteで削除するミドルウェアが存在していない場合にエラーを出すようにしたんですね: 何も出さないよりは正しそう」

# actionpack/lib/action_dispatch/middleware/stack.rb# 132
    def delete(target)
-     middlewares.delete_if { |m| m.name == target.name }
+     middlewares.reject! { |m| m.name == target.name } || (raise "No such middleware to delete: #{target.inspect}")
    end

🔗 rails gで指定するインデックス種別が無効な場合にエラーを出すようにした


つっつきボイス:「従来は以下の:indxeみたいなスペルミスが無視されていたのを、エラーを出すようにしたそうです」

# 同PRより
bin/rails g model post title:string:indxe

「Railsのジェネレータを普段使っていないから、post title:string:indexと書くとインデックスを付けられるとは知らなかった」「そういえばRailsを長くやっている人はscaffoldをあまり使わない印象がありますね」

参考: Rails ジェネレータとテンプレート入門 - Railsガイド

🔗Rails

🔗 GitHubがRails 7に追加したdisable_joinsの解説記事


つっつきボイス:「GitHub主席ソフトウェアエンジニアのEileenさんの記事です」「お、どこかで見たと思ったらRails 7に最近入ったdisable_joinsですね(ウォッチ20210426): マルチプルデータベース間であたかもjoinsしているかのように関連付けを取り出そうとするとSQLではJOINできないので、Active Recordが代わりに分割してクエリを発行してくれる機能」「あ、そうでした」「EileenさんがこうやってRailsコミット記事を出すことで、Railsに貢献する意義をGitHub社内にアピールできるという側面もありそうですね: よさそうな記事👍

# 同記事より
class Dog < AnimalsRecord
  has_many: treats, through: :humans, disable_joins: true
  has_many :humans
end
-- 同記事より
SELECT "humans"."id" FROM "humans" WHERE "humans"."dog_id" = ?  [["dog_id", 1]]
SELECT "treats".* FROM "treats" WHERE "treats"."human_id" IN (?, ?, ?)  [["human_id", 1], ["human_id", 2], ["human_id", 3]]

Rails 7: has_many :through関連付けにdisable_joins: trueオプションが追加(翻訳)

🔗 Rails 7のモデル暗号化導入の経緯


つっつきボイス:「BasecampがやっているHEYの中の人が書いた、Rails 7のモデル暗号化機能のいきさつ記事だそうです」「そういえば暗号化機能はもともとHEYで使っていたものを切り出したという話がありましたね」「HEYの中で第三者によるセキュリティ監査も受けたそうです」「この記事もなかなかよさそう👍

「モデル属性の暗号化はRails 7の新機能の中では比較的大きな位置を占めそうなので、Action Mailboxとかよりも使う人は多いんじゃないかな」「そんな気がしますね」

🔗 MemoWise gem: メモ化支援gem(Ruby Weeklyより)

panorama-ed/memo_wise - GitHub


つっつきボイス:「MemoWiseは、Rubyのメモ化(memoisation)を以下のようにmemo_wiseなどで書けるgemなんですね: どこかで見たかも」

# 同記事より: 通常のメモ化
class Example
  def slow_value
    @slow_value ||= begin
      ...
    end
  end
end
# 同記事より: MemoWiseの場合
class Example
  prepend MemoWise

  def slow_value
    ...
  end
  memo_wise :slow_value
end

参考: メモ化 - Wikipedia

「インスタンス変数とメソッド名を取り違えそうになると記事に書かれていました」「そうそう、アクセサメソッドをインスタンス変数と同じ名前にするとそうなりがちなので注意が必要ですね」

「一見メモ化のためだけのgemを作るほどでもなさそうにも見えますが、単なる利便性よりも、ここでメモ化が行われることをmemo_wiseで明示的に示すことでコードの可読性を高める効果が期待できそうかなと思いました👍」「なるほど」

つっつきの後で以下の記事も見つけました。

参考: Optimizing MemoWise Performance @ ja.cob.land

Ruby: インスタンス変数初期化のメモ化`||=`はほとんどの場合不要

🔗 RailsへのDDoS攻撃の影響を最小化する(Hacklinesより)

参考: DoS攻撃 - Wikipedia


つっつきボイス:「記事ではrack-attackミドルウェアによるスロットリングが取り上げられていますね: ちょうど最近使いました」「私も使ってます」

rack/rack-attack - GitHub

「記事ではCloudflareなどのサービスを用いたDNSレベルの防御の話もしている」「ふむふむ」

「DDoS対策は、まずRailsサーバーにいかにDDoSを届かせないかが重要だと思います: rack-attackのスロットリングについても、記事にもあるようにfail2banと組み合わせられればより防御を固められるでしょうね」

参考: Fail2ban
参考: 不正アクセスからサーバを守るfail2ban。さくらのクラウド、VPSで使ってみよう! | さくらのナレッジ

「fail2banって初めて知りました」「fail2banはrack-attackと別にかなり昔からある不正アクセス遮断用のソフトウェアで、iptablesと統合されることもよくありますし、いろんなところで使われています」「なるほど」「たしかにfail2banのページのつくりが昔っぽいかも」

参考: iptables - Wikipedia

「たとえばiptablesにfail2banを組み合わせると、一定時間内に何リクエスト以上来ると指定の秒数だけ遮断するといった設定を書けます: ただしfail2banはシステムに入っていないと使えません」「なるほど」「もちろんrack-attackでもスロットリングはできますが、DDoSがRackに届くことはRailsに届くことでもあるので、ガチのDDoS相手だときつい」「あ〜」「元記事にも書いてあるように、可能ならいわゆるWAF(Web Application Firewall)的なものも使いたいですね」

参考: Web Application Firewall - Wikipedia

同記事によると、rack-attackはfail2banなどのシステムレベルのツールが利用できない環境(Herokuなど)で便利だそうです。


前編は以上です。

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

週刊Railsウォッチ: ruby-spacyで自然言語処理、Ruby製x86-64アセンブラ、『タイムゾーン呪いの書』ほか(20210713後編)

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

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

Rails公式ニュース

Ruby Weekly

Hacklines

Hacklines

The post 週刊Railsウォッチ: GitHubによるdisable_joins解説、MemoWise gemでメモ化、RailsのDDoS攻撃対策ほか(20210719前編) first appeared on TechRacho.

週刊Railsウォッチ: ruby-gitでGit操作、最近のruby/debug、stdgems.org、Windows 365 Cloud PCほか(20210720後編)

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

お知らせ: 今週木金は祝日のため、来週は週刊Railsウォッチの代わりに通常記事を公開します。

🔗Ruby

🔗 ruby-git(Ruby Weeklyより)

ruby-git/ruby-git - GitHub


つっつきボイス:「RubyからGitを操作できるgemがあるんですね」

# 同リポジトリより
g = Git.open(working_dir, :log => Logger.new(STDOUT))

g.index
g.index.readable?
g.index.writable?
g.repo
g.dir

g.log   # returns array of Git::Commit objects
g.log.since('2 weeks ago')
g.log.between('v2.5', 'v2.6')
g.log.each {|l| puts l.sha }
g.gblob('v2.5:Makefile').log.since('2 weeks ago')

g.object('HEAD^').to_s  # git show / git rev-parse
g.object('HEAD^').contents
g.object('v2.5:Makefile').size
g.object('v2.5:Makefile').sha

g.gtree(treeish)
g.gblob(treeish)
g.gcommit(treeish)
# (略)

「インストールコマンドがsudo gem install gitなの、わかるけどちょっと面白かった」「内部でGitコマンドを使っているみたい」

「Gitのコマンドはひととおり使えそうかな」「★も1,500超えですね」「たしかにRubyでGitコマンドをシェル経由で呼び出して標準入力を自分で取ったりするより使い勝手はいいでしょうね: こういうGitバインディングがRubyで使えるのはよさそう👍

🔗 dry-transformer

dry-rb/dry-transformer - GitHub


つっつきボイス:「dry-transformerは、dry-rbシリーズの割と新しいgemだそうです」「新しいといっても2年前ぐらいかな」

「こんなふうに変換のパイプラインを記述できるらしい: mapperやreducerを思わせますね」

# dry-rb.orgより
class MyMapper < Dry::Transformer::Pipe
  import Dry::Transformer::ArrayTransformations
  import Dry::Transformer::HashTransformations

  define! do
    map_array do
      symbolize_keys
      rename_keys user_name: :name
      nest :address, [:city, :street, :zipcode]
    end
  end
end

mapper = MyMapper.new

mapper.(
  [
    { 'user_name' => 'Jane',
      'city' => 'NYC',
      'street' => 'Street 1',
      'zipcode' => '123'
    }
  ]
)
# => [{:name=>"Jane", :address=>{:city=>"NYC", :street=>"Street 1", :zipcode=>"123"}}]

参考: MapReduce - Wikipedia

「dry-transformerは以下の記事で知りました↓」「CSVのエクスポート/インポートやRSSのパース、コードハイライトなどに使えるとある: たしかにパーサーにはいいかも」

Dry::Transformer::HashTransformationsをインポートするとハッシュを受けるパイプを作れるみたい」

# 同記事より
class HanamiMasteryAdapter < Dry::Transformer::Pipe
  import Dry::Transformer::HashTransformations

  define! do
    deep_symbolize_keys
    nest :address, %i[city zip]
    rename_keys login: :name
  end
end

hash = {
  'id' => 1
  'login' => 'John',
  'city' => 'NY',
  'zip' => { 'number' => 1234 }
}
adapter.call(hash)
# => { id: name: 'John', address: { city: 'NY', zip: { number: 1234 } } }

「できればdry-transformerがうまくハマるような、もうちょっと複雑な例をコードで見たいですね: MapReduce的な処理は複数プロセス間でのデータのやりとりがメインになる印象が強いんですが、この記事だと1個のプロセス内でやるシンプルなコード例しか載っていない」「たしかに変換を1個作るだけだとありがたみがわかりにくいかも」「さまざまな変換処理をパイプでつなぎ替えて出力を変えられるとか、そういう実例が欲しい」

🔗 最近のruby/debug

つっつきボイス:「ruby/debugのドキュメントが全面的にリライトされたそうです🎉」「_ko1さん精力的に活動していてスゴい」

「1.0.0のリリースを目指しているようです↓」「今はv1.0.0.beta8なのね」

🔗 stdgems.org

同サイトは、以下のブログサイトにあった情報を独自ドメインに切り出したもののようです。

参考: Idiosyncratic Ruby

つっつきボイス:「ruby-jp Slackでこのstdgems.orgを見かけました」「おぉ、Rubyバージョンごとのdefault gemsとbundled gemsのリストをここで見られるんですね、これは地味にありがたい!」


stdgems.orgより

「こういうリストはそんなに頻繁に使わないんですけど、たまに探し回るんですよ」「たしかに」「gemのバージョン番号もちゃんと書かれているのがいいですね: Gemfile.lockで競合が発生したときにデフォルトでインストールされるgemのバージョンを調べることがあるんですが、こういう情報があると助かります: いいサイト👍

🔗 その他Ruby

つっつきボイス:「mrubyではなくmruby/cの変更ですね」

mrubyc/mrubyc - GitHub

「CRubyのFixnumとBignumがIntegerに統合されたのはいつだったかな…Ruby 2.4か↓」「もうだいぶ前なんですね」

参考: Ruby 2.4 unifies Fixnum and Bignum into Integer | BigBinary Blog

後でやってみると、Ruby 2.5.9やRuby 2.6.8では以下のwarningを出してIntegerが返り、Ruby 2.7.4とRuby 3.0.2ではwarningなしでIntegerが返りました。

irb(main):001:0> Fixnum
(irb):1: warning: constant ::Fixnum is deprecated
=> Integer

🔗 セキュリティ

🔗 Open Source Vulnerabilitiesデータベース


つっつきボイス:「Open Source VulnerabilitiesはGoogleがやっているプロジェクトのようですね」「現在の脆弱性交換スキーマ定義はこんな感じらしい↓」

# 同記事より
{
        "id": string,
        "modified": string,
        "published": string,
        "withdrawn": string,
        "aliases": [ string ],
        "related": [ string ],
        "package": {
                "ecosystem": string,
                "name": string,
                "purl": string,
        },
        "summary": string,
        "details": string,
        "affects": [ {
                "ranges": [ {
                        "type": string,
                        "repo": string,
                        "introduced": string,
                        "fixed": string
                } ],
                "versions": [ string ]
        } ],
        "references": [ {
                "type": string,
                "url": string
        } ],
        "ecosystem_specific": { see spec },
        "database_specific": { see spec },
}

「あるとありがたい情報ですしGoogleがこういうプロジェクトをやるのも理解できるけど、他の企業とかも巻き込んでアライアンス主体の運用になればプロジェクトの継続性についてより安心できるかなと思いました」「CVEはIDの付与と基本情報やリンクの提供にとどまっていますけど、ちゃんと継続的に運用されていますよね」「スキーマも欲しいけど継続的運用の方が欲しい」

参考: CVE - CVE

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

🔗 大きなGitリポジトリの操作


つっつきボイス:「はてブでバズっていた記事です」「シャロークローンはよく使われていて、パーシャルクローンも見た覚えがあったけど、リファレンスリポジトリ周りの話は初めて見た」


同記事より: リファレンスリポジトリの活用例

「こうした個別の手法や概念は昔からGitにありますが、ゲームなどのように画像などのアセットが多い大規模なサービスを扱うようになると、この記事で解説されているようなことが関連してくるでしょうね: 通常の開発だとなかなかそこまではいかないかもしれませんが」「なるほど」「GitHubのような大規模リポジトリサービスを構築する場合に必要になりそうですね」

「このgit fetch --unshallow知らなかった↓: シャロークローンで最初はスキップして取り込まなかったコミットを改めて全部取り直せると記事にもある」「へ〜!」

🔗言語/ツール/OS/CPU

🔗 Windows 365 Cloud PC


つっつきボイス:「Windows界隈がこれで盛り上がっていますね」「Twitterにも書きましたけど、企業のインターネット接続が果たして持つだろうかと思いましたね」「ああ、そこか!」「今はリモートワーク主体なので企業のネット回線にはそれほど負荷はかかっていませんけど、それが終わって全員出社するようになったときに負荷が一気に高まって詰まりそうな予感」「う〜む」

「多くの企業のインターネット接続が1Gbpsぐらいだとすると、社員がそこで普通にネット接続をシェアしている分には、社員全員がNetflixを見るみたいな無茶をしない限り問題ないんですが、Windows 365 Cloud PCのような感じで全員がリモートデスクトップ経由でVDIに常時接続すると回線が詰まるんじゃないかな」「それもそうですよね…」

参考: デスクトップ仮想化(VDI)の基本について学ぶ | IT価値創造塾

「逆に、社員同士での巨大ファイルのやりとりが非常に多い場合とかならWindows 365 Cloud PCの方がファイルのやりとりがクラウド内に閉じ込められる分軽くなるかもしれませんね」「なるほど」「シンクライアントの夢ふたたび感ある」「遅延がつらくなりそうなので、自分は使わないかな〜」

参考: シンクライアント - Wikipedia

🔗 Windowsリモートデスクトップ接続よもやま話

「最近調べたんですが、Windows 10リモートデスクトップのホスト機能はHomeエディションのみ利用不可で、たとえばProエディションとEnterpriseエディションでは同時に1台まで接続可能」「ふむふむ」「同時接続数はレジストリを変更すれば増やせるんですが、デスクトップ画面を表示できるのはあくまで1ユーザーのみで、他の同時接続はその画面をシェアする形になるので、いわゆるUnix系OSのマルチログイン的なことができないんですよ(別ユーザーで接続すると既存ユーザーはログアウトされる)」

「Windows Serverなら同時ログインデスクトップ数が2まで緩和されるので、たとえばAdministratorとoperatorが同時に異なるデスクトップを利用できるそうです」

参考: Windows10 - リモートデスクトップを有効にする方法 | PC設定のカルマ

「個人的にはHomeエディションでリモートデスクトップ接続が許可されて同時ログインデスクトップ数2まで使える方がうれしいかな: それが可能になればリモート接続でゲームができるから」「それはたしかに憧れの機能😆」「リモートPCにゲーム専用のユーザーを作っておけば、手元のPCのフォアグラウンドを落とさずにリモートでゲームを起動できるかなと思ってそのあたりを一生懸命調べたんですけど、どうやらWindows Serverじゃないとできないみたい」「残念」

「その一方で、Azureで動いているWindowsはマルチセッションが使えることが判明しました: このAzure Virtual Desktopがそうです↓」「へ〜!」「Azure環境ならマルチセッションで複数ユーザーが同時接続できるのに、通常のWindowsだと許されてなくて悲しい結論になってしまいました」

参考: Azure Virtual Desktop | リモート デスクトップ | Microsoft Azure

🔗 その他ツール(StatusCode Weeklyより)


つっつきボイス:「rfc.fyiはRFCファイルを爆速検索できるサービスですね」

「slowfil.esは何だろうと思ったら、https://slowfil.es/file?type=js&delay=2500みたいなURLをscriptタグとかで読み込ませると、遅延をかけたりステータスコードを404や500にしたレスポンスを返してくれるということらしい」「なるほど」「ごくたまに、開発中に遅いWebサイトを読み込ませるとどうなるかを手軽に試すときに使う感じかな」


後編は以上です。

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

週刊Railsウォッチ: GitHubによるdisable_joins解説、MemoWise gemでメモ化、RailsのDDoS攻撃対策ほか(20210719前編)

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

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

Ruby Weekly

StatusCode Weekly

statuscode_weekly_banner

The post 週刊Railsウォッチ: ruby-gitでGit操作、最近のruby/debug、stdgems.org、Windows 365 Cloud PCほか(20210720後編) first appeared on TechRacho.

Rails: Pagy gemでRailsアプリを高速ページネーション(翻訳)

$
0
0

概要

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

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


  • 2018/05/16: 初版公開
  • 2021/07/13: 更新

ddnexus/pagy - GitHub

202107/13時点ののPagy 4.10.1ではRuby 2.5以上が必須です。それ以前のバージョンのRubyではPagy3を使います。

Rails: Pagy gemでRailsアプリを高速ページネーション(翻訳)

私はRuby on Railsがバージョン0.8の頃から開発者として使っていることもあり、その後誇大広告気味のRubyGemを星の数ほど目にしてきました。

成熟したソフトウェア開発コミュニティと同様、特定の用途では事実標準のgemとして永らえているライブラリもありますが、他の多くのライブラリは誰もサポートしてくれないまま、採用に値しない瓦礫の山を築いています。

用途によっては1つや2つgemをあえて追加することもありますが、ほとんどどの機能にもそれぞれに相応しいチャンピオンが君臨しているものです

Railsの進化がたどった道

以下の図は、ここ10年の間に新しいライブラリやアイデアにコミュニティが貢献した様子を示しています。

年ごとのRubyGem数

当初のgem数は増加していますが、2015年をピークに以後は減少しています(成熟したとも言えるでしょうか)。私は、2018年のgem数は2009年とほぼ同レベルであろうと睨んでおり、この傾向が続くならば新しいgemの数は今後も減少し続け、安定期に入るでしょう

ここからさまざまな疑問が湧き起こります。「Railsの潜在能力は2015年がピークだったのか?」「Railsというプラットフォームの採用も減少しているのか?」「むしろこれは成熟の証で、Railsは大企業が採用してよいものになったのだろうか?」

私としては、このデータだけでは何とも言えません。むしろそうした視点とは違うアプローチによって、今日の私たちの仕事に大きなインパクトをもたらす部分に着目してみたいと思います

新しいgemの数が減少するに連れて、今の私たちの仕事に大きく影響するような画期的なものはなかなか出現しなくなるだろうと考えられます。しかしそんなことはありません。それを今から証明いたします。

Pagyとの出会い

Ruby on Railsで開発したことがある方なら、アプリのindexページをwill_paginateやKaminariでページネーションしたことがおそらくあるでしょう。Rails経験の浅い方なら、今後そうしたページネーションgemを探すことになるでしょう。

PagyはRails向けの新しいページネーションライブラリです。パフォーマンスを念頭に置いて開発されており、かつ新規または既存のRailsアプリの使いやすさを損ないません。

はい、今皆さんが思っていることを当ててみせましょう。

「お、今度のページネーションgemこそ俺たちが求めていたもの…なわけないだろ!」

そこについては私も同感です。オープンソースでソリューションが分散してしまうのはよくないと思います。しかも、このPagy gemによってもたらされる改良点のいくつかは、既にKaminariでも実装が提案されたのですが、リジェクトされました。おそらく、改善にはKaminariコア部分の大規模な変更が避けられなかったのでしょう。

何かが華々しく登場したら、ときにはそれが本当にベストなのかどうか一歩引いて考える必要があると、私は固く信じています。そしてページネーションについて言うなら、このgemには疑いの余地はありません。理由を説明しましょう。

pagy-kaminari-will_paginate-memory-used-per-page-shown

この図がすべてを物語っていると私は信じています

アプリが数百〜数千ものユーザーを同時にさばいている状態では、ちっぽけな機能を1つ足すだけでもリソースに大きな影響を及ぼすことは容易に想像が付きます。20ページに対して実施したテストの結果を見ると、will_paginateはうまくやっている一方、Kaminariのメモリ容量は著しく増大しています。しかしPagyはwill_paginateよりさらに優秀です。数千人ものユーザーをさばこうとするとき、この違いがもたらすインパクトを今一度考えてみてください

さらに詳しく見てみましょう。今度はそれぞれのgemのメモリフットプリントを比較します。

pagy-kaminari-will_paginate-total-memory-used

Kaminariと同じジョブを、Pagyは遥かに少ないメモリで実行できます。will_paginateもKaminariよりは省メモリですが、それでもPagyと比べると7倍です。さらに皆さまに考慮いただきたいのは、will_paginateが最後にリリースされたのは1年も前のことで、リポジトリにはプルリクが何十個もたまったまま、最終コミットの日付は2017年7月のままになっている点です。

訳注

will_pagenateは、2020/02/25に3.3.0がリリースされています。また、2021/07/13時点でオープンされているプルリクはゼロです。

今仮に新規プロジェクトがスタートすることがあったとしても、will_paginateは選択肢には含めないでしょう。

はい、皆さんはここできっとこう思うでしょう。

「よし気に入った。ところでPagyが他のgemと比べてメモリフットプリントがここまで少ない理由が謎なんだけど?」

よくぞ聞いてくださいました。ご説明いたしましょう。

pagy-kaminari-will_paginate-number-of-objects-created

実は私も、当初同じ疑問を抱いたのでした。

「Kaminariが生成するオブジェクト数が6,000個超えってどういうこと?」

もちろんwill_paginateの方が省メモリではあるものの、それでも20ページのページネーションでオブジェクト数3,000個超えは多すぎです。一方Pagyのオブジェクト数は400個を下回っています。

Pagyの方が優秀な理由

pagyのどこがそんなに違うのでしょうか?ご説明いたします。

  • PagyはRubyオブジェクトではなくintegerで計算している
  • Pagyのコアコードは60行にも満たない
  • Pagyはアプリのモデルに癒着しておらず、HTMLやURLや複数形化(pluralization)や式展開を独自に生成する。
  • Pagyは一般的なヘルパーではなく用途に特化した特殊なコードを用いている。
  • コードが専門化していることで、作者が1行ずつ丁寧にベンチマークを取っている(コードが100行もなければ十分可能です)。

上の理由のおかげで、pagy gemのコードはきわめて理解しやすいという実にうれしいおまけまでついています。

さらなる考察

私がRailsアプリを開発した当初はwill_paginateを使っていましたが、その理由は当時はその業務にベストだったからです。数か月後にKaminariがリリースされると、そちらに乗り移りました(あまりにも昔のことなので、乗り換えた理由はよく思い出せません)。そしてつい数週間前にPagyがリリースされるまで、Kaminariを使い続けていました。

私が初めてPagyを知ったときに心底驚いたのは、自分がwill_paginateからKaminariに移行したときにパフォーマンスのことをまったく考慮していなかったことでした。後者は前者よりずっと遅かったにもかかわらず、です。今の私はパフォーマンスを考慮するようになりました。

これまで私たちが設計・リリースしたアプリで、いったいいくつのgemを取り入れてはパフォーマンスを下げまくっていたことでしょう。

will_paginateやKaminariからPagyへの移行は実に簡単で、必要なコードはわずか数行で済みます。次回の私の記事では皆さまに関心を持っていただけるよう、具体的な移行方法を解説することをお約束いたします。

免責事項: 本記事の画像およびパフォーマンスはPagyの作者が行ったベンチマークテストを用いています。ベンチマークのソースコードはddnexus/pagination-comparisonでご覧いただけます。RubyGem採用の推移チャートには、こちらでご覧いただけるRubyGems.orgのデータを用いております。

関連記事

Rails: パーシャルと`collection:`でN+1クエリを回避してビューを高速化(翻訳)

kaminariでRubyの地雷を踏んだ

The post Rails: Pagy gemでRailsアプリを高速ページネーション(翻訳) first appeared on TechRacho.

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

$
0
0

概要

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

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

  • 2019/05/16: 初版公開
  • 2021/07/19: 更新

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: その後close)。現時点では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: その後close)。現在は、添付ファイルの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: その後close)ので、今後の更新情報をフォローしてください。

🔗 「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ほか(翻訳)

The post Rails 6のB面に隠れている地味にうれしい機能たち(翻訳) first appeared on TechRacho.

Railsの技: 特定スコープ内でuniquenessバリデーションをかける(翻訳)

$
0
0

概要

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

Railsの技: 特定スコープ内でuniquenessバリデーションをかける(翻訳)

データベースでのバリデーションとアプリケーションでのバリデーションを一致させるのはよい考えです。モデルにvalidates :name, presence: trueというバリデーションがあるなら、データベース側にもそれに対応するnot null制約を付けるべきです。uniquenessバリデーションの場合は、データベースのUNIQUEインデックスと合わせて使うべきです。

現実のアプリケーションではバリデーションがもっと複雑になりがちですが、それでもできる限りこの方法を続けるべきです。

私の場合、レコードを特定のスコープ内に限って一意にする必要が生じることがよくあります。

たとえば典型的なプロジェクト管理ツールを構築中だとしましょう。Projectに持たせる名前は一意にしてUI画面内で区別できるようにしたいのですが、その名前をグローバルに一意にしたくありません。あるプロジェクトの名前をOnboadingにしたとしても、他の顧客がその名前を使うことに制約を加えるべきではありません。

ありがたいことに、Railsでは「バリデーションスコープ」という便利な機能が使えます。

使い方

Railsのuniquenessバリデーションルールでscope:オプションを使うと、uniquenessチェックで考慮すべきカラムを追加で指定できます。

class Project < ApplicationRecord
  belongs_to :account

  has_many :tasks

  validates :name, presence: true, uniqueness: { scope: :account_id }
end

このルールは「プロジェクト名は、このアカウントのスコープ内で一意でなければならない」という意味です。言い換えると、nameaccount_idの組み合わせが一意である必要がありますが、アカウントが異なれば同じプロジェクト名を使えます。

前述したように、アプリケーションレベルのバリデーションをデータベース制約にも反映したくなるでしょう。

その場合はマルチカラムインデックスを使うことになります。マルチカラムインデックスは以下のように通常のRailsマイグレーションで設定できます。

class CreateProject < ActiveRecord::Migration[6.0]
  def change
    create_table :projects do |t|
      ...
    end

    add_index :projects, [:name, :account_id], unique: true
  end
end

オプション

scope:には複数のカラムを渡せます。

たとえばレストラン向けアプリを構築していて、ゲストが1軒のレストランにつき1日に1回しか予約できないようにしたいとします。

class Reservation < ApplicationRecord
  belongs_to :guest
  belongs_to :restaurant

  validates :guest_id, uniqueness: {
    scope: [ :restaurant_id, :reservation_date ]
  }
end

デフォルトのエラーメッセージ「{field} has already been taken」のままではそっけないので、以下のようにエラーメッセージも変更するとよいでしょう。

validates :guest_id, uniqueness: {
  scope: [ :restaurant_id, :reservation_date ],
  message: "Only one reservation per guest per day is permitted"
}

原注: PostgreSQLの場合はデフォルトのインデックス名の上限が63文字までとなっています。モデルやカラム名が長くなる場合はインデックス名を変更する必要が生じるかもしれません。

add_index :reservations, [:guest_id, :restaurant_id, :reservation_date],
  unique: true,
  name: "idx_reserveration_guest_date_uniq"

参考資料

関連記事

https://techracho.bpsinc.jp/hachi8833/2021_07_15/108763

The post Railsの技: 特定スコープ内でuniquenessバリデーションをかける(翻訳) first appeared on TechRacho.

Rails 6.1: Active Storageのファイルをプロキシ経由で配信する(翻訳)

$
0
0

概要

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

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

参考: 「Rails 6.1で新しく入る機能について」iCARE Dev Meetup #12 の登壇内容 https://icare.connpass.com/event/183716/

Rails 6.1: Active Storageのファイルをプロキシ経由で配信する(翻訳)

Railsはどの機能も手軽に使えることで知られていますが、ストレージについては苦労がつきものです。Rails 5や6で導入されたActive Storageにはこの数年さまざまな改良が加えられ、多数のgemを使わなくてもRailsコアに依存するだけで基本的な機能を使えるようになりました。プロキシ経由のファイル配信もそうした機能のひとつです。

この機能を掘り下げる前に、プロキシの意味と一般のプロキシサーバーについておさらいしてみます。英語のproxyは「自分の代わりに何かをする権限を与える」という意味です。これをコンピュータサイエンスの世界に置き換えると、プロキシサーバーは本質的に元のリクエストの「バイパス」の役割を果たします。リクエストが送信先のサーバーで実行されるのではなく、リクエストの処理がプロキシサーバーに移管され、プロキシサーバーが代わりにレスポンスを返します。

これは、特に静的アセットを配信するときに有用です(ファイルアップロードなど)。Railsサーバーはビジネスロジックのクエリ対応だけを担当し、ファイル配信のようなリソースを食うタスクはCDNに任せる形になります。こうしたCDNは、ストリーミングやマルチロケーション配信といった多くの強化機能を提供しています。

変更前

プロキシ機能が導入される前は、Webサーバーから直接ファイルを配信していました。これによってアプリケーションサーバーの負荷は軽減しましたが、まだ改良の余地があります。ActiveStorageが生成するファイル用のURLにアクセスすると、短時間だけ有効な署名付きURLにリダイレクトします。これがデフォルトのファイル配信戦略です1

  <%= image_tag rails_storage_redirect_path(current_user.profile_picture) %>

変更後

#34477でプロキシ機能が導入され、署名付きURLにリダイレクトすることなく、背後のストレージサービスからファイルを配信できるようになりました。

この機能を有効にするには、config.active_storage.resolve_model_to_routeコンフィグを以下のように設定します。

 config.active_storage.resolve_model_to_route = :rails_storage_proxy

後は以下のように書くだけです。

<%= image_tag current_user.profile_picture %>

特定の添付ファイルを明示的にプロキシしたい場合は、以下のコードが使えます。

 <%= image_tag rails_storage_proxy_path(current_user.profile_picture) %>

関連記事

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


  1. 訳注: このリダイレクトの挙動がデフォルトであることが原文では「変更前」セクションに書かれていますが、#34477のActive Storage READMEには「変更後」の仕様として追記されています。 

The post Rails 6.1: Active Storageのファイルをプロキシ経由で配信する(翻訳) first appeared on TechRacho.

週刊Railsウォッチ: SorbetでRailsアプリの型シグネチャを書く、activerecord-cte gemとanycable-client gem(20210803前編)

$
0
0

こんにちは、hachi8833です。RubyKaigi Takeout 2021のチケット販売が開始されました。

週刊Railsウォッチについて

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

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

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

今回は以下の公式更新情報から見繕いました。

🔗 Active SupportのNilClass#tryNilClass#try!がRuby 2.7以降で遅かった問題を修正


つっつきボイス:「以前#34068でRuby 2.5向けにNilClass#tryNilClass#try!を高速化したけど、Ruby 2.7以降だと逆に遅くなることがわかったので元に戻したんですね」

# activesupport/lib/active_support/core_ext/object/try.rb#L5
module ActiveSupport
  module Tryable #:nodoc:
-   def try(method_name = nil, *args, &block)
-     if method_name.nil? && block_given?
+   def try(*args, &block)
+     if args.empty? && block_given?
        if block.arity == 0
          instance_eval(&block)
        else
          yield self
        end
-     elsif respond_to?(method_name)
-       public_send(method_name, *args, &block)
+     elsif respond_to?(args.first)
+       public_send(*args, &block)
      end
    end
    ruby2_keywords(:try)

-   def try!(method_name = nil, *args, &block)
-     if method_name.nil? && block_given?
+   def try!(*args, &block)
+     if args.empty? && block_given?
        if block.arity == 0
          instance_eval(&block)
        else
          yield self
        end
      else
-       public_send(method_name, *args, &block)
+       public_send(*args, &block)
      end
    end
    ruby2_keywords(:try!)
  end
end
...

class NilClass
...
- def try(_method_name = nil, *)
+ def try(*)
    nil
  end

- def try!(_method_name = nil, *)
+ def try!(*)
    nil
  end
end

🔗 ハッシュ構文でorderしたときのeager_loading?を修正

ハッシュ構文でorderしたときのeager_loading?を修正。

外側テーブルをハッシュ構文でorderしたときにeager_loading?が正しく動くようになった。

Post.includes(:comments).order({ "comments.label": :ASC }).eager_loading?
#=> true

Jacopo Beschi
同PRより


つっつきボイス:「eager_loading?はeager loadingされているかどうかをtrue/falseで返すだけのメソッドなのに、これでエラーになったらびっくりする」「よくぞ見つけた感」「修正を見ると以前はStringしか想定されていなかったんですね↓」

# activerecord/lib/active_record/relation/query_methods.rb#L1550
      def column_references(order_args)
-       references = order_args.grep(String)
+       references = order_args.flat_map do |arg|
+         case arg
+         when String
+           arg
+         when Hash
+           arg.keys
+         end
+       end
        references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
        references
      end

🔗 パラレルテストの最小数を設定可能になった


つっつきボイス:「test_parallelization_minimum_number_of_testsコンフィグが追加されたんですね」「デフォルトの最小パラレルテスト数は50か」「環境によって使えるメモリ量も違うので、これはたしかにコンフィグ可能であって欲しい👍

# Changelogより
config.active_support.test_parallelization_minimum_number_of_tests = 100

参考: 3 並列テスト — Rails テスティングガイド - Railsガイド

🔗 データベースごとにスキーマダンプを無効にできるようになった


つっつきボイス:「マルチプルデータベース向けの機能っぽいですね」「schema_dump: falseでデータベースごとにスキーマダンプをオフにできる」「細かいですけど見出しのdumbはdumpのつもりだったんでしょうね」

# 同Changelogより
# config/database.yml
production:
  schema_dump: false

「ところでスキーマダンプをデータベースごとにオフにしたいシチュエーションって何だろう?🤔」「言われてみるとこの機能が欲しい理由がプルリクに書かれていませんね」

追いかけボイス:「もしかするとRailsのmigrationで管理していないDBが接続先にある場合に使いたいのかもしれませんね: 手動で作ったschema dumpを読み込ませたいといったケースがありえるのかも」

🔗 belongs_to関連付けにトラッキング変更メソッドが2つ追加

belongs_to関連付けが直前のsaveで新しいレコードを指しているかどうかと、次のsaveで新しいレコードを指しているかどうかを調べられるようになる。

post.category # => #<Category id: 1, name: "Ruby">

post.category = Category.second   # => #<Category id: 2, name: "Programming">
post.category_changed?            # => true
post.category_previously_changed? # => false

post.save!

post.category_changed?            # => false
post.category_previously_changed? # => true

利用例: Hotwireで、ブログ記事のカテゴリが変更されたら直前のカテゴリから削除するようブロードキャストする。関連付けの直前のターゲットのアクセサが必要だが、これは別途追加可能。
同PRより


つっつきボイス:「belongs_to関連付けに関連付け名_changed?関連付け名_previously_changed?が生えてくるようになったみたい」「今まではbelongs_to関連付けにDirty的な機能がなかったのか」「早くも記事が出ていました↓」「使おうと思ったことはなかったけど、あれば使うかも」

参考: Rails 7 adds change tracking methods for belongs_to associations | Saeloun Blog

🔗Rails

🔗 SorbetでRailsアプリの型シグネチャを書く方法(Ruby Weeklyより)

# 同記事より: Sorbetを使うRubyコードサンプル
# typed: true
class Foo
  extend T::Sig

  sig { params(num: Integer).returns(Integer) }
  def self.double(num)
    num * 2
  end
end

Foo.double('bar') #=> Expected Integer but found String("bar") for argument num
T.reveal_type(Foo.double(10)) #=> Revealed type: Integer

つっつきボイス:「記事にあるTapiocaは、たしかSorbet用のRBI(Ruby interface)を生成するShopifyのgem↓」「sorbet-rails gemなども含めてSorbet関連ツールはひととおり揃ってきたようですね」

Shopify/tapioca - GitHub

chanzuckerberg/sorbet-rails - GitHub

sorbet/sorbet-typed - GitHub

「このあたりを見極めるには、Sorbet環境の整ったRailsプロジェクトで数か月ぐらい開発を体験してから、自分でもスクラッチでいくつかアプリを作ってみたりする必要があるかも」「自分もそんな気がします」「Sorbetで開発が順調に回っているところを実際に体験してみないとなかなかわからなそう」「これができるとどのあたりが嬉しいんでしょうか?」「少数精鋭のプロジェクトだと実感しにくいですが、特に人数が多いプロジェクトがSorbetなどで型チェックがうまく回るようになれば、メンバーの出入りがあったときの安心感が違いますし、他にも嬉しいことがいろいろあると思います」「お〜」

🔗 activerecord-cte(Ruby Weeklyより)

vlado/activerecord-cte - GitHub


つっつきボイス:「activerecord-cte、このgem名だけでおぉっという気持ちになりますね: .withでCTE(Common Table Expression: 共通テーブル式)をActive Recordで書ける」「しかもMySQLとPostgreSQLのどちらでも使えるんですって」「お〜マジですか」

# 同リポジトリより
Post.with(
  posts_with_comments: Post.where("comments_count > ?", 0),
  posts_with_tags: Post.where("tags_count > ?", 0)
)
# 同リポジトリより
posts = Arel::Table.new(:posts)
top_posts = Arel::Table.new(:top_posts)

anchor_term = posts.project(posts[:id]).where(posts[:comments_count].gt(1))
recursive_term = posts.project(posts[:id]).join(top_posts).on(posts[:id].eq(top_posts[:id]))

Post.with(:recursive, top_posts: anchor_term.union(recursive_term)).from("top_posts AS posts")
# WITH RECURSIVE "popular_posts" AS (
#   SELECT "posts"."id" FROM "posts" WHERE "posts"."comments_count" > 0 UNION SELECT "posts"."id" FROM "posts" INNER JOIN "popular_posts" ON "posts"."id" = "popular_posts"."id" ) SELECT "posts".* FROM popular_posts AS posts
-- 同リポジトリより
WITH posts_with_comments AS (
  SELECT * FROM posts WHERE (comments_count > 0)
), posts_with_tags AS (
  SELECT * FROM posts WHERE (tags_count > 0)
)
SELECT * FROM posts

「recursive CTEもこうやって書けるのね↓」

# 同リポジトリより: recursive CTE
posts = Arel::Table.new(:posts)
top_posts = Arel::Table.new(:top_posts)

anchor_term = posts.project(posts[:id]).where(posts[:comments_count].gt(1))
recursive_term = posts.project(posts[:id]).join(top_posts).on(posts[:id].eq(top_posts[:id]))

Post.with(:recursive, top_posts: anchor_term.union(recursive_term)).from("top_posts AS posts")
# WITH RECURSIVE "popular_posts" AS (
#   SELECT "posts"."id" FROM "posts" WHERE "posts"."comments_count" > 0 UNION SELECT "posts"."id" FROM "posts" INNER JOIN "popular_posts" ON "posts"."id" = "popular_posts"."id" ) SELECT "posts".* FROM popular_posts AS posts

参考: Understanding SQL Server Recursive CTE By Practical Examples

「複雑なクエリでCTEを使うのは考えものですが、ちょっとしたサブクエリなどでWITHを少しだけ使いたいときならこのgemを使うとよさそう👍」「おぉ」「CTEは複雑になったときにActive Recordが解釈できるかどうかが問題なんですが、CTE自体は通常のSQLでも多用される強力な機能なので、ちゃんと動くならActive RecordでもシンプルなCTEが標準でサポートされてもいいなという気持ちです」「ちゃんと動くならですね😆

🔗 AnyCable用JavaScriptクライアント


同記事より

anycable/anycable-client - GitHub


つっつきボイス:「Evil MartiansのVladimirさんがAnyCable↓用のJSクライアントも作ったそうです」「AnyCableはRailsのAction Cableの高速版的なgemでしたね」「元記事によると、このanycable-clientはAction CableとJSONプロトコルレベルで完全互換とある」「TypeScriptでクライアントを書けるのはよさそう👍

AnyCable 1.0: RubyとGoによるリアルタイムWebの4年間(翻訳)

🔗 SeleniumとCupriteとPlaywright


つっつきボイス:「ruby-jp Slackで見かけた上の記事にTechRachoの翻訳記事が引用されていたのでピックアップしました」「あ〜、こういう問題は実際にやってみないとわからないヤツでしょうね」「最初からCupriteで書いていたらもっとスムーズだったのかな?」「既に動いているE2Eテストはさんざん試行錯誤して書かれることが多そうなので、差し替えたときにデフォルトの挙動が細かく違ったりするのかも」

「これで思い出しましたけど、ちょっと前の銀座Railsの発表で、playwright-ruby-clientというgemを作った方がSeleniumやCupriteなどのWebドライバ周りも含めて解説していましたね」「お、後で探してみます」

参考: Playwright

以下のスライドとリポジトリです。なおplaywrightは「劇作家」という意味だそうです。

YusukeIwaki/playwright-ruby-client - GitHub

🔗 その他Rails


つっつきボイス:「RubyMineの新バージョンが出た」「今回もRBS周りが改良されているようですね」「手元のRubyMineは2021.1.3でした」

「ところで、最近自分のWindows環境でDocker for Windowsをアップデートしたらなぜか急に速くなったんですよ」「へ〜!」「まだ調べたわけではありませんがストレージアクセスが速くなった感じがする: これならボリュームマウントしてもいいかなと思ったけど環境構築で1日ぐらいつぶれそうなので、そのうちに試してみようかな」

参考: Windows に Docker Desktop をインストール — Docker-docs-ja 19.03 ドキュメント


前編は以上です。

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

週刊Railsウォッチ: ruby-gitでGit操作、最近のruby/debug、stdgems.org、Windows 365 Cloud PCほか(20210720後編)

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

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

Rails公式ニュース

Ruby Weekly

The post 週刊Railsウォッチ: SorbetでRailsアプリの型シグネチャを書く、activerecord-cte gemとanycable-client gem(20210803前編) first appeared on TechRacho.


週刊Railsウォッチ: Rubyの可変長アロケーションプロジェクト、サーキットブレーカーgem、EC2-Classicが終了へほか(20210804後編)

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

🔗Ruby

🔗 Rubyの可変長アロケーションプロジェクトがフェーズIIに


#4680より


つっつきボイス:「Rubyのアロケーターをリニューアルして高速化するプロジェクトが進行中なんですね」「スゴい」「これはRubyKaigiに向いていそうなお題」「そのうち解説記事が出るといいですね」

サマリー:

  • 現在はアロケーション高速化のために、スロットサイズの異なるページをプールで使っている。要求されたサイズを収納できる最小のスロットサイズでオブジェクトが割り当てられる。これによって、フリーリストベースのアロケーションアルゴリズムに立ち返り、アロケーションのパフォーマンスが大幅に向上した。
  • サイズプールの成長パターンに合わせてヒープの成長アルゴリズムが変更された。ヒープのアロケーションと解放を繰り返すと「成長ヒープ」とみなされ、サイズの安定したヒープと異なる扱いになる。
    同issueより

🔗 time_up gemでRubyコードを計測する(Ruby Weeklyより)

testdouble/time_up - GitHub


つっつきボイス:「time_upというgemを作って測定してみた記事だそうです」

「測定したいコードにTimeUp.startでタイマー名を:processing:queryingのように複数指定できる」

# 同記事より
transactions = TimeUp.start(:querying) {
  Transaction.where(user: @user)
}

process_timer = TimeUp.start(:processing)
categorized_transactions = group_by_category(transactions)
monthly_spending = arrange_by_month(categorized_transactions)
process_timer.stop

payments = TimeUp.start(:querying) {
  PaymentFinder.find(@user, monthly_spending)
}

render json: TimeUp.start(:presenting) {
  present_summary(
    spending: monthly_spending,
    payments: payments
  )
}

「するとTimeUp.timerでタイマー名ごとのカウントや最大最小平均が取れるようになる」

# 同記事より
> TimeUp.elapsed(:presenting)
=> 1.0050820000469685
> query_timer = TimeUp.timer(:querying)
> query_timer.count
=> 2
> query_timer.timings
=> [0.5051469999598339, 0.8050760000478476]
> query_timer.min
=> 0.5051469999598339
> query_timer.max
=> 0.8050760000478476
> query_timer.mean
=> 0.6551115000038408

TimeUp.all_statsすると全タイマー名の測定結果を取れる」

# 同記事より
> TimeUp.total_elapsed
=> 2.822688000043854
> TimeUp.all_stats
=> {
  :querying=>{:elapsed=>1.3102230000076815, :count=>2, :min=>0.5051469999598339, :max=>0.8050760000478476, :mean=>0.6551115000038408},
  :processing=>{:elapsed=>0.5073829999892041, :count=>1, :min=>0.5073829999892041, :max=>0.5073829999892041, :mean=>0.5073829999892041},
  :presenting=>{:elapsed=>1.0050820000469685, :count=>1, :min=>1.0050820000469685, :max=>1.0050820000469685, :mean=>1.0050820000469685}
}

TimeUp.print_detailed_summaryで整形できる」

# 同記事より
> TimeUp.print_detailed_summary

=================================================================================
   Name     | Elapsed | Count |   Min   |   Max   |  Mean   |  Median  |  95th %
---------------------------------------------------------------------------------
:querying   | 1.31028 | 2     | 0.50520 | 0.80507 | 0.65514 | 0.65514  | 0.78221
:processing | 0.50314 | 1     | 0.50314 | 0.50314 | 0.50314 | 0.50314  | 0.50314
:presenting | 1.00508 | 1     | 1.00508 | 1.00508 | 1.00508 | 1.00508  | 1.00508

「測定ポイントごとのデータを一括で管理しつつ簡単な集計が行えるようですね: 記事タイトルにはベンチマークとあるけど計測という方が近いかな」「なるほど」「通常だと自分でグローバルな場所に値を保存することになるでしょうけど、time_up gemを使うと一括で管理できる: 知っていたら使うかも👍

🔗 メモリ上のRuby文字列を見る


つっつきボイス:「お〜、 /procファイルシステムからプロセスの生ユーザーメモリ空間を解析して、その中からRStringとして使われている部分を参照して復元したんですね」「これは面白い❤」「RStringなら比較的やりやすいでしょうね: これがRObjectだと難易度が上がりそう」「こういう作業は理解が深まりますね: Ruby内部でどのようにメモリをマッピングして使っているかを学ぶのによさそう👍

🔗 サーキットブレーカーgem 2種類

yammer/circuitbox - GitHub

wsargent/circuit_breaker - GitHub


つっつきボイス:「Jeremy Evansさんの近著『Polished Ruby Programming』↓を読んでいて、サーキットブレーカーはgemでやる方が楽という記述があったので探してみました」

「今さらですけどサーキットブレーカーって何でしょう?」「サーキットブレーカーはKubernetesやマイクロサービスなどでサービス同士がチェインする部分で使われるパターンですね」「お〜」「以下のIstioドキュメントに簡単な説明があった↓」

参考: Istio / Circuit Breaking

サーキットブレーキングは、レジリエントな(回復力のある)マイクロサービスアプリケーション構築で重要なパターンです。サーキットブレーキングを使うと、障害やレイテンシの急増、その他のネットワークの特異性による望ましくない影響を制限するアプリケーションを書けます。
istio.ioより

「たとえば、あるサービスがリクエストを返すために別のサービスに問い合わせる必要があってみたいなチェインが繰り返されると、途中で1箇所でも障害が起きたときにさんざん待たされた末に全部コケたりしますけど、そういうのをうまく扱うためにサーキットブレーカーを挟む感じですか?」「だいたいそんな感じ: タイムアウトなども細かく管理できます」「お〜」「サーキットブレーカーはKubernetesやIstioのようなサービスには必ずあって、マイクロサービスのコントロールプレーンの話題にもよく登場します」「なるほど」

参考: コントロールプレーン | TED用語集 | 東京エレクトロンデバイス

参考: 分散サービス環境へのCircuit Breakerの適用 - LINE ENGINEERING



engineering.linecorp.comより

「サーキットブレーカーでは、たとえばチェインしているサービスの手前の段の処理が軽くて奥の段の処理が重いとすると、軽い手前のサービスがリクエストをたくさん投げると奥の処理が詰まってしまうので、奥の段の処理が詰まっていることをサーキットブレーカーが何らかの形で検知すると、手前の段で早期に失敗させる、というような処理を行います」「なるほどそういう感じですか」

「でgemの方を見てみると、Circuitboxは文字どおり失敗時に転送している↓」

Circuitbox.circuit(:your_service, exceptions: [Net::ReadTimeout]) do
  Net::HTTP.get URI('http://example.com/api/messages')
end

「以下のコンフィグでタイムアウトやスリープやスレッショルドなどを指定できる↓」

# github.com/yammer/circuitboxより
class ExampleServiceClient
  def circuit
    Circuitbox.circuit(:your_service, {
      # exceptions circuitbox tracks for counting failures (required)
      exceptions:       [YourCustomException],

      # seconds the circuit stays open once it has passed the error threshold
      sleep_window:     300,

      # length of interval (in seconds) over which it calculates the error rate
      time_window:      60,

      # number of requests within `time_window` seconds before it calculates error rates (checked on failures)
      volume_threshold: 10,

      # the store you want to use to save the circuit state so it can be
      # tracked, this needs to be Moneta compatible, and support increment
      # this overrides what is set in the global configuration
      cache: Circuitbox::MemoryStore.new,

      # exceeding this rate will open the circuit (checked on failures)
      error_threshold:  50,

      # Logger to use
      # This overrides what is set in the global configuration
      logger: Logger.new(STDOUT),

      # Customized notifier
      # overrides the default
      # this overrides what is set in the global configuration
      notifier: Notifier.new
    })
  end
end

「もうひとつのcircuit_breaker gemの方はincludeして使う感じで、できることは上と似ているかな」「なるほど」

# github.com/wsargent/circuit_breakerより
require 'circuit_breaker'
class TestService
  include CircuitBreaker

  def call_remote_service() ...

  circuit_method :call_remote_service

  # Optional
  circuit_handler do |handler|
    handler.logger = Logger.new(STDOUT)
    handler.failure_threshold = 5
    handler.failure_timeout = 5
    handler.invocation_timeout = 10
    handler.excluded_exceptions = [NotConsideredFailureException]
  end

  # Optional
  circuit_handler_class MyCustomCircuitHandler
end

「サーキットブレーカーをちゃんと使ったことはまだありませんが、サービスが大規模に連携するようになるとこういうgemが欲しくなるのかも」

🔗 サーキットブレーカー自作は避けたい

「そういえばJeremy Evansさんが『サーキットブレーカーを自分で作るとつらいよ』みたいなことを書いていました」「はい、この種の機能を自作するのは避けた方がよいと思います」

「たとえば自分の実装で排他制御周りをしくじってリソースが永遠に開放されなくなってしまうバグを踏むと、サーキットブレーカーが誤動作してすべてのリクエストが止まってしまったりします」「ありゃ〜それはつらい」「かといって逆に排他制御の設定を厳しくしすぎると、今度はサーキットブレーカーがボトルネックになってしまったりします」「う〜む」「どちらもサーキットブレーカー的なものを自分でちょろっと作ったときにありがちなバグなので、自前実装は避けたい」

参考: 排他制御 - Wikipedia


Azureのドキュメントによると、以下の『Release It!』という書籍でサーキットブレーカーパターンが広まったそうです。

🔗 その他Ruby


つっつきボイス:「GSoCって何だろうと思ったらGoogle Summer of Codeのようですね」「オープンソースソフトウェアをテーマに毎年開催されている学生向けのイベント」「そういえばGoogle Summer of CodeでRubyのプロジェクトも見たことあります↓」

参考: Ruby | Google Summer of Code
参考: Google Summer of Code - Wikipedia

🔗DB

🔗 rails-pg-extras(Ruby Weeklyより)

pawurb/rails-pg-extras - GitHub


つっつきボイス:「RailsのPostgreSQL関連機能を拡張するのかな」「heroku-pg-extrasをRailsに移植したgemのようです↓」「Herokuと無関係に使えそうですね」

heroku/heroku-pg-extras - GitHub

「何ができるんでしょう?」「MySQLで言うINFORMATION_SCHEMA的な情報をRubyオブジェクトとして取得できるみたい↓」「お〜、Railsコンソールでも取れるのかな?」「サンプル出力を見るとRubyのコードからメトリクス情報がオブジェクトとして取得できているようなので、できるということでしょうね」

# 同リポジトリより
RailsPGExtras.index_cache_hit

$ rake pg_extras:index_cache_hit

| name                  | buffer_hits | block_reads | total_read | ratio             |
+-----------------------+-------------+-------------+------------+-------------------+
| teams                 | 187665      | 109         | 187774     | 0.999419514948821 |
| subscriptions         | 5160        | 6           | 5166       | 0.99883855981417  |
| plans                 | 5718        | 9           | 5727       | 0.998428496595076 |
(truncated results for brevity)

「この種のデータベースメトリクスをRubyのコードから参照したいと思ったことはなかったけど、こういうふうにRubyのコンテキストで使えるなら、たとえばメトリクスをrakeタスクで処理したりRailsコンソールでデバッグしながら見たりできそう」「おぉ」「他にも、最近だとWebアプリのステータスを返す専用のURLを用意して各種情報を見られるようにすることがありますけど、そこにこういう情報を追加してメトリクスを継続的に収集できるようにするといった使い方も考えられますね👍

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

🔗 EC2-Classicが終了に向かう


つっつきボイス:「お〜我のツイート、morimorihogeさんのツイートを見て反応したヤツです」「EC2-Classicを使っていた人はただでさえ相当少数派だと思うので、EC2-Classic終了の影響をすぐに理解できる人が果たしてどれだけいるのかなと思ったりしました」「自分はEC2-Classicから移行した経験あるのでわかりますけど、EC2-Classicって随分昔の話ですよね」「そうそう」

「自分もツイートしましたけど、EC2-Classicが終わるとPVインスタンスが動かなくなるのが問題」「あ〜わかります」「PVがなくなるとHVMだけになるので、PV->HVMの移行はそれなりに大変そう」

参考: インスタンスタイプマトリックス | AWS

「まだPVで動いていたものが世の中にあったとは」「AWSはそう簡単に古いサービスを終了しないのがよい点ですが、長期間サポートにも限度はあるので終了は仕方がないでしょうね」「これはもう終了やむなし」

「それにPVインスタンスだった頃のAWSの常識的な運用は、現代のAWS運用から見るとかなりかけ離れているんですよ」「たしかに」「たとえば当時はRDSのようなサービスが高い割にメリットがあまり感じられなくて、コストを考えるとRDSを使わずに自分でEC2にMySQLサーバーを立てることが多かった」「そうそう、当時はよくそれでやってました」「今は?」「今はRDSを使うメリットが圧倒的に大きくなりましたね」

参考: Amazon RDS(マネージドリレーショナルデータベース)| AWS

「現代のAWSの常識で組まれたシステムなら比較的移行しやすいんですが、EC2-Classicという時点で昔の常識でシステムが組まれている可能性が高いので、PVからHVMに移行したりDBを移行したりするのは大変そうだなと思いました」

「今もEC2-Classic使ってるサービスってどのぐらいあるんだろう?」「HVMインスタンスへの移行が終わってさえいればそれほど大変ではないんですが、うんと古くからあるサービスだと移行する機会のないままEC2-Classicを使い続けているところもあるでしょうね」「つらそう…」「PV->HVM移行ではたとえばプライマリパーティションが変わるのでMBR(Master Boot Record)も変わるんですよ」「え〜!」「だからddコマンドでコピーして終わりというわけにいかない」

参考: マスターブートレコード - Wikipedia
参考: dd (UNIX) - Wikipedia

「調べればPV->HVM移行のノウハウは見つかりますけど、何しろ古いのでやってみないとわからない部分も多いし、そもそもEC2-Classicをやっていた人数が少ない」「EC2-Classic自体を知らない人も多そうですよね」「Classic-ELBのことだと思う人がいたりして」「ありそう」「Classic-ELBは今も問題なく使えますけど、EC2-Classicの古さはレベルが違う」

参考: What is a Classic Load Balancer? - Elastic Load Balancing

「EC2-Classic終了は大きいニュースなんですね」「古くからやっているところにとってはかなり大きいニュースだと思います」「リリース記事を見ると2021/10/30には動いていないEC2-Classicのインスタンスが起動できなくなって、翌年の2022/08/15にはすべて終了するとありますね」「割とすぐなのか〜」「AWSの終了スケジュールは少なくともまず前倒しになることはないと思います: 逆に何やかやで延期する可能性の方があるかも😆

「EC2-Classicがサービスインしたのが2006年か: 当時はCore 2ぐらいの時代だから、サービス止めたいのもわかる」「あの当時はAWSコンソールもなければシンガポールリージョンもなかった」「USのリージョンだけだったんですね」「自分がAWSを使い始めたのはEIPが始まった頃だから2008年ぐらいかな」

参考: Intel Core 2 - Wikipedia
参考: Elastic IP アドレス - Amazon Elastic Compute Cloud

🔗言語/ツール/OS/CPU

🔗 PHPチュートリアルのSQLインジェクション


つっつきボイス:「ググって見つかったPHPチュートリアルで30件中16件にSQLインジェクションが見つかったという記事でした」「mysqli_queryを直接使っている時点でヤバい」「これ直接使わないヤツですよね」

// 同記事より
// Don't do this!
mysqli_query("SELECT * FROM user WHERE id = '" . $_POST["user'] . "'");

「考えてみれば、RailsはいわゆるCGIも経験せずにいきなりWeb開発ができるわけで、むしろ特異なのかも」「まあたしかに」「他の言語だと、昔ながらのCGIから始めてHTML埋め込みでWebページにhello worldや日付を表示するところから学んだものですが、Railsはrails newから始まりますから」「まるで違いますよね」

参考: Common Gateway Interface - Wikipedia

「Railsだけやってきた人から見るとわかりにくいかもしれませんが、昔は他の言語のチュートリアルの一環としてこういうふうに生SQLを動かしてみるという教え方が普通でしたね」「そうそう」「インターネット昔話になってきた」

「脆弱性うんぬんを別にすれば、チュートリアルなどでこういうふうに1行1行何が行われているかを確かめながら学ぶことは初学者にとって有用な面もあると思うんですよ」「わかります」「業務で使わなければOK」

「逆にRailsは初学者にとって便利すぎて裏で何が行われているのかさっぱりわからないですよね」「ブレークポイント置いても追いきれない😆」「Web開発をRailsから始めた人の中にはCGI時代のWebアプリの仕組みを知らずに普通に開発している人もいると思うので、昔と今とどちらの学び方がいいのか自分には断定しきれないかなという気持ちです」


後編は以上です。

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

週刊Railsウォッチ: ruby-gitでGit操作、最近のruby/debug、stdgems.org、Windows 365 Cloud PCほか(20210720後編)

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

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

Ruby Weekly

Publickey

publickey_banner_captured

The post 週刊Railsウォッチ: Rubyの可変長アロケーションプロジェクト、サーキットブレーカーgem、EC2-Classicが終了へほか(20210804後編) first appeared on TechRacho.

週刊Railsウォッチ: システムテスト用headlessドライバにCupriteが追加、rails-mini-profiler、Jeremy Evansインタビューほか(20210810)

$
0
0

こんにちは、hachi8833です。今回は短縮版でお送りします。

週刊Railsウォッチについて

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

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

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

前回の公式情報の続きです。

🔗 redirect_toallow_other_hostオプションが追加

許可されていないオープンリダイレクトでエラーをraiseする。
redirect_toallow_other_hostオプションを追加。ActionController::Base.raise_on_open_redirects = trueでこの振舞いをオプトインできる。
Gannon McGibbon
同Changelogより


つっつきボイス:「許可されていないオープンリダイレクトをraise_on_open_redirectsコンフィグでエラーをraiseできるようになった」「ここで例外を出してくれるのは優しいですね」「基本的にはraise_on_open_redirects = trueにしたうえで、オープンリダイレクトを許す場合にのみredirect_toreirect_back_or_toの引数にallow_other_host: trueを追加する感じで使うんでしょうね」

「URL直書きでよそのサイトにリダイレクトするのは基本的にはイレギュラーですが、その必要が生じることもありうるので、許可されてないサイトへのリダイレクトをraiseするかどうかを選べるのはいい👍

参考: オープンリダイレクトとは(Open-Redirect) | 【情報処理 用語集】

後でガイドの更新を見ると、現行はデフォルトがfalseで、7.0になったらデフォルトでtrueにするようです。

# guides/source/configuring.md#1098
#### For '7.0', defaults from previous versions below and:

+- `config.action_controller.raise_on_open_redirects`: `true`
...

#### Baseline defaults:

+- `config.action_controller.default_protect_from_forgery`: `false`

🔗 システムテスト用に登録可能なheadlessドライバリストのpoltergeistとcapybara-webkitが非推奨化されてcupriteが追加

Poltergeistとcapybara-webkitは既にメンテされなくなっている。
* https://github.com/teampoltergeist/poltergeist
* https://github.com/thoughtbot/capybara-webkit

たまたま以下の利用法を見かけた。

https://github.com/rails/rails/blob/main/actionpack/lib/action_dispatch/system_testing/driver.rb

近年はもっぱらSeleniumとHeadless Chromeが使われており、poltergeistやcapybara-webkitの利用は推奨されない。
CupriteはPoltergeistに代わるよい選択肢(参考)。
ガイドのPoltergeist関連部分はCupriteに置き換えるのがよいだろう。
同PRより


つっつきボイス:「お〜poltergeistとcapybara-webkitがついに非推奨化」「poltergeistはさすがに非推奨化でもいいかな」「今後はCupriteですか」「poltergeistとcapybara-webkitもしばらくは使えますね」「プルリクメッセージで引用されているEvil MartiansのCuprite記事は翻訳版が以下にあります↓」

2020年のRailsでブラウザテストを「正しく」行う方法(翻訳)

「お、このプルリクを出したYusukeIwakiさんは、まさにこの間の銀座Rails#34でPlaywrightやCupriteなどのドライバについて発表していた人ですね↓(ウォッチ20210803)」「あ、ホントだ」

参考: なんだかんだで1年半くらいRuby向けにブラウザの自動操作ライブラリを作っている - YusukeIwakiのブログ

🔗 Float::NaNBigDecimal::NaNのダーティーチェックを修正

RubyのFloat::NaNBigDecimal::NaN特殊な値であり、==で比較できない。
Marcelo Lauxen
同PRより


つっつきボイス:「Float::NaNBigDecimal::NaNがゼロ除算などで生成されたときの問題が修正されたんですね」「#41663のバグを踏んで見つけたのか」

      def test_changed?
        type = Decimal.new
        assert type.changed?(0.0, 0, "wibble")
        assert type.changed?(5.0, 0, "wibble")
        assert_not type.changed?(5.0, 5.0, "5.0wibble")
        assert_not type.changed?(5.0, 5.0, "5.0")
        assert_not type.changed?(-5.0, -5.0, "-5.0")
        assert_not type.changed?(5.0, 5.0, "0.5e+1")
+       assert_not type.changed?(BigDecimal("0.0") / 0, BigDecimal("0.0") / 0, BigDecimal("0.0") / 0)
+       assert type.changed?(BigDecimal("0.0") / 0, 0.0 / 0.0, 0.0 / 0.0)
      end

🔗 ActiveModel::Errors#inspectのメッセージをスリム化

# 同PRより: 従来
#<ActiveModel::Errors:0x00007ff68cda24f8 @base=#<Foo id: 6, created_at: "2021-07-09 04:28:48.056662000 +0000",
updated_at: "2021-07-09 04:28:48.168576000 +0000", email: "user@example.com", name: "Foo Bar", company: "Foo",
activated_at: "2021-07-09 04:28:39.039853000 +0000">, @errors=[]>

# 修正後: Errorsが空の場合
#<ActiveModel::Errors []>

# 修正後: Errorsが空でない場合
#<ActiveModel::Errors [#<ActiveModel::Error attribute=base, type=invalid, options={}>]>

つっつきボイス:「ActiveModel::Errors#inspect@errorsがある場合はその配列を出力して、@baseを出力しないように変更したのか」「通常ActiveModel::Errorsのオブジェクトをinspectするときは@errorsをチェックしたいはずなので、inspectではActiveModel::Errors@errorsに限定して配列を出力したいということかなと思いました」

🔗 Middleware#removeを追加

削除するミドルウェアが存在しない場合にraiseするMiddleware#removeを追加。
Middleware#removeの動作はMiddleware#deleteと同様だが、ミドルウェアが存在しない場合はエラーをraiseする。
Alex Ghiculescu, Petrik de Heus
同PRより


つっつきボイス:「以前は存在しないミドルウェアを削除しようとしても何も起きなかったのをエラーをraiseするMiddleware#removeが追加された」「割とシンプルな改修ですね」

「ところでMiddleware#removeは最近見たような気がする🤔」「あ、以前の#42655↓はMiddleware#deleteを修正して存在しないミドルウェアでエラーをraiseするようにしたけど破壊的変更の影響範囲が大きかったので、#42821ではMiddleware#deleteを元に戻してからMiddleware#removeを追加してこちらでエラーをraiseするようにしたのか」

🔗Rails

🔗 rails-mini-profiler(Ruby Weeklyより)

hschne/rails-mini-profiler - GitHub


つっつきボイス:「rack-mini-profilerはよく使われていますけど、それとは違うんでしょうか?」「READMEによるとrack-mini-profilerやScount APMなどのAPMツールに強くインスパイアされてこのrails-mini-profilerを作ったそうです」

MiniProfiler/rack-mini-profiler - GitHub

「お〜、なかなかいい感じにビジュアライズしている」「どのAPIが何秒かかるみたいなのが一覧できるんですね」


rack-mini-profiler READMEより

# rack-mini-profiler READMEより
# routes.rb
Rails.application.routes.draw do
  ...

  mount RailsMiniProfiler::Engine => '/rails_mini_profiler'
end

「APIのみのRailsアプリでも使えるみたい」「さすがにproduction環境は想定してないようです」「でしょうね」「ローカル開発で便利そうだし、プロファイリングするリクエスト数を絞り込めるみたいなのでproductionでも使いようがあるかも: とりあえず入れてみてもよさそう👍」「後で入れてみようっと」「ハリネズミがかわいい❤

# rack-mini-profiler READMEより
RailsMiniProfiler.configure do |config|
  config.enabled = proc { |env| env.headers['RMP_ENABLED'].present? }
end

🔗 RSpecでbullet gemを無視する方法(Ruby Weeklyより)

flyerhzm/bullet - GitHub


つっつきボイス:「この記事は、bullet gemに限らず、RSpecのテストで特定の機能を動かしたくないときに使える簡単なベストプラクティスという感じかな」「そうそう、こうやってテストヘルパーで無効にしたりしますね」

# 同記事より
config.before(:each, bullet: :skip) do
  Bullet.enable = false
end

config.after(:each, bullet: :skip) do
  Bullet.enable = true
end

🔗Ruby

🔗 Rubyの新しいデバッガを紹介

ruby/debug - GitHub


つっつきボイス:「先週紹介しそびれましたが、最近ruby/debugで頻繁にコントリビュートしているst0012さんの記事です」「お〜スタンくんが中の人になってる」「ruby/debugが触れるレベルになってきたところなのでこういう記事はありがたいですね👍

🔗 Ruby 3でHTTPサーバーをスクラッチから作った(Ruby Weeklyより)


つっつきボイス:「RubyのFiberやRactorなども使ってHTTPサーバーをスクラッチから書く記事か」「久々のマルチスレッドサーバー系記事ですね」「こういうシンプルなコード例を手元で動かして試せるのがよさそう」

# 同記事より(コメントは省略)
def start
  queue = Ractor.new do
    loop do
      conn = Ractor.receive
      Ractor.yield(conn, move: true)
    end
  end

  WORKERS_COUNT.times.map do
    Ractor.new(queue, self) do |queue, server|
      loop do
        # this method blocks until the queue yields a connection
        conn = queue.take
        request = RequestParser.call(conn)
        status, headers, body = server.app.call(request)
        HttpResponder.call(conn, status, headers, body)
      rescue => e
        puts e.message
      ensure
        conn&.close
      end
    end
  end

  listener = Ractor.new(queue) do |queue|
    socket = TCPServer.new(HOST, PORT)
    socket.listen(SOCKET_READ_BACKLOG)
    loop do
      conn, _addr_info = socket.accept
      queue.send(conn, move: true)
    end
  end
  Ractor.select(listener)
end

「これで思い出したんですが、最近のPHP 8.1にもついにfiberが入ったという記事を見かけました」「お、このPHPのRFCですね↓」「fiberってRuby独自の概念かと思ってたけどコンピュータサイエンスの用語だったのか〜」

参考: PHP: rfc:fibers
参考: ファイバー (コンピュータ) - Wikipedia

🔗 Jeremy Evansインタビュー


つっつきボイス:「少し前の記事ですが、『Polished Ruby Programming』の著者であるJeremy Evansさんのインタビューです」「お顔初めて見ました」「Jeremy EvansさんはSequelのメンテナーやっているのが有名かも」

「記事の方は、どうやらJeremy Evansさんが昨年のRubyPrize 2020で受賞したタイミングでのインタビューみたいですね」「へ〜、Jeremy EvansさんはOpenBSD版のRubyもメンテしているのか」「OpenBSD版のRubyはこの人にかかっているんですね」「この記事訳してみたいです」

参考: OpenBSD - Wikipedia

🔗『Polished Ruby Programming』

「『Polished Ruby Programming』は自分も読んでいるところですが、Ruby中級者がスキルアップするのにいい本だと思いました」「自分も読んでみて同感です」

「同書の特徴は、Rubyに特化していて他の言語にはなかなか応用できなさそうなところ: Java的なオブジェクト指向やデザインパターンの流儀をRubyに持ち込んだ形で書かれた本はよくありますけど、この本はそれをいったん忘れてRubyならではの最適化された書き方を再定義しているところがありますね」「あ〜そういう感じですか」「この本でのRubyの書き方はたとえばJavaにはなかなか持ち込めないんじゃないかなと思ったりしました」「そうかも」「内容はとてもいいです👍」「今ポチりました〜」

「同書はRubyKaigiについてこられる中級者上級者なら問題なく読めると思います」「そうですね」「逆にRuby初心者にはハードでしょうね」「この本の書き方が当たり前だと思うと他の言語がやりにくくなったりして」

🔗 その他Ruby(Ruby公式ニュースより)


つっつきボイス:「Ruby Award 2022の募集が始まった🎉」「毎年12月に福岡県で開催されているRubyのイベントですね」「自社でRubyやRailsを使った試みをやっていれば応募できます」

🔗 その他

🔗 n月刊ラムダノート


つっつきボイス:「Rubyコミッターのmametterさんがこの『n月刊ラムダノート』に寄稿していたのでさっきポチりました」「計算機好きのための技術解説情報誌ですか」「紙でしか買えないのかと思ったらPDF版も買えることにやっと気づきました」「PDF版の方がちょっと安い」「ページが多すぎなくて気軽に読めるのがよさそう」「まとめ版もあるのでポチってみた」


今回は以上です。

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

週刊Railsウォッチ: Rubyの可変長アロケーションプロジェクト、サーキットブレーカーgem、EC2-Classicが終了へほか(20210804後編)

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

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

Ruby 公式ニュース

Rails公式ニュース

Ruby Weekly

The post 週刊Railsウォッチ: システムテスト用headlessドライバにCupriteが追加、rails-mini-profiler、Jeremy Evansインタビューほか(20210810) first appeared on TechRacho.

週刊Railsウォッチ: カウンタキャッシュをスレッドセーフに更新、Journey::Ast追加、GitLabをAWS Graviton2で動かすほか(20210818前編)

$
0
0

こんにちは、hachi8833です。RubyKaigi Takeout 2021のスケジュール/講演タイトル/スピーカーが発表されました。

週刊Railsウォッチについて

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

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

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

今回は以下の公式更新情報からです。既に次の更新情報もいくつか出ています。

🔗 /favicon.icoへの内部ルーティングを追加


つっつきボイス:「今までrails newするといつも/favicon.icoでエラーになっていたのでエラー抑制用のコンフィグをいつも足していましたけど、ついに修正されたんですね」「お〜マジで、このエラーいつも目にしていました」「よかった😋

# railties/lib/rails/templates/rails/welcome/index.html.erb
<% ruby_on_rails_logo_favicon_data_uri = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIwcHgiIGhlaWdodD0iMzIwcHgiIHZpZXdCb3g9IjAgMCAzMjAgMzIwIiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgogICAgPHRpdGxlPkljb248L3RpdGxlPgogICAgPGcgaWQ9Ikljb24iIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPgogICAgICAgIDxnIGlkPSJSdWJ5LU9uLVJhaWxzLUxvZ28iIHRyYW5zZm9ybT0idHJhbnNsYXRlKDUuMDAwMDAwLCAyMC4wMDAwMDApIiBmaWxsPSIjRDgxRTAwIj4KICAgICAgICAgICAgPHBhdGggZD0iTTIxLjkzMzA3MDYsMjg1IEwxNjguMTc1NDI2LDI4NSBDMTQ3Ljk0MDg1OCwxNTAuNjkxNzA2IDE5Ni44ODIzODMsNjYuMzE0MjkwMiAzMTUsMzEuODY3NzUzNiBDMzE1LDIxLjc4NTEyODQgMzE1LDMxLjg2Nzc1MzYgMzE1LDIxLjc4NTEyODQgQzE2MS4yNTI4MywyNC45MTE2Mjc4IDYzLjU2Mzg1MzMsMTEyLjY0OTkxOCAyMS45MzMwNzA2LDI4NSBaIiBpZD0iUGF0aCIvPgogICAgICAgICAgICA8cG9seWdvbiBpZD0iUGF0aCIgcG9pbnRzPSI0MC40MDgyNjQzIDE4NS45MTU3MSAxMi43MzI2NDk0IDE3NC40MDE2NjMgLTEuNDIxMDg1NDdlLTE0IDIwNS40NDI3MSAyOS41NzExOTY2IDIxNi42NDcxMTUiLz4KICAgICAgICAgICAgPHBvbHlnb24gaWQ9IlBhdGgiIHBvaW50cz0iMTgwLjQ3MzEwMiAyNjguMDczNjQzIDIwNC45MzYwMTYgMjc2LjQxMDk2NiAyMDQuOTM2MDE2IDI1NC41MDg2NzMgMTgwLjQ4OTY0NCAyNDQuMzM4MTA3Ii8+CiAgICAgICAgICAgIDxwb2x5Z29uIGlkPSJQYXRoIiBwb2ludHM9IjEwMC41ODk1MTkgOTcuMjI4NzYwNiA3Ni42ODQwMTU2IDc5LjE2ODcwMjEgNTUuMjQ0MDc5MSAxMDAuMTgzMTA1IDgxLjUxNDMyMzkgMTE3LjQzMzcyNSIvPgogICAgICAgICAgICA8cG9seWdvbiBpZD0iUGF0aCIgcG9pbnRzPSIxODQuNTc1Njc5IDE4NC44OTYyOTUgMjA3Ljk1MjAzIDIwMC4yNDY2MSAyMTEuNzI3NzI5IDE4MS4yMDUyNjYgMTg5Ljg2MzY1MyAxNjQuNjg3NDYiLz4KICAgICAgICAgICAgPHBvbHlnb24gaWQ9IlBhdGgiIHBvaW50cz0iMjYxLjczNDAxIDY1Ljg5NTk0NDYgMjY5LjM1NDIzNCA4Mi4zMjk1NCAyODUuMzE4MjU2IDcyLjcwNDg1OTYgMjc4LjMxMDEyOSA1Ni45ODI5ODk1Ii8+CiAgICAgICAgICAgIDxwb2x5Z29uIGlkPSJQYXRoIiBwb2ludHM9IjI2MS45MTM3IDE2LjE4NDU0NzMgMjU1LjU5ODQ3OSA3LjEwNTQyNzM2ZS0xNSAyMzIuNzk2MDIgMy40ODg5NjMyMyAyNDAuNDYzODczIDIwLjAyNTI3MjUiLz4KICAgICAgICAgICAgPHBvbHlnb24gaWQ9IlBhdGgiIHBvaW50cz0iMjExLjkzNDExOSAxMTEuNTgwNjc1IDIyNi43MjI1NDcgMTI3Ljc5MjYwMSAyMzguMDIzOTI1IDExMy45MDM3MTUgMjIzLjQ2ODM3NSA5Ni41NDY4Njg1Ii8+CiAgICAgICAgICAgIDxwb2x5Z29uIGlkPSJQYXRoIiBwb2ludHM9IjE3OS42ODY4NTggMzguNDk1MjIxOCAxNjQuNjQxOTMxIDIwLjAyNTI3MjUgMTM5Ljg0NzYyIDMyLjU1NTI5OTIgMTU2LjYwMTMyNCA1MC45MjE2NzUzIi8+CiAgICAgICAgPC9nPgogICAgPC9nPgo8L3N2Zz4=" %>
<!DOCTYPE html>
<html>
<head>
  <title>Ruby on Rails</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <link rel="shortcut icon" href="<%= ruby_on_rails_logo_favicon_data_uri %>" />

「よく見るとRailsのファビコンをsvgで追加しているんですね」「これっていいんだろうか?」「このファビコンをデフォルトのまま使い続けると、そのWebアプリがRails製ということがそこから推測できるので、個人的にはRailsのファビコンよりはゼロバイトの画像を返すなどの方が、推測の手がかりを増やさないという点で好ましいかもしれないと思いました」「それもそうですね」「アプリがどんな言語やフレームワークでできているかというシグネチャ情報の収集は、攻撃の予備動作にもなりえます」

🔗 Journey::Astを追加


つっつきボイス:「ASTは抽象構文木ですね」「JourneyはRailsのAction Dispatchのルーティング周りに関連するモジュールだったかな」「Journeyって今言われるまで全然知りませんでした」「Railsのルーティングになぞらえて旅(journey)という言葉をかけたのかも」

参考: 抽象構文木 - Wikipedia
参考: rails/actionpack/lib/action_dispatch/journey at main · rails/rails

「今見つけた記事↓にあるこの図がASTをNFA(非決定性有限オートマトン)で表したものですね」「こういうふうになるのか〜」

参考: Railsのルーティングを支える技術 - Journeyについて - Qiita


『Railsのルーティングを支える技術』より

参考: 非決定性有限オートマトン - Wikipedia

「このJourney::Astはルーティング周りのパフォーマンス改善のために追加されたみたい」「不要なルーティング探索をJourney::Astで削減したんですね」「RailsのJourneyを触れる人はなかなかいないという話を聞いたことがありますけど、久しぶりにJourneyが改修された👍

# 同PRより
master:
  TOTAL    (pct)     SAMPLES    (pct)     FRAME
    52   (0.5%)          52   (0.5%)     ActionDispatch::Journey::Nodes::Node#symbol?
    58   (0.5%)          45   (0.4%)     ActionDispatch::Journey::Scanner#scan
    45   (0.4%)          45   (0.4%)     ActionDispatch::Journey::Nodes::Cat#type
    43   (0.4%)          43   (0.4%)     ActionDispatch::Journey::Visitors::FunctionalVisitor#terminal
    303  (2.7%)          43   (0.4%)     ActionDispatch::Journey::Visitors::Each#visit
    69   (0.6%)          40   (0.4%)     ActionDispatch::Routing::Mapper::Scope#each

this commit:
  TOTAL    (pct)     SAMPLES    (pct)     FRAME
    82   (0.6%)          42   (0.3%)     ActionDispatch::Journey::Scanner#next_token
    31   (0.2%)          31   (0.2%)     ActionDispatch::Journey::Nodes::Node#symbol?
    30   (0.2%)          30   (0.2%)     ActionDispatch::Journey::Nodes::Node#initialize

🔗 メッセージ改善2件


つっつきボイス:「1件目はActionController::InvalidAuthenticityTokenをraiseするときにwarningも表示するようになった」「Ruby on Rails Discussionsで提案した人がプルリク投げてるんですね↓」

参考: Proposal for improving InvalidAuthenticityToken error when invalid same origin - rubyonrails-core - Ruby on Rails Discussions

「Railsを始めて間もない人にとっては何が起こっているかわかりにくいと思うので、warningも欲しいのはワカル」「今でもエラーを見てググれば調べられますけどね」「Webのセキュリティ関連要素も昔より増えてきましたし、ググって見つけた情報が古い可能性もあるので、warningも出力する方が親切でしょうね👍

# actionpack/lib/action_controller/metal/request_forgery_protection.rb#L225
      class Exception
+       attr_accessor :warning_message
+
        def initialize(controller)
          @controller = controller
        end

        def handle_unverified_request
-         raise ActionController::InvalidAuthenticityToken
+         raise ActionController::InvalidAuthenticityToken, warning_message
        end
# actionpack/test/controller/request_forgery_protection_test.rb#L710
+ def test_raised_exception_message_explains_why_it_occurred
+   forgery_protection_origin_check do
+     session[:_csrf_token] = @token
+     @controller.stub :form_authenticity_token, @token do
+       exception = assert_raises(ActionController::InvalidAuthenticityToken) do
+         @request.set_header "HTTP_ORIGIN", "http://bad.host"
+         post :index, params: { custom_authenticity_token: @token }
+       end
+       assert_match(
+         "HTTP Origin header (http://bad.host) didn't match request.base_url (http://test.host)",
+         exception.message
+       )
+     end
+   end
+ end

「2件目は、今までだとbin/rails db:migrate -hでrakeの一般的なヘルプが表示されていたのを、db:migrateのヘルプを出せるようになったみたい」「ヘルプの量が増えるとrakeのヘルプが邪魔になりがちでしたね」「これでググらずに調べられる👍

🔗 config_accessorアクセサでデフォルト値を定義できるようになった


つっつきボイス:「ActiveSupport::Configurableのアクセサにdefault:オプションを渡せるようになったのか」「イニシャライザで設定しなくてもよくなった🎉」「これはある方がよいでしょうね👍: ActiveSupport::Configurableはあまり使ったことはありませんが、Active Supportにはこういう地味に便利な機能がまだまだあります」

# activesupport/test/configurable_test.rb#L65
  test "configuration accessors can take a default value as an option" do
    parent = Class.new do
      include ActiveSupport::Configurable
      config_accessor :foo, default: :bar
    end

    assert_equal :bar, parent.foo
  end

参考: ActiveSupport::Configurable の話 - scramble cadenza

🔗 Middleware#removeMiddleware#delete!にリネーム


つっつきボイス:「前回マージされたMiddleware#removeウォッチ20210810)がさらにリネームされてMiddleware#delete!になったそうです」「また変わった😆」「早!」

「元々Middleware#deleteという前からあった機能がエラーをraiseするように変更されていたんですが、挙動を変えると互換性に問題があることがわかったので前回Middleware#deleteを元に戻してMiddleware#removeを追加したいう流れでした: でもdeleteremoveが両方存在して機能が違うのはたしかにわかりにくそうなので、今回Middleware#removeMiddleware#delete!にリネームしたということみたい」

「たしかに!でエラーをraiseする方がRubyっぽくてわかりやすいかも」「!を付けたらエラーをraiseするというのはActive Recordのfind_byfind_by!createcreate!savesave!などの使われ方に沿った挙動でしょうね: 一方”!があるとより破壊的になる”と考えれば、!を付けると無言で削除する方がより破壊的とも言えるので、どちらの命名がよいかは悩ましいかも」「あ、そうか」「この#42867の場合は既存のdeleteを変えたくないという事情があったので、!を付けたらエラーをraiseする方に倒すしかなさそうかな」「たしかに」

🔗Rails

🔗 カウンタキャッシュをスレッドセーフに更新する(Ruby Weeklyより)


つっつきボイス:「カウンタキャッシュ更新の競合状態を防ぐ記事ですね: これは昔からよく問題になっています」「そうそう」

「以下のサンプルコードのようにマルチスレッドを絡めてみると割とすぐ競合が発生する」

# 同記事より
class UnsafeTransaction
  def self.run
    account = Account.find(1)
    account.update!(balance: 0)

    threads = []
    4.times do
      threads << Thread.new do
        balance = account.reload.balance
        account.update!(balance: balance + 100)

        balance = account.reload.balance
        account.update!(balance: balance - 100)
      end
    end

    threads.map(&:join)

    account.reload.balance
  end
end

参考: class Thread::Mutex (Ruby 3.0.0 リファレンスマニュアル)

「記事ではミューテックスやActive Recordのlock!で回避する方法のほかに、Active Recordのupdate_countersメソッドのアトミックな性質を使って競合を回避する方法も紹介されている↓」「へ〜!」「面白いけど、Rubyのようなスクリプト言語でアトミックとか意識したくない気持ちもちょっとあるかな: C言語などでは普通の発想なんですが」

# 同記事より
class CounterTransaction
  def self.run
    account = Account.find(1)
    account.update!(balance: 0)

    threads = []
    4.times do
      threads << Thread.new do
        Account.update_counters(account.id, balance: 100)

        Account.update_counters(account.id, balance: -100)
      end
    end

    threads.map(&:join)

    account.reload.balance
  end
end

参考: ミューテックス - Wikipedia
参考: lockActiveRecord::Locking::Pessimistic

「お、concurrent-rubyにはAtomicFixnumというクラスがあるのか↓: 実際の内部実装ではミューテックスあたりを使っていそうに見える」

ruby-concurrency/concurrent-ruby - GitHub

「トランザクションを張ったうえで別途カウンタキャッシュを更新するコードを書くと、たまに競合が発生するという問題は実は昔からあって、真面目に回避しようとすると複雑になりがち」「ふむふむ」「カウンタキャッシュはRailsの機能などを使えば簡単に実現できるんですが、複雑なトランザクションが絡んでくるとデッドロックしたりする: そこに引っかかるようなコードを書かなければたいてい問題にならないので、経験している人もいれば経験せずに済む人もいたりします」

「発生の可能性がつきまとうのはカウンタキャッシュの仕組み上仕方ないんですが、カウンタキャッシュで競合が起きる可能性があるということだけでも知っておくと損はないと思います: よさそうな記事👍

Rails向け高機能カウンタキャッシュ gem「counter_culture」README(翻訳)

「ところで、記事の末尾にあるリンクをたどるとハイゼンバグ(Heisenbug)という知らない用語があったんですが、やはり『ハイゼンベルグの不確定性原理』のもじりでした」「調査しようとすると競合状態が変わって再現が難しいマルチスレッド系バグとかが、ちょうどそういう感じでしょうね」

参考: 特異なバグ - Wikipedia
参考: 不確定性原理 - Wikipedia

🔗 Railsアプリでコードをdeprecateする(Ruby Weeklyより)


つっつきボイス:「アプリでのdeprecationの書き方の記事」「ウォッチの『先週の改修』ではよく見かけますけど、考えてみたらアプリのコードでもRailsのActiveSupport::Deprecationを使っていいんですね」「もちろん使っていいんですよ😆」「オープンソースのRailsアプリなどで普通に有効な書き方ですね」

def process_widget
  ActiveSupport::Deprecation.warn(
    "#process_widget is deprecated. " \
    "Use #send_widget_to_processor instead."
  )
  # other code ...
end

ActiveSupport::Deprecationで書いておくと、テストコードを回したときにもdeprecation warningが表示されるのが便利です」「なるほど!」

🔗 NokogiriがHTML5の機能をサポート(Ruby Weeklyより)


つっつきボイス:「CRuby限定でNokogiriがHTML5をサポートしたそうです」「Nokogiriにマージされたnokogumboって何だろう↓」「初めて見ました」

rubys/nokogumbo - GitHub

「お〜、nokogumboはHTML5のfragmentや内部フェッチやカスタム属性とかも使えるのか↓: これまでNokogiriがHTML5を読み込めなかったのかと思ったら、HTML5のこうした機能がNokogiriで使えるようになったということのようですね」「なるほど」

# rubys/nokogumboより: fragmentのパース
require 'nokogumbo'
doc = Nokogiri::HTML5.fragment(string)
# rubys/nokogumboより
require 'nokogumbo'
doc = Nokogiri::HTML5.get(uri)

参考: Links - The complete HTML5 tutorial
参考: data-* - HTML: HyperText Markup Language | MDN

「今回はNokogiriのマイナーバージョンアップで大きな改修ではなさそうなので、従来どおりにも使えそうかな」「nokogumboがCで書かれているのでJRubyとかでは動かないのはしょうがない」

🔗 GitLabのArmベースAWS Graviton2記事


つっつきボイス:「へ〜、GitLabをArmベースのAWS Graviton2インスタンスに置くと23%安くなって36%パフォーマンスが向上するという記事」「この図はGitLabがベンチマークに使った環境なのね↓」


同記事より

「AWS Gravitonがわかってなかった😅」「AWSが作っているArmプロセッサのインスタンスですね: 出たのは最近ですが、もう割と使われていると思いますよ」「なるほど、それの新しいのがGraviton2ですか」

参考: AWS Graviton (EC2 に最良の料金とパフォーマンスを提供 | AWS
参考: AWS Graviton2 を搭載した新しい EC2 M6g インスタンス | Amazon Web Services ブログ

「RDBMSなら今でもArmプロセッサでまったく問題なく動かせますが、GitLabのような大規模Railsアプリで使われているgemをArmプロセッサ上でビルドして実行できたということは、BPSが今メインで使っているGitLabサーバーも、原理的にはArmベースのAWS Graviton2インスタンスに引っ越し可能ということになりますね」「お〜」「社内でも検討してみようかな😋


前編は以上です。

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

週刊Railsウォッチ: システムテスト用headlessドライバにCupriteが追加、rails-mini-profiler、Jeremy Evansインタビューほか(20210810)

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

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

Rails公式ニュース

Ruby Weekly

The post 週刊Railsウォッチ: カウンタキャッシュをスレッドセーフに更新、Journey::Ast追加、GitLabをAWS Graviton2で動かすほか(20210818前編) first appeared on TechRacho.

週刊Railsウォッチ: SorbetのRuby AOTコンパイラが公開、「Compiler Explorer」にRubyが追加、Ractorで非同期通信ほか(20210823後編)

$
0
0

こんにちは、hachi8833です。もろもろの事情で後編の公開が遅れました🙇

週刊Railsウォッチについて

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

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

🔗Ruby

🔗 SorbetのRuby AOTコンパイラ(Ruby Weeklyより)


つっつきボイス:「まだ実験段階だそうです」「SorbetのRuby用コンパイラはJITではなくAhead-Of-Time(事前)方式で、これまでStripe社内のみで使っていたのが公開されたみたい」「今度のRubyKaigi Takeout 2021でこれの話が聞けそうな気がしますね」「あ、そうかも」

参考: 事前コンパイラ - Wikipedia


同記事より

その後発表されたRubyKaigi Takeout 2021のスケジュールを確認してみましたが、Sorbetを手掛けているStripe社のスピーカーは惜しくもいませんでした。

🔗 Compiler ExplorerにRubyも追加(Ruby Weeklyより)


つっつきボイス:「Compiler Explorer?」「なるほど、こういうふうにソースコードをコンパイルできるのか↓」「このサイトのサポート言語にRubyも加わったそうです」「お〜、たとえば左にRubyのコードを書くと右にコンパイル結果が出力されるんですね」


godbolt.orgより

「Rubyのバージョンも2.5.9〜3.0.2から選べる、やだこれスゴい」「バージョンによってコンパイル結果が変わる可能性もあるので、選べるのはありがたい」「コンパイルボタンはどこかなと思ったら、書いた途端にコンパイルされるみたい」「Rubyの場合はVMの命令シーケンス(ISeq)をdisasmした結果が出るのか」「これは面白い!」「C言語にするとCPUとコンパイラのさまざまな組み合わせを選べました」

# godbolt.orgより
== disasm: #<ISeq:<compiled>@example.rb:1 (1,0)-(4,3)> (catch: FALSE)
0000 definemethod                           :square, square           (   2)[Li]
0003 putobject                              :square
0005 leave

== disasm: #<ISeq:square@example.rb:2 (2,0)-(4,3)> (catch: FALSE)
local table (size: 1, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] num@0<Arg>
0000 putstring                              ""                        (   3)[LiCa]
0002 leave

参考: RubyVM::InstructionSequence#disasm (Ruby 3.0.0 リファレンスマニュアル)

「ちょっとしたコード最適化で部分的なコンパイル結果をチェックしたり、結果の比較をプルリクに添えたりするのに使えるかも👍: 自分でここまでカリカリにチューニングするかどうかはわからないけど」「なるほど!」「コードの最適化では、なぜかこう書く方が速いということもあったりしますけど、そういうときに納得したり説得したりするときの材料に使えそう」「何より見てて楽しい」「見てて楽しいですね」

🔗 Ractorで安全な非同期通信の実験(Ruby Weeklyより)


同記事より


つっつきボイス:「Ractorを初めてやってみた記事だそうです」「Ractorでノンブロッキングに書く方法とブロッキングに書く方法が紹介されていますね: ここまでできればほぼスレッド並かも」「お〜」「このブログには他にもいろんなRuby記事があってよさそう👍

# 同記事より
receiver_ractor = Ractor.new do
  loop do
    message = Ractor.receive
    sleep 1
    puts "Processed #{message}"
  end
end

counter = 0
while true
  counter += 1
  receiver_ractor.send(counter)
end

参考: ruby/ractor.md at master · ruby/ruby
参考: class Thread (Ruby 3.0.0 リファレンスマニュアル)

🔗 その他Ruby

つっつきボイス:「bundle install --without defaultとは?」「おそらくですが、Rubyのbundlerを普通に実行するとGemfile.lockにあるgemを全部インストールしますけど、そうではなくて特定のgemだけをインストールしたいということでしょうね」「あ〜なるほど」「Gemfile.lockに書かれている特定のgemだけインストールしたい、他のはインストールしたくないという状況かなと思いました」「こうやるとできるんですね」

参考: rubygems/bundler at master · rubygems/rubygems

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

🔗 ElasticのクライアントライブラリとAWS OpenSearch(Publickeyより)


つっつきボイス:「ああ、ElasticとAWSのこのニュースは考えさせられましたね」

「Elasticがこうするまでのいきさつってどんなでしたっけ?」「以下の記事などにあるように以前からRedisやElasticなどがAWSに申し立てしています↓」

参考: AWSをElasticが名指しで非難。ElasticsearchとKibanaのライセンスを、AWSが勝手にマネージドサービスで提供できないように変更へ - Publickey

「ElasticがElastic Cloud上で運営しているElasticsearchのホスティングサービスがAWSのサービスと競合する点(ElasticのはAWSより後ですが)や、AWSがElasticsearchという商標を使ってサービスしている点などが問題視されているようです」「あ〜」

参考: Amazon Elasticsearch Service(Elasticsearchを簡単にデプロイ、保護、運用)| AWS
参考: Elastic Cloud:マネージドのElasticsearchと検索 | Elastic

「いちユーザーとしては、既に持っているAWSアカウントでELK Stackを立てられるのは便利かつ嬉しいし、Amazon ESもそれなりに息の長いサービスなので、既に使われているプロダクトも結構世の中にあるはず」「ふむふむ」「今回ElasticSearch公式がクライアントライブラリに手を加えたことで、こうした”善意の”ユーザーまで影響を受けるようになってしまうのはいろいろ残念」

参考: ELK Stack: Elasticsearch、Logstash、Kibana | Elastic

「もちろんElasticの言い分もとても理解できるんですが、こういう現場の前方互換が失われるような変更を入れられてしまうと、長く使われる可能性のあるエンタープライズ系のシステムで使うのをちょっとためらうかも」「気持ちわかります」

「もうひとつ気になるのが、Elasticと似たような形でAWSと対立しているRedisなどの動向」「Redisもですか」

参考: Redis、MongoDB、Kafkaらが相次いで商用サービスを制限するライセンス変更。AWSなどクラウドベンダによる「オープンソースのいいとこ取り」に反発 - Publickey

「Elasticは明らかに法人ですが、Redisはどうだったかな…Redis Labsがスポンサーになっているのか↓」

参考: Redis Labs - Wikipedia

「正直、ElasticsearchやRedisってできれば自前ではホスティングしたくないサービスの筆頭」「そうそう、自分たちで運用するとつらいヤツ」「Redisはスケールアップするときに一度止めないといけないとか、ノウハウが結構必要になるので、できればやっぱりクラウドのものを使いたい」

「以前NVIDIAのCUDAがEULAに記載された利用方法を後から変更したために、データセンターに導入できなくなってビットコインを掘ったりするのに使えなくなったことがありましたね」「ありゃ〜」「利用要件を回避するためにやむなく古いCUDAを使い続けたりしているそうです」「後から変えるとやっぱりもめますよね」「以前機械学習で遊んだときにCUDAを入れたことありますが、めちゃデカかった」

参考: CUDA - Wikipedia
参考: エヌビディアが消費者向けGPUのライセンスを変更、データセンターへの導入を禁止 | 日経クロステック(xTECH)

🔗 JavaScript

🔗 Deno 1.13にネイティブHTTPサーバーAPIが搭載

denoland/deno - GitHub


つっつきボイス:「Denoって今どのぐらい使われているのかな?」「追ってみたことはありませんが、かなり頻繁にアップデートされているようです」「今調べて知りましたが、Denoは次世代のNodeみたいな位置づけなんですね」「Nodeの作者がさまざまな反省を込めてDenoを作ったそうです」

参考: DenoとNode.jsの大きな違い - keroxpのScrapbox

「CloudFrontのLambda@EdgeのようなエッジコンピューティングとかだったらDenoは十分いけるかなという気持ちはありますね」「お〜」「いきなり大規模なものに使うのはまだためらいがありますが、小規模なプログラムならイレギュラーなことも起こりにくいでしょうし、マイクロサービス的なものと新しいプログラミング言語環境は割と相性がよさそうな印象もあります」

参考: CloudFront Lambda@Edge での AWS Lambda の使用 - AWS Lambda

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

🔗 ちいさなWebブラウザを作ってセキュリティを学ぶ


つっつきボイス:「お、今年のセキュリティ・キャンプ↓はどことなく大学の集中講義みたいな雰囲気」「スライドはアウトラインで、その下の資料に詳しく書かれているそうです」「セキュリティ・キャンプも今の御時世なのでオンライン開催か」「こういう資料を無料で参照できるのはいい👍

参考: セキュリティ・キャンプ:IPA 独立行政法人 情報処理推進機構

「セキュリティ・キャンプの対象はメインが高校生で次点が大学生、ときには中学生もいたりするんですよ」「こういう場で学べる今の若者は強いですね」「オンライン開催になったことで以前より参加しやすくなった反面、こういう独特の場のアツい空気みたいなものに触れる機会がなくなったのは残念」「そうなんですよね…」

🔗言語/ツール/OS/CPU

🔗 CO2センサー


つっつきボイス:「これは自分も見かけて、いい記事だと思いました👍」「そう言えば以前CO2センサーの記事を出してましたよね↓」「そうそう、自分が記事で使ったセンサーはCO2を直接測定していないので精度があまり高くないと言われています」

自宅作業環境の二酸化炭素濃度をM5Stack+CCS811で計測してみた

「こうした商用センサーの精度はいろいろ難しい面があって、製造側にしてみれば精度が高くなくても売れればいいというモチベーションがある一方、それを指摘する側は手間もかかるし訴訟リスクもあるしお金にもならないということになりやすい」「あ〜たしかに」

「元記事では大学の研究室がやっていますけど、これは果たして大学の仕事なのかという面もあるわけです: たとえば国や非営利の公的機関が大学の研究室に依頼してこうした精度を調査するしくみができれば、精度の低い商用センサーを使ったために論文のデータが信頼できなくなるということも減ってよいと思うんですけどね」「ふむふむ」「こういう調査は今後の新製品に対して継続的に実施する必要もありますし、ロットによって違いが生じたりもするので、本来は国や非営利の公的機関が定期的に研究室に調査を依頼するのが望ましいと思います」「なるほど」「製品として売られているセンサーの精度みたいなテーマは世の中でとても有用ですが、単体では論文になりにくいんですよ」


後編は以上です。

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

週刊Railsウォッチ: カウンタキャッシュをスレッドセーフに更新、Journey::Ast追加、GitLabをAWS Graviton2で動かすほか(20210818前編)

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

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

Ruby Weekly

Publickey

publickey_banner_captured

The post 週刊Railsウォッチ: SorbetのRuby AOTコンパイラが公開、「Compiler Explorer」にRubyが追加、Ractorで非同期通信ほか(20210823後編) first appeared on TechRacho.

Railsセキュリティ修正6.0.4.1と6.1.4.1 がリリースされました

$
0
0

Ruby on Rails セキュリティ修正6.0.4.1と6.1.4.1がリリースされました。

英語版Changelogをまとめて見るにはGItHubのリリースタグ↓が便利です。v6.0.4.1タグの日付は日本時間の2020/08/20 1:15、v6.1.4.1は2020/08/20 1:25でした。

詳しくは以下のコミットとRuby on Rails Discussionsのトピックをご覧ください。

🔗 セキュリティ修正の概要

セキュリティ修正は1件です。

🔗 1. CVE-2021-22942 ActionDispatch::HostAuthorizationミドルウェアでオープンリダイレクトの可能性

影響

特殊な細工を施されたX-Forwarded-Hostヘッダーと特定の”allowed host”フォーマットが組み合わさることで、Action PackのHostAuthorizationミドルウェアによってユーザーが悪意のあるWebサイトにリダイレクトされる可能性があります。

“allowed host”の先頭にドットが付加されているアプリケーションが影響を受けます。たとえば設定ファイルに以下があるとします。

config.hosts <<  '.EXAMPLE.com'

“allowed host”の先頭にドットが付加されていると、特殊な細工を施されたヘッダーを用いて悪意のあるWebサイトにリダイレクトされる可能性があります。
この脆弱性はCVE-2021-22881と似ていますが、CVE-2021-22881ではドメイン名の大文字小文字の区別は考慮されていませんでした。
discuss.rubyonrails.orgより

影響を受けるRailsバージョン
Rails 6.0.0以降
影響を受けないRailsバージョン
Rails 6.0.0未満
修正済みバージョン
Rails 6.0.4.1および6.1.4.1
パッチ
discuss.rubyonrails.orgにあり

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

関連記事

Railsセキュリティ修正6.1.3.2/6.0.3.7/5.2.4.6/5.2.6がリリースされました

The post Railsセキュリティ修正6.0.4.1と6.1.4.1 がリリースされました first appeared on TechRacho.

FormObjectにおける`#to_model`について

$
0
0

こんにちはgengenです。
今回はRailsにおけるデザインパターンであるFormObjectについての記事になります。

FormObjectは、バリデーションや特定のユースケース向けの処理をモデルから分離する手法です。
ネット上のFormObjectについての記事ではFormObjectクラスに、操作対象になるActiveRecordのインスタンスを返すように#to_modelを実装する例をよく見ます。
このやり方は結構ピーキーで拡張性とかを考えると避けた方が無難ではないか?ということについて書いていきたいと思います。

なぜ#to_model実装するのか

FormObjectは色々な実現方法があると思いますが、Rails: Form Objectと#to_modelを使ってバリデーションをモデルから分離する(翻訳)のようにActiveModel::ModelをincludeしたFormObjectクラスに#to_modelをオーバーライドする場合について考えます。

このような形で#to_modelを実装すると、送信先を類推するのに使われる.model_nameや、labelのやバリデーションエラーメッセージの出力に使われる.human_attribute_nameがActiveRecordのクラスに対して呼び出されるようになり、特に設定したりしなくてもscaffoldしたcontrollerやviewはほぼそのまま使うことができます。

何が問題なのか

単にバリデーションを分離するだけであればこのようにしても困ることはないと思います。
しかし、

  • ActiveRecordとは属性名を変えたい
  • 一つの属性を二つに分けて入力させたい
  • ActiveRecordと無関係の入力を処理したい
  • 他のActiveRecordモデルを同じフォームで処理したい

上記のようにActiveRecordの属性とフォームにズレが出てきた場合、そのままでの対応が難しくなります。
大元のActiveRecordのtranslation設定などをいじって無理やり対応できなくもないですが、
分離したかったはずなのに、ActiveRecordの実装や設定が特定のユースケースに引っ張られるのは本末転倒ではないでしょうか。

https://apidock.com/rails/ActiveModel/Conversion/to_model

If your model does not act like an Active Model object, then you should define :to_model yourself returning a proxy object that wraps your object with Active Model compliant methods.
apidock.comより

ドキュメントによるとActiveModelではないオブジェクトをActiveModelとして扱いたい時は、ActiveModel互換のプロキシオブジェクトを#to_modelに挿し込もうということなので、今回の扱いは正攻法ではないと思います。

どうしたらいいのか

困ってから#to_modelを剥がすとなると影響範囲が大きくなりますし、初めからそんなことせず愚直に送信先指定したり属性名のtranslationを設定したりした方がいいのではないでしょうか。
そもそも送信先については、
ActiveRecordからユースケース固有のバリデーションを分離する必要がある≒ActiveRecordがRESTfulルーティングと一対一で対応するような単純な設計ではない
という時点でパスヘルパーで書いた方がわかりやすくて親切だと個人的には思います。

終わりに

Rails wayからはみ出す時はきっちりはみ出した方が予後が良いんじゃないかな〜という気がしました。
ここまで読んでいただき、ありがとうございました。

関連記事

Rails: Form Objectと`#to_model`を使ってバリデーションをモデルから分離する(翻訳)


The post FormObjectにおける`#to_model`について first appeared on TechRacho.

Rails: 一括削除でのトランザクションとロールバックの注意点

$
0
0

業務で以下のような処理を書いていたのですが、色々とハマったので自分用のまとめがてらに紹介します。

  • 複数テーブルのレコードをバッチで一括削除する
  • どれかのレコードの削除に失敗したらロールバックし、履歴テーブルに失敗記録を保存する

Rails

ApplicationRecord.transaction do
  books.each(&:destroy!)
  movies.each(&:destroy!)
  # 履歴テーブルへ成功時の保存処理
rescue => e
  # 履歴テーブルへ失敗時の保存処理
  raise ActiveRecord::Rollback
end

全件削除できないときにロールバックする方法

レコードを一斉削除するメソッドとしては destroy_all がありますが、これは true / false を返します。
https://apidock.com/rails/ActiveRecord/Relation/destroy_all

def destroy_all
  records.each(&:destroy).tap { reset }
end

よって、全件削除できないときにロールバックしたかったら以下のような方法を取ることになります。

業務では each(&:destory!) を選択しました。

テスト(RSpec)

before do
  movies_double = instance_double('movies_double')
  allow(::Movie).to receive(:where).and_return(movies_double)
  allow(movies_double).to receive(:each).and_raise(StandardError)
end

# 削除に失敗したとき、ロールバックや履歴テーブルへの保存が行われることをテストする

スタブ化する対象

Railsのコードを補足して再掲します。

def destroy_records
  ApplicationRecord.transaction do
    books.each(&:destroy!)
    movies.each(&:destroy!)
    # 履歴テーブルへ成功時の保存処理
  rescue => e
    # 履歴テーブルへ失敗時の保存処理
    raise ActiveRecord::Rollback
  end
end

def books
  ::Book.where(...)
end

def movies
  ::Movie.where(...)
end

レコード削除に失敗した想定でテストを書きます。

今回は、削除に失敗するような条件を before_destroy で記述していないので、予期せぬエラーで削除に失敗した、という想定で、削除対象のオブジェクトが例外を投げるようにスタブ化を行います。
ここで、ロールバックされることをテストしたいわけですので、実際に一度はレコードを削除しないといけません。
books の方をスタブ化してエラーをraiseするようにすると、 books は一度も削除されません。

そこで、 movies をスタブ化して、 books の方は実際に削除してからロールバックされてるか確認することにしています。

スタブ化するクラス

movies をスタブ化したいのですが、このインスタンスのクラス Movie::ActiveRecord_Relation はprivate constantのため、今回のテストで使用できません。

before do
  allow_any_instance_of(Movie::ActiveRecord_Relation).to receive(:each).and_raise(StandardError)
end

とすると、以下のようにエラーになります。

NameError:
  private constant #<Class:0x0000556b07354db0>::ActiveRecord_Relation referenced

そこで、movies インスタンスを ::Movie.where で返すタイミングでスタブ化しています。

before do
  movies_double = instance_double('movies_double')
  allow(::Movie).to receive(:where).and_return(movies_double)
  allow(movies_double).to receive(:each).and_raise(StandardError)
end

最後に

こういった処理はまとめて学習する機会がなかなか無いので、軽い内容ですが復習のために記事にしてみました。


The post Rails: 一括削除でのトランザクションとロールバックの注意点 first appeared on TechRacho.


週刊Railsウォッチ: Rails 7でのimport maps導入、Steepで型を導入、KubernetesでRailsを動かすためのガイドほか(20210830前編)

$
0
0

こんにちは、hachi8833です。以下をお見逃しの方はTwitterの#ginzarailsタグである程度追いかけられると思います。

週刊Railsウォッチについて

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

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

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

今回は以下の公式更新情報からです。本家が2つ先まで進んでいます。

🔗 deprecation warningを一括オプトアウト可能に


つっつきボイス:「config.active_support.report_deprecations = falseを指定すればdeprecation warningをまとめてオプトアウトできるようになった: 乱用は避けたいけどやむを得ず一時的にそうしたくなることもあるヤツ」「以前は以下を両方指定しないといけなかったんですね」「オプトアウトできる機能はあってもいいと思います👍

# changelogより
config.active_support.deprecation = :silence
config.active_support.disallowed_deprecation = :silence

🔗 Action CableのJSをES Moduleとしてトランスパイルせずに出力

Railsの全JSを可能な限りES2017およびESM(ES Module)をターゲットとするよう更新しよう。これによってバンドルなしでこれらの出力をブラウザで直接利用できるようになり、かつES Moduleを使えるようになる。これはWebpackerなしのデフォルトへの布石となる。
ここでは後方互換性問題の考慮が必要。これが互換性を妨げるのであれば2つの異なる出力を生成してもよい。
同PRより

参考: ブラウザで覚えるES Modules入門 - JavaScriptでモジュールを使う時代 - ICS MEDIA


つっつきボイス:「Rails 7でWebpackerレスを目指す方向」「ESMへトランスパイルせずにJSを出力するようになるといえば、Rails 7のJSがimport mapsベースになる話も出ていますね」「ちょうどこの後でも取り上げています」「考えてみればAction CableのJSライブラリは元からオープンなコードなので、難読化のためにわざわざトランスパイルすることもないんですよね」「たしかに」

参考: トランスコンパイラ - Wikipedia

🔗 development環境のNoDatabaseError画面に[Create database]ボタンを追加


つっつきボイス:「お〜、ActiveRecord::NoDatabaseErrorエラー画面にCreate databaseボタンができた」「当然ながらdevelopmentモードにしか出ないようになっている」「UIがますます親切になった👍


同PRより

🔗 ActiveRecord::QueryMethods#in_order_ofを追加


つっつきボイス:「おぉ、ついにActive Recordにもin_order_ofが入った🎉」「別のものが前からあったんでしょうか?」「少し前にRailsにEnumerable#in_order_ofが入っていたんですが↓、今回それのActive Record版もできて適用範囲が広がってきた感じですね」「なるほど!」

参考: rails commit log流し読み(2021/08/05) - なるようになるブログ

概要
これにより、SQL式に基づいてレコードを返す際に明示的な順序を指定できるようになる。デフォルトでは次のようにCASEで実現される。

Post.in_order_of(:id, [3, 5, 1])

上は以下のSQLを生成する。

SELECT "posts".* FROM "posts" ORDER BY CASE "posts"."id" WHEN 3 THEN 1 WHEN 5 THEN 2 WHEN 1 THEN 3 ELSE 4 END ASC

しかしこの機能はMySQLではFIELD関数の形で組み込まれているので、コネクションアダプタは代わりに次のようなSQLを生成する。

SELECT "posts".* FROM "posts" ORDER BY FIELD("posts"."id", 1, 5, 3) DESC

この機能は#41333でEnumerableに追加された機能に強くインスパイアされている。cc:@dhh
同PRより

「なるほど、in_order_ofのSQLはCASE文に続いてWHEN 3 THEN 1 WHEN 5 THEN 2...みたいに展開されるのか: まあそうなるでしょうね」「改行なしだと一瞬考え込んでしまったけど今理解しました」「力技だけどその分わかりやすいかも」「MySQLだとFIELD関数があるのでこう書けるんですって↓」「in_order_ofはあっていい機能👍

-- Changelogより
SELECT "posts".* FROM "posts" ORDER BY FIELD("posts"."id", 1, 5, 3) DESC

🔗 ActiveRecord::Relation#structurally_compatible?が追加


つっつきボイス:「呼び出し元のActiveRecord::Relationと引数に渡したActiveRecord::Relationが構造的に互換かどうか、つまり両者が同じカラムをselectしていて互換であればtrueを返すということのようですね」「おぉ?」「たとえば以下の2つ目だとjoinsで両者のカラムが異なっているのでfalseが返る」

# activerecord/lib/active_record/relation/query_methods.rb#751
# 与えられたリレーションがこのリレーションと構造的に互換性があるかどうかをチェックし、
# エラーを出さずに`#and`や`#or`メソッドを使えるかどうかを判断する。
# 「構造的に互換性がある」は、両者が同じモデルをスコープしていて
# `#where`(`#group`が定義されていない場合)または#having(`#group`が存在する場合)
# によってのみ異なることと定義される。
Post.where("id = 1").structurally_compatible?(Post.where("author_id = 3"))
# => true

Post.joins(:comments).structurally_compatible?(Post.where("id = 1"))
# => false

「そして以下のように両者を#orでつなげる場合はcurrent.or(other)で、つなげない場合はModel.where(id: current)のようにラップしてから#orでつないでいる」「なるほど」「特にスコープでjoinsされた場合だと#and#orでつないでいいか考えることがちょくちょくあるので、これはあっていいメソッド👍」「名前はちょっと長いけど、知っていたら使うかも」

# 同PRより
relations = [...]
relations.drop(1).inject(relations.first) do |current, other|
  if current.structurally_compatible?(other)
    current.or(other)
  else
    Model.where(id: current).or(Model.where(id: other))
  end
end

🔗Rails

🔗 RailsプロジェクトにSteepで型を導入


つっつきボイス:「これは自分も読みました: RBSを自動生成するところから始めてひととおりやれる、具体的ないい記事👍」「RubyMineのRBS対応も進んでいますし、小さな新規プロジェクトでやってみてもよさそう」

「ちょうどこのツイートも見つけました」「RBSとSteepはRuby 2.6以降から使える、いいですね〜」

🔗 AWS LambdaでRails

aws/aws-lambda-ruby-runtime-interface-client - GitHub


つっつきボイス:「取り上げるのが遅れましたが、7月末の銀座Rails #35の@joker1007さんの発表です」「これもいい発表でした」

「元々Amazon ECSのタスク実行機能が使いにくいという問題があって、この問題は実際に踏んでみないとわかりにくいんですが、その部分をLambdaでやる方がいいという趣旨」

「AWSのコンテナをスケジューリングで動かす方法は現在2とおりあります: 1つはFargateやECSのscheduled taskと呼ばれるもので、コンテナをワンショットでバッチ的に実行する」「ふむふむ」「しかしECSの実行そのものがかなり重いのが不便: 具体的にはECSのイメージが置かれているリポジトリのパスがECSコンテナのタスク定義ファイルに書かれていて、起動のたびにそこからイメージをダウンロードしてECSの実行環境に展開することではじめて実行される」「聞くからに重そうですね…」「下手すると起動に数分かかることもあります」

参考: スケジュールされたタスク - Amazon ECS

「2つ目のLambdaは同じようにコンテナを起動しますが、ECSのタスク起動よりずっと速い」「お〜」「Lambdaにはコンテナサイズや実行時間に制限がありますが、実行時間が短くて何度も実行するようなものならLambdaでやる方がずっといい、というのがスライドのこのあたりの話↓」「なるほど」「ECSは常にECR(Elastic Container Registry)からDockerイメージをダウンロードするのと、タスクスケジューラが毎回コールドスタートするので遅いんですが、Lambdaは起動済みのDockerコンテナがあればウォームスタートできるので数秒おきや数分おきに起動するタスクだと特に速い: ちょうど自分もこのあたりにハマったことがありました」

参考: Amazon ECR(Docker イメージの保存と取得)| AWS

🔗 Rails 7でのimport maps導入

rails/importmap-rails - GitHub


つっつきボイス:「ついこの間話題になった、Rails 7のJSコードをWebpack経由でトランスパイラを通す代わりにimport mapsを使う話の動画について、DHHが記事も書いていたので取り上げました」「これについては既にTwitterにも書きましたけど、今後のRailsがこういうWebpackerレスな流れになることはほぼDHHが決めているようなので、このDHHの記事と解説動画、あとこれに関連する上の#42856の改修内容は一度見て押さえておくことをおすすめします: 技術的にもそれほど難しいものではありませんし、字幕をオンにすれば英語もそれほど大変ではないので」「なるほど」「RailsでTypeScript使いたい人たちはどうなるんだろう」

「ところで、DHHはReactの場合の動画も後追いで公開したんですよ↓」「これは知りませんでした」

「2本目のReact動画は、Railsがトランスパイラを使わなくなるとReactのJSXやTypeScriptなどが書けなくなるという問題を自分も含めていろんな人が指摘したので↓、DHHがJSXについては『こうすれば書けるよ』という回答として公開したものです」

参考: JSX の導入 – React

「ところが2本目の動画を見ると実際にはJSXそのものを書けるのではなくて、どうやらJSファイルにJSXライクなコードを書けるhというヘルパーを使うみたいなんですよ: import html from "htm"というコードをたどるとdevelopit/htmというライブラリがどうもそれみたい」「ありゃ」


Alpha preview: Using React with importmaps on Rails 7 – YouTubeより

「developit/htmのREADMEにも『プレーンなJavaScriptにトランスパイラなしでJSXライクな構文で書ける』とあるのでたぶんこれかなと思います」

developit/htm - GitHub

🔗 徳丸先生のRailsセキュリティ関連解説動画


つっつきボイス:「こちらの動画は、銀座Rails #34の徳丸先生のセッションを主催者承認のうえでYouTube動画で公開したそうです」「たしかに銀座Railsで見たヤツ」

「ところで徳丸先生のYouTubeチャンネルにもだいぶ動画が増えていますね」「動画だと見るのに時間がかかるのが大変」「そこですよね」「文章と動画とどちらを好むかは年齢層によって変わってきそうですけど」「せめて動画内でセリフや文字を検索できたらいいかも」「たしかに」「話し言葉と書き言葉はどうしても違ってくるので、自分は記事で読みたいかな」

🔗 KubernetesでRailsを動かす決定版ガイド


つっつきボイス:「KubernetesでRailsを動かす情報を、ドメインまで取って公開しているサイトです」「Kubernetesで単にRailsのDockerイメージを動かしてログを取るぐらいならさほど難しくありませんが、secretの扱いやrakeタスクやマイグレーションのようなRailsで最低限必要なことをひととおり網羅しているようですね: KubernetesでRailsを初めて動かす人向けの入り口として便利そう👍」「お〜」

🔗 その他Rails


つっつきボイス:「@yasaichiさんの発表が以下のスライド↓の続編になりそうなので期待しています」「9/15(水)開催だからRubyKaigi Takeout 2021の翌週か、とりあえず申し込んでおこう」


前編は以上です。

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

週刊Railsウォッチ: SorbetのRuby AOTコンパイラが公開、「Compiler Explorer」にRubyが追加、Ractorで非同期通信ほか(20210823後編)

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

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

Rails公式ニュース

Ruby Weekly

The post 週刊Railsウォッチ: Rails 7でのimport maps導入、Steepで型を導入、KubernetesでRailsを動かすためのガイドほか(20210830前編) first appeared on TechRacho.

週刊Railsウォッチ:TruffleRubyでdig_fetchを実装、ruby/debug gem、AWSハンズオン教材ほか(20210901後編)

$
0
0

こんにちは、hachi8833です。遅ればせながら私もRubyKaigi Takeout 2021のTシャツを注文しました。

参考: Novelties - RubyKaigi Takeout 2021

週刊Railsウォッチについて

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

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

🔗Ruby

🔗 TruffleRubyでdig_fetchを実装して高速化

# 同記事より
def self.dig_fetch(obj, idxs)
  idxs_size = idxs.size
  n = 0
  while n < idxs_size
    idx = idxs[n]

    obj = obj[idx]
    if obj.nil?
      raise KeyError.new("key not found: #{idx.inspect}", :receiver => self, :key => idx)
    end
    n += 1
  end

  obj
end

つっつきボイス:「dig_fetchというメソッド名がスゴい」「Rubyのdigfetchはデータ構造が見えていれば高速化の余地はあるかも」「そういえばruby-jp Slackで最近Rubyのdigfetch周りが話題になっていたのを見かけた気がします」「お、知りませんでした」

参考: instance method Hash#fetch (Ruby 2.3.0)
参考: Hash#dig (Ruby 3.0.0 リファレンスマニュアル)

「TruffleRubyはOracle Labsがこれだけ精力的に手を加え続けているんだから、どこかのproductionで使われていてもよさそう」「気になりますね」

oracle/truffleruby - GitHub

後で探すと、ShopifyがTruffleRubyのproduction利用に向けて実験を重ねているという記事を見かけました。またRubyKaigi Takeout 2021でもShopifyのメンバーがTruffleRubyの正規表現について発表することになっています。

参考: Optimizing Ruby Lazy Initialization in TruffleRuby with Deoptimization — Development (2021)
参考: Just-in-Time Compiling Ruby Regexps on TruffleRuby — Schedule - RubyKaigi Takeout 2021

🔗 ko1さんのdebug gemブログ


つっつきボイス:「ko1さんがruby/debugのブログを始めたそうです」「お〜、こういう情報が記事として記録されていくのは大事👍」「歴史大事ですね」「そうでないと端から失われていってしまうので」「relineを使ってdebugにREPL機能も追加されたのか」「記事でも紹介されているst0012さんのruby/debug紹介記事↓は近々TechRachoで翻訳を公開します」

参考: A Sneak Peek of Ruby’s New Debugger! - DEV Community
参考: REPL - Wikipedia

ruby/reline - GitHub

🔗 その他Ruby


つっつきボイス:「大倉さんがRubyConf 2021に登壇🎉」「英語で登壇つよい」「RubyConf 2021はオンラインとオフライン両方で開催とは頑張ってる」「開催地のデンバーは米国コロラド州で、州の布告に基づいてイベント開催してるようですね」

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

🔗 Amazon MemoryDB for Redis


つっつきボイス:「Amazon MemoryDB for RedisはRedisの互換実装によるサービスで、先々週ぐらいに発表されていましたね: 値段はお安くないですが、Redisを自前で立てると運用が大変なので、こういうサービスはやはり便利だと思います👍

🔗 東大のAWSハンズオン教材


つっつきボイス:「このAWSハンズオン教材は昨年公開されていたヤツかな」「ホントだ、2020年となってました」「東大クラスでないとなかなかこれだけの教材は用意できないでしょうね」「特に考えなくても上から順にやっていけば終わるので、東大の単位としては取りやすい方かも」「たまに考えないといけない場面で詰まるかもしれないので、TAやSAのサポートは必要かもしれませんね」

参考: 大学職員のTA・SAの仕事の違いは? | 大学職員の仕事・なり方・年収・資格を解説 | キャリアガーデン

「逆に東大のCPU実験授業はCPUつくってコンパイラつくって動作テストするところまでやるというエグさ」「そうそう、東大の名物授業」

参考: ほんとうのコンピュータ自作/CPU実験 — 東大 理学部情報科学科/大学院情報理工学系研究科|情報科学科NAVIgation

「東大のAWSハンズオン教材は、内容の賞味期限が切れないうちにやるのがいいと思います👍」「そうそう、今年なら十分いける」「こういうハンズオン授業は教材アップデートのためにも毎年やって欲しいですね」

🔗 その他クラウド

つっつきボイス:「Dockerfileでヒアドキュメントが書ける機能が正式にリリースされた🎉」「これで&& \を書かなくてよくなりますね」「変数展開もできる↓」「今までなかったのが不思議だったぐらい」

# moby/buildkit READMEより
# syntax = docker/dockerfile:1.3-labs
FROM alpine
ARG FOO=bar
COPY <<-eot /app/foo
    hello ${FOO}
eot

参考: Dockerで新しくサポートされるようになったヒアドキュメントを試してみました。 | オスースBlog

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

🔗 ブラウザのデザインモード


つっつきボイス:「これ今頃知りました」「ブラウザコンソールでdocument.designMode = "on"を入力するとブラウザが編集可能になるのね」「ずっと以前からHTMLElement.contentEditableという属性を使うとどの要素でも編集可能にできますけど、designModeは初めて見た」

参考: Document.designMode - Web API | MDN
参考: HTMLElement.contentEditable - Web API | MDN

「ちなみにブラウザ用WYSIWYGエディタによってはHTMLElement.contentEditableを使って実装しているものがあります」「お〜」「しかもHTMLElement.contentEditableを使うとHTMLのスタイルタグも自動的に付けられるのでHTML的に扱いやすいんですよ」

HTMLElement.contentEditableは相当昔からあった」「IE5の独自仕様だったのが取り入れられたという記事↓があったのでかなり古そう」「document.designModeもMDNにIE6に関する記述があるので同じぐらい古そうですね」

参考: HTMLのcontenteditable属性 - 備忘帳 - オレンジ工房
参考: “contentEditable” | Can I use... Support tables for HTML5, CSS3, etc

🔗言語/ツール/OS/CPU

🔗 クラック


つっつきボイス:「朝起きたら自宅の3Dプリンタからこんなのが出力されてビビったというReddit記事だそうです」「あぁクラックされたのね」「これはビビる」「普通のネットワークプリンタなんかもクラッキングされやすいという話は昔からありますけどね」

「ところで3Dプリンタも昔よりだいぶ使いやすくなりましたよね」「自宅に置くまではまだやれてないです」「よく使うなら買いたいけど、まだそこまではいかない」「コンビニで3Dプリントできたらいいんですけどね」「専有時間が長いから難しいんじゃないかな」「専門店で3Dプリントやったことならあります」「試行錯誤やトラブルシュートとかも考えると、最初はサポートのある場所でやってみるのがいいかもですね」


後編は以上です。

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

週刊Railsウォッチ: Rails 7でのimport maps導入、Steepで型を導入、KubernetesでRailsを動かすためのガイドほか(20210830前編)

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

The post 週刊Railsウォッチ:TruffleRubyでdig_fetchを実装、ruby/debug gem、AWSハンズオン教材ほか(20210901後編) first appeared on TechRacho.

ActiveRecordで日付・時刻の範囲検索をシンプルに書く方法

$
0
0

更新情報

  • 2016/08/19: 初版公開
  • 2021/08/26: 更新

こんにちは、hachi8833です。

Active Recordで日付範囲を指定して読み出そうとすると、おそらく次のようなコードになるでしょう。

Pattern.where(“updated_at BETWEEN ? AND ?”, from, to)

社内のSlackチャンネルのログを遡ってて、Active Recordでwhere(updated_at: range_obj_start..range_obj_end)のように、Rangeオブジェクトを#whereの値指定として渡せるというやりとりを見つけたので、確認してみました。

範囲演算子とは

RubyのRangeオブジェクトでは、.....という範囲演算子を使えます。

条件式以外の場所では式1から式2までの範囲オブジェクトを返します。範囲オブジェクトはRangeクラスのインスタンスです。...で生成された範囲オブジェクトは 終端を含みません
範囲式 (Ruby 3.0.0 リファレンスマニュアル)より(強調は筆者)

終端を含まないのは.....のどっちだったかときどきわからなくなったりしますね。

なお、数学用語では端の値を含む範囲を「閉区間」、端の値を含まない範囲を「開区間」と呼んでいます(Wikipedia: 区間(数学))。

Range#newでオブジェクトを生成できます。ここでの範囲演算子は..なので閉区間ですね。

Range.new(Time.zone.now, Time.zone.now.tomorrow)

pry-rails gemを導入したRailsコンソールで出力しました。

range_new

.....で日時を範囲指定

範囲演算子を思い出したところで、適当なRailsプロジェクトをbundle exec rails cでコンソール起動し、Active Recordの適当なモデル(ここではPatternというモデル)のupdated_atカラムに次のクエリをそれぞれ実行してみます。両者の違いはRubyの範囲指定子.....だけです。

Pattern.where(updated_at: Time.zone.today.beginning_of_day..Time.zone.today.end_of_day).to_sql
Pattern.where(updated_at: Time.zone.today.beginning_of_day...Time.zone.today.end_of_day).to_sql

AR_range

1番目の..のSQLでは、ストレートにBETWEENを使っています。
2番目の...のSQLでは、end_of_dayに終端を含まないよう、<を使って自動展開しています。

SELECT `patterns`.* FROM `patterns` WHERE (`patterns`.`updated_at` BETWEEN '2016-08-18 00:00:00' AND '2016-08-18 23:59:59')
SELECT `patterns`.* FROM `patterns` WHERE (`patterns`.`updated_at` >= '2016-08-18 00:00:00' AND `patterns`.`updated_at` < '2016-08-18 23:59:59')

もし終端値のクラス(DateとかDatetimeとか)に応じて<<=を切り替えようとすると、クラスのチェックが必要になるので煩雑になってしまいます。

BETWEEN>=<の切り替えなら、終端値のクラスを気にせず、大小関係が定義されている値の範囲にシンプルに適用できます。ささやかですが、うまい処理ですね。

参考

関連記事

ActiveRecordのRangeHandlerクラスとRubyの範囲メソッドRange#exclude_end?

The post ActiveRecordで日付・時刻の範囲検索をシンプルに書く方法 first appeared on TechRacho.

週刊Railsウォッチ:ActiveRecord::QueryLogs追加、spring gemがデフォルトから削除、fast_gettextほか(20210906前編)

$
0
0

こんにちは、hachi8833です。今週はいよいよRubyKaigi Takeout 2021ですね。

週刊Railsウォッチについて

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

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

お知らせ: 来週の週刊Railsウォッチはお休みいたします🙇

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

公式に追いつくべく今回は2回分取り上げます。

import-mapsは先週取り上げたので(ウォッチ20210831)、importmaps-railsリポジトリを貼ります。

rails/importmap-rails - GitHub


つっつきボイス:「DHHは今後このgemを推していくんでしょうね」「予想はしていたけどRails 7以上なのか〜」「今やってるプロジェクトでimport mapsが使えたらと思ったんですが残念」

🔗 Marginalia gemがActiveRecord::QueryLogsとして追加


つっつきボイス:「Marginalia?」「Basecampのgemだそうです」「そのgemがネイティブのActiveRecord::QueryLogsとして追加されたみたい」

basecamp/marginalia - GitHub

「MarginaliaはSQLクエリのログにどのアプリのどのコントローラのどのアクションからのクエリかというコメントをこういうふうに追加するのね↓: そういえばこんなgemがあった」「名前でぱっとわかりにくそう」「造語かと思ったら”傍注”という意味でした」「入っていたら使うかも👍

# basecamp/marginalia READMEより
Account Load (0.3ms)  SELECT `accounts`.* FROM `accounts` 
WHERE `accounts`.`queenbee_id` = 1234567890 
LIMIT 1 
/*application:BCX,controller:project_imports,action:show*/

ActiveRecord::QueryLogsを追加

Active Recordで生成されるすべてのSQLクエリにコンフィグ可能なタグが自動追加可能になった。

# config/application.rb
module MyApp
  class Application < Rails::Application
    config.active_record.query_log_tags_enabled = true
  end
end

デフォルトでは、クエリタグに「アプリケーション」「コントローラ」「アクション」の詳細が追加される。

class BooksController < ApplicationController
  def index
    @books = Book.all
  end
end
GET /books
# SELECT * FROM books /*application:MyApp;controller:books;action:index*/

静的な値やProcを含むカスタムタグをアプリケーション設定で定義可能。

config.active_record.query_log_tags = [
  :application,
  :controller,
  :action,
  {
    custom_static: "foo",
    custom_dynamic: -> { Time.now }
  }
]

Keeran Raj Hawoldar, Eileen M. Uchitelle, Kasper Timm Hansen
同Changelogより

以下は作成中の移行ガイドだそうです。

参考: upgrade.md

🔗 マルチDBのrails db:setuprails db:resetで特定のデータベースを指定できるようになった

これは、データベース固有のセットアップとリセットのタスクを可能にする試み。自分たちはマルチプルデータベースを広範囲に使っていて、すべてのデータベースを一度にリセットすることは避けたいと考えている。この変更により、名前空間ごとにデータベース固有のセットアップやリセットのタスクが追加される。デフォルトのセットアップタスクやおよびリセットタスクは、説明文を除いて変更していない。

ひとつ問題があるとすれば、db:seedタスクはデータベースに依存しないため、データベースに依存するタスクはすべてをseedすることになる点。特定のデータベースだけをseed可能だろうか?見たところ、seedファイルはどのActive Recordモデルでも参照できるので、できなさそうに思える。

たとえば、primaryとeventsという2つのデータベースがある場合、以下のタスクが利用できる。

rails db:reset                   # Drops and recreates all databases from their schema for the current environment and loads the seeds
rails db:reset:events            # Drops and recreates the events database from its schema for the current environment and loads the seeds
rails db:reset:primary           # Drops and recreates the primary database from its schema for the current environment and loads the seeds
rails db:setup                   # Creates all databases, loads all schemas, and initializes with the seed data (use db:reset to also drop all databases first)
rails db:setup:events            # Creates the events database, loads the schema, and initializes with the seed data (use db:reset:events to also drop the database first)
rails db:setup:primary           # Creates the primary database, loads the schema, and initializes with the seed data (use db:reset:primary to also drop the database first)

同PRより


つっつきボイス:「マルチプルDBで特定のデータベースだけを設定したりリセットしたりできるrakeタスクが追加されたんですね」「今までできなかったとは」「これは欲しい機能👍

🔗 spring gemをデフォルトインストールから削除


つっつきボイス:「development環境やtest環境でRailsをプリロードして起動を速くするspring gemがRailsのデフォルトから消えるそうです」「いいねがかなり多いですね」

rails/spring - GitHub

コンピュータが高速になったので、小〜中規模アプリでspringを使う大きなメリットがほぼなくなった。よって、たまに問題を起こすspringをデフォルトで入れてつらい思いをする必要はもうない。
同PRより

「自分もspringはいつも真っ先にオフにしてる」「私も」「テストが理由なく落ちるときにspringを無効にすると解消するということがちょくちょくありましたね」「マイグレーションがなぜか失敗したときもよくありました」「ネイティブ環境だとspringもそれなりに有用なのかもしれないけど、最近はDocker環境で使う人が増えていますし、それもあって外したのかもしれませんね」

🔗 classicモード廃止に伴う孤立メソッド削除


つっつきボイス:「ActiveSupport::Dependencyからorphanな(孤立した)メソッドが続々削除されたそうです」「privateなインターフェイスだし消しても大丈夫そう」

参考: 定数の自動読み込みと再読み込み (Classic) - Railsガイド

「もうひとつのプルリクはActiveSupport::Dependencyにあるsafe_constantizeが削除された」「constantizeは使っていたけどsafe_constantizeは知らなかったな〜」

参考: constantizeString

(これはclassicモードの削除に伴うActiveSupport::Dependenciesのお掃除の一環)

ActiveSupport::Dependenciesconstantizeメソッドとsafe_constantizeメソッドはprivateでオートローディングとは関係しておらず、単にinflectorに転送される。これらは歴史的な理由で残されていたが、今や既に不要。
publicなインターフェイスはStringクラスにある。

model_name.constantize

したがって以下の代わりに上のように書ける。

ActiveSupport::Dependencies.constantize(model_name)

既にフレームワークの大半がこのようになっており、残りはわずか。
#43058より

🔗 weekday_options_for_selectビューヘルパーが追加


つっつきボイス:「ブラウザで平日の曜日選択のプルダウンを表示するweekday_options_for_selectってありそうでなかったのか」「ビューのフォームヘルパーなんですね」「これはあっていいメソッド👍

自分はこれまで多くのRailsアプリで平日の曜日を選択するヘルパーを手作りしなければならなかった。Railsには優秀なヘルパーがほとんど揃っているので、このヘルパーがないことにいささか驚いていた。ちょうどヘルパーをまた実装しなければならなくなり、他の開発者もこのヘルパーがないことに驚いていたので、Railsにプルリクを投げてもいい頃合いだと思った。

このプルリクはFormOptionHelperFormBuilderに2つのメソッドを追加し、Tags::WeekdaySelectクラスを追加する。

weekday_options_for_select
# => "<option value=\"Sunday\">Sunday</option>\n<option value=\"Monday\">Monday</option>\n
# <option value=\"Tuesday\">Tuesday</option>\n<option value=\"Wednesday\">Wednesday</option>\n
# <option value=\"Thursday\">Thursday</option>\n<option value=\"Friday\">Friday</option>\n
# <option value=\"Saturday\">Saturday</option>"

weekday_options_for_selectではselected値を受け取るほかに:index_as_valueオプションと:day_formatオプションも受け取れる。

weekday_options_for_select(nil, day_format: :abbr_day_names)
# => "<option value=\"Sun\">Sun</option>\n<option value=\"Mon\">Mon</option>\n
# <option value=\"Tue\">Tue</option>\n<option value=\"Wed\">Wed</option>\n
# <option value=\"Thu\">Thu</option>\n<option value=\"Fri\">Fri</option>\n
# <option value=\"Sat\">Sat</option>"

weekday_options_for_select(nil, index_as_value: true)
# => "<option value=\"0\">Sunday</option>\n<option value=\"1\">Monday</option>\n
# <option value=\"2\">Tuesday</option>\n<option value=\"3\">Wednesday</option>\n
# <option value=\"4\">Thursday</option>\n<option value=\"5\">Friday</option>\n
# <option value=\"6\">Saturday</option>"

weekday_options_for_selectは以下のような場合で使うヘルパーメソッド。

weekday_select(:model, :weekday)
# => "<select name=\"model[weekday]\" id=\"model_weekday\"><option value=\"Sunday\">Sunday</option>\n
# <option value=\"Monday\">Monday</option>\n<option value=\"Tuesday\">Tuesday</option>\n
# <option value=\"Wednesday\">Wednesday</option>\n<option value=\"Thursday\">Thursday</option>\n
# <option value=\"Friday\">Friday</option>\n<option value=\"Saturday\">Saturday</option></select>"

weekday_selectメソッドはFormBuilderでも使われるので、以下のように書くと

<!-- 同PRより -->
<%= form_for @digest do |f| %>
  <%= f.weekday_select :weekday %>
  <%= f.submit %>
<% end %>

以下のようなHTMLが生成される。

<!-- 同PRより -->
<select name="digest[weekday]" id="digest_weekday">
  <option value="Sunday">Sunday</option>
  <option value="Monday">Monday</option>
  <option value="Tuesday">Tuesday</option>
  <option value="Wednesday">Wednesday</option>
  <option value="Thursday">Thursday</option>
  <option value="Friday">Friday</option>
  <option value="Saturday">Saturday</option>
</select>

同PRより

🔗 特定データベースのyaml設定にdatabase_tasks: falseオプションが追加


つっつきボイス:「通常はたとえばrails db:migrateを実行するとすべてのデータベースにコネクションを張るけど、database_tasks: falseを指定したデータベースにはコネクションを張らなくなるようですね」「ふむふむ」「以下のmy_animals_databaseでアクセスを一切行いたくない場合に使うということだと思います: コマンドラインでもいいような気はしますが、yamlで設定できるとより便利そう👍

データベース設定オプションdatabase_tasksを追加
「スキーマ管理」「マイグレーション」「seed」などの管理タスクがない外部データベースに接続したい場合、データベースごとにdatabase_tasks: falseオプションを設定できるようになった。

# config/database.yml
production:
  primary:
    database: my_database
    adapter: mysql2
  animals:
    database: my_animals_database
    adapter: mysql2
    database_tasks: false

Weston Ganger
同Changelogより

🔗 削除されたオプション


つっつきボイス:「どちらもDHHによるプルリクです」「#42996で--skip-gemfileオプションが削除されて、#42998で--skip-pumaオプションが削除されたんですね」「歴史的なオプションみたい」

いにしえの小競り合いの記念碑をいつまでも残しておく必要はない。
#42996より
必要な設定を削除するためのオプションには意味がない。
#42996より

🔗Rails

🔗 大量のActionMailジョブをSidekiqで一括処理する(Ruby Weeklyより)


つっつきボイス:「deliver_laterで個別のジョブを大量に投げて詰まるのはよくあるヤツ」「1万通のメールで40分待ちはつらそう…」「この記事ではジョブの登録の詰まりが問題になっているみたいですね」

「Sidekiqのpush_bulkを使うと複数ジョブをまとめて投げられるのか」「メールジョブごとにパラメータを渡したい場合はActionMailer::Parameterized::DeliveryJobを使う必要があるのね」「メールによって送り先や文面をパラメータで変えるのはよくありますね」「ジョブを用意する段階でパラメータを渡しておいてからpush_bulkでまとめてプッシュする方が負荷が小さくなる、たしかに」「アトミックな処理を行う大量のジョブを1個ずつ登録するのはやりたくないですね」

参考: Method: Sidekiq::Client#push_bulk — Documentation for mperham/sidekiq (master)

# 同記事より
def enqueue_many_parametrized_mails(mail_class, template, args_array)
  job = ActionMailer::Parameterized::DeliveryJob

  # convert template and args array into an array of arrays containing args
  # for ActionMailer::DeliveryJob objects
  mailer_job_args = args_array.map { |args|
    [job.new(
      mail_class.name,
      template.to_s,
      "deliver_now",
      {some_arg: "foo"},
      *args
    ).serialize]
  }

  Sidekiq::Client.push_bulk(
    "class" => ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper,
    "wrapped" => job,
    "queue" => MAILER_QUEUE,
    "args" => mailer_job_args
  )
end

参考: Rails6 のちょい足しな新機能を試す41(MailDeliveryJob 編) - Qiita

🔗 fast_gettext: 高速i18n gem(Ruby Weeklyより)

grosser/fast_gettext - GitHub

# 同リポジトリより
FastGettext.with_locale 'gsw_CH' do
  FastGettext._('Car was successfully created.')
end
# => "Z auto isch erfolgriich gspeicharat worda."

つっつきボイス:「i18n(国際化)のgemのようです」「gettextライブラリをrubyで再実装したみたい」

参考: gettext - Wikipedia

「fast_gettextはgettextより12倍高速でガベージが530分の1でスレッドセーフですって」「Active SupportのI18n::Simpleも比較されてる」「大量の国際化テキストを処理しないといけない場合に必要になってくるんでしょうね: 自分はあまりその必要に迫られたことはありませんが、海外のサイトだと国際化の言語数が多い分切実なのかも」

Hash FastGettext GetText ActiveSupport I18n::Simple
Speed* 0.08s 0.14s 1.75s 3.75s
Objects* 11K 15K 8017K 7107K
Included backends db, yml, mo, po, logger, chain mo yml (db/key-value/po/chain in other I18n backends)

同リポジトリより


前編は以上です。

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

週刊Railsウォッチ:TruffleRubyでdig_fetchを実装、ruby/debug gem、AWSハンズオン教材ほか(20210901後編)

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

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

Rails公式ニュース

Ruby Weekly

The post 週刊Railsウォッチ:ActiveRecord::QueryLogs追加、spring gemがデフォルトから削除、fast_gettextほか(20210906前編) first appeared on TechRacho.

週刊Railsウォッチ: 責任あるモンキーパッチの当て方、gem脆弱性スキャンツール、Docker Desktop課金プラン改定ほか(20210907後編)

$
0
0

こんにちは、hachi8833です。来月出るんですね。

週刊Railsウォッチについて

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

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

お知らせ: 来週の週刊Railsウォッチはお休みいたします🙇

🔗Ruby

🔗 Twist: 出版原稿レビュープラットフォーム(Ruby Weeklyより)

radar/twist-v2 - GitHub


つっつきボイス:「ややこしいですが、本を執筆している著者が出版前に原稿をレビューしてもらうためのプラットフォームを作ったのがこのTwistだそうです」「自分でこのアプリを作ってみたさまざまな知見を記事にした感じかな: よさそうな記事👍

「twist-v2はバックエンドを切り離した今風の設計っぽい↓」「ディレクトリ構成はRailsっぽく見えるしZeitwerkもあるけど、RailsアプリでもSinatraアプリでもなくHanamiで、dry-rbrom-rbyなどもいろいろ使って作ってみたらしい、へ〜」「フロントエンドはReactで、GraphQL経由でサーバーを呼び出しているのね」

DB <-> Backend Repositories <-> Backend GraphQL endpoint <-> Frontend <-> Browser
radar/twist-v2 READMEより

「そういえば作者のRyan Biggさんは以下の記事を書いた人でした↓」「CurrentAttributesが有害というのはワカル」

Railsの`CurrentAttributes`は有害である(翻訳)

🔗 責任あるモンキーパッチの当て方(Ruby Weeklyより)


つっつきボイス:「ちょっと長い記事です」「2011年にRuby 1.8.7で巨大Railsアプリをやっていた話がじわじわ来ますね」「え、そのときにString#%にモンキーパッチを当てたのか!」「マジで?」「String#%は文字列フォーマット用メソッドですね: こういうどこでも使われそうなメソッドにモンキーパッチを当てるのは怖い…」

# 同記事より: String#%にモンキーパッチを当てたときの挙動
replacements = {
  horse_count: 3,
  horses: {
    one: "is 1 horse",
    other: "are %{horse_count} horses"
  }
}

# "there are 3 horses in the barn"が出力される
"there %{horse_count:horses} in the barn" % replacements

参考: String#% (Ruby 3.0.0 リファレンスマニュアル)

「モンキーパッチが失敗する主な原因↓とかいろいろ面白そう」

  • パッチそのものが壊れた場合: 上述のコードベースでは、同じメソッドで複数の実装が競合するのみならず、「勝った」メソッドも動いてくれなかった
  • 仮定が正しくなかった場合: ホストコードが更新されてパッチが期待どおりに当たらなくなっていた
    同記事より

「そういうときのためにRubyにはrefinementがあるのに、と思ったら、意外にもこの記事にはrefinementの話がまったくなかった」「あら、ホントだ」

RubyのRefinement(翻訳: 公式ドキュメントより)

「ざっと眺めた限りではRubyの経験が豊富な人という印象: ちゃんと読んだら学びがありそう👍」「翻訳してみたくなりました」

🔗 Snykのgem脆弱性スキャンツールとRails脆弱性データベース(Ruby Weeklyより)


つっつきボイス:「このSnykは自社でさまざまな言語を対象とする脆弱性スキャンCLIツールを出していて、それを使ってGemfileのセキュリティチェックを行う記事のようですね」「価格表を見た感じではこのツールでビジネスをやっているみたい」「今でもGitHubのDependabotとかを使えば脆弱性を通知できますけどね」「フリープランもあって自動テストでこういうツールを回せるならちょっと使ってみてもいいかも」

snyk/snyk - GitHub

dependabot/dependabot-core - GitHub

参考: Language support summary – Docs Library | Snyk

「Snykはこういうgemごとの脆弱性データベースも公開しているそうです↓」「お、これはなかなか便利そう」「npmやpipなどの脆弱性情報もあるんですね」

参考: rails vulnerabilities | Snyk

🔗 pnglitch: pngファイルを壊すgem(Ruby Weeklyより)

ucnv/pnglitch - GitHub


つっつきボイス:「以下のサイトにあるような感じでpngファイルをいい感じに壊すgemだそうです」「ああpngファイルってたしかにこういうふうに壊れますよね😆」「面白い😆」「pnglitchって文字どおりpngのglitch(故障)なのね」

参考: The Art of PNG Glitch


ucnv.github.ioより

「何のためのgemなんでしょうね?」「それがよくわからなくて、その割に★がたくさんあるのも謎です」「デスクトップやZoomの背景にしてビックリさせるとか?😆」「そういえば液晶に線が入ったような感じになるスクリーンセーバーもありましたね」

READMEによると、単に壊れたpngを眺めて楽しむためのgemのようです。後でart of pnglitchでググるとそれっぽいサイトがたくさん出てきました。

🔗 Unixのepoch時間をRubyオブジェクトに変換する(Ruby Weeklyより)

参考: UNIX時間 - Wikipedia


つっつきボイス:「Time#atin:オプションのドキュメントがなかったので、記事書いた人がプルリクを投げたのね↓」「マージされてよかった🎉

参考: Update docs for Time#at method by prathamesh-sonpatki · Pull Request #2929 · ruby/ruby
参考: class Time (Ruby 3.0.0 リファレンスマニュアル)

「お、これは例のmilitary time zone」「『タイムゾーン呪いの書』にも載っていた、1文字で表すタイムゾーンですね(ウォッチ20210713)」

# 同記事より
>> Time.at(ts, in: "E")
#=> 2020-03-02 09:13:24 +0500
>> Time.at(ts, in: "Z")
#=> 2020-03-02 04:13:24 UTC

参考: List of military time zones - Wikipedia

「このエラーメッセージを見ると、military time zoneにはA〜IとK〜ZはあるけどJがないのが面白い↓」

# 同記事より
>> Time.at(ts, in: "IST")
Traceback (most recent call last):
        2: from (irb):12
        1: from (irb):12:in `at'
ArgumentError ("+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset)

🔗DB

🔗 UUIDとULID


つっつきボイス:「はてブで見つけました」「記事にもあるように、UUID(バージョン1)はランダム部が桁の上位にあるために、データベースのプライマリキーに使うとクラスタインデックスのデータ分布が散ってしまう点がパフォーマンス上不利ですね」

「このあたりはデータのlocality(局所性)の話題で、一般にlocalityが高いほどキャッシュヒットしやすくなるのでパフォーマンス上有利な代わりに、障害に弱くなる傾向があるというトレードオフの関係がありますが、RDBMSのインデックスの話であれば一部だけが破損するようなケースは想定しにくいのでlocalityが高い方が有利です」「なるほど」「逆に分散システムだと、パフォーマンスを少々犠牲にしてでもlocalityを下げることもあります」

参考: 参照の局所性 - Wikipedia

「分散システムではデータが一様に分布する方が障害に強くなりますが、この記事のような単一のデータベースシステムの場合は基本的にlocalityが高い方がパフォーマンス上有利」「ふむふむ」「もともとUUIDの設計はパフォーマンスよりも分散システム上でIDが衝突しないことがメインなんですよ: RDBMSでlocalityが高い方が効率がよくなるならULIDを使うのがいいでしょうね」「なるほど!」

参考: ULID - shimojubox

「元記事は図も含めて丁寧に書かれているので、UUID周りを知らなかった人にはおすすめ👍: このサイトは他のデータベース記事もよいです」

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

🔗 Docker Desktopの課金プラン改定


つっつきボイス:「Docker Desktopの件は今だいぶ騒がれているので、そのうち方針が変わるんじゃないかなと思いたい」「乗り換えを考える記事もいろいろ出始めてますね」

参考: Docker Desktop有料化の影響 - Qiita

「今回発表された課金プランだとDocker Desktopがいろいろ使いにくくなってしまうのが残念」「ですよね」「課金されるかどうかは売上ベースか従業員数ベースで決まるんですが、Docker Desktopを使ってない他部署の売上も課金の閾値に影響することになりますし、大企業がこれからDocker Desktopを導入しようとしたときに売上が見込めないうちから課金対象になると話が通しにくくなりそう」「あ〜」「課金とは別の話ですが、アカウントを”人”に紐付けるのが必須なので、開発メンバーの引き継ぎとかがやりづらい」「誰か退職するとCIが止まりそうですよね」

「ツイートにも書きましたけど、自分たちはローカルでDocker Desktopをクライアントとして使えればよくて、それでいて有償のクラウド機能に欲しいものがないところに食い違いを感じるんですよ」「そこですよね」「課金そのものはあっていいと思うので、もっと有用感のある課金プランを検討して欲しい気持ち」「それこそお金を払ってでもPersonal版を使いたい企業もありそう」

参考: Docker Pricing & Monthly Plan Details | Docker


後編は以上です。

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

週刊Railsウォッチ:ActiveRecord::QueryLogs追加、spring gemがデフォルトから削除、fast_gettextほか(20210906前編)

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

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

Ruby Weekly

The post 週刊Railsウォッチ: 責任あるモンキーパッチの当て方、gem脆弱性スキャンツール、Docker Desktop課金プラン改定ほか(20210907後編) first appeared on TechRacho.

Viewing all 1384 articles
Browse latest View live