型注釈なしで Ruby オブジェクトから YAML を生成する方法


Ruby オブジェクトを YAML にシリアライズするのは非常に簡単です. Ruby の標準ライブラリには、内部で Psych を使用する yaml モジュールが組み込まれています.必要なのは require 'yaml' だけで、準備完了です!しかし、落とし穴があります.
Photo および Album クラスがあり、AlbumPhoto の配列が含まれているとします. Album を YAML にシリアライズします.このようなスクリプトは次のようになります.

require 'yaml'

class Photo
  attr_reader :file

  def initialize(file)
    @file = file
  end
end

class Album
  attr_accessor :name, :photos

  def initialize(name, photos)
    @name = name
    @photos = photos
  end
end

photos = [Photo.new("DSC_0001.jpg"), Photo.new("DSC_0002.jpg"), Photo.new("DSC_0003.jpg")]
album = Album.new("Outdoors", photos)

puts album.to_yaml


上記のスクリプトは以下を出力します.

--- !ruby/object:Album
name: Outdoors
photos:
- !ruby/object:Photo
  file: DSC_0001.jpg
- !ruby/object:Photo
  file: DSC_0002.jpg
- !ruby/object:Photo
  file: DSC_0003.jpg


これらのクラス注釈型のものは何ですか?!それらは、アイテムがシリアル化されたオブジェクトのタイプを定義します.この YAML がデシリアライズされると、Ruby は各項目をそのクラス アノテーションで定義されたオブジェクトにデシリアライズしようとします.この YAML をスクリプトまたはアプリのコンテキストでのみ使用する場合は、問題ありません.ただし、持ち運びが必要な場合は😬.

クラス アノテーションなしでオブジェクトを YAML にシリアル化するには、まずオブジェクトを Hash に変換し、次に YAML に変換する必要があります.

この例は非常に単純なので、これらのオブジェクトを具体的に Hash es に変換するメソッドを手で書くだけで済みます.しかし、アプリが成長するにつれて、これは持続不可能になるため、任意のオブジェクトを Hash に変換する汎用モジュールを作成する方法を見てみましょう.

module Hashify
  # Classes that include this module can exclude certain
  # instance variable from its hash representation by overriding
  # this method
  def ivars_excluded_from_hash
    []
  end

  def to_hash
    hash = {}
    excluded_ivars = ivars_excluded_from_hash

    # Iterate over all the instance variables and store their
    # names and values in a hash
    instance_variables.each do |var|
      next if excluded_ivars.include? var.to_s

      value = instance_variable_get(var)
      value = value.map(&:to_hash) if value.is_a? Array

      hash[var.to_s.delete("@")] = value
    end

    return hash
  end
end


上記のモジュールを Photo および Album クラスに含めて、クラス アノテーションなしで YAML にシリアル化できるようになりました.

class Photo
  include Hashify

  ...
end

class Album
  include Hashify

  ...
end

photos = [Photo.new("DSC_0001.jpg"), Photo.new("DSC_0002.jpg"), Photo.new("DSC_0003.jpg")]
album = Album.new("Outdoors", photos)

puts album.to_hash.to_yaml


スクリプトは次のように出力されます.

---
name: Outdoors
photos:
- file: DSC_0001.jpg
- file: DSC_0002.jpg
- file: DSC_0003.jpg


これは単純な YAML であり、YAML シリアライザーを使用して任意の言語を使用して作成された任意のアプリで使用できます.

この記事は、最初に my blog に公開されました.