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

週刊Railsウォッチ(20190917-1/2前編)Sidekiq 6.0がリリース、銀座Rails#13と「出張!Railsウォッチ」、るびま0060号、ロックイン回避の落とし穴ほか

こんにちは、hachi8833です。

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

週刊Railsウォッチ「公開つっつき会」第15回のお知らせ(無料)

第15回目公開つっつき会は、10月5日(木)19:30〜にBPS会議スペースにて開催されます。皆さまのお気軽なご参加をお待ちしております🙇。

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

公式の更新情報がなかったので、今回も6-0-stableを中心に見繕いました。

(6.0)insert allなどでクエリキャッシュをクリアするようにした

insertinsert_allupsertupsert_allではクエリキャッシュをクリアするようになった。
Eugene Kenny
changelogより

# activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb#L154
+     def exec_insert_all(sql, name) # :nodoc:
+        exec_query(sql, name)
+     end
# activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb#L9
        def included(base) #:nodoc:
          dirties_query_cache base, :insert, :update, :delete, :truncate, :truncate_tables,
-           :rollback_to_savepoint, :rollback_db_transaction
+           :rollback_to_savepoint, :rollback_db_transaction, :exec_insert_all

          base.set_callback :checkout, :after, :configure_query_cache!
          base.set_callback :checkin, :after, :disable_query_cache!
        end
# activerecord/lib/active_record/insert_all.rb#L23
    def execute
      message = +"#{model} "
      message << "Bulk " if inserts.many?
      message << (on_duplicate == :update ? "Upsert" : "Insert")
-     connection.exec_query to_sql, message
+     connection.exec_insert_all to_sql, message
    end

つっつきボイス:「#37142 のupsert_allのバグIssue↓に対する修正対応という感じですね」

# 37142より
ActiveRecord::Base.connection.enable_query_cache!
#=> true
User.create(name: "Fred")
#=> #<User id: 1, name: "Fred", created_at: "2019-09-06 02:24:38", updated_at: "2019-09-06 02:24:38">
u = User.first   # ここでクエリキャッシュができる
#=> #<User id: 1, name: "Fred", created_at: "2019-09-06 02:24:38", updated_at: "2019-09-06 02:24:38">
User.upsert_all([{id: u.id, name: "Amy", created_at: u.created_at, updated_at: Time.now}])
#=> #<ActiveRecord::Result:0x00007f9b16b439e0 @columns=[], @rows=[], @hash_rows=nil, @column_types={}>
User.first.inspect
#=> #<User id: 1, name: "Fred", created_at: "2019-09-06 02:24:38", updated_at: "2019-09

再現手順を実行するとActive Recordがクエリキャッシュを読み込むため古い結果が返され、クエリが発生しない。このクエリキャッシュはdatabase_statementsでクエリが変更された場合に自動的にデータのキャッシュをクリアするようセットアップされるが、新しいupsert/insert系メソッドではそうなっていない。
#37142より大意

(6.0)エンドレスRangeでinclude?を呼ぶと落ちる問題を修正

# activesupport/lib/active_support/core_ext/range/include_time_with_zone.rb#L11
    def include?(value)
-     if first.is_a?(TimeWithZone)
+     if self.begin.is_a?(TimeWithZone)
        cover?(value)
-     elsif last.is_a?(TimeWithZone)
+     elsif self.end.is_a?(TimeWithZone)
        cover?(value)
      else
        super
      end
    end

つっつきボイス:「endless Rangeでは #first / #lastではなく #begin / #end を使うべきという話みたいなのだけど、この辺のドキュメント、Rubyの日本語リファレンスマニュアルの方だと違いが書かれていなくて、英語のRDocの方には書かれているという差があるみたい」「😳」

後で見てみると、日本語リファレンスマニュアルにはendless Rangeについての記述自体がありませんでした。
Ruby 2.6.4のPryでやってみると↓、エンドレスRangeのlastはエラーになり、endだとエラーになりませんでした。なお...でも同じです。

