Last Updated: February 25, 2016
·
1.782K
· danielpclark

Create a Game Instance in Rails

So you need to load a class instance in Rails for your visitors to play your game. But Rails generally is a static website with static database lookups. So for this we're going to assign a class instance to a game room.

First let's generate our project; we're going to start with a prebuilt devise/bootstrap starter. Here's a quick way to get started in Mac/Linux. Otherwise you can manually add at least devise to fully utilize this code.

wget bit.ly/newrails
chmod +x newrails
newrails GameServer
cd GameServer

Then lets use the rails generator to build the game room scaffolding:

rails g scaffold Game room:string:index min_players:integer max_players:integer kind:integer:index instance:integer
rake db:migrate

This should get a lot of the work done for us, but let's tidy up some of the generated output. Open the file app/assets/stylesheets/games.css.scss and drop this in.

table { 
    border-spacing: 10px;
    border-collapse: separate;
}

td {
  padding-left: 8px;
  padding-right: 8px;
  margin-left: 8px;
  margin-right: 8px;
}

Then open up your partial view for the form in app/views/games/_form.html.erb and change the form fields to this:

<div class="field">
  <%= f.label :room %><br>
  <%= f.text_field :room %>
</div>
<div class="field">
  <%= f.label :min_players %><br>
  <%= f.select :min_players, [2,3,4,5], selected: 2 %>
</div>
<div class="field">
  <%= f.label :max_players %><br>
  <%= f.select :max_players, [2,3,4,5], selected: 5 %>
</div>
<div class="field">
  Game: <%= f.select :kind, Game.kinds.keys, selected: :holdem %>
</div>
<div class="actions">
  <%= f.submit %>
</div>

You'll notice that we've removed the "instance" field, and we've changed 3 of the other fields to select. But don't run it yet, because we need to define the model for Game.kinds. Here's the model app/models/games.rb

class Game < ActiveRecord::Base
  enum kind: [ :holdem, :gin, :rummy ]
  validates_presence_of :room
  validates_presence_of :min_players
  validates_presence_of :max_players
  validates :min_players, numericality: { less_than_or_equal_to: :max_players, message: "must be less than, or equal to, Max players!" }
end

Alright now we have the validations, and the game type enumerator built-in so your partial form view should work. (in this example we won't be using gin or rummy)

Next let's add our game loader. To do this we'll create a helper method in our app/controllers/application_controller.rb file

def load_game(klass, id=nil)
  ObjectSpace.each_object(klass).to_a.select {|obj| obj::__id__ == id.to_i}.first || klassy = klass.new
end
helper_method :load_game

Note that the purpose of the function is to reference the "current game" that the game room is running by the Class::__id__ method. We will hand this a game class, such as HoldEm, and then it will be sure to return the correct running instance of the class HoldEm for us to use in Rails. And if an instance isn't running then the klassy = klass.new part runs calling HoldEm.new and returns the instance of it. NOTE: you need to have the assignment here of klassy = or else the class instance WILL DISAPPEAR since it wasn't assigned "within" the scope in which it was declared.

Now let's look at how we'll use this in our games controller. What we're going to do is use the "show" page as the actual game room; so the game only needs to be running in that page. So we'll modify the show method in app/controllers/games_controller.rb to this:

def show
  @card_game = load_game(HoldEm, (@game.instance? ? @game.instance : nil))
  unless @game.instance == @card_game::__id__
    @game.update_attribute(:instance, @card_game::__id__)
  end
end

You should know that @game is already declared in before_action :set_game, only: [:show, :edit, :update, :destroy] at the beginning of the file. Here @card_game gets assigned the actual game object we want to start up, or continue, and then makes sure the class id is saved in the database table, for this game room, under instance. As long as the game is running this game room will properly load the correct game class. And to perform any of your game functions you can just call them on @card_game.

Now let's update the show view so you can see the class instance being used. Change your app/views/games/show.html.erb file to this:

<p id="notice"><%= notice %></p>
<p>
  <strong>Room:</strong>
  <%= @game.room %> <span style="color:grey;">(<%= @card_game::__id__ %>)</span>
</p>
<p>
  <strong>Min players:</strong>
  <%= @game.min_players %>
</p>
<p>
  <strong>Max players:</strong>
  <%= @game.max_players %>
</p>
<p>
  <strong>Kind:</strong>
  <%= @game.kind %>
</p>
<%= link_to 'Edit', edit_game_path(@game) %> |
<%= link_to 'Back', games_path %>

If you've run into any problems yet it may be because you don't have a HoldEm class defined. For now you can create a lib/holdem.rb file and put the bare-bones in it:

class HoldEm
end

And then make sure it's loaded from the lib folder. In my case though I felt it was more at home being loaded within the controller. So in my application controller I added one line:

class ApplicationController < ActionController::Base
  require Rails.root.join('lib','holdem.rb')

Now one last change to make. In your visitors controller we need to add a redirect. So open app/controllers/visitors_controller.rb and add this:

class VisitorsController < ApplicationController
  def index
    if user_signed_in?
      redirect_to games_url
    end
  end
end

Now everything should be ready. Run your rails server and create an account. Once you're signed into devise it will redirect you to the list of game rooms. Create one, then on the show page click refresh as often as you like and you will see the class id right next to the game room name. This will run faithfully for you, if you want to prove a case where the game class itself disappears then try removing klassy = in the application_controller and refresh several times. You will now see the number change ever so often. Be sure to put it back!

Congratulations! You now have control over running your game class per game room and ensuring the class is properly loaded!

If you're worried about the possibility of too many classes "some how" being created you can add this within the game_controllers destroy method

@card_game = nil
ObjectSpace.garbage_collect

Although we're not given too much control when it comes to garbage collection, this does seem to destroy any objects that are no longer assigned.

Well I hope this was very helpful for you to get your game room created in Rails with it's own game instance! Now make the games, and have fun!

Share, Comment, Code, and God Bless!

-Daniel P. Clark
@6ftdan