SQLite's Arel visitor wraps CTE branches in extra parentheses, making
the UNION ALL inside recursive CTEs invalid. Also SQLite lacks LPAD()
and CAST(... AS BINARY). Fix by using the existing pathname column for
ordering on SQLite, which already encodes the hierarchical path.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace MySQL-specific LPAD() with SQLite's format() for zero-padded
row numbering, and skip CAST(... AS BINARY) on SQLite where string
comparisons are already binary by default.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without this guard, the last admin in the system could delete their own
account, making the application unmanageable. This adds a model method
`User#sole_admin?`, a controller guard in `RegistrationsController#destroy`,
and disables the delete button in the profile edit view when the current
user is the only remaining admin.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Test infrastructure:
- Allow www.example.com host in test env (ActionDispatch::HostAuthorization
was blocking all integration test requests)
- Include Devise::Test::IntegrationHelpers in ActionDispatch::IntegrationTest
so tests can sign in with sign_in(user)
Controller tests:
- Rewrite UsersControllerTest to match actual routes/actions (no new/create/
edit/destroy); sign in as admin; test update-self rejection via turbo_stream
- Fix Default::UnitsControllerTest to sign in before requesting the index
SQLite compatibility in Unit#defaults_diff:
- Hoist the inner "units" CTE to the outer WITH RECURSIVE level (fixes nested
WITH syntax error) — this was the existing TODO in the code
- Use Unit.joins(...) for the recursive part instead of a raw Arel::SelectManager
so the SQLite visitor does not wrap it in parentheses inside UNION ALL
- Drop the named "units" CTE (conflicts with the table name under WITH RECURSIVE
in SQLite); apply the user/defaults scope directly on the base case
- Qualify GROUP BY columns to avoid ambiguity when bases_units is joined
- Qualify ORDER BY :multiplier/:symbol to avoid ambiguity (Unit.ordering)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The repo's config/database.yml already handles both SQLite (default) and
MySQL (DB_ADAPTER=mysql) via ERB. Remove the redundant steps that overwrote
it with a hardcoded version, and pass DB_ADAPTER=mysql for the MySQL job.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- .gitea/workflows/test.yml: two parallel CI jobs (SQLite and MySQL),
each generates its own database.yml inline and runs the test suite
- lib/tasks/test_multi_db.rake: `rails test:all_adapters` runs both
adapters sequentially using DATABASE_URL to switch at runtime
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the fieldset-based layout with the app's standard
.labeled-form CSS grid so email, password and retype fields
stack vertically (label left, input right) exactly like the
existing sign-in and registration forms.
Section headings and checkbox rows are given explicit grid-column
spans via inline styles so they span the full form width rather
than being constrained to the label column.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the CLI-only setup (db:seed + manual application.rb edits)
with a web wizard shown automatically on first visit when no admin
account exists yet.
SetupController (GET/POST /setup) collects the admin e-mail and
password, a "skip e-mail confirmation" toggle, and an option to
seed the built-in default units. Once submitted it creates the
admin User, persists the chosen options as Setting records, and
redirects to the sign-in page.
ApplicationController gains a redirect_to_setup_if_needed
before_action that catches every request (including Devise routes)
when no admin exists, so a fresh installation always lands on the
wizard rather than an empty sign-in form.
A new Setting model provides a lightweight key-value store for
runtime options that were previously hard-coded in application.rb
(e.g. skip_email_confirmation). RegistrationsController now reads
that flag from the database instead of from the application config.
Seeds.rb is kept for headless / automated deployments and skips
admin creation when an admin already exists (idempotent), with a
comment pointing to the web wizard as the preferred path.
Also extends the SQLite nil-limit fix (|| Float::INFINITY) to the
Quantity model, which suffered the same ArgumentError as Unit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduce config.skip_email_confirmation in application.rb.dist.
When set to true, new registrations are automatically confirmed
without requiring email verification — useful for installations
where outgoing email is not configured or for development/testing.
Implemented by calling skip_confirmation! in build_resource before
the record is saved, so no confirmation email is ever sent.
Also fix ArgumentError raised in length validations when
type_for_attribute(:column).limit returns nil, which happens with
SQLite for string columns that have no explicit limit in the
migration. Guard with || Float::INFINITY so the validation is
effectively skipped when the database imposes no limit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>