Implements a fully-featured action requests table in a single LiveView module using Flop, Ecto, and SQLite. Includes: - Fuzzy search, status/assignment filters, column sorting, 25-per-page pagination - Real-time updates, bookmarkable URLs via `handle_params/3` - JS-disabled fallback with GET forms (no duplicate logic) - 1,000,000 seeded records, Tailwind + DaisyUI styling, light/dark themes - Comprehensive README with comparisons to Django+React/Rails+React stacks - 31 tests covering all scenarios Tech: Phoenix 1.8+, LiveView, Flop, Ecto, SQLite, Elixir 1.15+
90 lines
2.8 KiB
Elixir
90 lines
2.8 KiB
Elixir
# Script for populating the database. You can run it as:
|
|
#
|
|
# mix run priv/repo/seeds.exs
|
|
#
|
|
# Inside the script, you can read and write to any of your
|
|
# repositories directly:
|
|
#
|
|
# ActionRequestsDemo.Repo.insert!(%ActionRequestsDemo.SomeSchema{})
|
|
#
|
|
# We recommend using the bang functions (`insert!`, `update!`
|
|
# and so on) as they will fail if something goes wrong.
|
|
|
|
alias ActionRequestsDemo.Repo
|
|
alias ActionRequestsDemo.ActionRequests.ActionRequest
|
|
|
|
# Configuration
|
|
total_records = 1_000_000
|
|
# SQLite has a max parameter limit of 32766
|
|
# With 7 fields per record, max is ~4681 records per batch
|
|
# Using 4000 to be safe
|
|
batch_size = 4_000
|
|
num_batches = div(total_records, batch_size)
|
|
|
|
IO.puts("Seeding #{total_records} action requests in #{num_batches} batches of #{batch_size}...")
|
|
IO.puts("Started at: #{DateTime.utc_now()}")
|
|
|
|
statuses = ["resolved", "unresolved"]
|
|
|
|
# Helper function to generate a single record
|
|
generate_record = fn ->
|
|
patient_name = Faker.Person.name()
|
|
status = Enum.random(statuses)
|
|
assigned_user_id = if :rand.uniform() > 0.3, do: Enum.random(1..5), else: nil
|
|
days_ago = :rand.uniform(365)
|
|
|
|
inserted_at =
|
|
DateTime.utc_now()
|
|
|> DateTime.add(-days_ago * 24 * 3600, :second)
|
|
|> DateTime.truncate(:second)
|
|
|
|
delivery_scheduled_at =
|
|
if :rand.uniform() > 0.4 do
|
|
days_ahead = :rand.uniform(14)
|
|
|
|
NaiveDateTime.utc_now()
|
|
|> NaiveDateTime.add(days_ahead * 24 * 3600, :second)
|
|
|> NaiveDateTime.truncate(:second)
|
|
else
|
|
nil
|
|
end
|
|
|
|
%{
|
|
id: Ecto.UUID.generate(),
|
|
patient_name: patient_name,
|
|
status: status,
|
|
assigned_user_id: assigned_user_id,
|
|
delivery_scheduled_at: delivery_scheduled_at,
|
|
inserted_at: inserted_at,
|
|
updated_at: inserted_at
|
|
}
|
|
end
|
|
|
|
# Process batches sequentially (SQLite doesn't handle concurrent writes well)
|
|
start_time = System.monotonic_time(:millisecond)
|
|
|
|
Enum.each(1..num_batches, fn batch_num ->
|
|
# Generate batch of records
|
|
records = for _i <- 1..batch_size, do: generate_record.()
|
|
|
|
# Insert batch in a single transaction
|
|
{count, _} = Repo.insert_all(ActionRequest, records)
|
|
|
|
if rem(batch_num, 10) == 0 do
|
|
elapsed = div(System.monotonic_time(:millisecond) - start_time, 1000)
|
|
progress = batch_num * batch_size
|
|
rate = if elapsed > 0, do: div(progress, elapsed), else: 0
|
|
IO.puts("Progress: #{progress}/#{total_records} (#{div(progress * 100, total_records)}%) - #{rate} records/sec")
|
|
end
|
|
|
|
count
|
|
end)
|
|
|
|
end_time = System.monotonic_time(:millisecond)
|
|
elapsed_seconds = div(end_time - start_time, 1000)
|
|
records_per_second = div(total_records, max(elapsed_seconds, 1))
|
|
|
|
IO.puts("\n✓ Successfully seeded #{total_records} action requests!")
|
|
IO.puts("Total time: #{elapsed_seconds} seconds (#{records_per_second} records/sec)")
|
|
IO.puts("Finished at: #{DateTime.utc_now()}")
|