Ruby Serialising and deserialising json

47 Views Asked by At

I have this two function to serialise and deserialise game state. I got it working okay. But It feels very cluttered and messy. Is there a better approach of doing this?

There is two different class variables I need to serialise one from Main class and three from Game class which is parent of guess class. Below methods are called from Main class:

  def save_game(obj)
    saved_data = {
      score: obj.score,
      secret_word: obj.new_guess.secret_word,
      running_guess: obj.new_guess.running_guess,
      not_in_word: obj.new_guess.not_in_word
    }.to_json
    save_file(saved_data)
  end

  def load_game(obj)
    saved_data = JSON.load_file('saved/saved_game.json')
    obj.score = saved_data['score']
    obj.new_guess.secret_word, = saved_data['secret_word']
    obj.new_guess.running_guess = saved_data['running_guess']
    obj.new_guess.not_in_word = saved_data['not_in_word']
    display_secret_hidden(obj.new_guess.running_guess)
    display_not_included_letters(obj.new_guess.not_in_word)
    play_rounds
  end

I am trying to understand this find alternate solution but so far unable to do this in another way.

1

There are 1 best solutions below

0
Todd A. Jacobs On

You're Missing a Data Object

Whether it's one of Ruby's new Data Objects, or an old-fashioned Struct like Mom used to make, you'd be better off passing around instance variables for some kind of data structure than trying to serialize each element on the fly in different places.

Whether you're serializing to JSON, YAML, or a binary format like Marshal, there are ways to make your code a lot cleaner. Marshal and YAML do a better job of serializing objects; JSON is a litle more limited in that regard unless you take some explicit steps. So, I'm going to demonstate with Marshal for ease of revivifying serializd objects.

The following example wraps your game saves into a Games class with accessible attributes for @file and @save_data. Consider the following:

class Game
  attr_accessor :file, :save_data
  SaveData =
    Struct.new *%i[score secret_word running_guess not_in_word]

  def initialize(filename="game_save.bin", **kwargs)
    @file = filename
    @save_data = SaveData.new
    @save_data.members.map { @save_data[_1] = kwargs[_1] }
  end

  def save
    File.open(@file, ?w) { Marshal.dump @save_data, _1 }
  end

  def load
    @save_data = Marshal.load File.read(file)
  end
end

if __FILE__ == $0
  # create a new game and populate some data.
  game = Game.new score: 1, foo: 2, not_in_word: 3

  # save the game to a file, then clobber the file
  # and display the now-empty Struct.
  game.save
  game.save_data = Game::SaveData.new
  game.save_data

  # load and display the previous save
  game.load
end

Using a Struct gives you a data object that you can pass around, and they're ideal for this purpose. By combining a Struct object with the destructuring of a globbed keyword aruument, you vastly simplify and DRY up your code without losing anything.