简体   繁体   中英

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

I have a bunch of environment variables with my config in them:

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

To make a DB connect string from them it's something like:

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

That way it works in development/test where DB_PASSWORD and DB_PORTNUM aren't set, and works in production where they are. But this bit is ugly:

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

The desired semantics are: prepend if not nil and return nil otherwise. Ideally it would be something like this:

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

Using Object.try something like this:

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

But that doesn't work because prepend mutates the string (why?) and env strings are frozen. The alternative:

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

But this doesn't work when the environment variable is not set because you can't dup nil.

Is there a nice one-liner here or am I stuck with the messiness?

Use objects and the standard library:

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

It's unfortunate that both String#insert and String#prepend modify the strings, but String#sub should work:

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

Or with a bit more intention:

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

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

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

从ruby 2.3您可以将安全导航运算符String#sub结合使用(如@Matt 指出的

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

Ugh. Your readability has really suffered because you're trying to do it all in a single line. Don't do that.

I'd do something like:

Set up the ENV for the example...

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

The real code starts here:

$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"

Ternary statements are replacements for if / then / else statements, not for simple if / then . Trying to make them fit only results in confusing code, so don't go there.

People get enamored with cramming code all in one line, and, a long time ago, back when we used interpreted BASIC, it helped speed up the code, but today's languages rarely benefit from that. Instead, what happens is it makes code indecipherable. Writing code that is indecipherable is a fast path to being called up to explain yourself in a code-review, followed by being told to rewrite it and never do it again.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM