Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6bcf1d9
Add a Nix dev shell
nygrenh Nov 6, 2026
8125491
Add Sorbet and Tapioca and type the app as strict
nygrenh Nov 6, 2026
1e4fcca
Fix bugs strict typing exposed: 404s, missing records, reports, signup
nygrenh Nov 6, 2026
b650c9a
Add lefthook and a branch-ready check, fix RuboCop offenses
nygrenh Nov 6, 2026
a4d5b68
Replace PhantomJS and mimic with Cuprite and Puma
nygrenh Nov 6, 2026
cc93645
Compile Sass with Dart Sass via dartsass-sprockets
nygrenh Nov 6, 2026
dfe5614
Cache in Redis with Rails redis_cache_store
nygrenh Nov 6, 2026
fe9b22d
Process logo variants with libvips instead of ImageMagick
nygrenh Nov 6, 2026
1a12c0d
Drop unused gems, binstubs and spec stubs, update simplecov
nygrenh Nov 6, 2026
635067b
Update Rails to 7.2 and adopt its defaults
nygrenh Nov 6, 2026
a2088cb
Update Rails to 8.0, adopt its defaults and order callbacks
nygrenh Nov 6, 2026
5cd1813
Update Rails to 8.1 and adopt its defaults
nygrenh Nov 6, 2026
85290ce
Run on Ruby 4.0
nygrenh Nov 7, 2026
2e916e4
Upgrade Rack, Puma, mail and Doorkeeper
nygrenh Nov 7, 2026
948c6e6
Upgrade rspec-rails, RuboCop and the remaining gems
nygrenh Nov 7, 2026
564f58e
Enable Lint, Security and Performance cops and autocorrect them
nygrenh Nov 7, 2026
70594ce
Enable Rails, RSpec and FactoryBot cops, fix specs and bugs found
nygrenh Nov 7, 2026
600aa13
Run tmc-langs without a shell and run Brakeman in CI
nygrenh Nov 7, 2026
c859200
Build on a Ruby 4 base image tag instead of latest
nygrenh Nov 7, 2026
eb56094
Use a cache namespace Readthis entries cannot collide with
nygrenh Nov 7, 2026
4ae7ea1
Run Postgres 16 in the dev and sandbox compose files
nygrenh Nov 7, 2026
42c33b3
Kill tmc-langs when submission packaging times out
nygrenh Nov 7, 2026
15549af
Build on the published latest base image
nygrenh Nov 9, 2026
c037d79
Guard login redirects, batch sheet point counts, drop dead test code
nygrenh Nov 9, 2026
9ad7181
Load the Rails 8.1 defaults directly, keeping YJIT off
nygrenh Nov 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
use flake
34 changes: 34 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,40 @@ on:
branches: [ master, bs4 ]
workflow_dispatch:
jobs:
sorbet:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_HOST_AUTH_METHOD: trust
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
RAILS_ENV: development
DATABASE_URL: postgres://postgres@localhost:5432/tmc-sorbet
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '4.0'
bundler-cache: true
- name: Type check
run: bundle exec srb tc
- name: Verify gem RBIs
run: bin/tapioca gem --verify
- name: Load database schema
run: bin/rake db:create db:schema:load
- name: Verify DSL RBIs
run: bin/tapioca dsl --verify

spec_v8:
runs-on: ubuntu-latest
steps:
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ jobs:

steps:
- uses: actions/checkout@v4
- name: Set up Ruby 3.0
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.0
ruby-version: '4.0'
- name: Cache gems
uses: actions/cache@v4
with:
Expand All @@ -29,3 +29,5 @@ jobs:
bundle install --jobs 4 --retry 3
- name: Run RuboCop
run: bundle exec rubocop
- name: Run Brakeman
run: bundle exec brakeman --no-pager --quiet --ensure-ignore-notes
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,6 @@

# Ignore default active_storage
storage/

# Nix / direnv
/.direnv
308 changes: 305 additions & 3 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,131 @@
inherit_from: .rubocop_todo.yml

plugins:
- rubocop-capybara
- rubocop-factory_bot
- rubocop-performance
- rubocop-rails
- rubocop-rspec
- rubocop-sorbet
- rubocop-thread_safety

inherit_gem:
rubocop-rails_config:
- config/rails.yml

AllCops:
SuggestExtensions: false
TargetRubyVersion: 2.5
NewCops: enable
TargetRubyVersion: 4.0
TargetRailsVersion: 8.1
Exclude:
- 'ext/**/*'
- 'db/**/*'
- '**/vendor/**/*'

# rails_config sets DisabledByDefault; enabling a department opts in its default
# cops without turning off the rails_config ones.
Lint:
Enabled: true

Security:
Enabled: true

Performance:
Enabled: true

Rails:
Enabled: true

# Swagger docs are public and must skip ApplicationController's filters.
Rails/ApplicationController:
Exclude:
- 'app/controllers/api/v8/apidocs_controller.rb'

# There is no ApplicationMailer to inherit from.
Rails/ApplicationMailer:
Enabled: false

# SessionsHelper and AuthorizeCollectionHelper are controller mixins that hold request state.
Rails/HelperInstanceVariable:
Exclude:
- 'app/helpers/sessions_helper.rb'
- 'app/helpers/authorize_collection_helper.rb'

# API v8 returns the not-found message, which find words differently from find_by!(id:).
Rails/FindById:
Enabled: false

# Each association needs a data-retention decision, not a lint fix.
Rails/HasManyOrHasOneDependent:
Enabled: false

# The UI is not localised.
Rails/I18nLocaleTexts:
Enabled: false

# Each inverse_of needs testing against the association's callers.
Rails/InverseOf:
Enabled: false

# Console-run maintenance methods and the init script installer print progress for the operator.
Rails/Output:
Exclude:
- 'app/models/kafka_batch_update_points.rb'
- 'lib/ruby_init_script.rb'

# Brakeman's XSS checks cover html_safe and raw.
Rails/OutputSafety:
Enabled: false

# Rake tasks here don't use the app, and loading it would require a database config.
Rails/RakeEnvironment:
Enabled: false

# RemoteSandbox.all is a plain array of sandboxes, not a relation.
Rails/RedundantActiveRecordAllMethod:
AllowedReceivers:
- RemoteSandbox

# Dropping the validation changes the error messages API clients receive.
Rails/RedundantPresenceValidationOnBelongsTo:
Enabled: false

# Per-origin email templates live in config/email_templates, outside the view paths.
Rails/RenderInline:
Exclude:
- 'app/mailers/user_mailer.rb'

# update_column/update_all skip callbacks on purpose for counters and bulk updates.
Rails/SkipsModelValidations:
Enabled: false

# params.expect rejects shapes require/permit accept; each endpoint needs its own review.
Rails/StrongParametersExpect:
Enabled: false

# Time.zone.now formats differently from Time.now; each call site needs its own review.
Rails/TimeZone:
Enabled: false

# Needs unique indexes added in migrations, not a lint fix.
Rails/UniqueValidationWithoutIndex:
Enabled: false

# Decision tables of literal or constant results (Solution#visible_to?) read better unmerged.
Lint/DuplicateBranch:
IgnoreLiteralBranches: true
IgnoreConstantBranches: true

# Would rewrite the Swagger DSL's `key :'$ref'` to `key :$ref`.
Lint/SymbolConversion:
Enabled: false

# Style
# Ruby 3.1+ shorthand (`{ x: }`) would rewrite ~600 hashes; not worth the churn.
Style/HashSyntax:
EnforcedShorthandSyntax: either

Style/StringLiterals:
Enabled: true
EnforcedStyle: single_quotes
Expand Down Expand Up @@ -47,5 +162,192 @@ Metrics/ParameterLists:
Metrics/PerceivedComplexity:
Enabled: false

Performance/RegexpMatch:
Enabled: false
Style/MapToHash:
Enabled: true

