Backo
Backo

Reputation: 18901

Building a hash in a conditional way

I am using Ruby on Rails 3.0.10 and I would like to build an hash key\value pairs in a conditional way. That is, I would like to add a key and its related value if a condition is matched:

hash = {
  :key1 => value1,
  :key2 => value2, # This key2\value2 pair should be added only 'if condition' is 'true'
  :key3 => value3,
  ...
}

How can I do that and keep a "good" readability for the code? Am I "forced" to use the merge method?

Upvotes: 53

Views: 30655

Answers (11)

tokland
tokland

Reputation: 67900

A functional/immutable approach with Hash#compact:

hash = {
  key1: 1,
  key2: (2 if condition),
  key3: 3,
}.compact

Upvotes: 49

spirito_libero
spirito_libero

Reputation: 1332

In case you want to add few keys under single condition, you can use merge:

hash = {
  :key1 => value1,
  :key2 => value2,
  :key3 => value3
}

if condition
  hash.merge!(
    :key5 => value4,
    :key5 => value5,
    :key6 => value6
  )
end

hash

Upvotes: 2

dwhalen
dwhalen

Reputation: 1925

Probably best to keep it simple if you're concerned about readability:

hash = {}
hash[:key1] = value1
hash[:key2] = value2 if condition?
hash[:key3] = value3
...

Upvotes: 14

Mladen Jablanović
Mladen Jablanović

Reputation: 44110

Keep it simple:

hash = {
  key1: value1,
  key3: value3,
}

hash[:key2] = value2 if condition

This way you also visually separate your special case, which might get unnoticed if it is buried within hash literal assignment.

Upvotes: 7

Dushyant
Dushyant

Reputation: 4960

Simple as this:

hash = {
  :key1 => value1,
  **(condition ? {key2: value2} : {})
}

Hope it helps!

Upvotes: 3

Dave Morse
Dave Morse

Reputation: 757

I use merge and the ternary operator for that situation,

hash = {
  :key1 => value1,
  :key3 => value3,
  ...
}.merge(condition ? {:key2 => value2} : {})

Upvotes: 4

Excalibur
Excalibur

Reputation: 3367

Using fetch can be useful if you're populating a hash from optional attributes somewhere else. Look at this example:

def create_watchable_data(attrs = {})
  return WatchableData.new({
    id:             attrs.fetch(:id, '/catalog/titles/breaking_bad_2_737'),
    titles:         attrs.fetch(:titles, ['737']),
    url:            attrs.fetch(:url, 'http://www.netflix.com/shows/breaking_bad/3423432'),
    year:           attrs.fetch(:year, '1993'),
    watchable_type: attrs.fetch(:watchable_type, 'Show'),
    season_title:   attrs.fetch(:season_title, 'Season 2'),
    show_title:     attrs.fetch(:id, 'Breaking Bad')
  })
end

Upvotes: 0

Russell
Russell

Reputation: 12449

I prefer tap, as I think it provides a cleaner solution than the ones described here by not requiring any hacky deleting of elements and by clearly defining the scope in which the hash is being built.

It also means you don't need to declare an unnecessary local variable, which I always hate.

In case you haven't come across it before, tap is very simple - it's a method on Object that accepts a block and always returns the object it was called on. So to build up a hash conditionally you could do this:

Hash.new.tap do |my_hash|
  my_hash[:x] = 1 if condition_1
  my_hash[:y] = 2 if condition_2
  ...
end

There are many interesting uses for tap, this is just one.

Upvotes: 72

Martin DeMello
Martin DeMello

Reputation: 12346

Same idea as Chris Jester-Young, with a slight readability trick

def cond(x)
  condition ? x : :delete_me
end

hash = {
  :key1 => value1,
  :key2 => cond(value2),
  :key3 => value3
}

and then postprocess to remove the :delete_me entries

Upvotes: -1

Dmitry Maksimov
Dmitry Maksimov

Reputation: 2861

IF you build hash from some kind of Enumerable data, you can use inject, for example:

raw_data.inject({}){ |a,e| a[e.name] = e.value if expr; a }

Upvotes: 1

C. K. Young
C. K. Young

Reputation: 223193

First build your hash thusly:

hash = {
  :key1 => value1,
  :key2 => condition ? value2 : :delete_me,
  :key3 => value3
}

Then do this after building your hash:

hash.delete_if {|_, v| v == :delete_me}

Unless your hash is frozen or otherwise immutable, this would effectively only keep values that are present.

Upvotes: 0

Related Questions