假设我有一个如下所示的 Ruby 脚本来生成描述服务列表的 YAML:
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
是外部作用域的局部变量,因此在 service
方法内不可用:
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)
一种可能的解决方案是使用
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
它还会返回一个值,这对于测试或链式方法来说通常是一个好主意:
”--- - 名称:服务1 网址:https://service1.foo.example.com - 名称:服务2 网址:https://service2.foo.example.com ”