From 1324067305e4fa40cb18dc9aa12b14be2511ee7a Mon Sep 17 00:00:00 2001 From: OMiage Date: Sun, 17 Aug 2025 21:17:03 +0200 Subject: [PATCH] Responsive full Website --- .../api/v1/admin/forge_settings_controller.rb | 87 +++++++++++++++++++ .../admin/perks_lock_settings_controller.rb | 64 ++++++++++++++ app/controllers/api/v1/data_lab_controller.rb | 28 +++++- app/models/forge_setting.rb | 10 +++ app/models/perks_lock_setting.rb | 6 ++ .../data_lab/craft_metrics_calculator.rb | 12 +-- .../data_lab/forge_metrics_calculator.rb | 85 ++++++++++++++++++ .../data_lab/slots_metrics_calculator.rb | 38 +++++--- config/routes.rb | 4 + .../20250812120000_create_forge_settings.rb | 23 +++++ ...250812121000_create_perks_lock_settings.rb | 14 +++ db/schema.rb | 31 ++++++- db/seeds.rb | 3 +- db/seeds/forge.rb | 23 +++++ db/seeds/quests.rb | 48 +++++----- db/seeds/slots.rb | 6 +- 16 files changed, 435 insertions(+), 47 deletions(-) create mode 100644 app/controllers/api/v1/admin/forge_settings_controller.rb create mode 100644 app/controllers/api/v1/admin/perks_lock_settings_controller.rb create mode 100644 app/models/forge_setting.rb create mode 100644 app/models/perks_lock_setting.rb create mode 100644 app/services/data_lab/forge_metrics_calculator.rb create mode 100644 db/migrate/20250812120000_create_forge_settings.rb create mode 100644 db/migrate/20250812121000_create_perks_lock_settings.rb create mode 100644 db/seeds/forge.rb diff --git a/app/controllers/api/v1/admin/forge_settings_controller.rb b/app/controllers/api/v1/admin/forge_settings_controller.rb new file mode 100644 index 0000000..42189fa --- /dev/null +++ b/app/controllers/api/v1/admin/forge_settings_controller.rb @@ -0,0 +1,87 @@ +module Api + module V1 + module Admin + class ForgeSettingsController < BaseController + before_action :set_forge_setting, only: [:show, :update, :destroy] + + def index + settings = ForgeSetting.includes(:rarity).references(:rarity).order('rarities.id ASC') + render json: settings.map { |s| json_for(s) } + end + + def show + render json: json_for(@forge_setting) + end + + def create + setting = ForgeSetting.new(forge_setting_params) + if setting.save + invalidate_caches + render json: json_for(setting), status: :created + else + render json: { errors: setting.errors.full_messages }, status: :unprocessable_entity + end + end + + def update + if @forge_setting.update(forge_setting_params) + invalidate_caches + render json: json_for(@forge_setting) + else + render json: { errors: @forge_setting.errors.full_messages }, status: :unprocessable_entity + end + end + + def destroy + @forge_setting.destroy + invalidate_caches + head :no_content + end + + private + + def set_forge_setting + @forge_setting = ForgeSetting.find(params[:id]) + end + + def forge_setting_params + params.require(:forge_setting).permit( + :rarity_id, + :operation_type, + :supply, + :nb_previous_required, + :nb_digital_required, + :cash, + :fusion_core, + :bft_tokens, + :sponsor_marks_reward + ) + end + + def json_for(setting) + { + id: setting.id, + rarity: setting.rarity.name, + rarity_id: setting.rarity_id, + operation_type: setting.operation_type, + supply: setting.supply, + nb_previous_required: setting.nb_previous_required, + nb_digital_required: setting.nb_digital_required, + cash: setting.cash, + fusion_core: setting.fusion_core, + bft_tokens: setting.bft_tokens, + sponsor_marks_reward: setting.sponsor_marks_reward, + created_at: setting.created_at, + updated_at: setting.updated_at + } + end + + def invalidate_caches + Rails.cache.delete_matched("data_lab/forge/*") + end + end + end + end +end + + diff --git a/app/controllers/api/v1/admin/perks_lock_settings_controller.rb b/app/controllers/api/v1/admin/perks_lock_settings_controller.rb new file mode 100644 index 0000000..0092de0 --- /dev/null +++ b/app/controllers/api/v1/admin/perks_lock_settings_controller.rb @@ -0,0 +1,64 @@ +module Api + module V1 + module Admin + class PerksLockSettingsController < BaseController + before_action :set_setting, only: [:show, :update, :destroy] + + def index + settings = PerksLockSetting.includes(:rarity).order('rarities.id ASC') + render json: settings.map { |s| json_for(s) } + end + + def show + render json: json_for(@setting) + end + + def create + setting = PerksLockSetting.new(setting_params) + if setting.save + render json: json_for(setting), status: :created + else + render json: { errors: setting.errors.full_messages }, status: :unprocessable_entity + end + end + + def update + if @setting.update(setting_params) + render json: json_for(@setting) + else + render json: { errors: @setting.errors.full_messages }, status: :unprocessable_entity + end + end + + def destroy + @setting.destroy + head :no_content + end + + private + + def set_setting + @setting = PerksLockSetting.find(params[:id]) + end + + def setting_params + params.require(:perks_lock_setting).permit(:rarity_id, :star_0, :star_1, :star_2, :star_3) + end + + def json_for(s) + { + id: s.id, + rarity_id: s.rarity_id, + rarity: s.rarity.name, + star_0: s.star_0, + star_1: s.star_1, + star_2: s.star_2, + star_3: s.star_3 + } + end + end + end + end +end + + diff --git a/app/controllers/api/v1/data_lab_controller.rb b/app/controllers/api/v1/data_lab_controller.rb index 5d7456d..fd8d477 100644 --- a/app/controllers/api/v1/data_lab_controller.rb +++ b/app/controllers/api/v1/data_lab_controller.rb @@ -3,7 +3,9 @@ class Api::V1::DataLabController < ApplicationController before_action :authenticate_user! def slots_metrics - cache_key = "data_lab/slots/#{current_user.id}/#{params[:badge_rarity]}" + game = Game.find_by(name: "Boss Fighters") + slots_version = Slot.where(game: game).maximum(:updated_at)&.to_i || 0 + cache_key = "data_lab/slots/#{current_user.id}/#{params[:badge_rarity]}/#{slots_version}" response = Rails.cache.fetch(cache_key, expires_in: 1.hour) do DataLab::SlotsMetricsCalculator.new(current_user, params[:badge_rarity]).calculate end @@ -42,6 +44,30 @@ def craft_metrics render json: response end + def forge_metrics + type = params[:type].presence || "merge" + item = params[:item].presence || "digital" + + version = [ForgeSetting.maximum(:updated_at), Rarity.maximum(:updated_at)].compact.max&.to_i || 0 + cache_key = "data_lab/forge/#{current_user.id}/#{type}/#{item}/#{version}" + response = Rails.cache.fetch(cache_key, expires_in: 1.hour) do + DataLab::ForgeMetricsCalculator.new(current_user).calculate(type: type, item: item) + end + + render json: response + end + + def perks_lock + settings = PerksLockSetting.includes(:rarity).order('rarities.id ASC') + render json: settings.map { |s| { + "RARITY": s.rarity.name, + "NO STAR": s.star_0 || 0, + "1 STAR": s.star_1 || 0, + "2 STARS": s.star_2 || 0, + "3 STARS": s.star_3 || 0 + }} + end + def currency_metrics cache_key = "data_lab/currency/#{current_user.id}" response = Rails.cache.fetch(cache_key, expires_in: 1.hour) do diff --git a/app/models/forge_setting.rb b/app/models/forge_setting.rb new file mode 100644 index 0000000..00df571 --- /dev/null +++ b/app/models/forge_setting.rb @@ -0,0 +1,10 @@ +class ForgeSetting < ApplicationRecord + belongs_to :rarity + + OPERATION_TYPES = %w[merge_digital merge_nft craft_nft].freeze + + validates :operation_type, presence: true, inclusion: { in: OPERATION_TYPES } + validates :rarity_id, presence: true +end + + diff --git a/app/models/perks_lock_setting.rb b/app/models/perks_lock_setting.rb new file mode 100644 index 0000000..01bbb9f --- /dev/null +++ b/app/models/perks_lock_setting.rb @@ -0,0 +1,6 @@ +class PerksLockSetting < ApplicationRecord + belongs_to :rarity + validates :rarity_id, presence: true +end + + diff --git a/app/services/data_lab/craft_metrics_calculator.rb b/app/services/data_lab/craft_metrics_calculator.rb index 91c47ee..c374275 100644 --- a/app/services/data_lab/craft_metrics_calculator.rb +++ b/app/services/data_lab/craft_metrics_calculator.rb @@ -12,10 +12,10 @@ def initialize(user) end def calculate - @badges = load_badges - @badges.map do |badge| - rarity = badge.rarity.name - craft_data = badge.item_crafting + @badges = load_items + @badges.map do |item| + rarity = item.rarity.name + craft_data = item.item_crafting next unless craft_data flex_cost = calculate_flex_cost(craft_data.craft_tokens) @@ -24,7 +24,7 @@ def calculate { "1. rarity": rarity, - "2. supply": badge.supply, + "2. supply": item.supply, "3. nb_previous_rarity_item": nb_previous_rarity, "4. flex_craft": craft_data.craft_tokens, "5. flex_craft_cost": format_currency(flex_cost), @@ -40,7 +40,7 @@ def calculate_nb_previous_rarity(rarity) rarity.downcase == "common" ? 0 : 2 end - def load_badges + def load_items Item.includes(:type, :rarity, :item_crafting) .joins(:rarity) .where(types: { name: "Badge" }) diff --git a/app/services/data_lab/forge_metrics_calculator.rb b/app/services/data_lab/forge_metrics_calculator.rb new file mode 100644 index 0000000..7ce3b17 --- /dev/null +++ b/app/services/data_lab/forge_metrics_calculator.rb @@ -0,0 +1,85 @@ +module DataLab + class ForgeMetricsCalculator + include Constants::Utils + + def initialize(user) + @user = user + @currency_rates = { + "bft" => Constants::CurrencyConstants.currency_rates[:bft], + "sm" => Constants::CurrencyConstants.currency_rates[:sm] + } + end + + # params: type => "merge"|"craft", item => "digital"|"nft" + def calculate(type: "merge", item: "digital") + if type == "merge" && item == "digital" + merge_digital_table + elsif type == "merge" && item == "nft" + merge_nft_table + elsif type == "craft" && item == "nft" + craft_nft_table + else + [] + end + end + + private + + def merge_digital_table + # Inclure toutes les raretés disponibles, ordonnées + rarities = Rarity.order(:id).to_a + rarities.map do |rarity| + fs = ForgeSetting.find_by(rarity: rarity, operation_type: "merge_digital") + { + "RARITY": rarity.name, + "NB PREVIOUS RARITY ITEM": fs&.nb_previous_required, + "CASH": fs&.cash + } + end + end + + def merge_nft_table + rarities = Rarity.order(:id).to_a + rarities.map do |rarity| + fs = ForgeSetting.find_by(rarity: rarity, operation_type: "merge_nft") + bft_tokens = fs&.bft_tokens + sp_reward = fs&.sponsor_marks_reward + bft_cost = bft_tokens ? (bft_tokens * @currency_rates["bft"]).round(2) : nil + sp_value = sp_reward ? (sp_reward * @currency_rates["sm"]).round(2) : nil + { + "RARITY": rarity.name, + "SUPPLY": fs&.supply, + "NB PREVIOUS RARITY ITEM": fs&.nb_previous_required, + "CASH": fs&.cash, + "FUSION CORE": fs&.fusion_core, + "$BFT": fs&.bft_tokens, + "$BFT COST": bft_cost ? format_currency(bft_cost) : nil, + "SP. MARKS REWARD": fs&.sponsor_marks_reward, + "SP. MARKS VALUE": sp_value ? format_currency(sp_value) : nil + } + end + end + + def craft_nft_table + rarities = Rarity.order(:id).to_a + rarities.map do |rarity| + fs = ForgeSetting.find_by(rarity: rarity, operation_type: "craft_nft") + bft_tokens = fs&.bft_tokens + sp_reward = fs&.sponsor_marks_reward + bft_cost = bft_tokens ? (bft_tokens * @currency_rates["bft"]).round(2) : nil + sp_value = sp_reward ? (sp_reward * @currency_rates["sm"]).round(2) : nil + { + "RARITY": rarity.name, + "SUPPLY": fs&.supply, + "NB DIGITAL": fs&.nb_digital_required, + "$BFT": fs&.bft_tokens, + "$BFT COST": bft_cost ? format_currency(bft_cost) : nil, + "SP. MARKS REWARD": fs&.sponsor_marks_reward, + "SP. MARKS VALUE": sp_value ? format_currency(sp_value) : nil + } + end + end + end +end + + diff --git a/app/services/data_lab/slots_metrics_calculator.rb b/app/services/data_lab/slots_metrics_calculator.rb index cb3de1e..6d7c66b 100644 --- a/app/services/data_lab/slots_metrics_calculator.rb +++ b/app/services/data_lab/slots_metrics_calculator.rb @@ -13,13 +13,18 @@ def initialize(user, badge_rarity = "Common") end def calculate - slots = Slot.includes(:currency, :game) + # Slots du jeu principal, ordonnés, avec IDs réinitialisés via seed + main_game = Game.find_by(name: "Boss Fighters") + slots_rel = Slot.includes(:currency, :game) + .where(game: main_game) + .order(:id) + slots = slots_rel.to_a slots_costs = calculate_slots_cost(slots) # Calculer les valeurs constantes une seule fois - total_flex = slots.sum(:unlockCurrencyNumber) - total_cost = slots.sum(:unlockPrice) - total_slots = slots.count + total_flex = slots_rel.sum(:unlockCurrencyNumber) + total_cost = slots_rel.sum(:unlockPrice) + total_slots = slots.length # Calculer le ROI de base (constant pour toutes les raretés) base_roi = 340 # Valeur constante observée @@ -36,7 +41,7 @@ def calculate bonus_part = calculate_bonus_part(slot_cost[:"1. slot"]) total_part = normal_part + bonus_part - # Récupérer directement les valeurs des constantes pour le bonus BFT + # slot_id logique (1..N) plutôt que l'ID BD slot_id = slot_cost[:"1. slot"] # Calculer le 1.total_flex @@ -59,9 +64,9 @@ def calculate numeric_cost = total_cost.to_f end - # Pour le 3. total_bonus_bft - slot = Slot.find_by(id: slot_id) - total_bonus_bft = slot.bonus_bft_percent + # Pour le 3. total_bonus_bft: récupérer le slot N (index-1) dans la liste ordonnée + slot_record = slots[slot_id - 1] + total_bonus_bft = slot_record&.bonus_bft_percent || 0 # Calculer le 4. nb_tokens_roi en fonction du coût et de la valeur BFT en base tokens_roi = @bft_value > 0 ? (numeric_cost / @bft_value).round(0) : 0 @@ -101,7 +106,7 @@ def calculate private def calculate_slots_cost(slots) - slots.map do |slot| + slots.each_with_index.map do |slot, idx| # Pour le 3. flex_cost flex_amount = slot.flex_value * @user_rates[:flex] @@ -114,14 +119,14 @@ def calculate_slots_cost(slots) bonus_per_badge = ((bft_per_badge*(1+(slot.bonus_value/100.0)))-bft_per_badge).round(0) { - "1. slot": slot.id, + "1. slot": (idx + 1), "2. nb_flex": slot.flex_value, "3. flex_cost": format_currency(flex_amount), "4. bonus_bft": slot.bonus_value, "5. bft_per_badge": bft_per_badge, "6. bonus_per_badge": bonus_per_badge, - normalPart: calculate_normal_part(slot.id), - bonusPart: calculate_bonus_part(slot.id) + normalPart: calculate_normal_part(idx + 1), + bonusPart: calculate_bonus_part(idx + 1) } end end @@ -235,8 +240,8 @@ def calculate_unlocked_slots_with_rarity(slots, multiplier, base_roi) adjusted_flex = (total_flex * multiplier).round(0) adjusted_cost = (total_cost * multiplier).round(2) - slot = Slot.find_by(id: nb_slots) - total_bonus_bft = slot.bonus_bft_percent || 0 + slot = slot_by_index(nb_slots) + total_bonus_bft = slot&.bonus_bft_percent || 0 { "1. total_flex": adjusted_flex, @@ -248,5 +253,10 @@ def calculate_unlocked_slots_with_rarity(slots, multiplier, base_roi) "7. nb_charges_roi_3.0": ((base_roi * multiplier) / 3.0).round(0) } end + + def slot_by_index(n) + return nil if n.nil? || n <= 0 + Slot.order(:id).offset(n - 1).first + end end end diff --git a/config/routes.rb b/config/routes.rb index ea27446..b5e6128 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -107,6 +107,8 @@ get "data_lab/contracts", to: "data_lab#contracts_metrics" get "data_lab/badges", to: "data_lab#badges_metrics" get "data_lab/craft", to: "data_lab#craft_metrics" + get "data_lab/forge", to: "data_lab#forge_metrics" + get "data_lab/perks_lock", to: "data_lab#perks_lock" get "profile", to: "users#profile" patch "profile", to: "users#update_profile" @@ -163,6 +165,8 @@ resources :currencies resources :items resources :item_crafting + resources :forge_settings + resources :perks_lock_settings resources :item_recharge resources :matches, only: [ :index, :show, :destroy ] resources :nfts, only: [ :index, :show, :destroy ] diff --git a/db/migrate/20250812120000_create_forge_settings.rb b/db/migrate/20250812120000_create_forge_settings.rb new file mode 100644 index 0000000..5b4a748 --- /dev/null +++ b/db/migrate/20250812120000_create_forge_settings.rb @@ -0,0 +1,23 @@ +class CreateForgeSettings < ActiveRecord::Migration[8.0] + def change + create_table :forge_settings do |t| + t.references :rarity, null: false, foreign_key: true + t.string :operation_type, null: false # merge_digital, merge_nft, craft_nft + + # Champs communs/optionnels selon l'opération + t.integer :supply + t.integer :nb_previous_required + t.integer :nb_digital_required + t.integer :cash + t.integer :fusion_core + t.integer :bft_tokens + t.integer :sponsor_marks_reward + + t.timestamps + end + + add_index :forge_settings, [:rarity_id, :operation_type], unique: true, name: "index_forge_settings_on_rarity_and_operation" + end +end + + diff --git a/db/migrate/20250812121000_create_perks_lock_settings.rb b/db/migrate/20250812121000_create_perks_lock_settings.rb new file mode 100644 index 0000000..8f4fec9 --- /dev/null +++ b/db/migrate/20250812121000_create_perks_lock_settings.rb @@ -0,0 +1,14 @@ +class CreatePerksLockSettings < ActiveRecord::Migration[8.0] + def change + create_table :perks_lock_settings do |t| + t.references :rarity, null: false, foreign_key: true, index: { unique: true } + t.integer :star_0 + t.integer :star_1 + t.integer :star_2 + t.integer :star_3 + t.timestamps + end + end +end + + diff --git a/db/schema.rb b/db/schema.rb index 72cfefa..2a2d7e2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2025_07_25_093058) do +ActiveRecord::Schema[8.0].define(version: 2025_08_16_223802) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -58,6 +58,22 @@ t.index ["currency_id"], name: "index_currency_packs_on_currency_id" end + create_table "forge_settings", force: :cascade do |t| + t.bigint "rarity_id", null: false + t.string "operation_type", null: false + t.integer "supply" + t.integer "nb_previous_required" + t.integer "nb_digital_required" + t.integer "cash" + t.integer "fusion_core" + t.integer "bft_tokens" + t.integer "sponsor_marks_reward" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["rarity_id", "operation_type"], name: "index_forge_settings_on_rarity_and_operation", unique: true + t.index ["rarity_id"], name: "index_forge_settings_on_rarity_id" + end + create_table "games", force: :cascade do |t| t.string "name" t.datetime "created_at", null: false @@ -162,6 +178,17 @@ t.index ["provider"], name: "index_payment_methods_on_provider", unique: true end + create_table "perks_lock_settings", force: :cascade do |t| + t.bigint "rarity_id", null: false + t.integer "star_0" + t.integer "star_1" + t.integer "star_2" + t.integer "star_3" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["rarity_id"], name: "index_perks_lock_settings_on_rarity_id", unique: true + end + create_table "player_cycles", force: :cascade do |t| t.bigint "user_id", null: false t.integer "playerCycleType" @@ -423,6 +450,7 @@ add_foreign_key "badge_useds", "nfts", column: "nftId" add_foreign_key "currencies", "games" add_foreign_key "currency_packs", "currencies" + add_foreign_key "forge_settings", "rarities" add_foreign_key "item_craftings", "items" add_foreign_key "item_farmings", "items" add_foreign_key "item_recharges", "items" @@ -430,6 +458,7 @@ add_foreign_key "items", "types" add_foreign_key "matches", "users" add_foreign_key "nfts", "items", column: "itemId" + add_foreign_key "perks_lock_settings", "rarities" add_foreign_key "player_cycles", "users" add_foreign_key "rounds", "tournament_matches" add_foreign_key "rounds", "tournament_matches", column: "match_id" diff --git a/db/seeds.rb b/db/seeds.rb index a89bef4..7b6807a 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -12,7 +12,8 @@ 'slots.rb', # Création des slots (dépend des currencies) 'items.rb', # Création de tous les items (badges et contrats) 'contract_level_costs.rb', # Coûts de niveau des contrats - 'quests.rb' # Création des missions quotidiennes + 'quests.rb', # Création des missions quotidiennes + 'forge.rb' # Création des réglages Forge & Perks lock ] puts "\nDébut du seeding..." diff --git a/db/seeds/forge.rb b/db/seeds/forge.rb new file mode 100644 index 0000000..cdfe27d --- /dev/null +++ b/db/seeds/forge.rb @@ -0,0 +1,23 @@ +puts "Création des paramètres Forge (ForgeSettings)..." +rarities = Rarity.order(:id).to_a +ops = ["merge_digital", "merge_nft", "craft_nft"] + +rarities.each do |rarity| + ops.each do |op| + ForgeSetting.find_or_create_by!(rarity: rarity, operation_type: op) + end +end + +puts "Création des paramètres Perks Lock..." +rarities.each do |rarity| + pls = PerksLockSetting.find_or_initialize_by(rarity: rarity) + pls.star_0 ||= 0 + pls.star_1 ||= 0 + pls.star_2 ||= 0 + pls.star_3 ||= 0 + pls.save! +end + +puts "✓ Forge & Perks seeds initialisés" + + diff --git a/db/seeds/quests.rb b/db/seeds/quests.rb index 9999440..8aece2f 100644 --- a/db/seeds/quests.rb +++ b/db/seeds/quests.rb @@ -1,25 +1,29 @@ -puts "Creating quests..." +puts "Creating or updating quests..." -# Supprimer toutes les quêtes existantes -Quest.destroy_all +quests = [ + { + quest_id: 'daily_login', + title: 'Daily Login', + description: 'Log in to the game', + quest_type: 'daily', + xp_reward: 150, + progress_required: 1, + active: true + }, + { + quest_id: 'daily_matches', + title: 'Daily Matches', + description: 'Complete 5 matches today', + quest_type: 'daily', + xp_reward: 250, + progress_required: 5, + active: true + } +] -# Quêtes Quotidiennes -Quest.create!( - quest_id: 'daily_login', - title: 'Daily Login', - description: 'Log in to the game', - quest_type: 'daily', - xp_reward: 150, - progress_required: 1, - active: true -) +quests.each do |attrs| + quest = Quest.find_or_initialize_by(quest_id: attrs[:quest_id]) + quest.update!(attrs) +end -Quest.create!( - quest_id: 'daily_matches', - title: 'Daily Matches', - description: 'Complete 5 matches today', - quest_type: 'daily', - xp_reward: 250, - progress_required: 5, - active: true -) +puts "✓ Quests upserted successfully" diff --git a/db/seeds/slots.rb b/db/seeds/slots.rb index f28acda..c6ad435 100644 --- a/db/seeds/slots.rb +++ b/db/seeds/slots.rb @@ -77,9 +77,11 @@ } ] -puts " - Création de #{slots.length} slots (1 gratuit, 4 payants)" +puts " - (Re)création de #{slots.length} slots (1 gratuit, 4 payants)" + +# Nettoyage idempotent fort: on réinitialise la table et la séquence pour garder des IDs 1..5 +ActiveRecord::Base.connection.execute("TRUNCATE TABLE slots RESTART IDENTITY CASCADE") -# Création des slots slots.each do |slot_data| Slot.create!(slot_data) end