サービスのリストを記述する YAML を生成する次のような Ruby スクリプトがあるとします。
require 'yaml'
environment_slug = ENV.fetch('CI_ENVIRONMENT_SLUG')
YAML.dump([
{
'name' => 'service1',
'url' => "https://service1.#{environment_slug}.example.com",
},
{
'name' => 'service2',
'url' => "https://service2.#{environment_slug}.example.com",
},
], STDOUT)
ここで、サービス記述の生成を考慮に入れたいと思います。次の単純な試みは機能しません。environment_slug は外部スコープのローカル変数であり、したがってサービス メソッド内では使用できないためです。
require 'yaml'
environment_slug = ENV.fetch('CI_ENVIRONMENT_SLUG')
def service(name)
{
'name' => name,
'url' => "https://#{name}.#{environment_slug}.example.com",
}
end
YAML.dump([
service('service1'),
service('service2'),
], STDOUT)
考えられる解決策の 1 つは、define_method を使用することです。
require 'yaml'
environment_slug = ENV.fetch('CI_ENVIRONMENT_SLUG')
define_method :service do |name|
{
'name' => name,
'url' => "https://#{name}.#{environment_slug}.example.com",
}
end
YAML.dump([
service('service1'),
service('service2'),
], STDOUT)
ただし、define_method はかなり低レベルの機能であるように私には思えます。
別の可能な解決策は、定数を使用することです。
require 'yaml'
ENVIRONMENT_SLUG = ENV.fetch('CI_ENVIRONMENT_SLUG')
def service(name)
{
'name' => name,
'url' => "https://#{name}.#{ENVIRONMENT_SLUG}.example.com",
}
end
YAML.dump([
service('service1'),
service('service2'),
], STDOUT)
一貫性を保つために、おそらく (より複雑な例では) すべてのトップレベル変数を定数にするでしょう。ただし、すべて大文字で入力するのはかなり人間工学的ではなく、すべての「変数」に使用すると見た目も良くありません。
慣用的な Ruby コードを生成し、小さなスクリプト用に多くの定型文を作成したり、物事がより複雑になったときにすべてを再構築したりする必要がないスクリプトを作成するときの良い戦略は何でしょうか?
これはさまざまな方法で解決できますが、私見では、これをクラスにラップし、アクセサーを使用して Service#environment_slug を呼び出すのが最善です。 「url」キーの値を変更して、代わりにインスタンス変数 @environment_slug をレンダリングすることもできますが、これは主に好みとプログラマの意図の問題です。
require "yaml"
class Service
attr_reader :environment_slug
def initialize
@environment_slug = ENV.fetch("CI_ENVIRONMENT_SLUG")
end
def service(name)
{
"name" => name,
"url" => "https://#{name}.#{environment_slug}.example.com",
}
end
# Send your YAML to standard output and also return a value.
# Kernel#p will quote the output, so it costs you an extra
# line in your method to do them as separate operations.
def print
puts yml = [service('service1'), service('service2')].to_yaml
yml
end
end
if __FILE__ == $0
# Avoid KeyError when testing, or set a sensible default for
# ENV#fetch in your code above.
ENV["CI_ENVIRONMENT_SLUG"] = "foo"
Service.new.print
end
これにより、次が標準出力に出力されます。
---
- name: service1
url: https://service1.foo.example.com
- name: service2
url: https://service2.foo.example.com
また、値も返します。これは、メソッドのテストや連鎖メソッドに適していることがよくあります。