有没有更好的方法在 Ruby 中添加 if not nil?

Is there a nicer way to prepend if not nil in Ruby?

我有一堆环境变量,里面有我的配置:

DB_HOSTNAME=something.rds.amazonaws.com
DB_PORTNUM=9999
DB_USERNAME=production
DB_PASSWORD=xyzzy

要从它们创建数据库连接字符串,它类似于:

"postgres://" +
"#{ENV['DB_USERNAME']}#{ENV['DB_PASSWORD'] ? ":#{ENV['DB_PASSWORD']}" : nil}" + 
"@#{ENV['DB_HOSTNAME']}#{ENV['DB_PORTNUM'] ? ":#{ENV['DB_PORTNUM']}" : nil}" +
"/proper_scraper_#{$environment}"

这样它在 development/test 中工作,其中 DB_PASSWORDDB_PORTNUM 没有设置,并且在它们所在的生产环境中工作。但是这个有点丑:

ENV['DB_PASSWORD'] ? ":#{ENV['DB_PASSWORD']}" : nil

所需的语义是:如果不是 nil 则添加前缀,否则 return nil。理想情况下应该是这样的:

ENV['DB_PASSWORD'].try(:prepend, ':')

使用 Object.try 这样的东西:

  def try method, *args
    send(method, *args) if respond_to? method
  end

但这不起作用,因为前置会改变字符串(为什么?)并且 env 字符串被冻结。备选方案:

ENV['DB_PASSWORD'].dup.try(:prepend, ':')

但是当没有设置环境变量时这不起作用,因为你不能复制nil。

这里有漂亮的单线还是我陷入了混乱?

不幸的是,String#insertString#prepend 都修改了字符串,但 String#sub 应该可以:

ENV['DB_PASSWORD'].try(:sub,'',':')

或者多一点意图:

ENV['DB_PASSWORD'].try(:sub,/^/,':')

如果 Object#try 恰好支持块(如 ActiveSupport 的),

ENV['DB_PASSWORD'].try { |s| ":#{s}" }

使用对象和标准库:

require 'uri'

u = URI::Generic.build(
    scheme: "postgres", 
    host: ENV["DB_HOSTNAME"], 
    port: ENV["DB_PORTNUM"], 
    path: "/proper_scraper_#{$environment}",
)

u.user = ENV["DB_USERNAME"]
u.password = ENV["DB_PASSWORD"]

puts u.to_s

呃。您的可读性确实受到了影响,因为您试图在一行中完成所有操作。不要那样做。

我会这样做:

为示例设置 ENV...

ENV['DB_HOSTNAME'] = 'something.rds.amazonaws.com'
ENV['DB_PORTNUM'] = '9999'
ENV['DB_USERNAME'] = 'production'
ENV['DB_PASSWORD'] = 'xyzzy'

真正的代码从这里开始:

$environment = 'production'
db_hostname, db_portnum, db_username, db_password = %w[
  DB_HOSTNAME
  DB_PORTNUM
  DB_USERNAME
  DB_PASSWORD
].map{ |e| ENV[e] }

db_password = ':' + db_password if db_password
db_portnum = ':' + db_portnum if db_portnum

DSN = "postgres://%s%s@%s%s%s" % [
  db_username,
  db_password,
  db_hostname,
  db_portnum,
  "/proper_scraper_#{$environment}"
]
DSN # => "postgres://production:xyzzy@something.rds.amazonaws.com:9999/proper_scraper_production"

三元语句是 if/then/else 语句的替代,而不是简单的 if/then。试图让它们适合只会导致代码混乱,所以不要去那里。

人们热衷于在一行中塞满代码,很久以前,当我们使用解释型 BASIC 时,它有助于加快代码速度,但今天的语言很少能从中受益。相反,发生的事情是它使代码无法破译。编写难以理解的代码是在代码审查中被要求解释自己的快速途径,然后被告知重写它并且永远不再这样做。

从 ruby 2.3 您可以使用 safe navigation operator in combination with String#sub (As @Matt )

ENV["DB_PASSWORD]&.sub(/^/, ":")