如何将代码分解为 Ruby 脚本中的方法,同时保留对外部作用域的访问权限?

问题描述 投票:0回答:1

假设我有一个如下所示的 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 代码的脚本时,什么是一个好的策略,并且不需要我为小脚本编写大量样板或在事情变得更复杂时重构所有内容?

ruby closures idioms lexical-scope
1个回答
0
投票

实例变量、访问器和插值

您可以通过很多不同的方式解决这个问题,但恕我直言,最好的方法是将其包装在一个类中并使用访问器调用 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 ”

© www.soinside.com 2019 - 2024. All rights reserved.