Reputation: 663
I'm trying to build a small 2 players turn based game using Rails, to initialize the game only 1 player is needed who will set the field then another player can join. The each player in the game will have a land that can contain buildings, the relationship between them as follow:
#Game
has_many :lands
#Land
belongs_to :game
has_many :buildings
#Building
belongs_to :game
Only the game has a controller since it's the master of them all, so when a game is to be initialized the request will contain info to create the land and buildings, and all are treated as one, so if one of those records fail I want to not commit anything. I thought of using building.save if land.save
but it'll produce error since I'm saving a building to a land that doesn't exit but if I save the land first and the building fail then I'll need to delete the land and game, it gets complicated with multiple buildings going and various errors coming from multiple places to handle all those conditions.
What other options can I use to achieve this??
Edit: Game controller will be something like this:
class GamesController < ApplicationController
def create
#generate new land to contain buildings
land = Land.new(user: @current_user)
#generate new buildings from array of hashes, contains coords+land_id
buildings = []
params[:buildings].each do |building|
buildings.push Building.new(building.merge!({land: land}))
end
game = Game.new(user_1: @current_user, land_1: land)
land.game = game #set the game it belongs to
#some code here to save land+game+buildings
#if one of them failed then nothing is saved at all.
end
end
There's also the problem that I can't save game like this because it validates existence of land, and can't save land because it validates existence of game, same goes for buildings they validate existence of land. So I need a code that'll save them in one go and still success in validating them all.
Upvotes: 1
Views: 1631
Reputation: 102134
In Rails you can use accepts_nested_attributes_for
to create nested models from the same form:
class User < ActiveRecord::Base
has_many :pets
accepts_nested_attributes_for :pets
validates_associated :pet
end
class UserController < ApplicationController
def new
@user = User.new
@user.pets.build
end
def create
@user
end
def pet_params
params.require(:user).permit(pets_attributes: [:name, :type])
end
end
You also have transactions which can be used to insert records into the database and the roll back if a later condition fails.
class OrdersController < ApplicationController
def create
@order = Order.new(order_params)
Order.transaction do
@payment = Payment.new(customer: @order.customer, amount: order.amount)
raise ActiveRecord::Rollback unless @order.save && @payment.save
end
end
end
But even more important is not trying to do everything in one controller action. It leads to really brittle and overcomplicated designs.
Instead you might want to do your game setup in a number of steps.
POST /games
GET /games/1
/games/1/lands
. He is then redirected back to /games/1
. POST /lands/1/buildings
. But User 1 does not have enough resources so the validation for Building fails. We show the error messages to the user.As you have probably guessed by - browser game design requires the extensive use of ajax and javascript. Building a server side API is just a small part.
Upvotes: 0
Reputation: 1081
You can wrap the queries in a transaction:
ActiveRecord::Base.transaction do
# put your calls here
end
And you should use .save!
method, so that the exceptions created by validations get thrown, otherwise the queries fail silently.
Upvotes: 1