Module: QueryStream::DataResolver

Defined in:
lib/query_stream/data_resolver.rb

Overview

データファイル探索・読み込みモジュール

Constant Summary collapse

EXTENSIONS =

サポートする拡張子(優先順位順)

%w[.yml .yaml .json].freeze
YAML_PERMITTED_CLASSES =

YAML.safe_load_file で許可するクラス Symbol: symbolize_names: true のために必要 Time/Date/DateTime: 日付/時刻を含む実用データに対応

[Symbol, Time, Date, DateTime].freeze

Class Method Summary collapse

Class Method Details

.find_with_extensions(base_name, data_dir) ⇒ String?

指定名ですべての拡張子を試行する

Parameters:

  • base_name (String)

    拡張子なしファイル名

  • data_dir (String)

    データディレクトリ

Returns:

  • (String, nil)

    見つかったファイルパス、または nil



126
127
128
129
130
131
132
# File 'lib/query_stream/data_resolver.rb', line 126

def find_with_extensions(base_name, data_dir)
  EXTENSIONS.each do |ext|
    path = File.join(data_dir, "#{base_name}#{ext}")
    return path if File.exist?(path)
  end
  nil
end

.load_records(file_path) ⇒ Array<Hash>

レコード群をファイルから読み込む

YAML は safe_load_file で読み込み、!ruby/object 等の危険なタグを QueryStream::DataLoadError に変換して呼び出し元に通知する。

Parameters:

  • file_path (String)

    データファイルパス

Returns:

  • (Array<Hash>)

    レコード群(シンボルキー)

Raises:

  • (DataLoadError)

    YAML/JSON の構文エラー、許可されていないクラス等



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/query_stream/data_resolver.rb', line 69

def load_records(file_path)
  records = case File.extname(file_path).downcase
            when '.json'
              JSON.parse(File.read(file_path, encoding: 'utf-8'), symbolize_names: true)
            else
              YAML.safe_load_file(file_path,
                                  permitted_classes: YAML_PERMITTED_CLASSES,
                                  aliases: true,
                                  symbolize_names: true)
            end

  normalize_records(records, file_path)
rescue Psych::DisallowedClass => e
  raise DataLoadError.new(
    "データファイルに許可されていないクラス/タグが含まれています: #{e.message} (#{file_path})",
    file_path: file_path, cause_error: e
  )
rescue Psych::SyntaxError => e
  raise DataLoadError.new(
    "データファイルの YAML 構文エラー: #{e.message} (#{file_path})",
    file_path: file_path, cause_error: e
  )
rescue JSON::ParserError => e
  raise DataLoadError.new(
    "データファイルの JSON 構文エラー: #{e.message} (#{file_path})",
    file_path: file_path, cause_error: e
  )
end

.normalize_records(records, file_path) ⇒ Array<Hash>

読み込み結果をレコード配列へ正規化する。

常に配列を返すのが本メソッドの契約である。 空のデータファイルは YAML が nil を返すため、素通しすると呼び出し先(TemplateCompiler)が undefined method 'any?' for nil で落ちる——データを空にしただけの著者に NoMethodError を見せることになる。

配列でもハッシュでもない中身(先頭が文字列や数値だけの YAML)は、 レコード群として解釈しようがないので DataLoadError で理由を伝える。

Returns:

  • (Array<Hash>)


108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/query_stream/data_resolver.rb', line 108

def normalize_records(records, file_path)
  case records
  when nil then []
  when Hash then [records]
  when Array then records
  else
    raise DataLoadError.new(
      "データファイルはレコードの配列(またはハッシュ 1 件)である必要があります" \
      "#{records.class} が入っています): #{file_path}",
      file_path: file_path
    )
  end
end

.resolve(source_name, data_dir) ⇒ String?

データファイルのパスを解決する 指定名そのまま → 複数形(末尾に s を付与)→ 単数形の順で探索する

Parameters:

  • source_name (String)

    データ名(単数形または複数形)

  • data_dir (String)

    データディレクトリ

Returns:

  • (String, nil)

    見つかったファイルパス、または nil



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/query_stream/data_resolver.rb', line 42

def resolve(source_name, data_dir)
  # そのまま試行
  found = find_with_extensions(source_name, data_dir)
  return found if found

  # 複数形を試行(単数形→複数形: book → books)
  found = find_with_extensions("#{source_name}s", data_dir)
  return found if found

  # 単数形を試行(複数形→単数形: books → book)
  singular = Singularize.call(source_name)
  if singular != source_name
    found = find_with_extensions(singular, data_dir)
    return found if found
  end

  nil
end