From f122635caf4d720598e33f522f9ca81b258d0449 Mon Sep 17 00:00:00 2001 From: Jay Varner Date: Mon, 10 Aug 2026 16:14:14 -0400 Subject: [PATCH 01/10] Upgrade administrate to 1.0.0 (#14) * Clear administrate 1.0 upgrade blockers Vendors an in-house ActsAsTaggableField replacing the unmaintained administrate-field-acts_as_taggable gem (last released 2021, hard-pins administrate < 1.0.0), preserving the same selectize-based tag UI. Bumps administrate-field-jsonb to 0.4.8, which allows administrate < 2.0 instead of < 1.0.0. Also fixes two unrelated pre-existing bugs found while adding test coverage: reversed accessible_action? arguments in the media show and mentions edit views, and a flat-vs-nested iteration bug in the mentions form that raised NoMethodError on new/edit. Media's show page also called a nonexistent Medium#url; it now prefers the attached ActiveStorage image, falling back to the link column. Co-Authored-By: Claude Sonnet 5 * Upgrade administrate to 1.0.0 Restores sprockets-rails/sassc-rails as direct dependencies - administrate 1.0 dropped them transitively (it now bundles its own precompiled assets), but the app's own asset pipeline (app/assets/config/manifest.js) relies on them directly, and administrate-field-jsonb's stylesheet chain needs real Sass compilation. Drops the now-dead `//= require selectize` manifest line; administrate 1.0 bundles its own copy inside its single built JS file. Also fixes the admin system-test harness: it authenticated by embedding credentials in the visited URL (this API-only app has no session-based admin login), which worked fine until Turbo - newly bundled in 1.0 - started calling history.replaceState on every page load. Chrome rejects replaceState targets carrying userinfo, and since Administrate's JS is one bundled IIFE, that uncaught exception aborted everything after it, including selectize's own setup. Real admin users never hit this (their location.href never carries credentials - Chrome only uses them for the auth handshake), but it broke the tests. Switched to Selenium's CDP-based driver.register(username:, password:) so system specs authenticate without touching the URL. Verified manually in a real browser in addition to the full suite (322 examples, 0 failures). Co-Authored-By: Claude Sonnet 5 --------- Co-authored-by: Claude Sonnet 5 --- Gemfile | 11 +++- Gemfile.lock | 34 +++++------ app/assets/config/manifest.js | 4 +- .../javascripts/acts_as_taggable_field.js | 20 +++++++ .../stylesheets/acts_as_taggable_field.css | 9 +++ app/dashboards/mention_dashboard.rb | 2 +- app/fields/acts_as_taggable_field.rb | 56 +++++++++++++++++ app/views/admin/media/show.html.erb | 5 +- app/views/admin/mentions/_form.html.erb | 10 ++-- app/views/admin/mentions/edit.html.erb | 2 +- .../acts_as_taggable_field/_form.html.erb | 6 ++ .../acts_as_taggable_field/_index.html.erb | 1 + .../acts_as_taggable_field/_show.html.erb | 5 ++ spec/fields/acts_as_taggable_field_spec.rb | 60 +++++++++++++++++++ spec/requests/admin/about_pages_spec.rb | 4 +- spec/requests/admin/media_spec.rb | 7 --- spec/requests/admin/mentions_spec.rb | 12 ---- spec/support/capybara.rb | 11 ++++ spec/system/admin/letters_spec.rb | 13 ++-- 19 files changed, 209 insertions(+), 63 deletions(-) create mode 100644 app/assets/javascripts/acts_as_taggable_field.js create mode 100644 app/assets/stylesheets/acts_as_taggable_field.css create mode 100644 app/fields/acts_as_taggable_field.rb create mode 100644 app/views/fields/acts_as_taggable_field/_form.html.erb create mode 100644 app/views/fields/acts_as_taggable_field/_index.html.erb create mode 100644 app/views/fields/acts_as_taggable_field/_show.html.erb create mode 100644 spec/fields/acts_as_taggable_field_spec.rb diff --git a/Gemfile b/Gemfile index b667cf6..d293c44 100644 --- a/Gemfile +++ b/Gemfile @@ -48,6 +48,12 @@ gem 'administrate' gem 'administrate-field-jsonb' gem 'trix-rails', require: 'trix' +# administrate < 1.0 pulled these in transitively via sassc-rails; administrate 1.0 +# bundles its own assets and dropped that dependency, but the app's own asset +# pipeline (app/assets/config/manifest.js) still relies on Sprockets/Sass directly. +gem 'sprockets-rails' +gem 'sassc-rails' + group :development, :test do # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem gem 'debug', platforms: %i[mri mingw x64_mingw] @@ -67,6 +73,9 @@ group :test do # chromedriver binary itself (Selenium Manager) - no separate webdriver gem needed. gem 'capybara' gem 'selenium-webdriver' + # CDP support, needed for Selenium's driver.register(username:, password:) - used to + # authenticate admin system specs without embedding credentials in the visited URL. + gem 'selenium-devtools' end gem 'pundit', '~> 2.2' @@ -79,8 +88,6 @@ gem 'rspec_api_documentation', '~> 6.1' gem 'acts-as-taggable-on', '~> 11.0.0' -gem 'administrate-field-acts_as_taggable' - gem 'administrate-field-list', '~> 0.0.6' gem 'httparty', '~> 0.20.0' diff --git a/Gemfile.lock b/Gemfile.lock index 609e992..2fb177b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -78,22 +78,16 @@ GEM zeitwerk (>= 2.4, < 3.0) addressable (2.8.7) public_suffix (>= 2.0.2, < 7.0) - administrate (0.20.1) - actionpack (>= 6.0, < 8.0) - actionview (>= 6.0, < 8.0) - activerecord (>= 6.0, < 8.0) - jquery-rails (~> 4.6.0) + administrate (1.0.0) + actionpack (>= 6.0, < 9.0) + actionview (>= 6.0, < 9.0) + activerecord (>= 6.0, < 9.0) kaminari (~> 1.2.2) - sassc-rails (~> 2.1) - selectize-rails (~> 0.6) administrate-field-active_storage (0.4.2) administrate (>= 0.2.2) rails (>= 7.0) - administrate-field-acts_as_taggable (0.0.4) - acts-as-taggable-on (>= 6.0) - administrate (< 1.0.0) - administrate-field-jsonb (0.4.6) - administrate (< 1.0.0) + administrate-field-jsonb (0.4.8) + administrate (< 2.0) rails (>= 4.2) administrate-field-list (0.0.6) administrate @@ -205,10 +199,6 @@ GEM actionview (>= 5.0.0) activesupport (>= 5.0.0) jmespath (1.6.2) - jquery-rails (4.6.0) - rails-dom-testing (>= 1, < 3) - railties (>= 4.2.0) - thor (>= 0.14, < 2.0) json (2.7.2) kaminari (1.2.2) activesupport (>= 4.1.0) @@ -408,7 +398,8 @@ GEM activemodel (>= 6.1) hashie securerandom (0.3.1) - selectize-rails (0.12.6) + selenium-devtools (0.151.0) + selenium-webdriver (~> 4.2) selenium-webdriver (4.46.0) base64 (~> 0.2) logger (~> 1.4) @@ -428,8 +419,9 @@ GEM fugit (~> 1.8, >= 1.11.1) globalid (>= 1.0.1) sidekiq (>= 6.5.0) - sprockets (4.2.1) + sprockets (4.2.2) concurrent-ruby (~> 1.0) + logger rack (>= 2.2.4, < 4) sprockets-rails (3.5.2) actionpack (>= 6.1) @@ -439,7 +431,7 @@ GEM strscan (3.1.0) systemu (2.6.5) thor (1.3.1) - tilt (2.4.0) + tilt (2.8.0) timeout (0.4.1) trix-rails (2.4.0) rails (> 4.1) @@ -481,7 +473,6 @@ DEPENDENCIES addressable (~> 2.8) administrate administrate-field-active_storage (~> 0.4.1) - administrate-field-acts_as_taggable administrate-field-jsonb administrate-field-list (~> 0.0.6) aws-sdk-rails (~> 5.0) @@ -512,11 +503,14 @@ DEPENDENCIES rubocop-rails rubocop-rspec rubocop-rspec_rails + sassc-rails searchkick + selenium-devtools selenium-webdriver shoulda-matchers sidekiq (>= 7.2.2, < 8) sidekiq-cron (~> 2.4.0) + sprockets-rails trix-rails tzinfo-data uuid (~> 2.3) diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js index 41bd56d..7dc11fe 100644 --- a/app/assets/config/manifest.js +++ b/app/assets/config/manifest.js @@ -4,10 +4,8 @@ //= link_tree ../../../vendor/javascript .js //= link administrate-field-jsonb/application.css //= link administrate-field-jsonb/application.js -//= link administrate-field-taggable/application.js -//= link administrate-field-taggable/application.css +//= link acts_as_taggable_field.js //= link administrate-field-active_storage/application.css -//= require selectize //= require trix // application.js // application.css diff --git a/app/assets/javascripts/acts_as_taggable_field.js b/app/assets/javascripts/acts_as_taggable_field.js new file mode 100644 index 0000000..bf7e5cb --- /dev/null +++ b/app/assets/javascripts/acts_as_taggable_field.js @@ -0,0 +1,20 @@ +// Selectize init for ActsAsTaggableField's text input, ported from the +// unmaintained administrate-field-acts_as_taggable gem's application.js. +$(function() { + $(".field-unit--acts-as-taggable-field .field-unit__field > input").each(function(elem) { + var $this = $(this); + var opts = $this.data('tag-options'); + + $this.selectize({ + delimiter: ", ", + persist: false, + options: opts, + create: function(input) { + return { + value: input, + text: input + }; + } + }); + }) +}); diff --git a/app/assets/stylesheets/acts_as_taggable_field.css b/app/assets/stylesheets/acts_as_taggable_field.css new file mode 100644 index 0000000..6867434 --- /dev/null +++ b/app/assets/stylesheets/acts_as_taggable_field.css @@ -0,0 +1,9 @@ +.taggable_field .tag { + text-align: center; + font-size: 0.85em; + margin: 0 3px 3px 0; + padding: 2px 6px; + background: #f2f2f2; + color: #303030; + border: 0 solid #d0d0d0; +} diff --git a/app/dashboards/mention_dashboard.rb b/app/dashboards/mention_dashboard.rb index b3283c4..abae96a 100644 --- a/app/dashboards/mention_dashboard.rb +++ b/app/dashboards/mention_dashboard.rb @@ -18,7 +18,7 @@ class MentionDashboard < Administrate::BaseDashboard id: Field::Number, created_at: Field::DateTime, updated_at: Field::DateTime, - tags: Field::ActsAsTaggable + tags: ActsAsTaggableField }.freeze # COLLECTION_ATTRIBUTES diff --git a/app/fields/acts_as_taggable_field.rb b/app/fields/acts_as_taggable_field.rb new file mode 100644 index 0000000..528a3a3 --- /dev/null +++ b/app/fields/acts_as_taggable_field.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require 'administrate/field/text' +require 'administrate/engine' + +# In-house replacement for the unmaintained administrate-field-acts_as_taggable gem +# (last released 2021, hard-pins `administrate < 1.0.0`). Behavior is ported as-is +# from that gem's lib/administrate/field/acts_as_taggable.rb. +class ActsAsTaggableField < Administrate::Field::Text + class Engine < ::Rails::Engine + if defined?(Administrate::Engine) + Administrate::Engine.add_javascript 'acts_as_taggable_field' + Administrate::Engine.add_stylesheet 'acts_as_taggable_field' + end + end + + def context + options.fetch(:context, @attribute) + 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` + # association. + def attribute + "#{super.to_s.singularize}_list" + end + + def self.permitted_attribute(attr, **_opts) + "#{attr.to_s.singularize}_list" + end + + def tags + data + end + + def name + context.to_s + end + + def delimited + tags.join(', ').to_s + end + + def truncate + delimited[0...truncation_length] + end + + def tag_options + return [] unless defined?(ActsAsTaggableOn::Tag) + + ActsAsTaggableOn::Tag.for_context(context).order(:name).map do |t| + { text: t.name, value: t.name } + end + end +end diff --git a/app/views/admin/media/show.html.erb b/app/views/admin/media/show.html.erb index 17a3432..a2435a1 100644 --- a/app/views/admin/media/show.html.erb +++ b/app/views/admin/media/show.html.erb @@ -25,12 +25,13 @@ as well as a link to its edit page. t("administrate.actions.edit_resource", name: page.page_title), [:edit, namespace, page.resource], class: "button", - ) if accessible_action?(:edit) && accessible_action?(:edit, page.resource) %> + ) if accessible_action?(page.resource, :edit) %>
- <%= page.resource.alt_text %> + <% media_url = page.resource.image.attached? ? url_for(page.resource.image) : page.resource.link %> + <%= page.resource.alt_text %>
<%= page.resource.caption %>
<% page.attributes.each do |title, attributes| %> diff --git a/app/views/admin/mentions/_form.html.erb b/app/views/admin/mentions/_form.html.erb index 5d798ea..3ee5e47 100644 --- a/app/views/admin/mentions/_form.html.erb +++ b/app/views/admin/mentions/_form.html.erb @@ -33,10 +33,12 @@ and renders all form fields for a resource's editable attributes. <% end %> - <% page.attributes(controller.action_name).each do |attribute| -%> -
- <%= render_field attribute, f: f %> -
+ <% page.attributes(controller.action_name).each do |title, attributes| -%> + <% attributes.each do |attribute| %> +
+ <%= render_field attribute, f: f %> +
+ <% end -%> <% end -%>
diff --git a/app/views/admin/mentions/edit.html.erb b/app/views/admin/mentions/edit.html.erb index 62e2be2..f443901 100644 --- a/app/views/admin/mentions/edit.html.erb +++ b/app/views/admin/mentions/edit.html.erb @@ -34,7 +34,7 @@ It displays a header, and renders the `_form` partial to do the heavy lifting. t("administrate.actions.show_resource", name: page.page_title), [namespace, page.resource], class: "button", - ) if accessible_action?(:show) && accessible_action?(:show, page.resource) %> + ) if accessible_action?(page.resource, :show) %>
diff --git a/app/views/fields/acts_as_taggable_field/_form.html.erb b/app/views/fields/acts_as_taggable_field/_form.html.erb new file mode 100644 index 0000000..f84fb00 --- /dev/null +++ b/app/views/fields/acts_as_taggable_field/_form.html.erb @@ -0,0 +1,6 @@ +
+ <%= f.label field.attribute %> +
+
+ <%= f.text_field field.attribute, value: field.delimited, data: { tag_options: field.tag_options } %> +
diff --git a/app/views/fields/acts_as_taggable_field/_index.html.erb b/app/views/fields/acts_as_taggable_field/_index.html.erb new file mode 100644 index 0000000..d683ccc --- /dev/null +++ b/app/views/fields/acts_as_taggable_field/_index.html.erb @@ -0,0 +1 @@ +<%= field.truncate %> diff --git a/app/views/fields/acts_as_taggable_field/_show.html.erb b/app/views/fields/acts_as_taggable_field/_show.html.erb new file mode 100644 index 0000000..0651443 --- /dev/null +++ b/app/views/fields/acts_as_taggable_field/_show.html.erb @@ -0,0 +1,5 @@ +
+ <% field.tags.each do |tag| %> + <%= tag.name -%> + <% end %> +
diff --git a/spec/fields/acts_as_taggable_field_spec.rb b/spec/fields/acts_as_taggable_field_spec.rb new file mode 100644 index 0000000..7abbc56 --- /dev/null +++ b/spec/fields/acts_as_taggable_field_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'administrate/field/text' + +RSpec.describe ActsAsTaggableField do + describe '#attribute' do + it 'singularizes the underlying attribute and appends _list' do + field = described_class.new(:tags, [], nil) + expect(field.attribute).to eq('tag_list') + end + end + + describe '.permitted_attribute' do + it 'singularizes the attribute and appends _list' do + expect(described_class.permitted_attribute(:tags)).to eq('tag_list') + end + end + + describe '#tags' do + it 'returns the raw data' do + field = described_class.new(:tags, %w[a b], nil) + expect(field.tags).to eq(%w[a b]) + end + end + + describe '#delimited' do + it 'joins tag names with a comma and space' do + mention = create(:mention, tag_list: 'foo, bar') + field = described_class.new(:tags, mention.tags, nil) + + expect(field.delimited).to eq('foo, bar') + end + + it 'returns an empty string when there are no tags' do + field = described_class.new(:tags, [], nil) + expect(field.delimited).to eq('') + end + end + + describe '#truncate' do + it 'truncates the delimited tag list to the default length' do + mention = create(:mention, tag_list: 'a' * 60) + field = described_class.new(:tags, mention.tags, nil) + + expect(field.truncate.length).to eq(50) + end + end + + describe '#tag_options' do + it 'lists tags used in the tags context, sorted by name' do + create(:mention, tag_list: 'zebra, apple') + field = described_class.new(:tags, [], nil) + + expect(field.tag_options).to eq( + [{ text: 'apple', value: 'apple' }, { text: 'zebra', value: 'zebra' }] + ) + end + end +end diff --git a/spec/requests/admin/about_pages_spec.rb b/spec/requests/admin/about_pages_spec.rb index 716a164..d3159e9 100644 --- a/spec/requests/admin/about_pages_spec.rb +++ b/spec/requests/admin/about_pages_spec.rb @@ -46,10 +46,10 @@ expect(response.body).to include(%(href="#{new_admin_about_page_path}")) end - it 'still renders the "destroy" link on the show page' do + it 'still renders the "destroy" button on the show page' do about_page = create(:about_page) get admin_about_page_path(about_page), headers: admin_auth_headers - expect(response.body).to include(%(href="#{admin_about_page_path(about_page)}")) + expect(response.body).to include(%(action="#{admin_about_page_path(about_page)}")) .and include('button--danger') end end diff --git a/spec/requests/admin/media_spec.rb b/spec/requests/admin/media_spec.rb index 164afbd..96a15c7 100644 --- a/spec/requests/admin/media_spec.rb +++ b/spec/requests/admin/media_spec.rb @@ -18,13 +18,6 @@ describe 'GET show' do it 'renders successfully when authenticated' do - pending( - 'known bug: admin/media/show.html.erb:28 calls accessible_action?(:edit) with one ' \ - 'argument before the correct two-argument call - Administrate::ApplicationHelper#' \ - 'accessible_action? requires (target, action_name), so this raises ArgumentError ' \ - "before the &&'d correct call, or page.resource.url (line 33, Medium has no #url " \ - 'method), is ever reached' - ) medium = create(:medium) get admin_medium_path(medium), headers: admin_auth_headers expect(response).to have_http_status(:ok) diff --git a/spec/requests/admin/mentions_spec.rb b/spec/requests/admin/mentions_spec.rb index d071911..bc9506c 100644 --- a/spec/requests/admin/mentions_spec.rb +++ b/spec/requests/admin/mentions_spec.rb @@ -26,12 +26,6 @@ describe 'GET new' do it 'renders successfully when authenticated' do - pending( - 'known bug: admin/mentions/_form.html.erb:36 iterates page.attributes(...) as a ' \ - 'flat list of fields, but it actually yields [title, attributes] pairs (see ' \ - 'admin/letters/_form.html.erb for the correct nested form), so `attribute.html_class` ' \ - 'raises NoMethodError on the Array' - ) get new_admin_mention_path, headers: admin_auth_headers expect(response).to have_http_status(:ok) end @@ -39,12 +33,6 @@ describe 'GET edit' do it 'renders successfully when authenticated' do - pending( - 'known bug: admin/mentions/edit.html.erb:37 calls ' \ - 'accessible_action?(:show) with one argument before the correct two-argument call - ' \ - 'Administrate::ApplicationHelper#accessible_action? requires (target, action_name), so ' \ - 'this raises ArgumentError before the &&\'d correct call is ever reached' - ) mention = create(:mention) get edit_admin_mention_path(mention), headers: admin_auth_headers expect(response).to have_http_status(:ok) diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb index dfddde6..0346da3 100644 --- a/spec/support/capybara.rb +++ b/spec/support/capybara.rb @@ -24,5 +24,16 @@ config.before(:each, :js, type: :system) do driven_by Capybara.javascript_driver + + # Admin::ApplicationController gates every admin route behind HTTP Basic Auth, and + # this app has no session-based login to drive instead. Visiting with credentials + # embedded in the URL (http://user:pass@host/path) used to work, but Administrate + # 1.0 bundles Turbo, which calls history.replaceState on every page load - Chrome + # rejects replaceState targets that carry userinfo, and since Administrate's JS is + # one bundled IIFE, that uncaught exception aborts everything after it, including + # selectize's own setup. Real admin users never hit this (their location.href never + # carries credentials - Chrome only uses them for the auth handshake). Registering + # credentials via CDP instead keeps the visited URL clean and sidesteps it entirely. + Capybara.current_session.driver.browser.register(username: 'test', password: 'test') end end diff --git a/spec/system/admin/letters_spec.rb b/spec/system/admin/letters_spec.rb index 03b77fd..1b865bb 100644 --- a/spec/system/admin/letters_spec.rb +++ b/spec/system/admin/letters_spec.rb @@ -9,11 +9,6 @@ # break (jquery-ujs -> Turbo/Stimulus, changed asset bundling in 1.0) - and the only # way to actually verify it is a real browser, since no request spec executes JS. RSpec.describe 'Admin::Letters entity picker', :js do - def visit_with_basic_auth(path) - server = Capybara.current_session.server - visit "http://test:test@#{server.host}:#{server.port}#{path}" - end - def selectize "jQuery('.field-unit--has-many-through-field select')[0].selectize" end @@ -21,7 +16,7 @@ def selectize it 'initializes jQuery and the selectize widget on the entities field' do letter = create(:letter) - visit_with_basic_auth(edit_admin_letter_path(letter)) + visit edit_admin_letter_path(letter) expect(page.execute_script('return window.jQuery !== undefined')).to be true expect(page).to have_css('.selectize-control') @@ -33,7 +28,7 @@ def selectize entity = create(:person_entity, label: 'Zzz System Spec Findable Entity') Entity.reindex - visit_with_basic_auth(edit_admin_letter_path(letter)) + visit edit_admin_letter_path(letter) expect(page).to have_css('.selectize-control') page.execute_script("#{selectize}.addItem('#{entity.id}')") @@ -49,7 +44,7 @@ def selectize letter.save! Entity.reindex - visit_with_basic_auth(edit_admin_letter_path(letter)) + visit edit_admin_letter_path(letter) expect(page).to have_css(".item[data-value='#{entity.id}']") find(".item[data-value='#{entity.id}']").click @@ -68,7 +63,7 @@ def selectize letter.save! Entity.reindex - visit_with_basic_auth(edit_admin_letter_path(letter)) + visit edit_admin_letter_path(letter) expect(page).to have_css(".item[data-value='#{entity.id}']") find(".item[data-value='#{entity.id}']").click From b6473ef36b755ed0162ba286ab0d7c16b9262cdb Mon Sep 17 00:00:00 2001 From: Jay Varner Date: Tue, 11 Aug 2026 09:15:30 -0400 Subject: [PATCH 02/10] Clean out unused code and fill in gap in test coverage. --- Gemfile | 22 +-- Gemfile.lock | 21 --- .../admin/application_controller.rb | 2 - app/policies/application_policy.rb | 53 ------- config/initializers/rspec_api_documention.rb | 15 -- spec/acceptance_helper.rb | 12 -- spec/jobs/import_data_job_spec.rb | 7 - spec/models/big_sam_spec.rb | 7 - spec/models/collection_spec.rb | 7 - spec/models/event_type_spec.rb | 7 - spec/models/file_folder_spec.rb | 7 - spec/models/letter_collection_spec.rb | 7 - spec/models/letter_destination_spec.rb | 7 - spec/models/letter_file_spec.rb | 7 - spec/models/letter_origin_spec.rb | 7 - spec/models/letter_owner_spec.rb | 7 - spec/models/letter_publisher_spec.rb | 7 - spec/models/letter_recipient_spec.rb | 7 - spec/models/letter_sender_spec.rb | 7 - spec/models/mention_spec.rb | 7 - spec/requests/repositories_spec.rb | 130 ++++------------ .../markdow_example.mustache | 85 ----------- .../api_blueprint_index.mustache | 80 ---------- .../html_example.mustache | 140 ------------------ .../html_index.mustache | 33 ----- .../markdown_example.mustache | 85 ----------- .../markdown_index.mustache | 15 -- .../slate_example.mustache | 84 ----------- .../slate_index.mustache | 8 - .../textile_example.mustache | 80 ---------- .../textile_index.mustache | 15 -- 31 files changed, 31 insertions(+), 947 deletions(-) delete mode 100644 app/policies/application_policy.rb delete mode 100644 config/initializers/rspec_api_documention.rb delete mode 100644 spec/acceptance_helper.rb delete mode 100644 spec/jobs/import_data_job_spec.rb delete mode 100644 spec/models/big_sam_spec.rb delete mode 100644 spec/models/collection_spec.rb delete mode 100644 spec/models/event_type_spec.rb delete mode 100644 spec/models/file_folder_spec.rb delete mode 100644 spec/models/letter_collection_spec.rb delete mode 100644 spec/models/letter_destination_spec.rb delete mode 100644 spec/models/letter_file_spec.rb delete mode 100644 spec/models/letter_origin_spec.rb delete mode 100644 spec/models/letter_owner_spec.rb delete mode 100644 spec/models/letter_publisher_spec.rb delete mode 100644 spec/models/letter_recipient_spec.rb delete mode 100644 spec/models/letter_sender_spec.rb delete mode 100644 spec/models/mention_spec.rb delete mode 100644 templates/rspec_api_documentation/markdow_example.mustache delete mode 100644 templates/rspec_api_documentation/rspec_api_documentation/api_blueprint_index.mustache delete mode 100644 templates/rspec_api_documentation/rspec_api_documentation/html_example.mustache delete mode 100644 templates/rspec_api_documentation/rspec_api_documentation/html_index.mustache delete mode 100644 templates/rspec_api_documentation/rspec_api_documentation/markdown_example.mustache delete mode 100644 templates/rspec_api_documentation/rspec_api_documentation/markdown_index.mustache delete mode 100644 templates/rspec_api_documentation/rspec_api_documentation/slate_example.mustache delete mode 100644 templates/rspec_api_documentation/rspec_api_documentation/slate_index.mustache delete mode 100644 templates/rspec_api_documentation/rspec_api_documentation/textile_example.mustache delete mode 100644 templates/rspec_api_documentation/rspec_api_documentation/textile_index.mustache diff --git a/Gemfile b/Gemfile index d293c44..9b3924d 100644 --- a/Gemfile +++ b/Gemfile @@ -8,8 +8,6 @@ ruby '3.3.4' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails', branch: 'main' gem 'rails', '~> 7.2.1' -gem 'csv' - # Use postgresql as the database for Active Record gem 'pg', '~> 1.1' @@ -20,18 +18,6 @@ gem 'searchkick' # Use the Puma web server [https://github.com/puma/puma] gem 'puma', '~> 5.0' -# Build JSON APIs with ease [https://github.com/rails/jbuilder] -# gem 'jbuilder' - -# Use Redis adapter to run Action Cable in production -# gem 'redis', '~> 4.0' - -# Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis] -# gem 'kredis' - -# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword] -# gem 'bcrypt', '~> 3.1.7' - # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby] @@ -51,8 +37,8 @@ gem 'trix-rails', require: 'trix' # administrate < 1.0 pulled these in transitively via sassc-rails; administrate 1.0 # bundles its own assets and dropped that dependency, but the app's own asset # pipeline (app/assets/config/manifest.js) still relies on Sprockets/Sass directly. -gem 'sprockets-rails' gem 'sassc-rails' +gem 'sprockets-rails' group :development, :test do # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem @@ -78,14 +64,10 @@ group :test do gem 'selenium-devtools' end -gem 'pundit', '~> 2.2' - gem 'kaminari', '~> 1.2' gem 'jbuilder', '~> 2.11' -gem 'rspec_api_documentation', '~> 6.1' - gem 'acts-as-taggable-on', '~> 11.0.0' gem 'administrate-field-list', '~> 0.0.6' @@ -102,8 +84,6 @@ gem 'active_record_doctor', '~> 1.10' gem 'sidekiq', '>=7.2.2', '<8' -gem 'uuid', '~> 2.3' - gem 'addressable', '~> 2.8' gem 'importmap-rails', '~> 2.0' diff --git a/Gemfile.lock b/Gemfile.lock index 2fb177b..a5c643d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -131,7 +131,6 @@ GEM cronex (0.15.0) tzinfo unicode (>= 0.4.4.5) - csv (3.3.0) date (3.3.4) debug (1.9.2) irb (~> 1.10) @@ -217,8 +216,6 @@ GEM loofah (2.22.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) - macaddr (1.7.2) - systemu (~> 2.6.5) mail (2.8.1) mini_mime (>= 0.1.1) net-imap @@ -236,7 +233,6 @@ GEM multi_json (1.21.1) multi_xml (0.7.1) bigdecimal (~> 3.1) - mustache (1.1.1) namae (1.2.0) racc (~> 1.7) net-http (0.9.1) @@ -274,8 +270,6 @@ GEM public_suffix (6.0.1) puma (5.6.8) nio4r (~> 2.0) - pundit (2.4.0) - activesupport (>= 3.0.0) raabro (1.4.0) racc (1.8.1) rack (2.2.9) @@ -331,10 +325,6 @@ GEM roo (2.10.1) nokogiri (~> 1) rubyzip (>= 1.3.0, < 3.0.0) - rspec (3.13.0) - rspec-core (~> 3.13.0) - rspec-expectations (~> 3.13.0) - rspec-mocks (~> 3.13.0) rspec-core (3.13.0) rspec-support (~> 3.13.0) rspec-expectations (3.13.2) @@ -352,10 +342,6 @@ GEM rspec-mocks (~> 3.13) rspec-support (~> 3.13) rspec-support (3.13.1) - rspec_api_documentation (6.1.0) - activesupport (>= 3.0.0) - mustache (~> 1.0, >= 0.99.4) - rspec (~> 3.0) rubocop (1.65.1) json (~> 2.3) language_server-protocol (>= 3.17.0) @@ -429,7 +415,6 @@ GEM sprockets (>= 3.0.0) stringio (3.1.1) strscan (3.1.0) - systemu (2.6.5) thor (1.3.1) tilt (2.8.0) timeout (0.4.1) @@ -441,8 +426,6 @@ GEM unicode-display_width (2.5.0) uri (1.1.1) useragent (0.16.10) - uuid (2.3.9) - macaddr (~> 1.0) webrick (1.8.1) websocket (1.2.11) websocket-driver (0.7.6) @@ -479,7 +462,6 @@ DEPENDENCIES aws-sdk-ses (~> 1.0) bootsnap capybara - csv debug elasticsearch (~> 8) factory_bot_rails @@ -492,12 +474,10 @@ DEPENDENCIES namae (~> 1.1) pg (~> 1.1) puma (~> 5.0) - pundit (~> 2.2) rack-cors rails (~> 7.2.1) roo (~> 2.9) rspec-rails - rspec_api_documentation (~> 6.1) rubocop rubocop-factory_bot rubocop-rails @@ -513,7 +493,6 @@ DEPENDENCIES sprockets-rails trix-rails tzinfo-data - uuid (~> 2.3) RUBY VERSION ruby 3.3.4p94 diff --git a/app/controllers/admin/application_controller.rb b/app/controllers/admin/application_controller.rb index 40afcd6..cba7fbe 100644 --- a/app/controllers/admin/application_controller.rb +++ b/app/controllers/admin/application_controller.rb @@ -8,8 +8,6 @@ # you're free to overwrite the RESTful controller actions. module Admin class ApplicationController < Administrate::ApplicationController - # include Administrate::Punditize - before_action :authenticate_admin before_action do diff --git a/app/policies/application_policy.rb b/app/policies/application_policy.rb deleted file mode 100644 index d91c019..0000000 --- a/app/policies/application_policy.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true - -class ApplicationPolicy - attr_reader :user, :record - - def initialize(user, record) - @user = user - @record = record - end - - def index? - true - end - - def show? - true - end - - def create? - true - end - - def new? - true - end - - def update? - true - end - - def edit? - true - end - - def destroy? - true - end - - class Scope - def initialize(user, scope) - @user = user - @scope = scope - end - - def resolve - raise NotImplementedError, "You must define #resolve in #{self.class}" - end - - private - - attr_reader :user, :scope - end -end diff --git a/config/initializers/rspec_api_documention.rb b/config/initializers/rspec_api_documention.rb deleted file mode 100644 index da707a1..0000000 --- a/config/initializers/rspec_api_documention.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -# Monkey patch to properly display json :( -# https://github.com/zipmark/rspec_api_documentation/issues/456 -module RspecApiDocumentation - class RackTestClient < ClientBase - def response_body - if last_response.headers['Content-Type'].include?('json') - last_response.body.encode('utf-8') - else - '[binary data]' - end - end - end -end diff --git a/spec/acceptance_helper.rb b/spec/acceptance_helper.rb deleted file mode 100644 index 53dba02..0000000 --- a/spec/acceptance_helper.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' -require 'rspec_api_documentation/dsl' - -RspecApiDocumentation.configure do |config| - config.format = %i[html markdown] - config.template_path = Rails.root.join('templates', 'rspec_api_documentation') - config.keep_source_order = true - config.docs_dir = Rails.public_path.join('docs') - config.api_name = 'Beckett Data API Documentation' -end diff --git a/spec/jobs/import_data_job_spec.rb b/spec/jobs/import_data_job_spec.rb deleted file mode 100644 index b858132..0000000 --- a/spec/jobs/import_data_job_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe ImportDataJob do - pending "add some examples to (or delete) #{__FILE__}" -end diff --git a/spec/models/big_sam_spec.rb b/spec/models/big_sam_spec.rb deleted file mode 100644 index 8cb2214..0000000 --- a/spec/models/big_sam_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe BigSam do - pending "add some examples to (or delete) #{__FILE__}" -end diff --git a/spec/models/collection_spec.rb b/spec/models/collection_spec.rb deleted file mode 100644 index c6bfd2c..0000000 --- a/spec/models/collection_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Collection do - pending "add some examples to (or delete) #{__FILE__}" -end diff --git a/spec/models/event_type_spec.rb b/spec/models/event_type_spec.rb deleted file mode 100644 index 042fcf6..0000000 --- a/spec/models/event_type_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe EventType do - pending "add some examples to (or delete) #{__FILE__}" -end diff --git a/spec/models/file_folder_spec.rb b/spec/models/file_folder_spec.rb deleted file mode 100644 index 751e941..0000000 --- a/spec/models/file_folder_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe FileFolder do - pending "add some examples to (or delete) #{__FILE__}" -end diff --git a/spec/models/letter_collection_spec.rb b/spec/models/letter_collection_spec.rb deleted file mode 100644 index 33ff10f..0000000 --- a/spec/models/letter_collection_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe LetterCollection do - pending "add some examples to (or delete) #{__FILE__}" -end diff --git a/spec/models/letter_destination_spec.rb b/spec/models/letter_destination_spec.rb deleted file mode 100644 index 61fd41f..0000000 --- a/spec/models/letter_destination_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe LetterDestination do - pending "add some examples to (or delete) #{__FILE__}" -end diff --git a/spec/models/letter_file_spec.rb b/spec/models/letter_file_spec.rb deleted file mode 100644 index 5db75d9..0000000 --- a/spec/models/letter_file_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe LetterFile do - pending "add some examples to (or delete) #{__FILE__}" -end diff --git a/spec/models/letter_origin_spec.rb b/spec/models/letter_origin_spec.rb deleted file mode 100644 index 69658c6..0000000 --- a/spec/models/letter_origin_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe LetterOrigin do - pending "add some examples to (or delete) #{__FILE__}" -end diff --git a/spec/models/letter_owner_spec.rb b/spec/models/letter_owner_spec.rb deleted file mode 100644 index bbab35d..0000000 --- a/spec/models/letter_owner_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe LetterOwner do - pending "add some examples to (or delete) #{__FILE__}" -end diff --git a/spec/models/letter_publisher_spec.rb b/spec/models/letter_publisher_spec.rb deleted file mode 100644 index b136a6c..0000000 --- a/spec/models/letter_publisher_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe LetterPublisher do - pending "add some examples to (or delete) #{__FILE__}" -end diff --git a/spec/models/letter_recipient_spec.rb b/spec/models/letter_recipient_spec.rb deleted file mode 100644 index 356335d..0000000 --- a/spec/models/letter_recipient_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe LetterRecipient do - pending "add some examples to (or delete) #{__FILE__}" -end diff --git a/spec/models/letter_sender_spec.rb b/spec/models/letter_sender_spec.rb deleted file mode 100644 index 29a3ed8..0000000 --- a/spec/models/letter_sender_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe LetterSender do - pending "add some examples to (or delete) #{__FILE__}" -end diff --git a/spec/models/mention_spec.rb b/spec/models/mention_spec.rb deleted file mode 100644 index a302cff..0000000 --- a/spec/models/mention_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Mention do - pending "add some examples to (or delete) #{__FILE__}" -end diff --git a/spec/requests/repositories_spec.rb b/spec/requests/repositories_spec.rb index 4f3cd24..a3db5f5 100644 --- a/spec/requests/repositories_spec.rb +++ b/spec/requests/repositories_spec.rb @@ -2,128 +2,58 @@ require 'rails_helper' -# This spec was generated by rspec-rails when you ran the scaffold generator. -# It demonstrates how one might use RSpec to test the controller code that -# was generated by Rails when you ran the scaffold generator. -# -# It assumes that the implementation code is generated by the rails scaffold -# generator. If you are using any extension libraries to generate different -# controller code, this generated spec may or may not pass. -# -# It only uses APIs available in rails and/or rspec-rails. There are a number -# of tools you can use to make these specs even more expressive, but we're -# sticking to rails and rspec-rails APIs to keep things simple and stable. - RSpec.describe '/repositories' do - # This should return the minimal set of attributes required to create a valid - # Repository. As you add validations to Repository, be sure to - # adjust the attributes here as well. - let(:valid_attributes) { - skip('Add a hash of attributes valid for your model') - } - - let(:invalid_attributes) { - skip('Add a hash of attributes invalid for your model') - } - - # This should return the minimal set of values that should be in the headers - # in order to pass any filters (e.g. authentication) defined in - # RepositoriesController, or in your router and rack - # middleware. Be sure to keep this updated too. - let(:valid_headers) { - {} - } - describe 'GET /index' do - it 'renders a successful response' do - Repository.create! valid_attributes - get repositories_url, headers: valid_headers, as: :json + it 'renders a successful response with all repositories' do + create_list(:repository, 3) + get repositories_url, as: :json expect(response).to be_successful + expect(json.count).to eq(3) end end describe 'GET /show' do - it 'renders a successful response' do - repository = Repository.create! valid_attributes + it 'renders the requested repository' do + repository = create(:repository) get repository_url(repository), as: :json expect(response).to be_successful + expect(json[:id]).to eq(repository.id) + expect(json[:label]).to eq(repository.label) end - end - - describe 'POST /create' do - context 'with valid parameters' do - it 'creates a new Repository' do - expect { - post repositories_url, - params: { repository: valid_attributes }, headers: valid_headers, as: :json - }.to change(Repository, :count).by(1) - end - it 'renders a JSON response with the new repository' do - post repositories_url, - params: { repository: valid_attributes }, headers: valid_headers, as: :json - expect(response).to have_http_status(:created) - expect(response.content_type).to match(a_string_including('application/json')) - end + it 'renders a not_found response for an unknown id' do + get repository_url(SecureRandom.uuid), as: :json + expect(response).to have_http_status(:not_found) end + end - context 'with invalid parameters' do - it 'does not create a new Repository' do - expect { - post repositories_url, - params: { repository: invalid_attributes }, as: :json - }.not_to change(Repository, :count) - end - - it 'renders a JSON response with errors for the new repository' do - post repositories_url, - params: { repository: invalid_attributes }, headers: valid_headers, as: :json - expect(response).to have_http_status(:unprocessable_entity) - expect(response.content_type).to match(a_string_including('application/json')) - end + # RepositoriesController only overrides index/show - create/update/destroy fall + # through to ApplicationController's defaults, which all `head :not_implemented`. + describe 'POST /create' do + it 'does not create a new Repository' do + expect { + post repositories_url, params: { repository: { label: 'New Repository' } }, as: :json + }.not_to change(Repository, :count) + expect(response).to have_http_status(:not_implemented) end end describe 'PATCH /update' do - context 'with valid parameters' do - let(:new_attributes) { - skip('Add a hash of attributes valid for your model') - } - - it 'updates the requested repository' do - repository = Repository.create! valid_attributes - patch repository_url(repository), - params: { repository: new_attributes }, headers: valid_headers, as: :json - repository.reload - skip('Add assertions for updated state') - end - - it 'renders a JSON response with the repository' do - repository = Repository.create! valid_attributes - patch repository_url(repository), - params: { repository: new_attributes }, headers: valid_headers, as: :json - expect(response).to have_http_status(:ok) - expect(response.content_type).to match(a_string_including('application/json')) - end - end - - context 'with invalid parameters' do - it 'renders a JSON response with errors for the repository' do - repository = Repository.create! valid_attributes - patch repository_url(repository), - params: { repository: invalid_attributes }, headers: valid_headers, as: :json - expect(response).to have_http_status(:unprocessable_entity) - expect(response.content_type).to match(a_string_including('application/json')) - end + it 'does not update the requested repository' do + repository = create(:repository, label: 'Original') + patch repository_url(repository), params: { repository: { label: 'Changed' } }, as: :json + expect(response).to have_http_status(:not_implemented) + expect(repository.reload.label).to eq('Original') end end describe 'DELETE /destroy' do - it 'destroys the requested repository' do - repository = Repository.create! valid_attributes + it 'does not destroy the requested repository' do + repository = create(:repository) expect { - delete repository_url(repository), headers: valid_headers, as: :json - }.to change(Repository, :count).by(-1) + delete repository_url(repository), as: :json + }.not_to change(Repository, :count) + expect(response).to have_http_status(:not_implemented) end end end diff --git a/templates/rspec_api_documentation/markdow_example.mustache b/templates/rspec_api_documentation/markdow_example.mustache deleted file mode 100644 index 16263d0..0000000 --- a/templates/rspec_api_documentation/markdow_example.mustache +++ /dev/null @@ -1,85 +0,0 @@ -# {{ resource_name }} API -{{# resource_explanation }} - -{{{ resource_explanation }}} -{{/ resource_explanation }} - -## {{ description }} - -### {{ http_method }} {{ route }} -{{# explanation }} - -{{ explanation }} -{{/ explanation }} -{{# has_parameters? }} - -### Parameters - -| Name | Description | Required | Scope | Default | -|------|-------------|----------|-------|---------| -{{# parameters }} -| {{ name }} | {{ description }} | {{ required }} | {{ scope }} | | {{ default }} -{{/ parameters }} - -{{/ has_parameters? }} -{{# has_response_fields? }} - -### Response Fields - -| Name | Description | Scope | -|------|-------------|-------| -{{# response_fields }} -| {{ name }} | {{ description }} | {{ scope }} | -{{/ response_fields }} - -{{/ has_response_fields? }} -{{# requests }} -### Request - -#### Headers - -
{{ request_headers_text }}
- -#### Route - -
{{ request_method }} {{ request_path }}
-{{# request_query_parameters_text }} - -#### Query Parameters - -
{{ request_query_parameters_text }}
-{{/ request_query_parameters_text }} -{{# request_body }} - -#### Body - -
{{{ request_body }}}
-{{/ request_body }} -{{# curl }} - -#### cURL - -
{{ curl }}
-{{/ curl }} - -{{# response_status }} -### Response - -#### Headers - -
{{ response_headers_text }}
- -#### Status - -
{{ response_status }} {{ response_status_text}}
- -{{# response_body }} -#### Body - -~~~ -{{{ response_body }}} -~~~ - -{{/ response_body }} -{{/ response_status }} -{{/ requests }} \ No newline at end of file diff --git a/templates/rspec_api_documentation/rspec_api_documentation/api_blueprint_index.mustache b/templates/rspec_api_documentation/rspec_api_documentation/api_blueprint_index.mustache deleted file mode 100644 index 865f24a..0000000 --- a/templates/rspec_api_documentation/rspec_api_documentation/api_blueprint_index.mustache +++ /dev/null @@ -1,80 +0,0 @@ -FORMAT: 1A -# {{ api_name }} -{{# sections }} - -# Group {{ resource_name }} -{{# resource_explanation }} - -{{{ resource_explanation }}} -{{/ resource_explanation }} -{{# description }} - -{{ description }} -{{/ description }} -{{# routes }} - -## {{ route_name }} [{{ route }}] -{{# description }} - -description: {{ description }} -{{/ description }} -{{# explanation }} - -explanation: {{ explanation }} -{{/ explanation }} -{{# has_parameters? }} - -+ Parameters -{{# parameters }} - + {{ name }}{{# example }}: {{ example }}{{/ example }}{{# properties_description }} ({{ properties_description }}){{/ properties_description }}{{# description }} - {{ description }}{{/ description }} -{{/ parameters }} -{{/ has_parameters? }} -{{# has_attributes? }} - -+ Attributes (object) -{{# attributes }} - + {{ name }}{{# example }}: {{ example }}{{/ example }}{{# properties_description }} ({{ properties_description }}){{/ properties_description }}{{# description }} - {{ description }}{{/ description }} -{{/ attributes }} -{{/ has_attributes? }} -{{# http_methods }} - -### {{ description }} [{{ http_method }}] -{{# examples }} -{{# requests }} -{{# has_request? }} - -+ Request {{ description }}{{# request_content_type }} ({{ request_content_type }}){{/ request_content_type }} -{{/ has_request? }} -{{# request_headers_text }} - - + Headers - - {{{ request_headers_text }}} -{{/ request_headers_text }} -{{# request_body }} - - + Body - - {{{ request_body }}} -{{/ request_body }} -{{# has_response? }} - -+ Response {{ response_status }} ({{ response_content_type }}) -{{/ has_response? }} -{{# response_headers_text }} - - + Headers - - {{{ response_headers_text }}} -{{/ response_headers_text }} -{{# response_body }} - - + Body - - {{{ response_body }}} -{{/ response_body }} -{{/ requests }} -{{/ examples }} -{{/ http_methods }} -{{/ routes }} -{{/ sections }} diff --git a/templates/rspec_api_documentation/rspec_api_documentation/html_example.mustache b/templates/rspec_api_documentation/rspec_api_documentation/html_example.mustache deleted file mode 100644 index 47474e0..0000000 --- a/templates/rspec_api_documentation/rspec_api_documentation/html_example.mustache +++ /dev/null @@ -1,140 +0,0 @@ - - - - {{resource_name}} API - - - - -
-

{{resource_name}} API

- {{# resource_explanation }} - -

{{{ resource_explanation }}}

- {{/ resource_explanation }} - -
-

{{ description }}

-

{{ http_method }} {{ route }}

- {{# explanation }} -

- {{{ explanation }}} -

- {{/ explanation }} - - {{# has_parameters? }} -

Parameters

- - - - - - - - - - {{# parameters }} - - - {{# scope }} - {{ scope }}[{{ name }}] - {{/ scope }} - {{^ scope }} - {{ name }} - {{/ scope }} - - - - - {{/ parameters }} - -
NameDescriptionDefault
- {{ description }} - - {{ default }} -
- {{/ has_parameters? }} - - {{# has_response_fields? }} -

Response Fields

- - - - - - - - - - - {{# response_fields }} - - - - - - - {{/ response_fields }} - -
NameDefaultDescriptionNot Null
- {{# scope }} - {{ scope }}[{{ name }}] - {{/ scope }} - {{^ scope }} - {{ name }} - {{/ scope }} - - {{ default }} - - {{ description }} - - {{ not_null }} -
- {{/ has_response_fields? }} - - {{# requests }} -

Request

- - {{# request_headers_text }} -

Headers

-
{{ request_headers_text }}
- {{/ request_headers_text }} - -

Route

-
{{ request_method }} {{ request_path }}
- - {{# request_query_parameters_text }} -

Query Parameters

-
{{ request_query_parameters_text }}
- {{/ request_query_parameters_text }} - - {{# request_body }} -

Body

-
{{{ request_body }}}
- {{/ request_body }} - - {{# curl }} -

cURL

-
{{ curl }}
- {{/ curl }} - - {{# response_status }} -

Response

- {{# response_headers_text }} -

Headers

-
{{ response_headers_text }}
- {{/ response_headers_text }} -

Status

-
{{ response_status }} {{ response_status_text}}
- {{# response_body }} -

Body

-
{{ response_body }}
- {{/ response_body }} - {{/ response_status }} - {{/ requests }} -
-
- - diff --git a/templates/rspec_api_documentation/rspec_api_documentation/html_index.mustache b/templates/rspec_api_documentation/rspec_api_documentation/html_index.mustache deleted file mode 100644 index aaedde2..0000000 --- a/templates/rspec_api_documentation/rspec_api_documentation/html_index.mustache +++ /dev/null @@ -1,33 +0,0 @@ - - - - {{ api_name }} - - - - -
-

{{ api_name }}

- {{{ api_explanation }}} - {{# sections }} -
-

{{ resource_name }}

- {{# resource_explanation }} - -

{{{ resource_explanation }}}

- {{/ resource_explanation }} - - -
- {{/ sections }} -
- - diff --git a/templates/rspec_api_documentation/rspec_api_documentation/markdown_example.mustache b/templates/rspec_api_documentation/rspec_api_documentation/markdown_example.mustache deleted file mode 100644 index 474ab56..0000000 --- a/templates/rspec_api_documentation/rspec_api_documentation/markdown_example.mustache +++ /dev/null @@ -1,85 +0,0 @@ -# {{ resource_name }} API -{{# resource_explanation }} - -{{{ resource_explanation }}} -{{/ resource_explanation }} - -## {{ description }} - -### {{ http_method }} {{ route }} -{{# explanation }} - -{{ explanation }} -{{/ explanation }} -{{# has_parameters? }} - -### Parameters - -| Name | Description | Required | Default | -|------|-------------|----------|---------| -{{# parameters }} -| {{ name }} | {{ description }} | {{ required }} | {{ default }} -{{/ parameters }} - -{{/ has_parameters? }} -{{# has_response_fields? }} - -### Response Fields - -| Name | Default | Description | Not Null | -|------|---------|-------------|----------| -{{# response_fields }} -| {{ name }} | {{ default }} | {{ description }} | {{ not_null }} | -{{/ response_fields }} - -{{/ has_response_fields? }} -{{# requests }} -### Request - -#### Headers - -
{{ request_headers_text }}
- -#### Route - -
{{ request_method }} {{ request_path }}
-{{# request_query_parameters_text }} - -#### Query Parameters - -
{{ request_query_parameters_text }}
-{{/ request_query_parameters_text }} -{{# request_body }} - -#### Body - -
{{{ request_body }}}
-{{/ request_body }} -{{# curl }} - -#### cURL - -
{{ curl }}
-{{/ curl }} - -{{# response_status }} -### Response - -#### Headers - -
{{ response_headers_text }}
- -#### Status - -
{{ response_status }} {{ response_status_text}}
- -{{# response_body }} -#### Body - -~~~ -{{{ response_body }}} -~~~ - -{{/ response_body }} -{{/ response_status }} -{{/ requests }} diff --git a/templates/rspec_api_documentation/rspec_api_documentation/markdown_index.mustache b/templates/rspec_api_documentation/rspec_api_documentation/markdown_index.mustache deleted file mode 100644 index c88754d..0000000 --- a/templates/rspec_api_documentation/rspec_api_documentation/markdown_index.mustache +++ /dev/null @@ -1,15 +0,0 @@ -# {{ api_name }} -{{{ api_explanation }}} - -{{# sections }} -## {{ resource_name }} -{{# resource_explanation }} - -{{{ resource_explanation }}} -{{/ resource_explanation }} - -{{# examples }} -* [{{ description }}]({{ dirname }}/{{ filename }}) -{{/ examples }} - -{{/ sections }} diff --git a/templates/rspec_api_documentation/rspec_api_documentation/slate_example.mustache b/templates/rspec_api_documentation/rspec_api_documentation/slate_example.mustache deleted file mode 100644 index 01fafb9..0000000 --- a/templates/rspec_api_documentation/rspec_api_documentation/slate_example.mustache +++ /dev/null @@ -1,84 +0,0 @@ -## {{ description }} - -{{# explanation }} -{{{ explanation }}} -{{/ explanation }} - -### Request - -#### Endpoint - -{{# requests}} -```plaintext -{{ request_method }} {{ request_path }} -{{ request_headers_text }} -``` -{{/ requests}} - -`{{ http_method }} {{ route }}` - -#### Parameters - -{{# requests}} -{{# request_query_parameters_text }} - -```json -{{ request_query_parameters_text }} -``` -{{/ request_query_parameters_text }} -{{# request_body }} - -```json -{{{ request_body }}} -``` -{{/ request_body }} - -{{# has_parameters? }} - -| Name | Description | -|:-----|:------------| -{{# parameters }} -| {{#scope}}{{scope}}[{{/scope}}{{ name }}{{#scope}}]{{/scope}} {{# required }}*required*{{/ required }} | {{{ description }}} | -{{/ parameters }} - -{{/ has_parameters? }} -{{^ has_parameters? }} -None known. -{{/ has_parameters? }} - -{{# response_status}} - -### Response - -```plaintext -{{ response_headers_text }} -{{ response_status }} {{ response_status_text}} -``` - -{{# response_body}} - -```json -{{{ response_body }}} -``` -{{/response_body}} - -{{/ response_status}} - -{{# has_response_fields? }} - -#### Fields - -| Name | Description | -|:-----------|:--------------------| -{{# response_fields }} -| {{#scope}}{{scope}}[{{/scope}}{{ name }}{{#scope}}]{{/scope}} | {{{ description }}} | -{{/ response_fields }} - -{{/ has_response_fields? }} - -{{# curl }} -```shell -{{{ curl }}} -``` -{{/ curl }} -{{/ requests}} diff --git a/templates/rspec_api_documentation/rspec_api_documentation/slate_index.mustache b/templates/rspec_api_documentation/rspec_api_documentation/slate_index.mustache deleted file mode 100644 index be0e5ae..0000000 --- a/templates/rspec_api_documentation/rspec_api_documentation/slate_index.mustache +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: {{ api_name }} -language_tabs: - - json: JSON - - shell: cURL ---- - -{{{ api_explanation }}} diff --git a/templates/rspec_api_documentation/rspec_api_documentation/textile_example.mustache b/templates/rspec_api_documentation/rspec_api_documentation/textile_example.mustache deleted file mode 100644 index a7e022b..0000000 --- a/templates/rspec_api_documentation/rspec_api_documentation/textile_example.mustache +++ /dev/null @@ -1,80 +0,0 @@ -h1. {{ resource_name }} API -{{# resource_explanation }} - -{{{ resource_explanation }}} -{{/ resource_explanation }} - -h2. {{ description }} - -h3. {{ http_method }} {{ route }} - -{{# explanation }} -{{ explanation }} - -{{/ explanation }} -{{# has_parameters? }} -h3. Parameters -{{# parameters }} - -Name : {{ name }} {{# required }} *- required -*{{/ required }} -Description : {{ description }} -{{/ parameters }} - -{{/ has_parameters? }} -{{# has_response_fields? }} -h3. Response Fields -{{# response_fields }} - -Name : {{ name }} -Description : {{ description }} -{{/ response_fields }} - -{{/ has_response_fields? }} -{{# requests }} -h3. Request - -h4. Headers - -
{{ request_headers_text }}
- -h4. Route - -
{{ request_method }} {{ request_path }}
- -{{# request_query_parameters_text }} -h4. Query Parameters - -
{{ request_query_parameters_text }}
- -{{/ request_query_parameters_text }} -{{# request_body }} -h4. Body - -
{{{ request_body }}}
- -{{/ request_body }} -{{# curl }} -h4. cURL - -
{{ curl }}
- -{{/ curl }} -{{# response_status }} -h3. Response - -h4. Headers - -
{{ response_headers_text }}
- -h4. Status - -
{{ response_status }} {{ response_status_text}}
- -{{# response_body }} -h4. Body - -
{{{ response_body }}}
- -{{/ response_body }} -{{/ response_status }} -{{/ requests }} diff --git a/templates/rspec_api_documentation/rspec_api_documentation/textile_index.mustache b/templates/rspec_api_documentation/rspec_api_documentation/textile_index.mustache deleted file mode 100644 index 564b410..0000000 --- a/templates/rspec_api_documentation/rspec_api_documentation/textile_index.mustache +++ /dev/null @@ -1,15 +0,0 @@ -h1. {{ api_name }} -{{{ api_explanation }}} - -{{# sections }} -h2. {{ resource_name }} -{{# resource_explanation }} - -{{{ resource_explanation }}} -{{/ resource_explanation }} - -{{# examples }} -* "{{ description }}":{{ dirname }}/{{ filename }} -{{/ examples }} - -{{/ sections }} From 366a41b82da932588f21aa4a9eb8ad88c5cc814a Mon Sep 17 00:00:00 2001 From: Jay Varner Date: Tue, 11 Aug 2026 10:55:08 -0400 Subject: [PATCH 03/10] Include publisher in index --- app/models/published_letter.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/models/published_letter.rb b/app/models/published_letter.rb index 69ec01b..487764b 100644 --- a/app/models/published_letter.rb +++ b/app/models/published_letter.rb @@ -30,10 +30,10 @@ class PublishedLetter < ApplicationRecord 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: :letter, optional: true - belongs_to :file_folder, foreign_key: 'letter_id', inverse_of: :letter, optional: true - belongs_to :letter_owner, foreign_key: 'letter_id', inverse_of: :letter, optional: true - belongs_to :letter_publisher, foreign_key: 'letter_id', inverse_of: :letter, optional: true + 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) @@ -53,7 +53,8 @@ def search_data repositories: repositories.select(&:published).map(&:label), languages: languages.map(&:label), published:, - volume: volume.to_s + volume: volume.to_s, + publisher: letter_publisher } end From 6e4581ca7f7b7bdc2bd96c78046b31fba4268df3 Mon Sep 17 00:00:00 2001 From: Jay Varner Date: Tue, 11 Aug 2026 11:04:46 -0400 Subject: [PATCH 04/10] Index the publisher's label --- app/models/published_letter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/published_letter.rb b/app/models/published_letter.rb index 487764b..2253916 100644 --- a/app/models/published_letter.rb +++ b/app/models/published_letter.rb @@ -54,7 +54,7 @@ def search_data languages: languages.map(&:label), published:, volume: volume.to_s, - publisher: letter_publisher + publisher: letter_publisher.label } end From 0f4aea0a02647e6cd91e2a81bcbeeade0cb428c8 Mon Sep 17 00:00:00 2001 From: Jay Varner Date: Tue, 11 Aug 2026 11:16:19 -0400 Subject: [PATCH 05/10] Index publisher label --- app/models/published_letter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/published_letter.rb b/app/models/published_letter.rb index 2253916..f50edc6 100644 --- a/app/models/published_letter.rb +++ b/app/models/published_letter.rb @@ -54,7 +54,7 @@ def search_data languages: languages.map(&:label), published:, volume: volume.to_s, - publisher: letter_publisher.label + publisher: letter_publisher&.label } end From 14fd1e7cd6ca98395f593bcf0bfd471ef7a32f6d Mon Sep 17 00:00:00 2001 From: Jay Varner Date: Tue, 11 Aug 2026 10:01:25 -0400 Subject: [PATCH 06/10] Patch and security bumps within existing Gemfile constraints Ran bundler-audit against the lockfile: 174 known-CVE advisory entries across 21 gems, mostly Rails-core ReDoS/XSS/path-traversal issues fixed by moving within the existing 7.2.x line, plus a large batch of nokogiri memory-safety issues. Used `bundle update --patch` rather than a plain `bundle update` after the latter silently pulled several unpinned direct dependencies (searchkick, rspec-rails, shoulda-matchers, rubocop) up whole major versions - those are explicitly deferred to their own follow-up work, not bundled in here. Beyond the patch-only pass, targeted a few specific fixes for CVEs a patch-level bump couldn't reach: nokogiri (1.16->1.19, several High-severity memory-safety issues), loofah/rails-html-sanitizer (both back this app's own RichTextField sanitizer), rexml, websocket-driver, net-imap, msgpack, and addressable (needed relaxing its Gemfile pin from `~> 2.8` to `~> 2.9`). Two gems still have open advisories, both requiring a major-version bump this pass deliberately defers: - httparty 0.20.0: High-severity SSRF/API-key-leakage CVE, fixed in >= 0.24.0. Every call site (lib/import.rb, app/jobs/import_data_job.rb) hits a single hardcoded host with no user- or redirect-controlled URL, so practical exploitability here looks low, but flagging clearly rather than silently leaving it. - puma 5.6.9: two High-severity PROXY-protocol-parsing CVEs, fixed in ~> 7.2.1 or >= 8.0.2. config/puma.rb never enables proxy_protocol on any bind directive, so the vulnerable code path isn't reachable as configured. Also fixed 5 mechanical rubocop offenses (frozen_string_literal comments, string style, ENV.fetch) that the toolchain patch bump newly surfaced. Left 3 Metrics/MethodLength and 1 Rails/I18nLocaleTexts finding alone - pre-existing code the stricter toolchain now flags differently, not something to refactor silently as a side effect of a dependency bump. Co-Authored-By: Claude Sonnet 5 --- Gemfile | 2 +- Gemfile.lock | 295 ++++++++++++----------- app/workers/resave_all_letters_worker.rb | 2 + config/importmap.rb | 4 +- spec/jobs/load_big_sam_job_spec.rb | 2 + spec/rails_helper.rb | 2 +- 6 files changed, 164 insertions(+), 143 deletions(-) diff --git a/Gemfile b/Gemfile index 9b3924d..a0bbdca 100644 --- a/Gemfile +++ b/Gemfile @@ -84,7 +84,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..b88e9c3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,83 +1,86 @@ GEM remote: https://rubygems.org/ specs: - actioncable (7.2.1) - actionpack (= 7.2.1) - activesupport (= 7.2.1) + actioncable (7.2.3.2) + actionpack (= 7.2.3.2) + activesupport (= 7.2.3.2) 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 (7.2.3.2) + actionpack (= 7.2.3.2) + activejob (= 7.2.3.2) + activerecord (= 7.2.3.2) + activestorage (= 7.2.3.2) + activesupport (= 7.2.3.2) 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 (7.2.3.2) + actionpack (= 7.2.3.2) + actionview (= 7.2.3.2) + activejob (= 7.2.3.2) + activesupport (= 7.2.3.2) mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (7.2.1) - actionview (= 7.2.1) - activesupport (= 7.2.1) + actionpack (7.2.3.2) + actionview (= 7.2.3.2) + activesupport (= 7.2.3.2) + cgi nokogiri (>= 1.8.5) racc - rack (>= 2.2.4, < 3.2) + rack (>= 2.2.4, < 3.3) 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 (7.2.3.2) + actionpack (= 7.2.3.2) + activerecord (= 7.2.3.2) + activestorage (= 7.2.3.2) + activesupport (= 7.2.3.2) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.2.1) - activesupport (= 7.2.1) + actionview (7.2.3.2) + activesupport (= 7.2.3.2) builder (~> 3.1) + cgi 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 (7.2.3.2) + activesupport (= 7.2.3.2) 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 (7.2.3.2) + activesupport (= 7.2.3.2) + activerecord (7.2.3.2) + activemodel (= 7.2.3.2) + activesupport (= 7.2.3.2) 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 (7.2.3.2) + actionpack (= 7.2.3.2) + activejob (= 7.2.3.2) + activerecord (= 7.2.3.2) + activesupport (= 7.2.3.2) marcel (~> 1.0) - activesupport (7.2.1) + activesupport (7.2.3.2) base64 + benchmark (>= 0.3) bigdecimal concurrent-ruby (~> 1.0, >= 1.3.1) connection_pool (>= 2.2.5) drb i18n (>= 1.6, < 2) logger (>= 1.4.2) - minitest (>= 5.1) + minitest (>= 5.1, < 6) securerandom (>= 0.3) tzinfo (~> 2.0, >= 2.0.5) acts-as-taggable-on (11.0.0) activerecord (>= 7.0, < 8.0) 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 +95,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 +114,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 +129,20 @@ GEM rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) - concurrent-ruby (1.3.4) + cgi (0.5.2) + 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) + 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,33 +151,33 @@ 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) @@ -181,24 +186,24 @@ GEM httparty (0.20.0) mime-types (~> 3.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) + 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,9 +216,9 @@ 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) + logger (1.6.6) + loofah (2.25.2) crass (~> 1.0.2) nokogiri (>= 1.12.0) mail (2.8.1) @@ -225,91 +230,101 @@ GEM matrix (0.4.3) mime-types (3.5.2) mime-types-data (~> 3.2015) - mime-types-data (3.2024.0820) + mime-types-data (3.2024.1203) 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) + multi_xml (0.7.2) bigdecimal (~> 3.1) 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) psych (5.1.2) stringio - public_suffix (6.0.1) - puma (5.6.8) + public_suffix (7.0.5) + puma (5.6.9) 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 (7.2.3.2) + actioncable (= 7.2.3.2) + actionmailbox (= 7.2.3.2) + actionmailer (= 7.2.3.2) + actionpack (= 7.2.3.2) + actiontext (= 7.2.3.2) + actionview (= 7.2.3.2) + activejob (= 7.2.3.2) + activemodel (= 7.2.3.2) + activerecord (= 7.2.3.2) + activestorage (= 7.2.3.2) + activesupport (= 7.2.3.2) bundler (>= 1.15.0) - railties (= 7.2.1) + railties (= 7.2.3.2) 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 (7.2.3.2) + actionpack (= 7.2.3.2) + activesupport (= 7.2.3.2) + cgi 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,23 +332,22 @@ 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 + rexml (3.4.4) roo (2.10.1) nokogiri (~> 1) rubyzip (>= 1.3.0, < 3.0.0) - rspec-core (3.13.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) + rspec-rails (6.1.5) actionpack (>= 6.1) activesupport (>= 6.1) railties (>= 6.1) @@ -341,7 +355,7 @@ GEM rspec-expectations (~> 3.13) rspec-mocks (~> 3.13) rspec-support (~> 3.13) - rspec-support (3.13.1) + rspec-support (3.13.7) rubocop (1.65.1) json (~> 2.3) language_server-protocol (>= 3.17.0) @@ -353,22 +367,22 @@ GEM rubocop-ast (>= 1.31.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.32.1) + rubocop-ast (1.32.3) parser (>= 3.3.1.0) rubocop-factory_bot (2.26.1) rubocop (~> 1.61) - rubocop-rails (2.26.0) + rubocop-rails (2.26.2) activesupport (>= 4.2.0) rack (>= 1.1) rubocop (>= 1.52.0, < 2.0) rubocop-ast (>= 1.31.1, < 2.0) - rubocop-rspec (3.0.4) + rubocop-rspec (3.0.5) rubocop (~> 1.61) rubocop-rspec_rails (2.30.0) rubocop (~> 1.61) rubocop-rspec (~> 3, >= 3.0.1) ruby-progressbar (1.13.0) - ruby-vips (2.2.2) + ruby-vips (2.2.5) ffi (~> 1.12) logger rubyzip (2.3.2) @@ -383,7 +397,7 @@ GEM searchkick (5.3.1) activemodel (>= 6.1) hashie - securerandom (0.3.1) + securerandom (0.3.2) selenium-devtools (0.151.0) selenium-webdriver (~> 4.2) selenium-webdriver (4.46.0) @@ -394,12 +408,12 @@ GEM websocket (~> 1.0) shoulda-matchers (6.4.0) activesupport (>= 5.2.0) - sidekiq (7.3.8) + 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 +427,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 @@ -453,7 +468,7 @@ PLATFORMS DEPENDENCIES active_record_doctor (~> 1.10) acts-as-taggable-on (~> 11.0.0) - addressable (~> 2.8) + addressable (~> 2.9) administrate administrate-field-active_storage (~> 0.4.1) administrate-field-jsonb 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/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/spec/jobs/load_big_sam_job_spec.rb b/spec/jobs/load_big_sam_job_spec.rb index 4271655..aaf26b0 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' 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')] From bbc20bd0aa60b4f153bb4452fca90b69d1d57d95 Mon Sep 17 00:00:00 2001 From: Jay Varner Date: Tue, 11 Aug 2026 10:21:14 -0400 Subject: [PATCH 07/10] Bump Ruby 3.3.4 -> 3.4.10 Rails 8 only requires Ruby >= 3.2 (this app is already well past that), so this is independent of the Rails bump - isolating "does the app work on new Ruby" from "does the app work on new Rails" as separate questions before compounding both. Re-adds the csv gem, removed earlier as an unused direct dependency: csv stopped being a Ruby default gem in 3.4+, and httparty 0.20.0 requires it internally without declaring it as its own dependency (confirmed - the app booted with a real deprecation warning on 3.3 before this bump, and boots silently now). Verified: full suite (304 examples, 0 failures), rubocop (same 4 pre-existing findings as before, nothing new from the Ruby bump itself), clean app boot. Still on Rails 7.2.3.2 for this commit. Co-Authored-By: Claude Sonnet 5 --- .ruby-version | 2 +- Gemfile | 6 +++++- Gemfile.lock | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) 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/Gemfile b/Gemfile index a0bbdca..4dcc509 100644 --- a/Gemfile +++ b/Gemfile @@ -3,11 +3,15 @@ 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' +# 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' + # Use postgresql as the database for Active Record gem 'pg', '~> 1.1' diff --git a/Gemfile.lock b/Gemfile.lock index b88e9c3..5058f4d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -136,6 +136,7 @@ GEM cronex (0.15.0) tzinfo unicode (>= 0.4.4.5) + csv (3.3.6) date (3.5.1) debug (1.9.2) irb (~> 1.10) @@ -477,6 +478,7 @@ DEPENDENCIES aws-sdk-ses (~> 1.0) bootsnap capybara + csv debug elasticsearch (~> 8) factory_bot_rails @@ -510,7 +512,7 @@ DEPENDENCIES tzinfo-data RUBY VERSION - ruby 3.3.4p94 + ruby 3.4.10p104 BUNDLED WITH 2.5.11 From b2f1ec0c2fb064008ed8a679dc237ae0d614f04f Mon Sep 17 00:00:00 2001 From: Jay Varner Date: Tue, 11 Aug 2026 11:09:26 -0400 Subject: [PATCH 08/10] Bump Rails 7.2.3.2 -> 8.1.3.1 acts-as-taggable-on had to move too: 11.0.0 hard-pins activerecord < 8.0, which makes it impossible to install alongside Rails 8 at all - not a discretionary bump like the others deferred in the earlier patch-bump commit, a forced one. Bumped to ~> 13.0 (12->13 has no breaking changes to the tag_list/for_context API ActsAsTaggableField depends on, per its CHANGELOG - only dropped-Ruby-version and added-Rails-version support). Ran `bin/rails app:update` and reviewed its diff file-by-file rather than accepting it wholesale: declined every config file overwrite (all just show cosmetic/new-default-flag differences from this app's deliberate customizations - CORS rules, custom filter_parameters, Sidekiq queue adapter, etc. - confirmed by diffing against a fresh Rails 8.1 scaffold app, nothing there is actually required for Rails 8 to work), dropped the generated bin/ci + config/ci.rb (assumes MiniTest via `bin/rails test`, this app uses RSpec with its own GitHub Actions workflow), and deleted 3 generated ActiveStorage migrations that are pure no-ops here (this app's existing single `create_active_storage_tables` migration, dated 2022, already creates everything they would). Keeps the generated, all-commented-out new_framework_defaults_8_1.rb checklist and config.load_defaults at 7.0 - deliberately not adopting new defaults as part of this bump, per Rails' own recommended upgrade path. Manual verification in a real browser (RAILS_ENV=test server, since dev credentials aren't available here) caught a real regression the automated suite had missed: ActsAsTaggableField's own field spec constructed the field directly with pre-fetched data, and the mentions request spec's factory had no tags by default, so neither ever exercised Administrate's actual field-construction path. Administrate::Field::Base#read_value (new in 1.0, works differently than in 0.20.1) lazily fetches data via `resource.try(attribute)` when data is nil - and that `attribute` call is polymorphic, landing on ActsAsTaggableField's own #attribute override (returns "tag_list", for form-building) instead of the raw :tags key. That fetched mention.tag_list (name strings) instead of mention.tags (real Tag records), and _show.html.erb's `tag.name` raised NoMethodError on a String - reproducible on develop today with any tagged mention, not something this Rails bump introduced. Fixed by overriding #read_value to use the raw @attribute ivar instead of the overridden method, and added regression coverage at both the field-unit level (constructing the field the way Administrate really does) and the request-spec level (a tagged mention through index/show/edit). Verified: full suite (308 examples, 0 failures - reset the local test DB after manual browser testing polluted it with non-transactional data), system specs passing (letters entity picker jQuery/selectize still initializes correctly), rubocop clean, and a real browser click-through across letters/mentions/entities admin pages. Co-Authored-By: Claude Sonnet 5 --- Gemfile | 12 +- Gemfile.lock | 135 +++++++++--------- app/fields/acts_as_taggable_field.rb | 16 +++ bin/dev | 2 + bin/rubocop | 8 ++ .../new_framework_defaults_8_1.rb | 76 ++++++++++ spec/fields/acts_as_taggable_field_spec.rb | 18 +++ spec/requests/admin/mentions_spec.rb | 24 ++++ 8 files changed, 222 insertions(+), 69 deletions(-) create mode 100644 bin/dev create mode 100644 bin/rubocop create mode 100644 config/initializers/new_framework_defaults_8_1.rb diff --git a/Gemfile b/Gemfile index 4dcc509..e00eeee 100644 --- a/Gemfile +++ b/Gemfile @@ -6,12 +6,16 @@ git_source(:github) {|_repo| "https://github.com/#{repo}.git" } 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' @@ -72,7 +76,11 @@ 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' diff --git a/Gemfile.lock b/Gemfile.lock index 5058f4d..3b4bc5b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,83 +1,84 @@ GEM remote: https://rubygems.org/ specs: - actioncable (7.2.3.2) - actionpack (= 7.2.3.2) - activesupport (= 7.2.3.2) + 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.3.2) - actionpack (= 7.2.3.2) - activejob (= 7.2.3.2) - activerecord (= 7.2.3.2) - activestorage (= 7.2.3.2) - activesupport (= 7.2.3.2) + 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.3.2) - actionpack (= 7.2.3.2) - actionview (= 7.2.3.2) - activejob (= 7.2.3.2) - activesupport (= 7.2.3.2) + 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.3.2) - actionview (= 7.2.3.2) - activesupport (= 7.2.3.2) - cgi + 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.3) + 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.3.2) - actionpack (= 7.2.3.2) - activerecord (= 7.2.3.2) - activestorage (= 7.2.3.2) - activesupport (= 7.2.3.2) + 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.3.2) - activesupport (= 7.2.3.2) + actionview (8.1.3.1) + activesupport (= 8.1.3.1) builder (~> 3.1) - cgi 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.3.2) - activesupport (= 7.2.3.2) + activejob (8.1.3.1) + activesupport (= 8.1.3.1) globalid (>= 0.3.6) - activemodel (7.2.3.2) - activesupport (= 7.2.3.2) - activerecord (7.2.3.2) - activemodel (= 7.2.3.2) - activesupport (= 7.2.3.2) + 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.3.2) - actionpack (= 7.2.3.2) - activejob (= 7.2.3.2) - activerecord (= 7.2.3.2) - activesupport (= 7.2.3.2) + 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.3.2) + activesupport (8.1.3.1) base64 - benchmark (>= 0.3) 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, < 6) + 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.9.0) public_suffix (>= 2.0.2, < 8.0) @@ -129,7 +130,6 @@ GEM rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) - cgi (0.5.2) concurrent-ruby (1.3.8) connection_pool (2.4.1) crass (1.0.7) @@ -222,12 +222,13 @@ GEM 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) @@ -296,20 +297,20 @@ GEM rackup (1.0.1) rack (< 3) webrick - rails (7.2.3.2) - actioncable (= 7.2.3.2) - actionmailbox (= 7.2.3.2) - actionmailer (= 7.2.3.2) - actionpack (= 7.2.3.2) - actiontext (= 7.2.3.2) - actionview (= 7.2.3.2) - activejob (= 7.2.3.2) - activemodel (= 7.2.3.2) - activerecord (= 7.2.3.2) - activestorage (= 7.2.3.2) - activesupport (= 7.2.3.2) + 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.3.2) + railties (= 8.1.3.1) rails-dom-testing (2.2.0) activesupport (>= 5.0.0) minitest @@ -317,10 +318,9 @@ GEM 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 (7.2.3.2) - actionpack (= 7.2.3.2) - activesupport (= 7.2.3.2) - cgi + 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) @@ -468,7 +468,7 @@ PLATFORMS DEPENDENCIES active_record_doctor (~> 1.10) - acts-as-taggable-on (~> 11.0.0) + acts-as-taggable-on (~> 13.0) addressable (~> 2.9) administrate administrate-field-active_storage (~> 0.4.1) @@ -476,6 +476,7 @@ DEPENDENCIES administrate-field-list (~> 0.0.6) aws-sdk-rails (~> 5.0) aws-sdk-ses (~> 1.0) + benchmark bootsnap capybara csv @@ -492,7 +493,7 @@ DEPENDENCIES pg (~> 1.1) puma (~> 5.0) rack-cors - rails (~> 7.2.1) + rails (~> 8.1.3) roo (~> 2.9) rspec-rails rubocop 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/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/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/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/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 From 49b2aeff450a4b7d6c72999a849246a3ce3cafda Mon Sep 17 00:00:00 2001 From: Jay Varner Date: Wed, 12 Aug 2026 09:39:32 -0400 Subject: [PATCH 09/10] Add other publishers to index --- app/models/concerns/letter_common.rb | 42 ++++++++++++++++++---------- app/models/letter.rb | 32 ++------------------- app/models/published_letter.rb | 39 ++++++-------------------- spec/models/published_letter_spec.rb | 9 ++++++ 4 files changed, 47 insertions(+), 75 deletions(-) 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/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..9f1936a 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) : [letter_publisher.label] + end end diff --git a/spec/models/published_letter_spec.rb b/spec/models/published_letter_spec.rb index 6fc8e53..80176cc 100644 --- a/spec/models/published_letter_spec.rb +++ b/spec/models/published_letter_spec.rb @@ -42,4 +42,13 @@ 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]).to(include('Endgame')) + expect(published_letter.search_data[:other_publishers]).to(include('Voice')) + end end From 9f83b3c91b5bd7d16d7f16de6758b3e1584ebe53 Mon Sep 17 00:00:00 2001 From: Jay Varner Date: Wed, 12 Aug 2026 09:39:54 -0400 Subject: [PATCH 10/10] Bump Ruby version for build --- .github/workflows/test.yml | 2 +- Dockerfile | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) 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/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