Although I've been using Ruby 1.9 for a while, I only recently discovered the newer hash syntax that's now supported:
settings = {
host: "localhost",
port: 5984
}
As opposed to:
settings = {
"host" => "localhost"
}
I like its similarity to JavaScript's object noation, and looks a bit like JSON, so I'll probably switch to using it with all my libraries, but I still want to support other users and my own projects which assume the old syntax.
So it really comes down to a fairly simple question of having to test for both symbols and strings. Is there an easy way to do both of these lines as one?
return true if settings["host"] and settings["db"]
return true if settings[:host] and settings[:db]
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.
ActiveSupport (from Rails) offers HashWithIndifferentAccess. You will need to explicitly use it instead of a standard Hash.
Beware though, a quote from the class itself:
This class has dubious semantics and we only have it so that people
can write params[:key] instead of params[‘key’] and they get the same
value for both keys.
So it really comes down to a fairly simple question of having to test for both symbols and strings. Is there an easy way to do both of these lines as one?
return true if settings["host"] and settings["db"]
return true if settings[:host] and settings[:db]
I'm not sure what you're really asking, because this doesn't seem totally related to the original title, but try:
# somewhere you get the values you are going to need to look up...
host = 'foo'
db = 'bar'
# then code goes by...
return true if settings[host.to_sym] and settings[db.to_sym]
# later you assign a symbol to one of the keys:
host = :foo
# more code goes by...
return true if settings[host.to_sym] and settings[db.to_sym]
It's all the same. Let Ruby covert from strings to symbols as necessary.
This works because:
'foo'.to_s # => "foo"
:foo.to_s # => "foo"
'foo'.to_sym # => :foo
:foo.to_sym # => :foo
You pick whether you're going to use symbols or strings for hash keys and let Ruby sort it out.
Related
I wrote some code that used a "dynamic hash" to return values for keys, where the values were calculated. I tested it under irb (RUBY_VERSION 2.3.3) and everything seemed good. Below is a trivial example demonstrating the idea.
PROPS = Hash.new { |hash,key| key.to_s + "!" }
"Foo: %{foo} Bar: %{bar}" % PROPS # => "Foo: foo! Bar: bar!"
PROPS[:xyzzy] # => "xyzzy!"
But then deploying my code into the environment where it used (a plugin for the modeling tool Sketchup) which apparently has Ruby 2.2.4 the string formatting example above yields a KeyError: key{foo} not found.
PROPS = Hash.new { |hash,key| key.to_s + "!" }
"Foo: %{foo} Bar: %{bar}" % PROPS # KeyError: key{foo} not found
PROPS[:xyzzy] # => "xyzzy!"
But accessing the hash with any key works fine... Reading at http://ruby-doc.org/core-2.2.4/Kernel.html#method-i-sprintf doesn't provided much in the way of specifying why hash defaults would not behave as expected.
Obviously I can do different things, like invent my own replacement functions and variable syntax. As an aside, apparently the "hashes" passed to "%" or sprintf must actually BE Hash objects, violating Ruby's supposed duck-typing flexibility.
I can confirm that it does not work in Ruby 2.1.5 .
I will give some hints on how you could find out which ruby code to define to get the stuff going without reading C or Ruby code from MRI.
I know, this is not a full answer, but the text is too long to give in a comment.
Following is an irb session
>> detector = Object.new
=> #<Object:0x00000002257900>
>> def detector.method_missing m
>> puts m.to_s
>> end
=> :method_missing
>> "Foo: %{fnoo} Bar: %{bar}" % detector
to_ary
to_hash
ArgumentError: one hash required
from (irb):37:in `%'
from (irb):37
from /home/felix/.rvm/rubies/ruby-2.1.5/bin/irb:11:in `<main>'
This tells us that during interpolation methods were called that are not implemented by our dummy "detector" Object (to_hash to be precisely; through other tests I know that to_ary is also called if given object is a Hash, so we can ignore that one).
It does however not tell us whether already something like detector.class or detector is_a Hash? etcpp. were called.
Now I await the downvotes ;)
Btw, if you want to dive in via C - and I came to believe that this is probably needed in this case - you can start digging here: https://github.com/ruby/ruby/blob/6d728bdae9de565ad9d0b2fee2d4c2a33c6f4eac/sprintf.c#L579 (more or less "sprintf" on ruby 2.1).
Is there any difference between :key => "value" (hashrocket) and key: "value" (Ruby 1.9) notations?
If not, then I would like to use key: "value" notation. Is there a gem that helps me to convert from :x => to x: notations?
Yes, there is a difference. These are legal:
h = { :$in => array }
h = { :'a.b' => 'c' }
h[:s] = 42
but these are not:
h = { $in: array }
h = { 'a.b': 'c' } # but this is okay in Ruby2.2+
h[s:] = 42
You can also use anything as a key with => so you can do this:
h = { C.new => 11 }
h = { 23 => 'pancakes house?' }
but you can't do this:
h = { C.new: 11 }
h = { 23: 'pancakes house?' }
The JavaScript style (key: value) is only useful if all of your Hash keys are "simple" symbols (more or less something that matches /\A[a-z_]\w*\z/i, AFAIK the parser uses its label pattern for these keys).
The :$in style symbols show up a fair bit when using MongoDB so you'll end up mixing Hash styles if you use MongoDB. And, if you ever work with specific keys of Hashes (h[:k]) rather than just whole hashes (h = { ... }), you'll still have to use the colon-first style for symbols; you'll also have to use the leading-colon style for symbols that you use outside of Hashes. I prefer to be consistent so I don't bother with the JavaScript style at all.
Some of the problems with the JavaScript-style have been fixed in Ruby 2.2. You can now use quotes if you have symbols that aren't valid labels, for example:
h = { 'where is': 'pancakes house?', '$set': { a: 11 } }
But you still need the hashrocket if your keys are not symbols.
key: "value" is a convenience feature of Ruby 1.9; so long as you know your environment will support it, I see no reason not to use it. It's just much easier to type a colon than a rocket, and I think it looks much cleaner. As for there being a gem to do the conversion, probably not, but it seems like an ideal learning experience for you, if you don't already know file manipulation and regular expressions.
Ruby hash-keys assigned by hash-rockets can facilitate strings for key-value pairs (e.g. 's' => x) whereas key assignment via symbols (e.g. key: "value" or :key => "value") cannot be assigned with strings. Although hash-rockets provide freedom and functionality for hash-tables, specifically allowing strings as keys, application performance may be slower than if the hash-tables were to be constructed with symbols as hash-keys. The following resources may be able to clarify any differences between hashrockets and symbols:
Ryan Sobol's Symbols in Ruby
Ruby Hashes Exaplained by Erik Trautman
The key: value JSON-style assignments are a part of the new Ruby 1.9 hash syntax, so bear in mind that this syntax will not work with older versions of Ruby. Also, the keys are going to be symbols. If you can live with those two constraints, new hashes work just like the old hashes; there's no reason (other than style, perhaps) to convert them.
Doing :key => value is the same as doing key: value, and is really just a convenience. I haven't seen other languages that use the =>, but others like Javascript use the key: value in their Hash-equivalent datatypes.
As for a gem to convert the way you wrote out your hashes, I would just stick with the way you are doing it for your current project.
*Note that in using key: value the key will be a symbol, and to access the value stored at that key in a foo hash would still be foo[:key].
I have never seen this construct for building a hash. do_stuff(records: records) Does this only work in a parameter list being sent to a method? Is it documented anywhere? I know it is Ruby 1.9+.
records = {
'example.com' => '1.2.3.4',
'hello.com' => '44.33.22.22',
}
def do_stuff(data = {} )
puts data
end
do_stuff(records: records)
There are two things going on here. The { key: value } syntax is new in Ruby 1.9. It is equivalent to { :key => value }.
Also, Ruby methods have some syntactical sugar that allows you to pass in a hash literal as the last argument of the method without including the curly braces. This is not new in Ruby 1.9. So
do_stuff(key: value)
Is equivalent to
do_stuff({ key: value })
Just to remind you, this only works if the hash is the last argument to the method.
The new syntax for Hashes in Ruby 1.9 allows you to drop the hash rocket.
#Pre 1.9
{:key => value}
#1.9+
{key: value}
Both of the above are equivalent.
One thing to keep in mind when using the new hash syntax is that the key will always be treated as a symbol.
the behavior I'm looking for is so I can define methods to get results like these:
> 2i
=> Complex(0,+2i)
> 2²
=> 4
For the first one, you could add a custom instance methods to Fixnum like this:
class Fixnum
def i
Complex(0, self)
end
end
> 2.i
# => (0+2i)
And as J-_-L pointed out, you could do the same for the second one in Ruby 1.9, though your rb file will need # encoding: utf-8 at the top.
And as DNNX pointed out, the issue of creating imaginary numbers in Ruby is already addressed by the Complex class:
require 'complex'
2.im
#=> (0+2i)
Is there a cleaner built-in way to do this?
ree> Pathname.new('/path/to').children.select{|e| e.directory?}.map{|d| d.basename.to_s}
=> ["test1", "test2"]
Ideally I would like to avoid the directory? call
Starting from Chandra's answer, depending on whether you need or not the full path, you can use
Dir['app/*/']
# => ["app/controllers/", "app/helpers/", "app/metal/", "app/models/", "app/sweepers/", "app/views/"
Dir['app/*/'].map { |a| File.basename(a) }
# => ["controllers", "helpers", "metal", "models", "sweepers", "views"]
If you use Ruby >= 1.8.7, Chandra's answer can also be rewritten as
Pathname.glob('app/*/').map(&:basename)
# you can skip .to_s unless you don't need to work with strings
# remember you can always use a pathname as string for the most part of Ruby functions
# or interpolate the value
Pathname.glob("/path/to/*/").map { |i| i.basename.to_s }