Conversation
Revamp admin form markup and page layouts for elections, nominations
There was a problem hiding this comment.
Pull request overview
Implements a first-pass election voting system for ruby.org.au, including admin tooling to create elections and nominations.
Changes:
- Adds core election domain models (Election, Nomination, Vote) and supporting DB migrations/schema updates.
- Introduces public election pages (index/show) and a ballot submission endpoint.
- Adds admin UI/controllers/views for managing elections and nominations, plus assorted dependency/Gemfile updates.
Reviewed changes
Copilot reviewed 52 out of 54 changed files in this pull request and generated 22 comments.
Show a summary per file
| File | Description |
|---|---|
| yarn.lock | Updates frontend dependency lockfile versions. |
| spec/views/elections/show.html.erb_spec.rb | Adds scaffold-generated view spec for elections show. |
| spec/views/elections/new.html.erb_spec.rb | Adds scaffold-generated view spec for elections new. |
| spec/views/elections/index.html.erb_spec.rb | Adds scaffold-generated view spec for elections index. |
| spec/views/elections/edit.html.erb_spec.rb | Adds scaffold-generated view spec for elections edit. |
| spec/routing/elections_routing_spec.rb | Adds routing spec for elections controller. |
| spec/requests/elections_spec.rb | Adds scaffold-generated request spec for elections CRUD. |
| spec/requests/admin/elections_spec.rb | Adds request spec coverage for admin election creation. |
| spec/models/vote_spec.rb | Adds placeholder model spec for Vote. |
| spec/models/user_spec.rb | Updates schema annotation for User. |
| spec/models/nomination_spec.rb | Adds placeholder model spec for Nomination. |
| spec/models/election_spec.rb | Adds model spec for Election#elected_users scoring behavior. |
| spec/helpers/elections_helper_spec.rb | Adds placeholder helper spec for ElectionsHelper. |
| spec/factories/votes.rb | Adds Vote factory and :for_nomination trait. |
| spec/factories/nominations.rb | Adds Nomination factory. |
| spec/factories/elections.rb | Adds Election factory with open/closed/pending traits. |
| db/schema.rb | Reflects new elections/nominations/votes tables and new user columns. |
| db/migrate/20260416104148_fix_election_columns.rb | Reworks election scoring columns to point_scale. |
| db/migrate/20251008023211_create_votes.rb | Creates votes table for polymorphic scored voting. |
| db/migrate/20251007062829_create_nominations.rb | Creates nominations table linking elections and users. |
| db/migrate/20251007055656_create_elections.rb | Creates elections table (initial scoring columns). |
| config/routes.rb | Adds public elections routes + ballot create; adds admin elections/nominations routes. |
| app/views/shared/_navigation.html.erb | Adds Admin navigation link to Elections. |
| app/views/elections/show.html.erb | Public election show page rendering election + ballot form. |
| app/views/elections/new.html.erb | Adds scaffold-style public election new view. |
| app/views/elections/index.json.jbuilder | Adds JSON index output for elections. |
| app/views/elections/index.html.erb | Public elections list page (current/previous sections). |
| app/views/elections/edit.html.erb | Adds scaffold-style public election edit view. |
| app/views/elections/_form.html.erb | Implements ballot form UI for scoring nominees. |
| app/views/elections/_election.json.jbuilder | Adds JSON partial for election. |
| app/views/elections/_election.html.erb | Election partial rendering header + ballot form. |
| app/views/admin/posts/new.html.erb | Admin post new page layout refactor. |
| app/views/admin/posts/edit.html.erb | Admin post edit page layout refactor. |
| app/views/admin/posts/_form.html.erb | Admin post form layout refactor. |
| app/views/admin/nominations/new.html.erb | Adds admin nomination creation page. |
| app/views/admin/nominations/_form.html.erb | Adds admin nomination form fields for member emails. |
| app/views/admin/elections/show.html.erb | Adds admin election show page with action links. |
| app/views/admin/elections/new.html.erb | Adds admin election new page. |
| app/views/admin/elections/index.html.erb | Adds admin elections index table page. |
| app/views/admin/elections/edit.html.erb | Adds admin election edit page. |
| app/views/admin/elections/_form.html.erb | Adds admin election form fields. |
| app/views/admin/elections/_election.html.erb | Adds admin election details + nominations listing partial. |
| app/models/vote.rb | Adds Vote model and election-open validation. |
| app/models/user.rb | Adds nomination associations on User. |
| app/models/nomination.rb | Adds Nomination model linking election/nominee/nominator and votes. |
| app/models/election.rb | Adds Election model, scopes, open?/closed?, and winner selection logic. |
| app/models/ballot.rb | Adds Ballot ActiveModel wrapper for the voting form. |
| app/helpers/elections_helper.rb | Adds ElectionsHelper module. |
| app/controllers/elections_controller.rb | Adds elections controller (scaffold-style actions; index/show used publicly). |
| app/controllers/ballots_controller.rb | Adds ballot submission endpoint creating votes. |
| app/controllers/admin/nominations_controller.rb | Adds admin nomination creation flow. |
| app/controllers/admin/elections_controller.rb | Adds admin election management controller actions. |
| Gemfile.lock | Updates gem dependency versions. |
| Gemfile | Reorders/adjusts gem declarations and groups. |
| <% unless @post.published_at %> | ||
| <div class="grid grid-cols-1 md:grid-cols-3 gap-4 sm:gap-6 items-start"> | ||
| <%= form.label :publish_scheduled_at, class: "block text-sm font-medium text-gray-700 md:text-right md:mt-2" %> | ||
| <div class="md:col-span-2"> | ||
| <%= form.datetime_field :publish_scheduled_at, | ||
| disabled: @post.publish_scheduled_at?, | ||
| class: "w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-ruby-red focus:border-ruby-red #{'opacity-60 cursor-not-allowed' if @post.publish_scheduled_at?}" %> | ||
| <% if @post.publish_scheduled_at? %> |
There was a problem hiding this comment.
This partial receives post as a local, but it uses the instance variable @post for publish scheduling logic. That will break if the partial is ever rendered with a different local name or without @post set. Use the post local consistently (post.published_at, post.publish_scheduled_at?).
| <% unless @post.published_at %> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-4 sm:gap-6 items-start"> | |
| <%= form.label :publish_scheduled_at, class: "block text-sm font-medium text-gray-700 md:text-right md:mt-2" %> | |
| <div class="md:col-span-2"> | |
| <%= form.datetime_field :publish_scheduled_at, | |
| disabled: @post.publish_scheduled_at?, | |
| class: "w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-ruby-red focus:border-ruby-red #{'opacity-60 cursor-not-allowed' if @post.publish_scheduled_at?}" %> | |
| <% if @post.publish_scheduled_at? %> | |
| <% unless post.published_at %> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-4 sm:gap-6 items-start"> | |
| <%= form.label :publish_scheduled_at, class: "block text-sm font-medium text-gray-700 md:text-right md:mt-2" %> | |
| <div class="md:col-span-2"> | |
| <%= form.datetime_field :publish_scheduled_at, | |
| disabled: post.publish_scheduled_at?, | |
| class: "w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-ruby-red focus:border-ruby-red #{'opacity-60 cursor-not-allowed' if post.publish_scheduled_at?}" %> | |
| <% if post.publish_scheduled_at? %> |
| <%= form.range_field :nomination_id_scores, | ||
| name: "ballot[nomination_id_scores][#{nomination.id}]", |
There was a problem hiding this comment.
This ballot form uses form.range_field :nomination_id_scores, but Ballot doesn’t define a nomination_id_scores attribute (no attr_accessor, no ActiveModel attributes). This will typically raise when rendering the field. Either use range_field_tag (since you’re manually setting name:) or add an accessor/attribute on Ballot for nomination_id_scores.
| <%= form.range_field :nomination_id_scores, | |
| name: "ballot[nomination_id_scores][#{nomination.id}]", | |
| <%= range_field_tag "ballot[nomination_id_scores][#{nomination.id}]", | |
| nil, |
| @@ -0,0 +1,30 @@ | |||
| <%= form_with(model: [:admin, :election, nomination], class: "space-y-6 sm:space-y-8") do |form| %> | |||
There was a problem hiding this comment.
The form model array is [:admin, :election, nomination], which doesn’t include the actual @election record. This will generate an incorrect action URL for the nested /admin/elections/:election_id/nominations routes. Pass the election instance (e.g., [:admin, @election, nomination]) and ensure the partial is rendered with election: available.
| <%= form_with(model: [:admin, :election, nomination], class: "space-y-6 sm:space-y-8") do |form| %> | |
| <% election = local_assigns.fetch(:election, @election) %> | |
| <%= form_with(model: [:admin, election, nomination], class: "space-y-6 sm:space-y-8") do |form| %> |
Replace direct Election.create! calls with FactoryBot.create in elections index and show view specs.
Refactor controller logic and tests
Improve BallotsController#create by loading all referenced Nomination records in a single query and indexing them by id to avoid N+1 queries
| vote = Vote.find_or_initialize_by( | ||
| voter: current_user, | ||
| votable: nomination, | ||
| ) |
To resolve issue #379
Continuing the work from @reentim
First iteration of an election system online