Add contestant categories to contests
This commit is contained in:
parent
ee476ab81b
commit
502649620b
37
app/controllers/categories_controller.rb
Normal file
37
app/controllers/categories_controller.rb
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
class CategoriesController < ApplicationController
|
||||||
|
before_action :set_contest
|
||||||
|
before_action :set_category, only: %i[ destroy]
|
||||||
|
|
||||||
|
def create
|
||||||
|
authorize @contest
|
||||||
|
|
||||||
|
@category = Category.new(category_params)
|
||||||
|
@category.contest_id = @contest.id
|
||||||
|
if @category.save
|
||||||
|
redirect_to edit_contest_path(@contest), notice: t("categories.new.notice")
|
||||||
|
else
|
||||||
|
redirect_to edit_contest_path(@contest), notice: t("categories.new.error")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
authorize @contest
|
||||||
|
|
||||||
|
@category.destroy
|
||||||
|
redirect_to edit_contest_path(@contest), notice: t("categories.destroy.notice")
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_contest
|
||||||
|
@contest = Contest.find(params[:contest_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_category
|
||||||
|
@category = Category.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def category_params
|
||||||
|
params.expect(category: [ :name ])
|
||||||
|
end
|
||||||
|
end
|
24
app/models/category.rb
Normal file
24
app/models/category.rb
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: categories
|
||||||
|
#
|
||||||
|
# id :integer not null, primary key
|
||||||
|
# name :string
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
# contest_id :integer not null
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# index_categories_on_contest_id (contest_id)
|
||||||
|
#
|
||||||
|
# Foreign Keys
|
||||||
|
#
|
||||||
|
# contest_id (contest_id => contests.id)
|
||||||
|
#
|
||||||
|
class Category < ApplicationRecord
|
||||||
|
belongs_to :contest
|
||||||
|
has_and_belongs_to_many :contestants
|
||||||
|
|
||||||
|
validates :name, presence: true
|
||||||
|
end
|
@ -26,6 +26,7 @@ class Contest < ApplicationRecord
|
|||||||
extend FriendlyId
|
extend FriendlyId
|
||||||
|
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
|
has_many :categories
|
||||||
has_many :completions, dependent: :destroy
|
has_many :completions, dependent: :destroy
|
||||||
has_many :contestants, dependent: :destroy
|
has_many :contestants, dependent: :destroy
|
||||||
has_many :puzzles, dependent: :destroy
|
has_many :puzzles, dependent: :destroy
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
class Contestant < ApplicationRecord
|
class Contestant < ApplicationRecord
|
||||||
belongs_to :contest
|
belongs_to :contest
|
||||||
has_many :completions, dependent: :destroy
|
has_many :completions, dependent: :destroy
|
||||||
|
has_and_belongs_to_many :categories
|
||||||
|
|
||||||
before_validation :initialize_time_seconds_if_empty
|
before_validation :initialize_time_seconds_if_empty
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
h4.mt-5 = t("contests.form.general")
|
||||||
= form_with model: contest do |form|
|
= form_with model: contest do |form|
|
||||||
.row.mb-3
|
.row.mb-3
|
||||||
.col
|
.col
|
||||||
@ -20,7 +21,7 @@
|
|||||||
= form.check_box :team, class: "form-check-input"
|
= form.check_box :team, class: "form-check-input"
|
||||||
= form.label :team
|
= form.label :team
|
||||||
.form-text = t("activerecord.attributes.contest.team_description")
|
.form-text = t("activerecord.attributes.contest.team_description")
|
||||||
.row.mb-3
|
.row.mb-3 style="display: none"
|
||||||
.col
|
.col
|
||||||
.form-check.form-switch
|
.form-check.form-switch
|
||||||
= form.check_box :allow_registration, class: "form-check-input"
|
= form.check_box :allow_registration, class: "form-check-input"
|
||||||
@ -28,4 +29,37 @@
|
|||||||
.form-text = t("activerecord.attributes.contest.allow_registration_description")
|
.form-text = t("activerecord.attributes.contest.allow_registration_description")
|
||||||
.row
|
.row
|
||||||
.col
|
.col
|
||||||
= form.submit submit_text, class: "btn btn-primary"
|
= form.submit submit_text, class: "btn btn-primary"
|
||||||
|
|
||||||
|
|
||||||
|
h4.mt-5 = t("contests.form.categories")
|
||||||
|
|
||||||
|
= form_with model: Category, url: "/contests/#{@contest.id}/categories" do |form|
|
||||||
|
- if @contest.categories.size > 0
|
||||||
|
.row
|
||||||
|
.col-6
|
||||||
|
table.table.table-striped.table-hover
|
||||||
|
thead
|
||||||
|
tr
|
||||||
|
th
|
||||||
|
= t("activerecord.attributes.category.name")
|
||||||
|
th
|
||||||
|
= t("activerecord.attributes.category.contestant_count")
|
||||||
|
tbody
|
||||||
|
- @contest.categories.each do |category|
|
||||||
|
tr.align-middle scope="row"
|
||||||
|
td
|
||||||
|
= category.name
|
||||||
|
td
|
||||||
|
= category.contestants.size
|
||||||
|
td
|
||||||
|
= link_to t("helpers.buttons.delete"), contest_category_path(@contest, category), data: { turbo_method: :delete }, class: "btn btn-sm btn-danger ms-2"
|
||||||
|
.row.mt-3
|
||||||
|
.col-4
|
||||||
|
.form-floating
|
||||||
|
= form.text_field :name, autocomplete: "off", value: nil, class: "form-control"
|
||||||
|
= form.label :name, class: "required"
|
||||||
|
= t("activerecord.attributes.category.new")
|
||||||
|
.row.mt-3
|
||||||
|
.col
|
||||||
|
= form.submit t("helpers.buttons.add"), class: "btn btn-primary"
|
@ -38,6 +38,10 @@ en:
|
|||||||
greater_than: "Participant names are required"
|
greater_than: "Participant names are required"
|
||||||
activerecord:
|
activerecord:
|
||||||
attributes:
|
attributes:
|
||||||
|
category:
|
||||||
|
contestant_count: Contestants count
|
||||||
|
new: New category
|
||||||
|
name: Category
|
||||||
completion:
|
completion:
|
||||||
contestant: Participant
|
contestant: Participant
|
||||||
display_time: Time
|
display_time: Time
|
||||||
@ -116,6 +120,12 @@ en:
|
|||||||
blank: Your email cannot be empty
|
blank: Your email cannot be empty
|
||||||
username:
|
username:
|
||||||
blank: Your username cannot be empty
|
blank: Your username cannot be empty
|
||||||
|
categories:
|
||||||
|
destroy:
|
||||||
|
notice: Category deleted
|
||||||
|
new:
|
||||||
|
error: The category name can't be empty
|
||||||
|
notice: Category added
|
||||||
completions:
|
completions:
|
||||||
destroy:
|
destroy:
|
||||||
notice: Completion deleted
|
notice: Completion deleted
|
||||||
@ -132,6 +142,9 @@ en:
|
|||||||
edit:
|
edit:
|
||||||
notice: Contest updated
|
notice: Contest updated
|
||||||
title: Edit contest settings
|
title: Edit contest settings
|
||||||
|
form:
|
||||||
|
categories: Participant categories
|
||||||
|
general: General parameters
|
||||||
index:
|
index:
|
||||||
title: Welcome %{username}!
|
title: Welcome %{username}!
|
||||||
manage_contests: Manage my contests
|
manage_contests: Manage my contests
|
||||||
|
@ -9,6 +9,10 @@ fr:
|
|||||||
greater_than: "Choisir une colonne pour les noms des participant.e.s est nécessaire"
|
greater_than: "Choisir une colonne pour les noms des participant.e.s est nécessaire"
|
||||||
activerecord:
|
activerecord:
|
||||||
attributes:
|
attributes:
|
||||||
|
category:
|
||||||
|
contestant_count: Nombre de participant.e.s
|
||||||
|
new: Nouvelle catégorie
|
||||||
|
name: Catégorie
|
||||||
completion:
|
completion:
|
||||||
contestant_id: Participant.e
|
contestant_id: Participant.e
|
||||||
display_time: Temps
|
display_time: Temps
|
||||||
@ -87,6 +91,12 @@ fr:
|
|||||||
blank: L'email est obligatoire
|
blank: L'email est obligatoire
|
||||||
username:
|
username:
|
||||||
blank: Le nom d'utilisateur.ice est obligatoire
|
blank: Le nom d'utilisateur.ice est obligatoire
|
||||||
|
categories:
|
||||||
|
destroy:
|
||||||
|
notice: Catégorie supprimée
|
||||||
|
new:
|
||||||
|
error: Le nom de la catégorie ne peut pas être vide
|
||||||
|
notice: Catégorie ajoutée
|
||||||
completions:
|
completions:
|
||||||
destroy:
|
destroy:
|
||||||
notice: Complétion supprimée
|
notice: Complétion supprimée
|
||||||
@ -103,6 +113,9 @@ fr:
|
|||||||
edit:
|
edit:
|
||||||
notice: Concours modifié
|
notice: Concours modifié
|
||||||
title: Paramètres du concours
|
title: Paramètres du concours
|
||||||
|
form:
|
||||||
|
categories: Catégories de participant.e.s
|
||||||
|
general: Paramètres généraux
|
||||||
index:
|
index:
|
||||||
title: Bienvenue %{username} !
|
title: Bienvenue %{username} !
|
||||||
manage_contests: Mes concours de puzzle
|
manage_contests: Mes concours de puzzle
|
||||||
|
@ -9,6 +9,7 @@ Rails.application.routes.draw do
|
|||||||
root "contests#index"
|
root "contests#index"
|
||||||
|
|
||||||
resources :contests do
|
resources :contests do
|
||||||
|
resources :categories, only: [ :create, :destroy ]
|
||||||
resources :completions
|
resources :completions
|
||||||
resources :contestants
|
resources :contestants
|
||||||
resources :puzzles
|
resources :puzzles
|
||||||
|
15
db/migrate/20250714115208_create_categories.rb
Normal file
15
db/migrate/20250714115208_create_categories.rb
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
class CreateCategories < ActiveRecord::Migration[8.0]
|
||||||
|
def change
|
||||||
|
create_table :categories do |t|
|
||||||
|
t.string :name
|
||||||
|
t.belongs_to :contest, null: false, foreign_key: true
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
create_join_table :categories, :contestants do |t|
|
||||||
|
t.index :category_id
|
||||||
|
t.index :contestant_id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
18
db/schema.rb
generated
18
db/schema.rb
generated
@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[8.0].define(version: 2025_06_27_070407) do
|
ActiveRecord::Schema[8.0].define(version: 2025_07_14_115208) do
|
||||||
create_table "active_storage_attachments", force: :cascade do |t|
|
create_table "active_storage_attachments", force: :cascade do |t|
|
||||||
t.string "name", null: false
|
t.string "name", null: false
|
||||||
t.string "record_type", null: false
|
t.string "record_type", null: false
|
||||||
@ -39,6 +39,21 @@ ActiveRecord::Schema[8.0].define(version: 2025_06_27_070407) do
|
|||||||
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
|
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "categories", force: :cascade do |t|
|
||||||
|
t.string "name"
|
||||||
|
t.integer "contest_id", null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["contest_id"], name: "index_categories_on_contest_id"
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table "categories_contestants", id: false, force: :cascade do |t|
|
||||||
|
t.integer "category_id", null: false
|
||||||
|
t.integer "contestant_id", null: false
|
||||||
|
t.index ["category_id"], name: "index_categories_contestants_on_category_id"
|
||||||
|
t.index ["contestant_id"], name: "index_categories_contestants_on_contestant_id"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "completions", force: :cascade do |t|
|
create_table "completions", force: :cascade do |t|
|
||||||
t.integer "time_seconds"
|
t.integer "time_seconds"
|
||||||
t.integer "contestant_id", null: false
|
t.integer "contestant_id", null: false
|
||||||
@ -141,6 +156,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_06_27_070407) do
|
|||||||
|
|
||||||
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
|
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
|
||||||
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
|
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
|
||||||
|
add_foreign_key "categories", "contests"
|
||||||
add_foreign_key "completions", "contestants"
|
add_foreign_key "completions", "contestants"
|
||||||
add_foreign_key "completions", "contests"
|
add_foreign_key "completions", "contests"
|
||||||
add_foreign_key "completions", "messages"
|
add_foreign_key "completions", "messages"
|
||||||
|
23
spec/factories/categories.rb
Normal file
23
spec/factories/categories.rb
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: categories
|
||||||
|
#
|
||||||
|
# id :integer not null, primary key
|
||||||
|
# name :string
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
# contest_id :integer not null
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# index_categories_on_contest_id (contest_id)
|
||||||
|
#
|
||||||
|
# Foreign Keys
|
||||||
|
#
|
||||||
|
# contest_id (contest_id => contests.id)
|
||||||
|
#
|
||||||
|
FactoryBot.define do
|
||||||
|
factory :category do
|
||||||
|
name { "MyString" }
|
||||||
|
end
|
||||||
|
end
|
23
spec/models/category_spec.rb
Normal file
23
spec/models/category_spec.rb
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: categories
|
||||||
|
#
|
||||||
|
# id :integer not null, primary key
|
||||||
|
# name :string
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
# contest_id :integer not null
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# index_categories_on_contest_id (contest_id)
|
||||||
|
#
|
||||||
|
# Foreign Keys
|
||||||
|
#
|
||||||
|
# contest_id (contest_id => contests.id)
|
||||||
|
#
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe Category, type: :model do
|
||||||
|
pending "add some examples to (or delete) #{__FILE__}"
|
||||||
|
end
|
Loading…
x
Reference in New Issue
Block a user