From fe50c93f3bd2f100f461c2e031e8b541053372f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Joubert?= Date: Sun, 25 Oct 2026 14:42:10 +0300 Subject: [PATCH 1/6] Require rails before defining the Railtie On Rails 8.1, requiring rails/railtie on its own raises NoMethodError (delegate_missing_to is undefined), which broke the test suite. Co-Authored-By: Claude Opus 5.5 --- lib/browser/rails.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/browser/rails.rb b/lib/browser/rails.rb index 5eee07bb..ee024d92 100644 --- a/lib/browser/rails.rb +++ b/lib/browser/rails.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "rails/railtie" +require "rails" require_relative "action_controller" require_relative "middleware/context/additions" From 068b5ff72025a3886c1b0398d5ac6f0dbfba2879 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Joubert?= Date: Sun, 25 Oct 2026 14:42:10 +0300 Subject: [PATCH 2/6] Test against Rails 7.2 and 8.0 instead of 7.0 and 7.1 Active Support < 8.1 passes the quirks_mode option that json 3 removed, so these gemfiles keep json 2. Co-Authored-By: Claude Opus 5.5 --- gemfiles/7_0.gemfile | 6 ------ gemfiles/7_1.gemfile | 6 ------ gemfiles/7_2.gemfile | 9 +++++++++ gemfiles/8_0.gemfile | 9 +++++++++ 4 files changed, 18 insertions(+), 12 deletions(-) delete mode 100644 gemfiles/7_0.gemfile delete mode 100644 gemfiles/7_1.gemfile create mode 100644 gemfiles/7_2.gemfile create mode 100644 gemfiles/8_0.gemfile diff --git a/gemfiles/7_0.gemfile b/gemfiles/7_0.gemfile deleted file mode 100644 index aeab23ae..00000000 --- a/gemfiles/7_0.gemfile +++ /dev/null @@ -1,6 +0,0 @@ -# frozen_string_literal: true - -source "https://rubygems.org" -gemspec path: ".." - -gem "rails", "~> 7.0.0" diff --git a/gemfiles/7_1.gemfile b/gemfiles/7_1.gemfile deleted file mode 100644 index b17e019b..00000000 --- a/gemfiles/7_1.gemfile +++ /dev/null @@ -1,6 +0,0 @@ -# frozen_string_literal: true - -source "https://rubygems.org" -gemspec path: ".." - -gem "rails", "~> 7.1.0" diff --git a/gemfiles/7_2.gemfile b/gemfiles/7_2.gemfile new file mode 100644 index 00000000..2309d348 --- /dev/null +++ b/gemfiles/7_2.gemfile @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +source "https://rubygems.org" +gemspec path: ".." + +gem "rails", "~> 7.2.0" + +# json 3 dropped the quirks_mode option that Active Support < 8.1 passes +gem "json", "< 3" diff --git a/gemfiles/8_0.gemfile b/gemfiles/8_0.gemfile new file mode 100644 index 00000000..068f3f79 --- /dev/null +++ b/gemfiles/8_0.gemfile @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +source "https://rubygems.org" +gemspec path: ".." + +gem "rails", "~> 8.0.0" + +# json 3 dropped the quirks_mode option that Active Support < 8.1 passes +gem "json", "< 3" From 0ee4a517d61ff7d91760ddcf613632a6c4fd1a80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Joubert?= Date: Sun, 25 Oct 2026 14:42:11 +0300 Subject: [PATCH 3/6] CI: test Ruby 3.2 to 4.0 on pull requests pull_request_target checks out main, so pull requests ran main's code instead of their own. Pin ubuntu-24.04, which still has Ruby 3.2 builds, and let setup-ruby cache the bundle. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ruby-tests.yml | 42 +++++++++----------------------- Rakefile | 2 +- 2 files changed, 13 insertions(+), 31 deletions(-) diff --git a/.github/workflows/ruby-tests.yml b/.github/workflows/ruby-tests.yml index 61c80675..410a3e35 100644 --- a/.github/workflows/ruby-tests.yml +++ b/.github/workflows/ruby-tests.yml @@ -2,7 +2,7 @@ name: ruby-tests on: - pull_request_target: + pull_request: push: branches: - main @@ -12,46 +12,28 @@ on: jobs: build: name: Tests with Ruby ${{ matrix.ruby }} and ${{ matrix.gemfile }} - runs-on: "ubuntu-latest" - if: | - github.actor == 'dependabot[bot]' && github.event_name == 'pull_request_target' || - github.actor != 'dependabot[bot]' + # ubuntu-latest is moving to 26.04, which has no Ruby 3.2 builds. + runs-on: "ubuntu-24.04" strategy: fail-fast: false matrix: - ruby: ["3.3", "3.4"] + ruby: ["3.2", "3.3", "3.4", "4.0"] gemfile: - Gemfile - - gemfiles/7_1.gemfile - - gemfiles/7_0.gemfile + - gemfiles/8_0.gemfile + - gemfiles/7_2.gemfile - steps: - - uses: actions/checkout@v4 + env: + BUNDLE_GEMFILE: ${{ matrix.gemfile }} - - uses: actions/cache@v4 - with: - path: vendor/bundle - key: > - ${{ runner.os }}-${{ matrix.ruby }}-gems-${{ - hashFiles(matrix.gemfile) }} + steps: + - uses: actions/checkout@v7 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} - - - name: Install gem dependencies - env: - BUNDLE_GEMFILE: ${{ matrix.gemfile }} - run: | - gem install bundler - bundle config path vendor/bundle - bundle update --jobs 4 --retry 3 + bundler-cache: true - name: Run Tests - env: - PGHOST: localhost - PGUSER: postgres - BUNDLE_GEMFILE: ${{ matrix.gemfile }} - run: | - bundle exec rake + run: bundle exec rake diff --git a/Rakefile b/Rakefile index e8861b73..57a66d9e 100644 --- a/Rakefile +++ b/Rakefile @@ -21,7 +21,7 @@ task :rubocop do --display-style-guide --display-cop-names --extra-details - --auto-correct + --autocorrect ] end end From d2c82560763045d87bce377c984c54061e9e063f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Joubert?= Date: Sun, 25 Oct 2026 14:42:11 +0300 Subject: [PATCH 4/6] Fix offenses reported by RuboCop 1.92 Co-Authored-By: Claude Opus 5.5 --- lib/browser/aliases.rb | 1 + lib/browser/device.rb | 9 ++++----- test/browser_test.rb | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/browser/aliases.rb b/lib/browser/aliases.rb index 33abe7a5..4fb2296c 100644 --- a/lib/browser/aliases.rb +++ b/lib/browser/aliases.rb @@ -24,6 +24,7 @@ module Aliases def self.included(target) target.class_eval do extend Forwardable + def_delegators :platform, *PLATFORM_ALIASES def_delegators :device, *DEVICE_ALIASES end diff --git a/lib/browser/device.rb b/lib/browser/device.rb index e64d3bf0..62b29784 100644 --- a/lib/browser/device.rb +++ b/lib/browser/device.rb @@ -199,14 +199,13 @@ def samsung? end # Regex taken from http://detectmobilebrowsers.com - # rubocop:disable Layout/LineLength + # rubocop:disable-next Layout/LineLength private def detect_mobile? psp? || - /zunewp7|crios/i.match?(ua) || - %r{(android|bb\d+|meego).+mobile|avantgo|bada/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino}i.match(ua) || - %r{1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-}i.match?(ua[0..3]) + /zunewp7|crios/i.match?(ua) || + %r{(android|bb\d+|meego).+mobile|avantgo|bada/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino}i.match(ua) || + %r{1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-}i.match?(ua[0..3]) end - # rubocop:enable Layout/LineLength private def platform @platform ||= Platform.new(ua) diff --git a/test/browser_test.rb b/test/browser_test.rb index 9f89d58d..8889446f 100644 --- a/test/browser_test.rb +++ b/test/browser_test.rb @@ -137,7 +137,7 @@ class BrowserTest < Minitest::Test test "removes duplicate items" do browser = Browser.new(Browser["SAFARI"]) - assert_equal ["safari"], (browser.meta.select {|item| item == "safari" }) + assert_equal(["safari"], browser.meta.select {|item| item == "safari" }) end test "detects meta aliased as to_a" do From 3c9e2a6275a0bbf93a05b5f02570c1616b4f15cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Joubert?= Date: Sun, 25 Oct 2026 18:43:41 +0300 Subject: [PATCH 5/6] Require Ruby 3.3 and stop testing Rails 7.2 Both reached end of life. Without Ruby 3.2 the workflow can run on ubuntu-latest again. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ruby-tests.yml | 6 ++---- CHANGELOG.md | 4 ++++ browser.gemspec | 2 +- gemfiles/7_2.gemfile | 9 --------- 4 files changed, 7 insertions(+), 14 deletions(-) delete mode 100644 gemfiles/7_2.gemfile diff --git a/.github/workflows/ruby-tests.yml b/.github/workflows/ruby-tests.yml index 410a3e35..3d45b801 100644 --- a/.github/workflows/ruby-tests.yml +++ b/.github/workflows/ruby-tests.yml @@ -12,16 +12,14 @@ on: jobs: build: name: Tests with Ruby ${{ matrix.ruby }} and ${{ matrix.gemfile }} - # ubuntu-latest is moving to 26.04, which has no Ruby 3.2 builds. - runs-on: "ubuntu-24.04" + runs-on: "ubuntu-latest" strategy: fail-fast: false matrix: - ruby: ["3.2", "3.3", "3.4", "4.0"] + ruby: ["3.3", "3.4", "4.0"] gemfile: - Gemfile - gemfiles/8_0.gemfile - - gemfiles/7_2.gemfile env: BUNDLE_GEMFILE: ${{ matrix.gemfile }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ea6b8e7..4f22a982 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Unreleased + +- Bump up minimum required ruby version to 3.3.0. + ## 6.2.0 - Add `Browser::Base#epiphany?`. diff --git a/browser.gemspec b/browser.gemspec index f77f233e..e7efb67e 100644 --- a/browser.gemspec +++ b/browser.gemspec @@ -12,7 +12,7 @@ Gem::Specification.new do |s| s.summary = "Do some browser detection with Ruby." s.description = s.summary s.license = "MIT" - s.required_ruby_version = ">= 3.2.0" + s.required_ruby_version = ">= 3.3.0" s.metadata["rubygems_mfa_required"] = "true" s.metadata["changelog_uri"] = "https://github.com/fnando/browser/blob/main/CHANGELOG.md" diff --git a/gemfiles/7_2.gemfile b/gemfiles/7_2.gemfile deleted file mode 100644 index 2309d348..00000000 --- a/gemfiles/7_2.gemfile +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -source "https://rubygems.org" -gemspec path: ".." - -gem "rails", "~> 7.2.0" - -# json 3 dropped the quirks_mode option that Active Support < 8.1 passes -gem "json", "< 3" From 667c511440e08fc1e6e0b54efbce63f4dd014de0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Joubert?= Date: Sun, 25 Oct 2026 18:43:41 +0300 Subject: [PATCH 6/6] Target Ruby 3.3 in RuboCop The offenses this reveals are autocorrected: regexp literals are frozen since Ruby 3.0, Set is autoloaded since 3.2, and blocks can be forwarded anonymously since 3.1. Co-Authored-By: Claude Opus 5.5 --- .rubocop.yml | 2 +- lib/browser/browser.rb | 1 - lib/browser/device/samsung.rb | 2 +- lib/browser/middleware.rb | 4 ++-- lib/browser/platform/ios.rb | 4 ++-- test/test_helper.rb | 4 ++-- 6 files changed, 8 insertions(+), 9 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 13ca8f16..2b8f76e2 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -3,7 +3,7 @@ inherit_gem: rubocop-fnando: .rubocop.yml AllCops: - TargetRubyVersion: 2.5 + TargetRubyVersion: 3.3 NewCops: enable Exclude: - bin/**/* diff --git a/lib/browser/browser.rb b/lib/browser/browser.rb index 1ea4d442..5826afc0 100644 --- a/lib/browser/browser.rb +++ b/lib/browser/browser.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "set" require "yaml" require "pathname" diff --git a/lib/browser/device/samsung.rb b/lib/browser/device/samsung.rb index e96aca46..8b02ca32 100644 --- a/lib/browser/device/samsung.rb +++ b/lib/browser/device/samsung.rb @@ -3,7 +3,7 @@ module Browser class Device class Samsung < Base - REGEX = /\(Linux.*?; Android.*?; (SAMSUNG )?(SM-[A-Z0-9]+).*?\)/i.freeze + REGEX = /\(Linux.*?; Android.*?; (SAMSUNG )?(SM-[A-Z0-9]+).*?\)/i def self.names @names ||= YAML.load_file(Browser.root.join("samsung.yml").to_s) diff --git a/lib/browser/middleware.rb b/lib/browser/middleware.rb index bb735156..df7ff932 100644 --- a/lib/browser/middleware.rb +++ b/lib/browser/middleware.rb @@ -7,10 +7,10 @@ module Browser class Middleware # Detect the most common assets. ASSETS_REGEX = - /\.(css|png|jpe?g|gif|js|svg|ico|flv|mov|m4v|ogg|swf)\z/i.freeze + /\.(css|png|jpe?g|gif|js|svg|ico|flv|mov|m4v|ogg|swf)\z/i # Detect the ACCEPT header. IE8 send */*. - ACCEPT_REGEX = %r{(text/html|\*/\*)}.freeze + ACCEPT_REGEX = %r{(text/html|\*/\*)} def initialize(app, &block) raise ArgumentError, "Browser::Middleware requires a block" unless block diff --git a/lib/browser/platform/ios.rb b/lib/browser/platform/ios.rb index 1fbff304..a17d0f0f 100644 --- a/lib/browser/platform/ios.rb +++ b/lib/browser/platform/ios.rb @@ -3,9 +3,9 @@ module Browser class Platform class IOS < Base - MATCHER = /(iPhone|iPad|iPod|CriOS)/.freeze + MATCHER = /(iPhone|iPad|iPod|CriOS)/ VERSION_MATCHER = - /OS (?\d+)_(?\d+)_?(?\d+)?/.freeze + /OS (?\d+)_(?\d+)_?(?\d+)?/ def version matches = VERSION_MATCHER.match(ua) diff --git a/test/test_helper.rb b/test/test_helper.rb index e6307ef5..47dc6aef 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -20,9 +20,9 @@ class Test Browser::Bot.instance_variable_set(:@detect_empty_ua, false) end - def assert_deprecated(message, file, line, &block) + def assert_deprecated(message, file, line, &) message = "#{message} (called from #{file}:#{line})" - _out, err = capture_io(&block) + _out, err = capture_io(&) assert_includes err, message end end