I want to create app for my own usage. I want to encourage my kids to eat more vegetables and fruits by creating a game. Game should have players, snacks and date. Every time, when game is created it should randomly assign selected in game snacks to selected in game players. When player eat randomly assigned snack, they gain points assigned to each snack and when player collect x points, they can exchange it for award. Each player has many games and could have many snacks in each game and each snack could belong to many games and to many players in each game.
I'm self learning beginner at rails, so if you have idea's how to modify it all, I'll be thankful for suggestions.
After few suggestions by random people, I got implement it all as below. So I'm stack at creating game, because I don't know how to after creating the game assign selected snacks to players because their id's are not saved and I don't know how to modify it. Probably I could change association's to be a mirror's like players associaton's but I'm not quite sure is it a good way.
Processing by GamesController#create as TURBO_STREAM
Parameters: {"authenticity_token"=>"[FILTERED]", "game"=>{"game_date(1i)"=>"2022", "game_date(2i)"=>"5", "game_date(3i)"=>"18", "player_ids"=>["1"], "snack_ids"=>["1"]}, "commit"=>"Create Game"}
Unpermitted parameter: :snack_ids. Context: { controller: GamesController, action: create, request: #<ActionDispatch::Request:0x00007fad6c1eed50>, params: {"authenticity_token"=>"[FILTERED]", "game"=>{"game_date(1i)"=>"2022", "game_date(2i)"=>"5", "game_date(3i)"=>"18", "player_ids"=>["1"], "snack_ids"=>["1"]}, "commit"=>"Create Game", "controller"=>"games", "action"=>"create"} }
Player Load (0.6ms) SELECT "players".* FROM "players" WHERE "players"."id" = ? [["id", 1]]
↳ app/controllers/games_controller.rb:27:in `create'
TRANSACTION (0.1ms) begin transaction
↳ app/controllers/games_controller.rb:30:in `block in create'
Player Exists? (0.5ms) SELECT 1 AS one FROM "players" WHERE "players"."name" = ? AND "players"."id" != ? LIMIT ? [["name", "Tata"], ["id", 1], ["LIMIT", 1]]
↳ app/controllers/games_controller.rb:30:in `block in create'
Game Create (1.4ms) INSERT INTO "games" ("game_date", "created_at", "updated_at") VALUES (?, ?, ?) [["game_date", "2022-05-18"], ["created_at", "2022-05-18 11:04:41.815456"], ["updated_at", "2022-05-18 11:04:41.815456"]]
↳ app/controllers/games_controller.rb:30:in `block in create'
PlayerGame Create (0.9ms) INSERT INTO "player_games" ("player_id", "game_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["player_id", 1], ["game_id", 10], ["created_at", "2022-05-18 11:04:41.820295"], ["updated_at", "2022-05-18 11:04:41.820295"]]
↳ app/controllers/games_controller.rb:30:in `block in create'
TRANSACTION (8.8ms) commit transaction
↳ app/controllers/games_controller.rb:30:in `block in create'
Redirected to http://localhost:3000/games/10
Completed 302 Found in 38ms (ActiveRecord: 13.3ms | Allocations: 9505)
Started GET "/games/10" for 127.0.0.1 at 2022-05-18 13:04:41 +0200
Processing by GamesController#show as TURBO_STREAM
Parameters: {"id"=>"10"}
Game Load (0.6ms) SELECT "games".* FROM "games" WHERE "games"."id" = ? LIMIT ? [["id", 10], ["LIMIT", 1]]
↳ app/controllers/games_controller.rb:66:in `set_game'
Rendering layout layouts/application.html.erb
Rendering games/show.html.erb within layouts/application
Rendered games/_game.html.erb (Duration: 1.3ms | Allocations: 222)
Rendered games/show.html.erb within layouts/application (Duration: 13.1ms | Allocations: 1433)
Rendered layout layouts/application.html.erb (Duration: 36.0ms | Allocations: 3738)
Completed 200 OK in 53ms (Views: 40.6ms | ActiveRecord: 0.6ms | Allocations: 5405)
Models associations looks like that :
class Game < ApplicationRecord
validates :game_date, comparison: { greater_than_or_equal_to: Date.today }
# after_create :assign_snacks_to_players
has_many :player_games
has_many :players, through: :player_games
has_many :snack_games
has_many :snacks, through: :snack_games
accepts_nested_attributes_for :snacks
end
class Player < ApplicationRecord
validates :name, length: {minimum: 3, maximum: 20}, uniqueness: true
validates :points, presence: true
validates :active, inclusion: { in: [true, false] }
has_many :player_games
has_many :games, through: :player_games
has_many :snack_games
has_many :snacks, through: :snack_games
end
class Snack < ApplicationRecord
validates :name, length: {minimum: 3, maximum: 20}, uniqueness: true
validates :points, presence: true
validates :active, inclusion: { in: [true, false] }
has_many :snack_games
has_many :games, through: :snack_games
end
class PlayerGame < ApplicationRecord
belongs_to :game
belongs_to :player
end
class SnackGame < ApplicationRecord
belongs_to :snack
belongs_to :game
belongs_to :player
end
Schema :
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# This file is the source Rails uses to define your schema when running `bin/rails
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2022_05_11_213227) do
create_table "games", force: :cascade do |t|
t.date "game_date", default: "2022-05-17", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "player_games", force: :cascade do |t|
t.integer "player_id", null: false
t.integer "game_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["game_id"], name: "index_player_games_on_game_id"
t.index ["player_id"], name: "index_player_games_on_player_id"
end
create_table "players", force: :cascade do |t|
t.string "name", null: false
t.integer "points", default: 0, null: false
t.boolean "active", default: true, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "snack_games", force: :cascade do |t|
t.integer "snack_id", null: false
t.integer "game_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "player_id", null: false
t.index ["game_id"], name: "index_snack_games_on_game_id"
t.index ["player_id"], name: "index_snack_games_on_player_id"
t.index ["snack_id"], name: "index_snack_games_on_snack_id"
end
create_table "snacks", force: :cascade do |t|
t.string "name", null: false
t.integer "points", default: 0, null: false
t.boolean "active", default: true, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_foreign_key "player_games", "games"
add_foreign_key "player_games", "players"
add_foreign_key "snack_games", "games"
add_foreign_key "snack_games", "players"
add_foreign_key "snack_games", "snacks"
end
I think player and snack controller's are not necessary so I include only games_controller
class GamesController < ApplicationController
before_action :set_game, only: %i[ show edit update destroy ]
# Find solution to assign players and snacks after create game not only from view but also from terminal
# GET /games or /games.json
def index
@games = Game.all
end
# GET /games/1 or /games/1.json
def show
end
# GET /games/new
def new
@game = Game.new
end
# GET /games/1/edit
def edit
end
# POST /games or /games.json
def create
@game = Game.new(game_params)
respond_to do |format|
if @game.save
format.html { redirect_to game_url(@game), notice: "Game was successfully created." }
format.json { render :show, status: :created, location: @game }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @game.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /games/1 or /games/1.json
def update
respond_to do |format|
if @game.update(game_params)
format.html { redirect_to game_url(@game), notice: "Game was successfully updated." }
format.json { render :show, status: :ok, location: @game }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @game.errors, status: :unprocessable_entity }
end
end
end
# DELETE /games/1 or /games/1.json
def destroy
@game.destroy
respond_to do |format|
format.html { redirect_to games_url, notice: "Game was successfully destroyed." }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_game
@game = Game.find(params[:id])
end
# Only allow a list of trusted parameters through.
def game_params
params.require(:game).permit(:game_date, player_ids: [], player_attributes: [:name])
end
end
and here is a form, but not correct with :snack_ids
<%= form_with(model: game) do |form| %>
<% if game.errors.any? %>
<div style="color: red">
<h2><%= pluralize(game.errors.count, "error") %> prohibited this game from being saved:</h2>
<ul>
<% game.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div>
<%= form.label :game_date, style: "display: block" %>
<%= form.date_select :game_date %>
</div>
<div>
<%= form.collection_check_boxes :player_ids, Player.where(active: true), :id, :name, include_hidden: false %>
</div>
<div>
<%= form.collection_check_boxes :snack_ids, Snack.where(active: true), :id, :name, include_hidden: false %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>
I know I could associate snack with player by creating in IRB for game SnackGame with player id and snack id but I dont know how to handle it in form and model