僕は技術のキャッチアップのために、定期的にgithubのトレンドを見ています。
最近、Rubyのカテゴリで「faker」という昔から使われているフェイクデータを作成するgemがよくトレンドに上がっており、リリースも今現在(2023年6月)も活発にされているようです。
fakerのようなフェイクデータの作成を行うライブラリは様々な言語で公開されており、単体テストを書く際に重宝しています。ユーザー名やメールアドレスを作成してもらえるのでデータの用意が非常に楽になります。fakerの存在自体は前から知っていたのですが、どのように実装されているのか、裏側で何が処理されているのかまでは知りませんでした。
せっかくトレンドで遭遇したので、fakerがどのように作られているのか裏側を見てみようと思います。
まずはfaker.rbから
gemのコードを読む場合はlib直下に配置されているgem名.rb
のファイルから見ていくと流れが追いやすいです。
今回はlib/faker.rbがあるので、このファイルから見ていきます。ざーっと目を通してみると設定値を保持させているFaker::Config
モジュールとFaker::Base
クラスの二つが定義されていました。Faker::Base
クラスに定義されている関数はどんな役割を持っているかはまだ分からないため、一旦、飛ばします。
lib/faker.rb
module Faker module Config class << self def locale=(new_locale) Thread.current[:faker_config_locale] = new_locale end : end end class Base class << self attr_reader :flexible_key : def fetch(key) : end : end end end
faker/faker.rb at main · faker-ruby/faker · GitHub
fakerの関数が呼び出される時
次は実際にfakerの関数が呼び出される際の処理の流れをコードを見て追ってみます。
なんとスタジオジブリの映画に登場するキャラクターの名前を取得できる関数があるので、こちらを見ていきます。
Faker::JapaneseMedia::StudioGhibli.character #=> "Chihiro"
lib/faker/japanese_mediaの配下にstudio_ghibli.rbというファイルが定義されています。
呼び出しているのはこの関数で間違いないようです。
lib/faker/japanese_media/studio_ghibli.rb
# frozen_string_literal: true module Faker class JapaneseMedia class StudioGhibli < Base class << self : def character fetch('studio_ghibli.characters') end : end end end end
faker/studio_ghibli.rb at main · faker-ruby/faker · GitHub
このファイルと、他のファイルも合わせて見てみると共通点が見えてきます。
lib/faker配下のファイルは全てmodule Faker
の内部にデータ名を表すクラス(eg: StudioGhibli)を定義しており、どのクラスもfaker.rbで定義されていたBaseクラスを継承しています。moduleではなくクラスを定義しているのはクラスメソッドとして呼び出せるのが楽という点と、Baseクラスを継承したいからだと思われます。
新しくjapanese_mediaにコードギアス(code_geass)を追加したければ、faker/japanese_media/code_geass.rbを作成し、Baseクラスを継承させるだけでコードの対応は完了です。非常に拡張性に富んだ設計になっていますね...。
- ジブリ: faker/japanese_media/studio_ghibli.rb
- スターウォーズ: lib/faker/movies/star_wars.rb
- 住所: lib/faker/default/address.rb
- (NEW) コードギアス: faker/japanese_media/code_geass.rb
そして、多くの場合にはデータ名を表すクラスはBaseクラスに定義されているfetch関数を呼び出しています。
faker/japanese_media/studio_ghibli.rb
def character fetch('studio_ghibli.characters') end
ということで、次はBaseクラスのfetch関数を見ていきましょう。
Baseクラス: fetch関数について
fetch関数は引数として受け取った値(eg: studio_ghibli.characters
)をtranslate関数からsample関数へ受け渡しているようです。
sample関数とtranslate関数も同じくBaseクラスに定義されている関数です。
lib/faker.rb
def fetch(key) fetched = sample(translate("faker.#{key}")) if fetched&.match(%r{^/}) && fetched&.match(%r{/$}) # A regex regexify(fetched) else fetched end end
translate関数
translate関数は内部でi18nという国際化対応のgemを使用しています。
i18nを使うことでymlファイルに定義された値を読み取る事が可能となり、コード内に大量を値を定義せずに済みます。またコードの修正をせずにymlファイルだけを変更すれば値の追加・削除が可能というのも嬉しいポイントです。
lib/faker.rb
def translate(*args, **opts) opts[:locale] ||= Faker::Config.locale opts[:raise] = true I18n.translate(*args, **opts) rescue I18n::MissingTranslationData : end
translate関数は引数の受け取り方が面白いです。
第1引数は配列、第2引数はハッシュとして可変長に値が受け取れるため、呼び出し側で複数のキーやオプションをまとめて指定することが可能となっています。
def translate(*args, **opt) puts args.class # Array puts args # studio_ghibli.characters puts opt.class # Hash puts opt # {} end translate('studio_ghibli.characters')
先ほど、fetch関数に指定されていたstudio_ghibli.characters
に対応するymlファイルがありました。
中を見てみるとquotes(名言)の項目には「バルス」だったり「親方!空から女の子が!」が定義されていて、とても面白いですね。
lib/locales/ja/studio_ghibli.ym
ja: faker: studio_ghibli: quotes: - "親方!空から女の子が!" - "バルス" - "40秒で仕度しな!"
faker/studio_ghibli.yml at main · faker-ruby/faker · GitHub
I18n.translateについて
深掘ると長くなってしまうので、手短に紹介します。
この関数は与えられたkeyに合致した値の一覧を配列で返してくれます。
translate('studio_ghibli.characters') # ["荻野 千尋", "ススワタリ", "湯婆婆"]
配列で値が返ってくるのは、i18n側で第1引数が配列で与えられた場合に配列を返すように実装されているからです。
# i18nのtranslate関数 def translate(key = nil, throw: false, raise: false, locale: nil, **options) # TODO deprecate :raise : if key.is_a?(Array) key.map do |k| translate_key(k, throw, raise, locale, backend, options) end else translate_key(key, throw, raise, locale, backend, options) end end alias :t :translate
sample関数とその後
sample関数は名前の通り、取得した値の一覧からランダムに値を1件取得してくれる関数です。
sample(["荻野 千尋", "ススワタリ", "湯婆婆"]) # "ススワタリ"
値の取得後、正規表現を使った条件によって処理が分岐します。この条件は値が/
で始まり、/
で終わっている時に真となります。
fetched&.match(%r{^/}) && fetched&.match(%r{/$}) # /foo/ # /foo/hoge/
条件に合致しなければ、取得した値(fetched)がそのまま返されてfetch関数の処理は終わりです。
これでfakerを使って値が取得されるまでの処理の流れを追い切ることができました。
まとめ
- faker.rbには多くのクラスが継承するBaseクラスが定義されている
- データ名を表すクラスはBaseクラスを継承しており、Baseクラスに定義された関数を呼び出している
- ファイル構成に規則性があるため、新たなデータの追加が簡単
- 内部ではi18nを使っており、値をymlファイルに定義している
- ymlファイルで値を管理しているため、値の追加・削除が簡単
構造や処理がシンプルながらも拡張性を確保するために上手く作られており、とても勉強になりました。
fakerのように共通する処理を持つ多種多様なクラスを定義する必要がある場合・大量の値を定義する必要がある場合にはfakerの実装が参考になりそうです。
少しでも「ええな〜」と思ったらイイネ!・シェア!・はてなブックマークを頂けると励みになります。