# Spec-only plugins: bug-finding cops on, style cops off.
RSpec:
Include:
- 'spec/**/*.rb'

Capybara:
Include:
- 'spec/**/*.rb'

FactoryBot:
Include:
- 'spec/**/*.rb'

RSpec/Be:
Enabled: true

RSpec/BeNil:
Enabled: true

RSpec/ChangeByZero:
Enabled: true

RSpec/DuplicatedMetadata:
Enabled: true

RSpec/EmptyExampleGroup:
Enabled: true

RSpec/ExpectActual:
Enabled: true

RSpec/ExpectOutput:
Enabled: true

RSpec/Focus:
Enabled: true

RSpec/HooksBeforeExamples:
Enabled: true

RSpec/IdenticalEqualityAssertion:
Enabled: true

RSpec/LeakyLocalVariable:
Enabled: true

RSpec/MissingExampleGroupArgument:
Enabled: true

RSpec/MissingExpectationTargetMethod:
Enabled: true

RSpec/NoExpectationExample:
Enabled: true
# Shared helpers that hold the expectations.
AllowedPatterns:
- ^expect_
- ^assert_
- ^should_be_
- ^(all|users)_own_subs$
- ^no_other_
- ^two_(subs|courses)_by_
- ^course_from_different_template$

RSpec/NotToNot:
Enabled: true

RSpec/OverwritingSetup:
Enabled: true

RSpec/PendingWithoutReason:
Enabled: true

RSpec/RepeatedDescription:
Enabled: true

RSpec/RepeatedExample:
Enabled: true

RSpec/RepeatedExampleGroupBody:
Enabled: true

RSpec/RepeatedExampleGroupDescription:
Enabled: true

RSpec/RepeatedIncludeExample:
Enabled: true

RSpec/RepeatedSubjectCall:
Enabled: true

RSpec/ReturnFromStub:
Enabled: true

RSpec/SkipBlockInsideExample:
Enabled: true

RSpec/UnspecifiedException:
Enabled: true

RSpec/VoidExpect:
Enabled: true

Capybara/CurrentPathExpectation:
Enabled: true

Capybara/RSpec/NegationMatcherAfterVisit:
Enabled: true

Capybara/VisibilityMatcher:
Enabled: true

FactoryBot/AttributeDefinedStatically:
Enabled: true

FactoryBot/FactoryAssociationWithStrategy:
Enabled: true

FactoryBot/FactoryClassName:
Enabled: true

FactoryBot/RedundantFactoryOption:
Enabled: true

# Specs and standalone scripts run single-threaded.
ThreadSafety:
Enabled: true
Include:
- 'app/**/*.rb'
- 'lib/**/*.rb'
- 'config/**/*.rb'

Sorbet/ValidSigil:
Enabled: true

Sorbet/EnforceSigilOrder:
Enabled: true

Sorbet/StrictSigil:
Enabled: true
Include:
- 'app/**/*.rb'
- 'lib/**/*.rb'
- 'config/**/*.rb'
Exclude:
# DSL files read with instance_eval; Sorbet ignores them.
- 'config/extra_fields/*'

Sorbet/EnforceSignatures:
Enabled: true
Include:
- 'app/**/*.rb'
- 'lib/**/*.rb'
- 'config/**/*.rb'
Exclude:
- 'config/extra_fields/*'
# Sigs live in sorbet/rbi/shims/lib_natsort.rbi.
- 'lib/natsort.rb'

Sorbet/SignatureBuildOrder:
Enabled: true

Sorbet/ConstantsFromStrings:
Enabled: true

# Cops that catch code a Rails upgrade breaks or deprecates.
Rails/EnumSyntax:
Enabled: true

Rails/EnumHash:
Enabled: true

Rails/DeprecatedActiveModelErrorsMethods:
Enabled: true

Rails/HttpPositionalArguments:
Enabled: true

Rails/TransactionExitStatement:
Enabled: true

Rails/ActionOrder:
Enabled: true

Rails/ActiveRecordCallbacksOrder:
Enabled: true
Loading
Loading