Hash Syntax in Ruby

Need to refactor to the new Ruby 1.9 hash syntax

In the Ruby version 1.9 has been introduced a new syntax for hash literals whose keys are symbols. Hashes use the "hash rocket" operator to separate the key and the value:

a_hash = { :a_key => 'a_value' }

In Ruby 1.9 this syntax is valid, but whenever the key is a symbol it's also possible to write it as:

a_hash = { a_key: 'a_value' }

And as the Ruby style guide says, you should prefer to use the Ruby 1.9 hash literal syntax when your hash keys are symbols (see):

# bad
hash = { :one => 1, :two => 2, :three => 3 }

# good
hash = { one: 1, two: 2, three: 3 }

And as an additional hint: Don't mix the Ruby 1.9 hash syntax with hash rockets in the same hash literal. When you've got keys that are not symbols stick to the hash rockets syntax (see):

# bad
{ a: 1, 'b' => 2 }

# good
{ :a => 1, 'b' => 2 }

So you can try with:

service 'apache' do
supports status: true, restart: true, reload: true
end

If you want to see what's the Rubocop "way" you can run this in the command line, this will autocorrect your code only for the HashSyntax warnings or flags:

rubocop --only HashSyntax --auto-correct

Hash syntax in Ruby

The new hash syntax in Ruby 1.9 still requires that strings be quoted, so instead of David you need "David".

Try this:

hash = { name: "David", age: 49 }

If the book used the bare word David without quotation marks, it is wrong. You might be interested in reading some of the other errata.

What are the benefits of the new hash syntax in Ruby 1.9?

It just looks nicer--it's syntactic sugar; it ends up being the same thing.

When mixing keys (ew, why would you do that?) I use the old hash-rocket syntax for the entire hash.

With symbol values I also use the old hash-rocket syntax for the entire hash–this looks icky:

{ ohai: :kthxbye }

I don't like mixing the two styles in the same hash–I think it's confusing.

This is all based on personal preference, though.

New Ruby 1.9 hash syntax

This works just fine for me:

def get(*args) p *args end

get 'detail', on: :member
# "detail"
# { :on => :member }

RUBY_ENGINE
# => jruby
JRUBY_VERSION
# => 1.6.6

EDIT: Now that you provided the error message, it looks like you are not running Ruby 1.9. The new hash syntax was introduced in Ruby 1.9, it doesn't work in older versions. You need to make sure that you are running Ruby 1.9, either by verifying that you are running the right Ruby implementation (e.g. YARV supports 1.9, MRI doesn't) or, if you are running a Ruby implementation that supports multiple language versions (e.g. JRuby) that you are passing the correct command line flags (e.g. jruby --1.9).

Ruby syntax with hash keys and symbols

The notation introduced in Ruby 1.9 is just a shortcut, and you can see what it means using irb:

h = { dependent: :destroy }
# => { :dependent => :destroy }

They're both symbols. Don't forget that a hash can be keyed by any object, not necessarily a symbol or a string. This is completely different from most languages where the key will be coerced into something consistent.

Using that example you can see what the types of the keys and values are:

h.keys
# => [:dependent]

h.values
# => [:destroy]

They're all symbols in this case.

How to create Hash with string keys by default

Use hash rocket syntax:

h = { "a" => 123 }
#=> {"a"=>123}
h['a']
#=> 123

Allowing for Ruby 1.9's hash syntax?

Even in Ruby < 1.9, you could use symbols for keys. For example:

# Ruby 1.8.7
settings = { :host => "localhost" }
puts settings[:host] #outputs localhost
settings.keys[0].class # => Symbol

Ruby 1.9 changes the way that you create hashes. It takes the key and converts it to a symbol for you, while eliminating the need for a hash rocket.

# Ruby 1.9.2
settings = { host: "localhost" }
settings[:host] # => "localhost"
settings.keys[0].class # => Symbol

In both cases, if I try to access settings[:name] with settings["name"], I'm going to get nil. All Ruby 1.9 does is allow for a new way of creating hashes. To answer your question, you cannot, as far as I know, use the new {key: value} syntax if you want backwards compatibility with Ruby 1.8.

Supporting Ruby 1.9's hash syntax in Ruby 1.8

I think you're out of luck, if you want to support 1.8 then you have to use =>. As usual, I will mention that you must use => in certain cases in 1.9:

  1. If the key is not a symbol. Remember that any object (symbols, strings, classes, floats, ...) can be a key in a Ruby Hash.
  2. If you need a symbol that you'd quote: :'this.that'.
  3. If you use MongoDB for pretty much anything you'll be using things like :$set => hash but $set: hash is a syntax error.

Back to our regularly scheduled programming.

Why do I say that you're out of luck? The Hash literal syntaxes (both of them) are hard-wired in the parser and I don't think you're going to have much luck patching the parser from your gem. Ruby 1.8.7's parse.y has this to say:

assoc    : arg_value tASSOC arg_value
{
$$ = list_append(NEW_LIST($1), $3);
}
;

and tASSOC is => so hash literals are hard-wired to use =>. 1.9.3's says this:

assoc    : arg_value tASSOC arg_value
{
/*%%%*/
$$ = list_append(NEW_LIST($1), $3);
/*%
$$ = dispatch2(assoc_new, $1, $3);
%*/
}
| tLABEL arg_value
{
/*%%%*/
$$ = list_append(NEW_LIST(NEW_LIT(ID2SYM($1))), $2);
/*%
$$ = dispatch2(assoc_new, $1, $2);
%*/
}
;

We have the fat-arrow syntax again (arg_value tASSOC arg_value) and the JavaScript style (tLABEL arg_value); AFAIK, tLABEL is also the source of the restrictions on what sorts of symbols (no :$set, no :'this.that', ...) can be used with the JavaScript-style syntax. The current trunk parse.y matches 1.9.3 for Hash literals.

So the Hash literal syntax is hard-wired into the parser and you're stuck with fat arrows if you want to support 1.8.



Related Topics



Leave a reply



Submit