From 3454d3052bfcf34f39bab29c94b9f6ad3afe6230 Mon Sep 17 00:00:00 2001 From: cryptogopher Date: Thu, 30 Apr 2026 18:23:00 +0200 Subject: [PATCH] Generate admin password on db:seed Test admin account creation in db:seed --- DESIGN.md | 11 +++++++++ config/initializers/devise.rb | 2 +- db/migrate/20230602185352_create_units.rb | 2 +- .../20250104194343_create_quantities.rb | 2 +- db/migrate/20250121230456_create_readouts.rb | 2 +- db/schema.rb | 6 ++--- db/seeds.rb | 14 +++++++---- db/sqlite3_schema.rb | 6 ++--- test/system/tasks_test.rb | 24 +++++++++++++++++++ 9 files changed, 54 insertions(+), 15 deletions(-) create mode 100644 test/system/tasks_test.rb diff --git a/DESIGN.md b/DESIGN.md index 0f6c1ba..d5d8cf1 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -32,3 +32,14 @@ whenever a change is considered, to avoid regressions. * as a standard with hardware implementations ensures both: computing efficiency and hardware/3rd party library compatibility as opposed to Ruby custom `BigDecimal` type + +### Database layer vs application layer data model constraints +* database constraints are the final frontier against data corruption, + * they should safeguard against data _consistency_ loss under _all_ data + (not schema) manipulation scenarios, including application level logic + errors and direct data manipulation, +* application constraints can be as restrictive as database constraints or more, + but not less, as it doesn't serve any use case, + * proper application level constraints should prevent unhandled database + exception occurences, e.g `ActiveRecord::InvalidForeignKey` for operations + performed through Models (i.e. not `#delete_all` etc.) diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index d3522ac..2313944 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -179,7 +179,7 @@ Devise.setup do |config| # ==> Configuration for :validatable # Range for password length. - config.password_length = 5..128 + config.password_length = 5..32 # Email regex used to validate email formats. It simply asserts that # one (and only one) @ exists in the given string. This is mainly diff --git a/db/migrate/20230602185352_create_units.rb b/db/migrate/20230602185352_create_units.rb index 089fe62..d615c97 100644 --- a/db/migrate/20230602185352_create_units.rb +++ b/db/migrate/20230602185352_create_units.rb @@ -1,7 +1,7 @@ class CreateUnits < ActiveRecord::Migration[7.0] def change create_table :units do |t| - t.references :user, foreign_key: true + t.references :user, foreign_key: {on_delete: :cascade} t.string :symbol, null: false, limit: 15 t.text :description t.decimal :multiplier, null: false, precision: 30, scale: 15, default: 1.0 diff --git a/db/migrate/20250104194343_create_quantities.rb b/db/migrate/20250104194343_create_quantities.rb index 7e1a0ee..59c98b5 100644 --- a/db/migrate/20250104194343_create_quantities.rb +++ b/db/migrate/20250104194343_create_quantities.rb @@ -1,7 +1,7 @@ class CreateQuantities < ActiveRecord::Migration[7.2] def change create_table :quantities do |t| - t.references :user, foreign_key: true + t.references :user, foreign_key: {on_delete: :cascade} t.string :name, null: false, limit: 31 t.text :description t.references :parent, foreign_key: {to_table: :quantities, on_delete: :cascade} diff --git a/db/migrate/20250121230456_create_readouts.rb b/db/migrate/20250121230456_create_readouts.rb index e9428d9..25cf6ca 100644 --- a/db/migrate/20250121230456_create_readouts.rb +++ b/db/migrate/20250121230456_create_readouts.rb @@ -1,7 +1,7 @@ class CreateReadouts < ActiveRecord::Migration[7.2] def change create_table :readouts do |t| - t.references :user, null: false, foreign_key: true + t.references :user, null: false, foreign_key: {on_delete: :cascade} t.references :quantity, null: false, foreign_key: true # :category + :value + :unit as a separate table? (NumericValue, TextValue) t.integer :category, null: false, default: 0 diff --git a/db/schema.rb b/db/schema.rb index 9d97716..9e833e9 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -71,10 +71,10 @@ ActiveRecord::Schema[8.0].define(version: 2025_01_21_230456) do end add_foreign_key "quantities", "quantities", column: "parent_id", on_delete: :cascade - add_foreign_key "quantities", "users" + add_foreign_key "quantities", "users", on_delete: :cascade add_foreign_key "readouts", "quantities" add_foreign_key "readouts", "units" - add_foreign_key "readouts", "users" + add_foreign_key "readouts", "users", on_delete: :cascade add_foreign_key "units", "units", column: "base_id", on_delete: :cascade - add_foreign_key "units", "users" + add_foreign_key "units", "users", on_delete: :cascade end diff --git a/db/seeds.rb b/db/seeds.rb index 0085c3d..328805b 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -7,17 +7,21 @@ User.transaction do break if User.find_by status: :admin - User.create! email: Rails.configuration.admin, password: 'admin', status: :admin do |user| + email = Rails.configuration.admin + password_length = SecureRandom.rand(Rails.configuration.devise.password_length) + password = SecureRandom.alphanumeric(password_length) + + User.create!(email: email, password: password, status: :admin) do |user| user.skip_confirmation! - print "Creating #{user.status} account '#{user.email}' with password '#{user.password}'..." + print "Creating #{user.status} account '#{user.email}'" \ + " with password '#{user.password}'..." end puts "done." - rescue ActiveRecord::RecordInvalid => exception - puts "failed. #{exception.message}" + puts "failed.", exception.message end # Formulas will be deleted as dependent on Quantities #[Source, Quantity, Unit].each { |model| model.defaults.delete_all } -require_relative 'seeds/units.rb' +load "db/seeds/units.rb" diff --git a/db/sqlite3_schema.rb b/db/sqlite3_schema.rb index 5002cdb..e2ea3fb 100644 --- a/db/sqlite3_schema.rb +++ b/db/sqlite3_schema.rb @@ -71,10 +71,10 @@ ActiveRecord::Schema[8.0].define(version: 2025_01_21_230456) do end add_foreign_key "quantities", "quantities", column: "parent_id", on_delete: :cascade - add_foreign_key "quantities", "users" + add_foreign_key "quantities", "users", on_delete: :cascade add_foreign_key "readouts", "quantities" add_foreign_key "readouts", "units" - add_foreign_key "readouts", "users" + add_foreign_key "readouts", "users", on_delete: :cascade add_foreign_key "units", "units", column: "base_id", on_delete: :cascade - add_foreign_key "units", "users" + add_foreign_key "units", "users", on_delete: :cascade end diff --git a/test/system/tasks_test.rb b/test/system/tasks_test.rb new file mode 100644 index 0000000..1a15b98 --- /dev/null +++ b/test/system/tasks_test.rb @@ -0,0 +1,24 @@ +require 'application_system_test_case' + +# NOTE: remove constant to avoid warnings due to double loading of +# rails/tasks/statistics.rake. To be removed after upgrade to Rails 8.1. +Object.send(:remove_const, :STATS_DIRECTORIES) + +Rails.application.load_tasks +# NOTE: for some reason task for checking pending migrations messes up +# transaction when run during test. It causes all DB changes made before its +# execution to be rolled back. +# Run it before tests, so any rake task dependent on it will see it as +# #already_invoked and won't run it during test. It is redundant anyway, as +# migrations are run before starting test suite. +Rake::Task['db:abort_if_pending_migrations'].invoke + +class TasksTest < ApplicationSystemTestCase + test "db:seed creates admin account" do + User.admin.delete_all + assert_output /Creating admin account/ do + Rake::Task['db:seed'].execute + end + assert User.admin.exists? + end +end