3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

お題は不問!Qiita Engineer Festa 2024で記事投稿!
Qiita Engineer Festa20242024年7月17日まで開催中!

Rails7.2で出る新機能を紹介 & どう実装されているかみてみる

Posted at

はじめに

Rails7.2の新機能について気になったので調べてみました。

Rails 7.2の新機能は、Ruby on Rails Guidesを参考にしていきす。
現時点だと、7.2.0.beta2までしか出ていないですが、この時点までのでまずは書いてきます。

rails new myapp --devcontainer

rails newに --devcontainer オプションが追加されました。

devcontainerを用意できるようになりました。

コードで言うとこの辺りです。

        class_option :devcontainer,        type: :boolean, default: false,
                                           desc: "Generate devcontainer files"

このdevcontainerオプションがtrueになったとき動くのがこの辺りです。

devcontainer_optionsを定義して、DevcontainerGeneratorクラスに渡し、 invoke_allDevcontainerGeneratorのPublicメソッドを実行させていますね。

    def devcontainer
      devcontainer_options = {
        database: options[:database],
        redis: !(options[:skip_action_cable] && options[:skip_active_job]),
        system_test: depends_on_system_test?,
        active_storage: !options[:skip_active_storage],
        dev: options[:dev],
        node: using_node?,
        app_name: app_name
      }


      Rails::Generators::DevcontainerGenerator.new([], devcontainer_options).invoke_all
    end

DevcontainerGeneratorこんな感じになっています。

    class DevcontainerGenerator < Base # :nodoc:
    # 省略
    # ~~
      def create_devcontainer
        empty_directory ".devcontainer"


        template "devcontainer/devcontainer.json", ".devcontainer/devcontainer.json"
        template "devcontainer/Dockerfile", ".devcontainer/Dockerfile"
        template "devcontainer/compose.yaml", ".devcontainer/compose.yaml"
      end
    # ~~
    # 省略

以下の3つのファイルを作成しています。

  1. .devcontainer/devcontainer.json
  2. .devcontainer/Dockerfile
  3. .devcontainer/compose.yaml

上記のファイルの元となっているテンプレートはここです。

allow_browser

Railsの各Actionにアクセスできるブラウザを制限する機能です。
使い方はこんな感じ。

class ApplicationController < ActionController::Base
  # allow_browser versions: :modern
  allow_browser versions: { safari: 16.4, firefox: 121, ie: false }, only: :show
end

AllowBrowser Moduleが追加されていて、ActionControllerでModuleを読み込んでいます。こちら

module ActionController
  extend ActiveSupport::Autoload
    # ~~ 省略
    autoload_under "metal" do
      autoload :AllowBrowser
      # ~~省略
    end
  end
end

AllowBrowser moduleallow_browserメソッドが定義されています。こちら

before_actionallow_browserメソッドを実行しているようです。

      def allow_browser(versions:, block: -> { render file: Rails.root.join("public/406-unsupported-browser.html"), layout: false, status: :not_acceptable }, **options)
        before_action -> { allow_browser(versions: versions, block: block) }, **options
      end

AllowBrowser#allow_browserメソッド内で実行されているallow_browserは以下のように定義されています。こちら

BrowserBlockerクラスでblocked?で判定していますね。

      def allow_browser(versions:, block:)
        require "useragent"


        if BrowserBlocker.new(request, versions: versions).blocked?
          ActiveSupport::Notifications.instrument("browser_block.action_controller", request: request, versions: versions) do
            instance_exec(&block)
          end
        end
      end

blocked?の判定の仕方はこんな感じです。
UserAgentが存在している」 かつ 「サポートされていないブラウザである」 場合は blocked?trueになるようです。

        def blocked?
          user_agent_version_reported? && unsupported_browser?
        end

Ruby3.1以上の必要がある

以下のような感じで3.1.0以上である必要があるように定義されていました。こちら

  spec.required_ruby_version = ">= 3.1.0"

デフォルトでpwa用のファイルが生成されるようになった

pwa_controllerが定義されています。こちら

class Rails::PwaController < Rails::ApplicationController # :nodoc:
  skip_forgery_protection

  def service_worker
    render template: "pwa/service-worker", layout: false
  end

  def manifest
    render template: "pwa/manifest", layout: false
  end
end

pwa/service-worker,pwa/manifestファイルが生成されます。
テンプレートはこちらにあります。

デフォルトでrubocop-rails-omakaseが用意されます

gemはこちら

rails new . --skip_rubocop でinstallをskipすることもできます。

rails new --devcontainer のときと同様に以下のようにオプションが定義されています。こちら

        class_option :skip_rubocop,        type: :boolean, default: nil,
                                           desc: "Skip RuboCop setup"

このオプションが以下の3箇所でrubocopは関係しています。

1つは.rubocop.ymlの生成 こちら

    def rubocop
      template "rubocop.yml", ".rubocop.yml"
    end

もう1つはbin/rubocopの生成 こちら

    def bin
      exclude_pattern = Regexp.union([(/rubocop/ if skip_rubocop?), (/brakeman/ if skip_brakeman?)].compact)
      directory "bin", { exclude_pattern: exclude_pattern } do |content|
        "#{shebang}\n" + content
      end
      chmod "bin", 0755 & ~File.umask, verbose: false
    end

もう1つは、CI上でrubocopのjobの生成もあります。こちら

<%- unless skip_rubocop? -%>
  lint:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: .ruby-version
          bundler-cache: true

      - name: Lint code for consistent style
        run: bin/rubocop -f github

デフォルトでGitHub ActionsのCIが用意されます。

rails new . --skip_ci でinstallをskipすることもできます。

