T.Aoukar
T.Aoukar

Reputation: 663

Rails manual commit records

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

Answers (2)

max
max

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.

  1. User 1 creates the game POST /games
  2. User 1 is redirected to GET /games/1
  3. Since there is no land the User 1 would create a land by filling in a form and which POSTS to /games/1/lands. He is then redirected back to /games/1.
  4. The user can now see the land we just created in the game view.
  5. User 1 decides to build a pig pen in Land 1. He presses the button which sends a 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

ssuljic
ssuljic

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

Related Questions