» (1..).last
RangeError: cannot get the last element of endless range
from (pry):24:in `last'
» (1..).end
»

(6.0、5.2.3)app/にREADME.mdを置くとdevelopment環境でエラーになる問題を修正

# railties/lib/rails/application.rb#L350
    def watchable_args #:nodoc:
      files, dirs = config.watchable_files.dup, config.watchable_dirs.dup

      ActiveSupport::Dependencies.autoload_paths.each do |path|
-       dirs[path.to_s] = [:rb]
+       File.file?(path) ? files << path.to_s : dirs[path.to_s] = [:rb]
      end

つっつきボイス:「6.0と5.2.3のLinuxのdevelopment環境の場合に起きたそうです」「autoload_pathsにディレクトリパスではなくファイルパスを書いているとダメだったのを、ファイルパスでも正常に動くようにした模様」

(master)classやmoduleもActiveJobの#perform引数に渡せるようにした

# activejob/lib/active_job/serializers/module_serializer.rb
+# frozen_string_literal: true
+
+module ActiveJob
+  module Serializers
+    class ModuleSerializer < ObjectSerializer # :nodoc:
+      def serialize(constant)
+        super("value" => constant.name)
+      end
+
+      def deserialize(hash)
+        hash["value"].constantize
+      end
+
+      private
+        def klass
+          Module
+        end
+    end
+  end
+end

つっつきボイス:「Factory method的なクラスをActiveJobのperform時に渡せるようになった、という感じのようだ」

# 同PRより
class EmailJob < ApplicationJob
  queue_as :default
  def perform(template_class, *arguments)
    template_class.new(*arguments).send!
  end
end

module Email
  class FooTemplate ... end
  class BarTemplate ... end
end

EmailJob.perform_later(Email::FooTemplate, ...)
EmailJob.perform_later(Email::BarTemplate, ...)

参考: Factory Method パターン - Wikipedia

(master)Rubyのキーワード引数変更に引き続き対応

先週(ウォッチ20190909)に続くキーワード引数周りの対応です。

# activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb#L480
-     def add_column(name, type, options)
+     def add_column(name, type, **options)
        name = name.to_s
        type = type.to_sym
        @adds << AddColumnDefinition.new(@td.new_column_definition(name, type, **options))
      end
    end
# activerecord/lib/active_record/migration/compatibility.rb#L153
-       def add_column(table_name, column_name, type, options = {})
+       def add_column(table_name, column_name, type, **options)
          if type == :primary_key
            type = :integer
            options[:primary_key] = true
          end
          super
        end

Rails

Sidekiq 6.0がリリース(Ruby Weeklyより)


sidekiq.orgより(CC BY-SA 4.0

「デーモン化をやめた」「ログ出力を一新」「Active Jobとの統合」が目に付きました。


つっつきボイス:「利用側としてはログフォーマッタが使えるようになったというくらいかな?: sidekiqctlバイナリがなくなった、とかも書いてあるので、セットアップスクリプト周りはプロジェクトによっては変更の必要があるのかもしれない」「おぉ」「公式のUpgrade noteの方が情報量ありそう↓」


以下は同アップグレードノートより:

ActiveJobでsidekiq_optionsを用いてSidekiqの機能や内部を直接設定できるようになった(retryサブシステムなど)。ActiveJobでうまく動かない一部の機能(unique jobなど)についてはネイティブのSidekiq::Worker APIが望ましい。

class MyJob < ActiveJob::Base
  queue_as :myqueue
  sidekiq_options retry: 10, backtrace: 20
  def perform(...)
  end
end
  • ログ出力が再設計されてフォーマッターやSidekiq付属のフォーマッタをpluggableにできるようになった
    • default: macOSの典型的な出力
    • heroku: Heroku実行時に特化した出力
    • json: インデックス検索用のJSONフォーマット(1行1ハッシュ)
  • 検出された環境に最適なフォーマッターが有効になる。明示的にログフォーマッターを設定することでオーバーライドもできる。詳しくはLogging Wiki参照。
Sidekiq.configure_server do |config|
  config.log_formatter = AcmeCorp::PlainLogFormatter.new
  # config.log_formatter = Sidekiq::Logger::Formatters::JSON.new
end
  • 以下を廃止: デーモン化、logfileやpidfileコマンド引数、sidekiqctlバイナリ
  • REDIS_PROVIDER変数は正しく使うこと
  • デフォルトのシャットダウン時間を8秒から25秒に延長
  • 以下はサポート対象外:
    • Rails 5より前
    • Ruby 2.5より前
    • Redis 4より前
  • Rails 6以降はZeitwerkモードでのみ動作する

Stimulusjsってどう?

Stimulusjsのリポジトリは★7500超えで、issueやPRがほぼ残っていないのがびっくりです。


つっつきボイス:「StumulusjsはBasecampが作っていてRailsとの相性がよさそうなのが気になったので」「フロントエンドも全部Railsエンジニアが書くならStumulusjsは悪くない選択肢にも見えるけど、Railsエンジニアってフロントエンドは触りたくないと言っている人もちらほら見る(そもそもサーバーサイドで手一杯で手が回らないという話も)ので、潤沢なRailsエンジニアの供給を前提とするのが果たして妥当かどうか、という話になるのかもしれないですね」

Rails 6ではDBのadvisory lockを無効にできる(RubyFlowより)

Rails 6ではdatabase.ymlでadvisory lockをオンオフできるそうです(デフォルトはtrue)。

production:
  adapter: postgresql
  advisory_locks: false

つっつきボイス:「migrationの時だけ参照される?ような説明になっているように見える: 無効にしたいシチュエーションがよくわからない🤔」

参考: 13.3. 明示的ロック -- Advisory lockは「勧告的ロック」と訳されてます
参考: 3.19 接続設定 -- Rails アプリケーションを設定する - Rails ガイド -- advisory_locks設定の説明

「ガイドによると、PostgreSQLのPgBouncer↓とかを使う時にもadvisory lockをオフにする必要があるかもしれないってありますね」

参考: PgBouncer - lightweight connection pooler for PostgreSQL

Rails開発者のためのPostgreSQLの便利技(翻訳)

銀座Rails#13で「出張!Railsウォッチ」

9/12の銀座Rails#13でmorimorihogeさんが「出張!Railsウォッチ」で登壇しました。お題はRails 6のAction Textです。

以下のスライドではTechRachoの翻訳記事「ActiveRecord::FixtureSet」にも言及いただきました🙇。


つっつきボイス:「今回は銀座Railsに参加できずでした😓」「報告記事上げます~」

後でmorimorihogeさんから伝え聞いた@a_matsudaさんのRailsパフォーマンス話の要点も興味深い内容でした😋。

追記

なお、前回の銀座Rails#12で評判だった@jnchitoさんのライブコーディング動画が有料公開されました🎉。

以下はその後のツイートです。

その他Rails


unubo.comより

つっつきボイス:「Unuboがはてブでバズってたので」「ニフティクラウドC4SAみたいに滅びる可能性も高いので、あくまで練習用という感じがするのと、あとどこの誰が運用しているのかがサイト見てもよくわからなかった🤔」

参考: NIFTYCloud-C4SA/support: ニフティクラウドC4SA ドキュメント・FAQ・質問等はこちらへ

ニフティクラウド C4SAは、2017年11月30日をもちまして、サービスを終了いたしました。 これまで長らくご愛顧賜り、誠にありがとうございました。
同リポジトリより

Ruby

るびま0060号リリース🎉


magazine.rubyist.netより


つっつきボイス:「るびまサイトがRSSフィードしていることについ昨日気づきましたので、Slackでフィードを受けるよう設定しました😅」「るびま、Rubyistは必読なので読んでない人はぜひ🎉」

スライド: Ruby 3のキーワード引数


同スライドより

上のスライドは今年のRubyKaigiより少し前に以下のissueに貼られているのを見つけました。最終的にJeremy Evansさんの案がベースになったようです。


つっつきボイス:「上のスライドは方針決めのためのもので、結局どうなるのか・どうすればいいのかをissueでまだ追いきれていないのですが😅、以下あたり↓が比較的まとまってそうです」「breaking changesなので、次のRubyKaigiでも話題になる気がしますね」「もしかするとmameさんがそのうちブログにまとめてくれるかもしれないとひっそり期待してます🙏」

RubyのUnboundMethod

この間のTokyo Rubyist Meetup@ベストマサフミさんが発表後にこれをテーマに即興LTをやったのを見て知りました。

参考: Ruby|UnboundMethodで遊んでみた - Qiita

その他Ruby

# 同記事より
# Defining sub_scorer which is used at multiple places
# and based on some condition it is updating scores
def sub_scorer(scores)
  ...
  scores << 50 if condition
  ...
end

# Defining final_scorer which may call sub_scorer
def final_scorer
  begin
    scores = [10, 20, 30].freeze
    sub_scorer(scores)
  rescue FrozenError => e
    # We can now gracefully handle Frozen object violations
    # based on the receiver
    if e.receiver == scores
      return "Can not modify scores"
    else
      return "Can not modify frozen objects"
    end
  end
end

final_scorer
#=> "Can not modify scores"

その他

書籍とか


つっつきボイス:「今度こそ技術書典行ってみたかったんですがまたしても都合がつかなくて😭」「技術書店、人が多いのが苦手過ぎて行けてないので自宅からOculus QuestとかでVR参加したいなあ」

「ロックインの回避」にロックインされるな(Morning Cup Of Codingより)


martinfowler.comより

ロックインを避けようとするあまり:

  • 余分な工数がかかる
  • コストもかかる
  • 利便性が落ちる
  • さらに複雑になる
  • さらに別のロックインに陥る

つっつきボイス:「martinfowler.comの記事ですが書いたのはMartin Fowlerさんではありませんでした」「あるある: リスク管理で大事なのはリスクの洗い出しとコストを含めたトレードオフの認識であって、無限のリソースをかけてロックインを回避するというのは全くの悪手(国防方面や原発・プラント制御みたいなものだと例外はあるけど)」


前編は以上です。

おたより発掘

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

週刊Railsウォッチ(20190910-2/2後編)buildersconと「20年後のソフトウェアテスト」、はてなブックマークがScalaに移行、「詳解PostgreSQL」、Go 1.13ほか

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

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

Rails公式ニュース

Ruby Weekly

RubyFlow

160928_1638_XvIP4h

Morning Cup Of Coding

morningcupofcoding_banner_captured


CONTACT

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