rails new . --skip_rubocop のときと同様に以下のようにオプションが定義されています。こちら

        class_option :skip_ci,             type: :boolean, default: nil,
                                           desc: "Skip GitHub CI files"

このオプションがfalse、つまりciを生成するときはこちらのように動きます。

  1. .github/workflows フォルダ作成
  2. .github/workflows/ci.yml ファイル作成
  3. .github/dependabot.yml ファイル作成
    def cifiles
      empty_directory ".github/workflows"
      template "github/ci.yml", ".github/workflows/ci.yml"
      template "github/dependabot.yml", ".github/dependabot.yml"
    end

デフォルトでbrakemanが用意されます

rails new . --skip_brakeman でinstallをskipすることもできます。

rails new --skip_rubocop のときと同様に以下のようにオプションが定義されています。こちら

        class_option :skip_brakeman,       type: :boolean, default: nil,
                                           desc: "Skip brakeman setup"

このオプションによって、bin/brakemanコマンドの生成がされます。

    def bin
      exclude_pattern = Regexp.union([(/rubocop/ if skip_rubocop?), (/brakeman/ if skip_brakeman?)].compact)
      directory "bin", { exclude_pattern: exclude_pattern } do |content|
        "#{shebang}\n" + content
      end
      chmod "bin", 0755 & ~File.umask, verbose: false
    end

またCIでbrakemanのJobの生成もされます。こちら

<%- if options[:javascript] == "importmap" -%>
  scan_js:
    runs-on: ubuntu-latest


    steps:
      - name: Checkout code
        uses: actions/checkout@v4


      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: .ruby-version
          bundler-cache: true


      - name: Scan for security vulnerabilities in JavaScript dependencies
        run: bin/importmap audit

デフォルトのpumaのスレッド数が5から3に変更

この変更は、デフォルトのスレッド数がいくつが相応しいのかというこのissueで話題になっていました。

結局3つになったようです。

puma.rbのテンプレートにあるthreads_countが3になっていました。こちら

threads_count = ENV.fetch("RAILS_MAX_THREADS", 3)
threads threads_count, threads_count

enqueue_after_transaction_commit オプション

トランザクションの最中にエンキューするか、Commit後にエンキューするかを設定できるオプションです。

Topic.transaction do
  topic = Topic.create

  NewTopicNotificationJob.perform_later(topic)
end

上記のコードのようなコードを書いたとき、今まではTransactionがCommitされる前にJobがキューに登録され、Jobが動く可能性がありました。

enqueue_after_transaction_commitオプションを使用すると、Transaction内にJobをキューするコードを書いても、commit後にキューさせることができます。

このオプションがある場所はこちら

      # It can be set on a per job basis:
      #  - `:always` forces the job to be deferred.
      #  - `:never` forces the job to be queued immediately.
      #  - `:default` lets the queue adapter define the behavior (recommended).
      class_attribute :enqueue_after_transaction_commit, instance_accessor: false, instance_predicate: false, default: :never
  • always
  • never
  • default

が選択できます。

この選択によって、以下のように、after_transactiontrue/falseになり、TransactionのCommit後にエンキューするかどうかに影響します。こちら

      def raw_enqueue
        after_transaction = case self.class.enqueue_after_transaction_commit
        when :always
          true
        when :never
          false
        else # :default
          queue_adapter.enqueue_after_transaction_commit?
        end

        if after_transaction
          self.successfully_enqueued = true
          ActiveRecord.after_all_transactions_commit do
            self.successfully_enqueued = false
            super
          end
          self
        else
          super
        end
      end

Ruby3.3+の場合デフォルトでYJITが有効化

Configurationクラスのinitializeyjitが追加されています。こちら

  def initialize(*)
    super
    # 省略
    @yjit                                    = false
    # 省略
  end

  def load_defaults(target_version)
    # 省略
    case target_version.to_s
    when "7.2"
      load_defaults "7.1"

      self.yjit = true

      if respond_to?(:active_job)
        active_job.enqueue_after_transaction_commit = :default
      end
    # 省略
  end

load_defaults内でRailsのバージョンが7.2だったら、yjitをtrueに変更しているのが分かります。

デフォルトのDockerfileにjemallocを設定してメモリ割り当てを最適化

jamellocについてはこちらが分かりやすいかもしれません。

コードだとこの辺りです。こちら

      def dockerfile_base_packages
        # Add curl to work with the default healthcheck strategy in Kamal
        packages = ["curl"]

        # ActiveRecord databases
        packages << database.base_package unless skip_active_record?

        # ActiveStorage preview support
        packages << "libvips" unless skip_active_storage?

        # jemalloc for memory optimization
        packages << "libjemalloc2"

        packages.compact.sort
      end

packagesという配列の変数に、libjemalloc2を入れています。

それを以下のDockerfileのテンプレートで展開して、apt installされるようです。こちら

RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y <%= dockerfile_base_packages.join(" ") %> && \
    rm -rf /var/lib/apt/lists /var/cache/apt/archives

bin/setupにpuma-devの設定の提案を追加

puma-devはDockerを使わない場合の、開発環境のRailsアプリの開発用サーバーの設定を楽にするものです。

その提案がbin/setupにコメントで追加されています。こちら

  # puts "\n== Configuring puma-dev =="
  # system "ln -nfs #{APP_ROOT} ~/.puma-dev/#{APP_NAME}"
  # system "curl -Is https://#{APP_NAME}.test/up | head -n 1"

おわりに

以上で、Rails Guideに載っている7.2の新機能については、ざっと紹介できたと思います。
Railsリポジトリの中身のコードをたくさん見れて楽しい機会でした。
今後、追加されたりしたら、追記していきます!

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?