diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8544b80..8dd7ebc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -68,7 +68,7 @@ jobs: - name: Install system requirements run: | sudo apt update - sudo apt install -y postgresql-client + sudo apt install -y postgresql-client libvips42 - name: Bundle Install run: | diff --git a/.ruby-version b/.ruby-version index a0891f5..84d6c67 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.3.4 +3.4.10 diff --git a/Dockerfile b/Dockerfile index 64a860f..acf172b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ruby:3.3.4 +FROM ruby:3.4.10 ARG RAILS_MASTER_KEY ARG REDIS_URL @@ -6,6 +6,7 @@ ARG REDIS_URL RUN apt-get update \ && apt-get install -y --no-install-recommends \ postgresql-client \ + libvips42 \ && rm -rf /var/lib/apt/lists/* WORKDIR /usr/src/app diff --git a/Gemfile b/Gemfile index 9b3924d..f75148a 100644 --- a/Gemfile +++ b/Gemfile @@ -3,10 +3,18 @@ source 'https://rubygems.org' git_source(:github) {|_repo| "https://github.com/#{repo}.git" } -ruby '3.3.4' +ruby '3.4.10' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails', branch: 'main' -gem 'rails', '~> 7.2.1' +gem 'rails', '~> 8.1.3' + +# csv stopped being a Ruby default gem in 3.4+; httparty requires it internally +# without declaring it as a dependency, so it must be explicit here. +gem 'csv' + +# benchmark won't be a default gem starting in Ruby 4.0; mini_magick requires it +# internally without declaring it as a dependency. +gem 'benchmark' # Use postgresql as the database for Active Record gem 'pg', '~> 1.1' @@ -16,7 +24,7 @@ gem 'elasticsearch', '~> 8' gem 'searchkick' # Use the Puma web server [https://github.com/puma/puma] -gem 'puma', '~> 5.0' +gem 'puma', '~> 8.0' # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby] @@ -25,7 +33,13 @@ gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby] gem 'bootsnap', require: false # Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images] -gem 'image_processing', '~> 1.2' +gem 'image_processing', '~> 2.0' +# mini_magick/ruby-vips became soft dependencies of image_processing in 2.0 and must be +# added explicitly. This app's config.load_defaults 7.0 (config/application.rb) sets +# config.active_storage.variant_processor = :vips - vips is the actually-active processor, +# not mini_magick (previously present only as an unused transitive dependency of +# image_processing 1.x's gemspec, which required both backends unconditionally). +gem 'ruby-vips', '~> 2.0' # Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible gem 'rack-cors' @@ -68,13 +82,17 @@ gem 'kaminari', '~> 1.2' gem 'jbuilder', '~> 2.11' -gem 'acts-as-taggable-on', '~> 11.0.0' +# 11.0.0 hard-pins activerecord < 8.0, which makes it impossible to install +# alongside Rails 8 at all - not a discretionary bump, a forced one. 12->13 has +# no breaking changes to the tag_list/for_context API ActsAsTaggableField uses +# (see CHANGELOG.md), only dropped-Ruby-version and added-Rails-version support. +gem 'acts-as-taggable-on', '~> 13.0' gem 'administrate-field-list', '~> 0.0.6' -gem 'httparty', '~> 0.20.0' +gem 'httparty', '~> 0.24.0' -gem 'roo', '~> 2.9' +gem 'roo', '~> 3.0' gem 'namae', '~> 1.1' @@ -84,7 +102,7 @@ gem 'active_record_doctor', '~> 1.10' gem 'sidekiq', '>=7.2.2', '<8' -gem 'addressable', '~> 2.8' +gem 'addressable', '~> 2.9' gem 'importmap-rails', '~> 2.0' diff --git a/Gemfile.lock b/Gemfile.lock index a5c643d..c7357ce 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,83 +1,87 @@ GEM remote: https://rubygems.org/ specs: - actioncable (7.2.1) - actionpack (= 7.2.1) - activesupport (= 7.2.1) + action_text-trix (2.1.19) + railties + actioncable (8.1.3.1) + actionpack (= 8.1.3.1) + activesupport (= 8.1.3.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (7.2.1) - actionpack (= 7.2.1) - activejob (= 7.2.1) - activerecord (= 7.2.1) - activestorage (= 7.2.1) - activesupport (= 7.2.1) + actionmailbox (8.1.3.1) + actionpack (= 8.1.3.1) + activejob (= 8.1.3.1) + activerecord (= 8.1.3.1) + activestorage (= 8.1.3.1) + activesupport (= 8.1.3.1) mail (>= 2.8.0) - actionmailer (7.2.1) - actionpack (= 7.2.1) - actionview (= 7.2.1) - activejob (= 7.2.1) - activesupport (= 7.2.1) + actionmailer (8.1.3.1) + actionpack (= 8.1.3.1) + actionview (= 8.1.3.1) + activejob (= 8.1.3.1) + activesupport (= 8.1.3.1) mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (7.2.1) - actionview (= 7.2.1) - activesupport (= 7.2.1) + actionpack (8.1.3.1) + actionview (= 8.1.3.1) + activesupport (= 8.1.3.1) nokogiri (>= 1.8.5) - racc - rack (>= 2.2.4, < 3.2) + rack (>= 2.2.4) rack-session (>= 1.0.1) rack-test (>= 0.6.3) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) useragent (~> 0.16) - actiontext (7.2.1) - actionpack (= 7.2.1) - activerecord (= 7.2.1) - activestorage (= 7.2.1) - activesupport (= 7.2.1) + actiontext (8.1.3.1) + action_text-trix (~> 2.1.15) + actionpack (= 8.1.3.1) + activerecord (= 8.1.3.1) + activestorage (= 8.1.3.1) + activesupport (= 8.1.3.1) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.2.1) - activesupport (= 7.2.1) + actionview (8.1.3.1) + activesupport (= 8.1.3.1) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) active_record_doctor (1.14.0) activerecord (>= 4.2.0) - activejob (7.2.1) - activesupport (= 7.2.1) + activejob (8.1.3.1) + activesupport (= 8.1.3.1) globalid (>= 0.3.6) - activemodel (7.2.1) - activesupport (= 7.2.1) - activerecord (7.2.1) - activemodel (= 7.2.1) - activesupport (= 7.2.1) + activemodel (8.1.3.1) + activesupport (= 8.1.3.1) + activerecord (8.1.3.1) + activemodel (= 8.1.3.1) + activesupport (= 8.1.3.1) timeout (>= 0.4.0) - activestorage (7.2.1) - actionpack (= 7.2.1) - activejob (= 7.2.1) - activerecord (= 7.2.1) - activesupport (= 7.2.1) + activestorage (8.1.3.1) + actionpack (= 8.1.3.1) + activejob (= 8.1.3.1) + activerecord (= 8.1.3.1) + activesupport (= 8.1.3.1) marcel (~> 1.0) - activesupport (7.2.1) + activesupport (8.1.3.1) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.3.1) connection_pool (>= 2.2.5) drb i18n (>= 1.6, < 2) + json logger (>= 1.4.2) minitest (>= 5.1) securerandom (>= 0.3) tzinfo (~> 2.0, >= 2.0.5) - acts-as-taggable-on (11.0.0) - activerecord (>= 7.0, < 8.0) + uri (>= 0.13.1) + acts-as-taggable-on (13.0.0) + activerecord (>= 7.1, < 8.2) zeitwerk (>= 2.4, < 3.0) - addressable (2.8.7) - public_suffix (>= 2.0.2, < 7.0) + addressable (2.9.0) + public_suffix (>= 2.0.2, < 8.0) administrate (1.0.0) actionpack (>= 6.0, < 9.0) actionview (>= 6.0, < 9.0) @@ -92,7 +96,7 @@ GEM administrate-field-list (0.0.6) administrate rails (>= 5.0) - ast (2.4.2) + ast (2.4.3) aws-eventstream (1.4.0) aws-partitions (1.1278.0) aws-sdk-core (3.254.1) @@ -111,9 +115,10 @@ GEM aws-sigv4 (~> 1.5) aws-sigv4 (1.12.1) aws-eventstream (~> 1, >= 1.0.2) - base64 (0.2.0) - bigdecimal (3.1.8) - bootsnap (1.18.4) + base64 (0.3.0) + benchmark (0.5.0) + bigdecimal (3.1.9) + bootsnap (1.18.6) msgpack (~> 1.2) builder (3.3.0) capybara (3.40.0) @@ -125,19 +130,20 @@ GEM rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) - concurrent-ruby (1.3.4) + concurrent-ruby (1.3.8) connection_pool (2.4.1) - crass (1.0.6) + crass (1.0.7) cronex (0.15.0) tzinfo unicode (>= 0.4.4.5) - date (3.3.4) + csv (3.3.6) + date (3.5.1) debug (1.9.2) irb (~> 1.10) reline (>= 0.3.8) diff-lcs (1.5.1) - drb (2.2.1) - elastic-transport (8.5.2) + drb (2.2.3) + elastic-transport (8.5.3) faraday (< 3) multi_json elasticsearch (8.19.3) @@ -146,59 +152,57 @@ GEM ostruct elasticsearch-api (8.19.3) multi_json - erubi (1.13.0) - et-orbi (1.4.0) + erubi (1.13.1) + et-orbi (1.4.1) tzinfo - factory_bot (6.4.6) - activesupport (>= 5.0.0) - factory_bot_rails (6.4.3) - factory_bot (~> 6.4) + factory_bot (6.5.6) + activesupport (>= 6.1.0) + factory_bot_rails (6.4.4) + factory_bot (~> 6.5) railties (>= 5.0.0) faker (3.4.2) i18n (>= 1.8.11, < 2) - faraday (2.14.2) + faraday (2.14.3) faraday-net_http (>= 2.0, < 3.5) json logger faraday-net_http (3.4.4) net-http (~> 0.5) - ffi (1.17.0-aarch64-linux-gnu) - ffi (1.17.0-aarch64-linux-musl) - ffi (1.17.0-arm-linux-gnu) - ffi (1.17.0-arm-linux-musl) - ffi (1.17.0-arm64-darwin) - ffi (1.17.0-x86-linux-gnu) - ffi (1.17.0-x86-linux-musl) - ffi (1.17.0-x86_64-darwin) - ffi (1.17.0-x86_64-linux-gnu) - ffi (1.17.0-x86_64-linux-musl) - fugit (1.12.2) + ffi (1.17.4-aarch64-linux-gnu) + ffi (1.17.4-aarch64-linux-musl) + ffi (1.17.4-arm-linux-gnu) + ffi (1.17.4-arm-linux-musl) + ffi (1.17.4-arm64-darwin) + ffi (1.17.4-x86-linux-gnu) + ffi (1.17.4-x86-linux-musl) + ffi (1.17.4-x86_64-darwin) + ffi (1.17.4-x86_64-linux-gnu) + ffi (1.17.4-x86_64-linux-musl) + fugit (1.12.3) et-orbi (~> 1.4) raabro (~> 1.4) globalid (1.2.1) activesupport (>= 6.1) - hashie (5.0.0) - httparty (0.20.0) - mime-types (~> 3.0) + httparty (0.24.2) + csv + mini_mime (>= 1.0.0) multi_xml (>= 0.5.2) - i18n (1.14.5) + i18n (1.14.8) concurrent-ruby (~> 1.0) - image_processing (1.13.0) - mini_magick (>= 4.9.5, < 5) - ruby-vips (>= 2.0.17, < 3) - importmap-rails (2.0.1) + image_processing (2.0.3) + importmap-rails (2.0.3) actionpack (>= 6.0.0) activesupport (>= 6.0.0) railties (>= 6.0.0) io-console (0.7.2) - irb (1.14.0) + irb (1.14.3) rdoc (>= 4.0.0) reline (>= 0.4.2) jbuilder (2.12.0) actionview (>= 5.0.0) activesupport (>= 5.0.0) jmespath (1.6.2) - json (2.7.2) + json (2.7.6) kaminari (1.2.2) activesupport (>= 4.1.0) kaminari-actionview (= 1.2.2) @@ -211,105 +215,113 @@ GEM activerecord kaminari-core (= 1.2.2) kaminari-core (1.2.2) - language_server-protocol (3.17.0.3) - logger (1.6.0) - loofah (2.22.0) + language_server-protocol (3.17.0.6) + lint_roller (1.1.0) + logger (1.6.6) + loofah (2.25.2) crass (~> 1.0.2) nokogiri (>= 1.12.0) - mail (2.8.1) + mail (2.9.1) + logger mini_mime (>= 0.1.1) net-imap net-pop net-smtp - marcel (1.0.4) + marcel (1.2.1) matrix (0.4.3) - mime-types (3.5.2) - mime-types-data (~> 3.2015) - mime-types-data (3.2024.0820) - mini_magick (4.13.2) mini_mime (1.1.5) - minitest (5.25.1) - msgpack (1.7.2) + mini_portile2 (2.8.9) + minitest (5.25.5) + msgpack (1.8.4) multi_json (1.21.1) - multi_xml (0.7.1) - bigdecimal (~> 3.1) + multi_xml (0.9.1) + bigdecimal (>= 3.1, < 5) namae (1.2.0) racc (~> 1.7) net-http (0.9.1) uri (>= 0.11.1) - net-imap (0.4.14) + net-imap (0.6.6) date net-protocol net-pop (0.1.2) net-protocol net-protocol (0.2.2) timeout - net-smtp (0.5.0) + net-smtp (0.5.1) net-protocol - nio4r (2.7.3) - nokogiri (1.16.7-aarch64-linux) + nio4r (2.7.5) + nokogiri (1.19.4) + mini_portile2 (~> 2.8.2) + racc (~> 1.4) + nokogiri (1.19.4-aarch64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.4-aarch64-linux-musl) racc (~> 1.4) - nokogiri (1.16.7-arm-linux) + nokogiri (1.19.4-arm-linux-gnu) racc (~> 1.4) - nokogiri (1.16.7-arm64-darwin) + nokogiri (1.19.4-arm-linux-musl) racc (~> 1.4) - nokogiri (1.16.7-x86-linux) + nokogiri (1.19.4-arm64-darwin) racc (~> 1.4) - nokogiri (1.16.7-x86_64-darwin) + nokogiri (1.19.4-x86_64-darwin) racc (~> 1.4) - nokogiri (1.16.7-x86_64-linux) + nokogiri (1.19.4-x86_64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.4-x86_64-linux-musl) racc (~> 1.4) ostruct (0.6.3) parallel (1.26.3) - parser (3.3.4.2) + parser (3.3.12.0) ast (~> 2.4.1) racc - pg (1.5.7) + pg (1.5.9) + prism (1.9.0) psych (5.1.2) stringio - public_suffix (6.0.1) - puma (5.6.8) + public_suffix (7.0.5) + puma (8.0.2) nio4r (~> 2.0) raabro (1.4.0) racc (1.8.1) - rack (2.2.9) + rack (2.2.23) rack-cors (2.0.2) rack (>= 2.0.0) rack-session (1.0.2) rack (< 3) rack-test (2.1.0) rack (>= 1.3) - rackup (1.0.0) + rackup (1.0.1) rack (< 3) webrick - rails (7.2.1) - actioncable (= 7.2.1) - actionmailbox (= 7.2.1) - actionmailer (= 7.2.1) - actionpack (= 7.2.1) - actiontext (= 7.2.1) - actionview (= 7.2.1) - activejob (= 7.2.1) - activemodel (= 7.2.1) - activerecord (= 7.2.1) - activestorage (= 7.2.1) - activesupport (= 7.2.1) + rails (8.1.3.1) + actioncable (= 8.1.3.1) + actionmailbox (= 8.1.3.1) + actionmailer (= 8.1.3.1) + actionpack (= 8.1.3.1) + actiontext (= 8.1.3.1) + actionview (= 8.1.3.1) + activejob (= 8.1.3.1) + activemodel (= 8.1.3.1) + activerecord (= 8.1.3.1) + activestorage (= 8.1.3.1) + activesupport (= 8.1.3.1) bundler (>= 1.15.0) - railties (= 7.2.1) + railties (= 8.1.3.1) rails-dom-testing (2.2.0) activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.6.0) - loofah (~> 2.21) - nokogiri (~> 1.14) - railties (7.2.1) - actionpack (= 7.2.1) - activesupport (= 7.2.1) + rails-html-sanitizer (1.7.1) + loofah (~> 2.25, >= 2.25.2) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (8.1.3.1) + actionpack (= 8.1.3.1) + activesupport (= 8.1.3.1) irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) thor (~> 1.0, >= 1.2.2) + tsort (>= 0.2) zeitwerk (~> 2.6) rainbow (3.1.1) rake (13.2.1) @@ -317,61 +329,69 @@ GEM psych (>= 4.0.0) redis-client (0.23.2) connection_pool - regexp_parser (2.9.2) - reline (0.5.9) + regexp_parser (2.9.3) + reline (0.5.12) io-console (~> 0.5) - rexml (3.3.6) - strscan - roo (2.10.1) + rexml (3.4.4) + roo (3.0.0) + base64 (~> 0.2) + csv (~> 3) + logger (~> 1) nokogiri (~> 1) - rubyzip (>= 1.3.0, < 3.0.0) - rspec-core (3.13.0) + rubyzip (>= 3.0.0, < 4.0.0) + rspec-core (3.13.6) rspec-support (~> 3.13.0) - rspec-expectations (3.13.2) + rspec-expectations (3.13.5) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-mocks (3.13.1) + rspec-mocks (3.13.8) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-rails (6.1.4) - actionpack (>= 6.1) - activesupport (>= 6.1) - railties (>= 6.1) - rspec-core (~> 3.13) - rspec-expectations (~> 3.13) - rspec-mocks (~> 3.13) - rspec-support (~> 3.13) - rspec-support (3.13.1) - rubocop (1.65.1) + rspec-rails (8.0.4) + actionpack (>= 7.2) + activesupport (>= 7.2) + railties (>= 7.2) + rspec-core (>= 3.13.0, < 5.0.0) + rspec-expectations (>= 3.13.0, < 5.0.0) + rspec-mocks (>= 3.13.0, < 5.0.0) + rspec-support (>= 3.13.0, < 5.0.0) + rspec-support (3.13.7) + rubocop (1.89.0) json (~> 2.3) - language_server-protocol (>= 3.17.0) - parallel (~> 1.10) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (>= 1.10) parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 2.4, < 3.0) - rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.31.1, < 2.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.49.0, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.32.1) - parser (>= 3.3.1.0) - rubocop-factory_bot (2.26.1) - rubocop (~> 1.61) - rubocop-rails (2.26.0) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.50.0) + parser (>= 3.3.7.2) + prism (~> 1.7) + rubocop-factory_bot (2.28.0) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) + rubocop-rails (2.36.0) activesupport (>= 4.2.0) + lint_roller (~> 1.1) rack (>= 1.1) - rubocop (>= 1.52.0, < 2.0) - rubocop-ast (>= 1.31.1, < 2.0) - rubocop-rspec (3.0.4) - rubocop (~> 1.61) - rubocop-rspec_rails (2.30.0) - rubocop (~> 1.61) - rubocop-rspec (~> 3, >= 3.0.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.44.0, < 2.0) + rubocop-rspec (3.10.2) + lint_roller (~> 1.1) + regexp_parser (>= 2.0) + rubocop (~> 1.86, >= 1.86.2) + rubocop-rspec_rails (2.32.0) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) + rubocop-rspec (~> 3.5) ruby-progressbar (1.13.0) - ruby-vips (2.2.2) + ruby-vips (2.3.0) ffi (~> 1.12) logger - rubyzip (2.3.2) + rubyzip (3.4.1) sassc (2.4.0) ffi (~> 1.9) sassc-rails (2.1.2) @@ -380,10 +400,9 @@ GEM sprockets (> 3.0) sprockets-rails tilt - searchkick (5.3.1) - activemodel (>= 6.1) - hashie - securerandom (0.3.1) + searchkick (6.1.2) + activemodel (>= 7.2) + securerandom (0.3.2) selenium-devtools (0.151.0) selenium-webdriver (~> 4.2) selenium-webdriver (4.46.0) @@ -392,14 +411,14 @@ GEM rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 4.0) websocket (~> 1.0) - shoulda-matchers (6.4.0) - activesupport (>= 5.2.0) - sidekiq (7.3.8) + shoulda-matchers (8.0.1) + activesupport (>= 7.2) + sidekiq (7.3.10) base64 - connection_pool (>= 2.3.0) + connection_pool (>= 2.3.0, < 3) logger - rack (>= 2.2.4) - redis-client (>= 0.22.2) + rack (>= 2.2.4, < 3.3) + redis-client (>= 0.23.0, < 1) sidekiq-cron (2.4.0) cronex (>= 0.13.0) fugit (~> 1.8, >= 1.11.1) @@ -413,27 +432,28 @@ GEM actionpack (>= 6.1) activesupport (>= 6.1) sprockets (>= 3.0.0) - stringio (3.1.1) - strscan (3.1.0) - thor (1.3.1) + stringio (3.1.9) + thor (1.3.2) tilt (2.8.0) - timeout (0.4.1) + timeout (0.6.1) trix-rails (2.4.0) rails (> 4.1) + tsort (0.2.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode (0.4.4.5) unicode-display_width (2.5.0) uri (1.1.1) - useragent (0.16.10) - webrick (1.8.1) + useragent (0.16.11) + webrick (1.8.2) websocket (1.2.11) - websocket-driver (0.7.6) + websocket-driver (0.8.2) + base64 websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.6.17) + zeitwerk (2.6.18) PLATFORMS aarch64-linux @@ -452,37 +472,40 @@ PLATFORMS DEPENDENCIES active_record_doctor (~> 1.10) - acts-as-taggable-on (~> 11.0.0) - addressable (~> 2.8) + acts-as-taggable-on (~> 13.0) + addressable (~> 2.9) administrate administrate-field-active_storage (~> 0.4.1) administrate-field-jsonb administrate-field-list (~> 0.0.6) aws-sdk-rails (~> 5.0) aws-sdk-ses (~> 1.0) + benchmark bootsnap capybara + csv debug elasticsearch (~> 8) factory_bot_rails faker - httparty (~> 0.20.0) - image_processing (~> 1.2) + httparty (~> 0.24.0) + image_processing (~> 2.0) importmap-rails (~> 2.0) jbuilder (~> 2.11) kaminari (~> 1.2) namae (~> 1.1) pg (~> 1.1) - puma (~> 5.0) + puma (~> 8.0) rack-cors - rails (~> 7.2.1) - roo (~> 2.9) + rails (~> 8.1.3) + roo (~> 3.0) rspec-rails rubocop rubocop-factory_bot rubocop-rails rubocop-rspec rubocop-rspec_rails + ruby-vips (~> 2.0) sassc-rails searchkick selenium-devtools @@ -495,7 +518,7 @@ DEPENDENCIES tzinfo-data RUBY VERSION - ruby 3.3.4p94 + ruby 3.4.10p104 BUNDLED WITH 2.5.11 diff --git a/app/controllers/entities_controller.rb b/app/controllers/entities_controller.rb index 6ffd2c7..073cd32 100644 --- a/app/controllers/entities_controller.rb +++ b/app/controllers/entities_controller.rb @@ -69,8 +69,12 @@ def letters private def reindex - Entity.reindex if ENV['RAILS_ENV'] == 'test' - PublishedEntity.reindex if ENV['RAILS_ENV'] == 'test' + # mode: :inline forces a synchronous reindex regardless of Searchable's + # `callbacks: :async` option - searchkick's default mode resolution falls back to + # the model's callbacks option (see Searchkick::Index#reindex_records), so without + # this the search immediately below could run against a not-yet-indexed record. + Entity.reindex(mode: :inline) if ENV['RAILS_ENV'] == 'test' + PublishedEntity.reindex(mode: :inline) if ENV['RAILS_ENV'] == 'test' rescue Searchkick::Error end diff --git a/app/controllers/letters_controller.rb b/app/controllers/letters_controller.rb index 94434e9..96f1ed8 100644 --- a/app/controllers/letters_controller.rb +++ b/app/controllers/letters_controller.rb @@ -55,7 +55,7 @@ def letters_from_results(results) volume: letter.volume, recipients: letter.recipients, destinations: letter.destinations, - origins: letter.orgins, + origins: letter.origins, mentions: letter.mentions } end @@ -134,8 +134,12 @@ def set_filters # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity def reindex - Letter.reindex if ENV['RAILS_ENV'] == 'test' - PublishedLetter.reindex if ENV['RAILS_ENV'] == 'test' + # mode: :inline forces a synchronous reindex regardless of Searchable's + # `callbacks: :async` option - searchkick's default mode resolution falls back to + # the model's callbacks option (see Searchkick::Index#reindex_records), so without + # this the search immediately below could run against a not-yet-indexed record. + Letter.reindex(mode: :inline) if ENV['RAILS_ENV'] == 'test' + PublishedLetter.reindex(mode: :inline) if ENV['RAILS_ENV'] == 'test' end # Only allow a list of trusted parameters through. diff --git a/app/dashboards/entity_dashboard.rb b/app/dashboards/entity_dashboard.rb index 2c6101d..d55f8aa 100644 --- a/app/dashboards/entity_dashboard.rb +++ b/app/dashboards/entity_dashboard.rb @@ -4,6 +4,7 @@ class EntityDashboard < Administrate::BaseDashboard include ActionView::Helpers::SanitizeHelper + # ATTRIBUTE_TYPES # a hash that describes the type of each of the model's fields. # diff --git a/app/fields/acts_as_taggable_field.rb b/app/fields/acts_as_taggable_field.rb index 528a3a3..ca78488 100644 --- a/app/fields/acts_as_taggable_field.rb +++ b/app/fields/acts_as_taggable_field.rb @@ -18,6 +18,22 @@ def context options.fetch(:context, @attribute) end + # Administrate::Field::Base#read_value (called from #initialize, before + # #attribute is overridden below has a chance to matter) lazily fetches data via + # `resource.try(attribute)` - and that `attribute` call is polymorphic, so it hits + # our own override below instead of the raw `:tags` attribute key, fetching + # `resource.tag_list` (an array of tag-name strings) instead of `resource.tags` + # (real ActsAsTaggableOn::Tag records). #tags/#delimited/#truncate all expect the + # latter, so this re-derives the same logic using @attribute (the raw ivar) rather + # than the overridden #attribute method. + def read_value(data) + if options.key?(:getter) + return options[:getter].respond_to?(:call) ? options[:getter].call(self) : resource.try(options[:getter]) + end + + data.nil? ? resource.try(@attribute) : data + end + # acts_as_taggable_on exposes a `_list` virtual attribute # (e.g. `tag_list` for the `:tags` context) for reading/writing a comma-delimited # tag string - this field operates on that attribute rather than the raw `tags` diff --git a/app/jobs/load_big_sam_job.rb b/app/jobs/load_big_sam_job.rb index e033577..191f072 100644 --- a/app/jobs/load_big_sam_job.rb +++ b/app/jobs/load_big_sam_job.rb @@ -5,6 +5,7 @@ class LoadBigSamJob < ApplicationJob include ActionView::Helpers::SanitizeHelper + queue_as :default # Raised to abandon a single row (e.g. an unparseable date) without treating it as a @@ -372,7 +373,7 @@ def get_entity(label: nil, type: nil, return_nil: false) def get_person(name) entity = nil names = Namae.parse(name).first - if names&.given && names&.family + if names&.given && names.family names.family = "Van #{names.family}" if names.particle&.downcase == 'van' names.family = "von #{names.family}" if names.particle&.downcase == 'von' names = mc_or_mac?(names) diff --git a/app/models/concerns/entity_common.rb b/app/models/concerns/entity_common.rb index 86adf35..1416591 100644 --- a/app/models/concerns/entity_common.rb +++ b/app/models/concerns/entity_common.rb @@ -198,7 +198,7 @@ def short_display lines.push("Date #{date_str}") if date_str.present? end - paragraphs = lines.map {|line| "

#{line}

" }.flatten.join.strip + paragraphs = lines.map {|line| "

#{line}

" }.join.strip Loofah.fragment("
#{paragraphs}
").scrub!(:prune).to_html end @@ -299,7 +299,7 @@ def full_display rows.push("See Also#{link_list}") if links.present? end - table_rows = rows.map {|row| "#{row}" }.flatten.join.strip + table_rows = rows.map {|row| "#{row}" }.join.strip Loofah.fragment("#{table_rows}
").scrub!(:prune).to_html end diff --git a/app/models/concerns/letter_common.rb b/app/models/concerns/letter_common.rb index 8cb701d..cb0ff27 100644 --- a/app/models/concerns/letter_common.rb +++ b/app/models/concerns/letter_common.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -# rubocop:disable Metrics/BlockLength - # Common stuff for indexing module LetterCommon extend ActiveSupport::Concern @@ -9,18 +7,34 @@ module LetterCommon included do attr_accessor :tags - # attr_readonly :content + has_many :mentions, foreign_key: 'letter_id', inverse_of: :letter, dependent: :destroy + has_many :entities, -> { distinct }, through: :mentions + + has_many :letter_destinations, foreign_key: 'letter_id', inverse_of: :letter, dependent: :destroy + has_many :destinations, -> { distinct }, through: :letter_destinations, source: :entity + + has_many :letter_senders, foreign_key: 'letter_id', inverse_of: :letter, dependent: :destroy + has_many :senders, -> { distinct }, through: :letter_senders, source: :entity + + has_many :letter_origins, foreign_key: 'letter_id', inverse_of: :letter, dependent: :destroy + has_many :origins, -> { distinct }, through: :letter_origins, source: :entity + + has_many :letter_recipients, foreign_key: 'letter_id', inverse_of: :letter, dependent: :destroy + has_many :recipients, -> { distinct }, through: :letter_recipients, source: :entity - # scope :published, lambda { - # includes(:repositories) - # .references(:repositories) - # .where( - # repositories: { - # published: true - # } - # ) - # # .where('letters.date BETWEEN ? AND ?', DateTime.new(1957), DateTime.new(1965, 12).at_end_of_month) - # } + has_many :letter_repositories, foreign_key: 'letter_id', inverse_of: :letter, dependent: :destroy + has_many :repositories, -> { distinct }, through: :letter_repositories + + has_many :letter_collections, foreign_key: 'letter_id', inverse_of: :letter, dependent: :destroy + has_many :collections, -> { distinct }, through: :letter_collections + + has_many :letter_languages, foreign_key: 'letter_id', inverse_of: :letter, dependent: :destroy + has_many :languages, -> { distinct }, through: :letter_languages + + belongs_to :letter_file, inverse_of: :letters, optional: true + belongs_to :file_folder, inverse_of: :letters, optional: true + belongs_to :letter_owner, inverse_of: :letters, optional: true + belongs_to :letter_publisher, inverse_of: :letters, optional: true scope :published, -> { where(published: true) } @@ -100,5 +114,3 @@ def volume_title end end end - -# rubocop:enable Metrics/BlockLength diff --git a/app/models/entity.rb b/app/models/entity.rb index dafc7b7..37287a6 100644 --- a/app/models/entity.rb +++ b/app/models/entity.rb @@ -104,9 +104,9 @@ def concat_label "#{event_types&.map(&:titleize)&.join(',')} #{description}".strip end when 'person' - if label && (last_name.nil? && first_name.nil?) + if label && last_name.nil? && first_name.nil? names = Namae.parse label - if names&.first&.given && names&.first&.family + if names&.first&.given && names&.first.family self.first_name = names.first.given self.last_name = names.first.family end diff --git a/app/models/faq.rb b/app/models/faq.rb index 8778860..1c07a89 100644 --- a/app/models/faq.rb +++ b/app/models/faq.rb @@ -4,7 +4,7 @@ class Faq < ApplicationRecord before_save :initial_position after_save :update_position - default_scope { order('position') } + default_scope { order(:position) } private diff --git a/app/models/letter.rb b/app/models/letter.rb index f4c780d..6fa0fcb 100644 --- a/app/models/letter.rb +++ b/app/models/letter.rb @@ -8,35 +8,6 @@ class Letter < ApplicationRecord before_destroy :remove_published after_save :reindex_published - has_many :mentions, dependent: :destroy - has_many :entities, -> { distinct }, through: :mentions - - has_many :letter_destinations, dependent: :destroy - has_many :destinations, -> { distinct }, through: :letter_destinations, source: :entity - - has_many :letter_senders, dependent: :destroy - has_many :senders, -> { distinct }, through: :letter_senders, source: :entity - - has_many :letter_origins, dependent: :destroy - has_many :origins, -> { distinct }, through: :letter_origins, source: :entity - - has_many :letter_recipients, dependent: :destroy - has_many :recipients, -> { distinct }, through: :letter_recipients, source: :entity - - has_many :letter_repositories, dependent: :destroy - has_many :repositories, -> { distinct }, through: :letter_repositories - - has_many :letter_collections, dependent: :destroy - has_many :collections, -> { distinct }, through: :letter_collections - - has_many :letter_languages, dependent: :destroy - has_many :languages, -> { distinct }, through: :letter_languages - - belongs_to :letter_file, optional: true - belongs_to :file_folder, optional: true - belongs_to :letter_owner, optional: true - belongs_to :letter_publisher, optional: true - def search_data { id_path: url_path, @@ -51,7 +22,8 @@ def search_data repositories: repositories.map(&:label), languages: languages.map(&:label), published:, - volume: volume.to_s + volume: volume.to_s, + fuck: 'me' } end diff --git a/app/models/published_letter.rb b/app/models/published_letter.rb index f50edc6..d95a00b 100644 --- a/app/models/published_letter.rb +++ b/app/models/published_letter.rb @@ -6,35 +6,6 @@ class PublishedLetter < ApplicationRecord self.table_name = 'letters' - has_many :mentions, foreign_key: 'letter_id', inverse_of: :letter, dependent: :destroy - has_many :entities, -> { distinct }, through: :mentions - - has_many :letter_destinations, foreign_key: 'letter_id', inverse_of: :letter, dependent: :destroy - has_many :destinations, -> { distinct }, through: :letter_destinations, source: :entity - - has_many :letter_senders, foreign_key: 'letter_id', inverse_of: :letter, dependent: :destroy - has_many :senders, -> { distinct }, through: :letter_senders, source: :entity - - has_many :letter_origins, foreign_key: 'letter_id', inverse_of: :letter, dependent: :destroy - has_many :origins, -> { distinct }, through: :letter_origins, source: :entity - - has_many :letter_recipients, foreign_key: 'letter_id', inverse_of: :letter, dependent: :destroy - has_many :recipients, -> { distinct }, through: :letter_recipients, source: :entity - - has_many :letter_repositories, foreign_key: 'letter_id', inverse_of: :letter, dependent: :destroy - has_many :repositories, -> { distinct }, through: :letter_repositories - - has_many :letter_collections, foreign_key: 'letter_id', inverse_of: :letter, dependent: :destroy - has_many :collections, -> { distinct }, through: :letter_collections - - has_many :letter_languages, foreign_key: 'letter_id', inverse_of: :letter, dependent: :destroy - has_many :languages, -> { distinct }, through: :letter_languages - - belongs_to :letter_file, foreign_key: 'letter_id', inverse_of: :letters, optional: true - belongs_to :file_folder, foreign_key: 'letter_id', inverse_of: :letters, optional: true - belongs_to :letter_owner, foreign_key: 'letter_id', inverse_of: :letters, optional: true - belongs_to :letter_publisher, foreign_key: 'letter_id', inverse_of: :letters, optional: true - def self.default_scope where(published: true) end @@ -54,11 +25,19 @@ def search_data languages: languages.map(&:label), published:, volume: volume.to_s, - publisher: letter_publisher&.label + other_publishers: } end def should_index? published end + + def other_publishers + return if letter_publisher.nil? + + doc = Nokogiri::HTML(letter_publisher.label) + text = doc.css('i').map(&:text) + text ? text.map(&:strip).reject!(&:empty?) : [letter_publisher.label] + end end diff --git a/app/workers/resave_all_letters_worker.rb b/app/workers/resave_all_letters_worker.rb index a84ef58..5d95750 100644 --- a/app/workers/resave_all_letters_worker.rb +++ b/app/workers/resave_all_letters_worker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ResaveAllLettersWorker include Sidekiq::Job diff --git a/bin/dev b/bin/dev new file mode 100644 index 0000000..5f91c20 --- /dev/null +++ b/bin/dev @@ -0,0 +1,2 @@ +#!/usr/bin/env ruby +exec "./bin/rails", "server", *ARGV diff --git a/bin/rubocop b/bin/rubocop new file mode 100644 index 0000000..5a20504 --- /dev/null +++ b/bin/rubocop @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +require "rubygems" +require "bundler/setup" + +# Explicit RuboCop config increases performance slightly while avoiding config confusion. +ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) + +load Gem.bin_path("rubocop", "rubocop") diff --git a/config/importmap.rb b/config/importmap.rb index 7fdb198..7c2f1f5 100644 --- a/config/importmap.rb +++ b/config/importmap.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + # Pin npm packages by running ./bin/importmap pin 'application' pin 'moment' # @2.30.1 -pin "js-datepicker" # @5.18.3 +pin 'js-datepicker' # @5.18.3 diff --git a/config/initializers/new_framework_defaults_8_1.rb b/config/initializers/new_framework_defaults_8_1.rb new file mode 100644 index 0000000..8f20895 --- /dev/null +++ b/config/initializers/new_framework_defaults_8_1.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +# Be sure to restart your server when you modify this file. +# +# This file eases your Rails 8.1 framework defaults upgrade. +# +# Uncomment each configuration one by one to switch to the new default. +# Once your application is ready to run with all new defaults, you can remove +# this file and set the `config.load_defaults` to `8.1`. +# +# Read the Guide for Upgrading Ruby on Rails for more info on each option. +# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html + +### +# Skips escaping HTML entities and line separators. When set to `false`, the +# JSON renderer no longer escapes these to improve performance. +# +# Example: +# class PostsController < ApplicationController +# def index +# render json: { key: "\u2028\u2029<>&" } +# end +# end +# +# Renders `{"key":"\u2028\u2029\u003c\u003e\u0026"}` with the previous default, but `{"key":"

<>&"}` with the config +# set to `false`. +# +# Applications that want to keep the escaping behavior can set the config to `true`. +#++ +# Rails.configuration.action_controller.escape_json_responses = false + +### +# Skips escaping LINE SEPARATOR (U+2028) and PARAGRAPH SEPARATOR (U+2029) in JSON. +# +# Historically these characters were not valid inside JavaScript literal strings but that changed in ECMAScript 2019. +# As such it's no longer a concern in modern browsers: https://caniuse.com/mdn-javascript_builtins_json_json_superset. +#++ +# Rails.configuration.active_support.escape_js_separators_in_json = false + +### +# Raises an error when order dependent finder methods (e.g. `#first`, `#second`) are called without `order` values +# on the relation, and the model does not have any order columns (`implicit_order_column`, `query_constraints`, or +# `primary_key`) to fall back on. +# +# The current behavior of not raising an error has been deprecated, and this configuration option will be removed in +# Rails 8.2. +#++ +# Rails.configuration.active_record.raise_on_missing_required_finder_order_columns = true + +### +# Controls how Rails handles path relative URL redirects. +# When set to `:raise`, Rails will raise an `ActionController::Redirecting::UnsafeRedirectError` +# for relative URLs without a leading slash, which can help prevent open redirect vulnerabilities. +# +# Example: +# redirect_to "example.com" # Raises UnsafeRedirectError +# redirect_to "@attacker.com" # Raises UnsafeRedirectError +# redirect_to "/safe/path" # Works correctly +# +# Applications that want to allow these redirects can set the config to `:log` (previous default) +# to only log warnings, or `:notify` to send ActiveSupport notifications. +#++ +# Rails.configuration.action_controller.action_on_path_relative_redirect = :raise + +### +# Use a Ruby parser to track dependencies between Action View templates +#++ +# Rails.configuration.action_view.render_tracker = :ruby + +### +# When enabled, hidden inputs generated by `form_tag`, `token_tag`, `method_tag`, and the hidden parameter fields +# included in `button_to` forms will omit the `autocomplete="off"` attribute. +# +# Applications that want to keep generating the `autocomplete` attribute for those tags can set it to `false`. +#++ +# Rails.configuration.action_view.remove_hidden_field_autocomplete = true diff --git a/config/puma.rb b/config/puma.rb index 1713441..7cdef17 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -17,7 +17,10 @@ # Specifies the `port` that Puma will listen on to receive requests; default is 3000. # -port ENV.fetch('PORT', 3000) +# Puma 8 defaults to binding IPv6 (::) instead of IPv4 (0.0.0.0) when a non-loopback +# IPv6 interface is available. Pinning the host explicitly preserves this app's +# existing IPv4 behavior regardless of Puma version or the host's network interfaces. +port ENV.fetch('PORT', 3000), '0.0.0.0' # Specifies the `environment` that Puma will run in. # diff --git a/spec/factories/entities.rb b/spec/factories/entities.rb index 865c590..52e8793 100644 --- a/spec/factories/entities.rb +++ b/spec/factories/entities.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -# rubocop:disable Layout/LineLength - FactoryBot.define do factory :entity do label { Faker::Movies::HitchhikersGuideToTheGalaxy.character } @@ -170,4 +168,3 @@ end end end -# rubocop:enable Layout/LineLength diff --git a/spec/fields/acts_as_taggable_field_spec.rb b/spec/fields/acts_as_taggable_field_spec.rb index 7abbc56..d82b9bd 100644 --- a/spec/fields/acts_as_taggable_field_spec.rb +++ b/spec/fields/acts_as_taggable_field_spec.rb @@ -22,6 +22,24 @@ field = described_class.new(:tags, %w[a b], nil) expect(field.tags).to eq(%w[a b]) end + + it 'fetches real Tag records from the resource when constructed the way Administrate ' \ + 'actually builds fields (data: nil, resource: given), not tag_list name strings' do + # Administrate::Field::Base#read_value calls `resource.try(attribute)` when data is + # nil - and that `attribute` call is polymorphic, landing on this class's own + # #attribute override (returns "tag_list") rather than the raw :tags attribute this + # field was constructed with. A prior version of #read_value used that overridden + # #attribute directly, so this fetched mention.tag_list (tag-name strings) instead of + # mention.tags (real Tag records), and _show.html.erb's `tag.name` raised + # NoMethodError on a String. Only reproducible by exercising the real construction + # path (data: nil) - constructing with data already populated skips read_value + # entirely, which is why this went undetected until manual browser verification. + mention = create(:mention, tag_list: 'battle, correspondence') + field = described_class.new(:tags, nil, nil, resource: mention) + + expect(field.tags).to all(be_a(ActsAsTaggableOn::Tag)) + expect(field.tags.map(&:name)).to contain_exactly('battle', 'correspondence') + end end describe '#delimited' do diff --git a/spec/jobs/load_big_sam_job_spec.rb b/spec/jobs/load_big_sam_job_spec.rb index 4271655..87464dd 100644 --- a/spec/jobs/load_big_sam_job_spec.rb +++ b/spec/jobs/load_big_sam_job_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' require 'fileutils' @@ -260,7 +262,7 @@ def valid_row(overrides = {}) skipped = job.instance_variable_get(:@row_skipped) expect(skipped.size).to eq(1) expect(skipped.first[:code]).to eq('BAD') - expect(skipped.first[:reason]).to match(/bad date/) + expect(skipped.first[:reason]).to include('bad date') expect(Letter.find_by(legacy_pk: 955).code).to eq('GOOD') end diff --git a/spec/models/published_letter_spec.rb b/spec/models/published_letter_spec.rb index 6fc8e53..67a63a5 100644 --- a/spec/models/published_letter_spec.rb +++ b/spec/models/published_letter_spec.rb @@ -42,4 +42,15 @@ expect(published_letter.repositories.count).to eq(2) expect(published_letter.search_data[:repositories].count).to eq(1) end + + it 'represents previous publisher' do + publisher = create(:letter_publisher, + label: 'Alan, "Letters" Endgame , Voice Ed. Maurice. (1998) 37.') + letter = create(:published_letter, letter_publisher: publisher) + published_letter = described_class.find(letter.id) + expect(published_letter.search_data[:other_publishers].count).to(eq(2)) + expect(published_letter.search_data[:other_publishers]).to(include('Endgame')) + expect(published_letter.search_data[:other_publishers]).to(include('Voice')) + expect(published_letter.search_data[:other_publishers]).not_to(include(' ')) + end end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index a9bc56b..4c3b332 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -49,7 +49,7 @@ # ActiveStorage::Current is a CurrentAttributes subclass — its values are # reset by ActiveSupport::Executor after each inline job. Setting url_options # in before(:each) ensures it's present whenever a spec calls .url. - config.before { ActiveStorage::Current.url_options = { host: ENV['RAILS_HOST'] } } + config.before { ActiveStorage::Current.url_options = { host: ENV.fetch('RAILS_HOST', nil) } } # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures config.fixture_paths = [Rails.root.join('spec', 'fixtures'), Rails.root.join('spec', 'fixtures', 'files')] diff --git a/spec/requests/admin/mentions_spec.rb b/spec/requests/admin/mentions_spec.rb index bc9506c..10363c3 100644 --- a/spec/requests/admin/mentions_spec.rb +++ b/spec/requests/admin/mentions_spec.rb @@ -14,6 +14,16 @@ get admin_mentions_path, headers: admin_auth_headers expect(response).to have_http_status(:ok) end + + # Regression test: ActsAsTaggableField's #tags previously blew up on a real tagged + # mention outside of a bare smoke test, since the untagged factory default never + # exercised the field's actual data-fetching path (see acts_as_taggable_field_spec.rb). + it 'renders the tags of a tagged mention' do + create(:mention, tag_list: 'battle, correspondence') + get admin_mentions_path, headers: admin_auth_headers + expect(response).to have_http_status(:ok) + expect(response.body).to include('battle').and include('correspondence') + end end describe 'GET show' do @@ -22,6 +32,13 @@ get admin_mention_path(mention), headers: admin_auth_headers expect(response).to have_http_status(:ok) end + + it 'renders the tags of a tagged mention' do + mention = create(:mention, tag_list: 'battle, correspondence') + get admin_mention_path(mention), headers: admin_auth_headers + expect(response).to have_http_status(:ok) + expect(response.body).to include('battle').and include('correspondence') + end end describe 'GET new' do @@ -37,5 +54,12 @@ get edit_admin_mention_path(mention), headers: admin_auth_headers expect(response).to have_http_status(:ok) end + + it 'pre-populates the tag list input of a tagged mention' do + mention = create(:mention, tag_list: 'battle, correspondence') + get edit_admin_mention_path(mention), headers: admin_auth_headers + expect(response).to have_http_status(:ok) + expect(response.body).to include('value="battle, correspondence"') + end end end diff --git a/spec/requests/letters_spec.rb b/spec/requests/letters_spec.rb index 04ef8bf..81844e8 100644 --- a/spec/requests/letters_spec.rb +++ b/spec/requests/letters_spec.rb @@ -102,6 +102,17 @@ expect(json[:letters].count).to eq(9) end + # Regression test: LettersController#letters_from_results called `letter.orgins` + # (typo for `origins`) on a Searchkick::HashWrapper. Searchkick 5.x silently + # returned nil for the typo'd key; 6.x raises NoMethodError instead, surfacing a + # bug that was always there but never covered - nothing asserted on the `origins` + # key in a response before this. + it 'includes the origins of a letter' do + create(:published_letter, origins: create_list(:place_entity, 1, label: 'Reynoldstown')) + get "#{letters_url}.json" + expect(json[:letters].first[:origins]).to eq(['Reynoldstown']) + end + it 'returns letters with start_date param' do create_list(:published_letter, 10) create(:published_letter, date: DateTime.new(1961, 2, 22))