Unique construct for passing a hash - ruby

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.

Related

How do I write an rspec test if an array of hashes has a attribute/value pair

Given an array of hashes I want to check if each one contains a certain key and value. The following did NOT work:
it { expect(some_array).to all( have_attributes(:some_key => 'some_value') ) }
I could not tell from the match error why it didn't work but I think it's something to do with expectations have_attributes has about the input arguments or environment.
Make a custom matcher as follows:
RSpec::Matchers.define :have_member_with_value do |expected_key, expected_value|
match do |actual|
actual[expected_key] == expected_value
end
end
Usage:
it { expect(some_array).to all( have_member_with_value(:some_key, "some_value") ) }
Sadly I'm not sure why the approach in the question does not work.
I think the assertion does not work because have_attributes does not work with plain ruby hash keys. You can't access hash keys the same as attributes if you're using a vanilla Ruby hash.
Consider:
a = OpenStruct.new(hello: 'you')
b = { hello: 'you' }
a.hello # this is an attribute automatically defined via OpenStruct
=> "you"
b.hello # this is a regular ol' key
NoMethodError: undefined method `hello' for {:hello=>"you"}:Hash
from (pry):79:in `<main>'
I believe the matcher would work if the object you were working with had the attribute accessor for whatever key-value you were looking for. Ex. If you had an array of OpenStructs, using both match_array and have_attributes would work. These are usually available automatically via metaprogramming if you're using a fancy library like ActiveRecord or OpenStruct.
Otherwise, you have to define these attributes yourself, or assert on the hash key rather than the attribute.
I would probably do something like this:
it do
expect(subject.body.map { |elem| elem[:some_key] }).to all( eq "some_value" ) }
end
I would loop through subject.body and write the expectation within the loop
e.g
subject.body.each do |entry|
it { expect(entry[:some_key]).to eq "some_value"}
end

How to select keys from hash and strip parts of the keyname with ruby

I have a config hash in my ruby project and I want to pick certain keys with their value to have them as a separate hash.
Project.config.to_h.select{ |k,v| k[/db_/] }
=> {:db_name => value, .... }
This returns me nicely all the k,v that I need. But I also want to strip the db_ from the keynames so that it returns me
=> {:name => value, ....}
I tried something like
Project.config.to_h.select{ |k,v| k[/db_/] }.each_key { |k| k.to_s.gsub(/db_/) }
But it returns the same hash like the above example. Any idea or suggestions to get this as a smooth one or two liner?
inject to the rescue (assuming the db_ is in the beginning):
Project.config.to_h.inject({}) do | a, (k, v) |
k =~ /\Adb_/ ? a.update($' => v) : a
end
inject can be used to iterate through an enumerable data structure and build any kind of output in the course.
$' is a magic variable containing the portion of the string after a successful regexp match.
So let's break down your solution and see what's wrong with it:
Project.config.to_h.select{ |k,v| k[/db_/] }.each_key { |k| k.to_s.gsub(/db_/) }
So:
k.to_s.gsub(/db_/)
is creating a new object (and not mutating the keys like you would expect), in this case an enumerator, when you don't pass gsub a second argument it returns an enumerator, so let's assume you did:
k.to_s.gsub(/db_/, "")
This would be creating new objects and not mutating the keys in the hash(to_s would create a new string object, and gsub doesn't mutate, instead creates a new object, gsub! is the mutant version)
Now let's assume the keys were strings and you could even mutate them (with gsub!), ruby would not let you mutate them in place, else ruby hash would be broken, when a string is used as a key it is frozen, see http://ruby-doc.org/core-2.2.2/Hash.html#5B-5D-3D-method
key should not have its value changed while it is in use as a key (an unfrozen String passed as a key will be duplicated and frozen).
So with this knowledge how would I implement this:
Project.config.to_h.each_with_object({}) do |(k,v), hsh|
next unless k[/db_/] # skip every key that doesn't match the regex (if you want the keys to not have part of them stripped then you can use an if/else along with the next line)
hsh[k.to_s.sub(/db_/, "")] = v
end

Explain hash rocket in this context

I've just written this code, that works although I am not entirely sure why:
scope = scope.where(Sequel.qualify(:meeting_graphs, :id) => query.ids)
I am specifically talking about the hash rocket.
Previously the code was this, which makes perfect sense:
scope = scope.where(id: query.ids)
First thing I do not understand is why it does this not work when I replace the hash rocket with a colon which I thought was the preferred syntax:
scope = scope.where(Sequel.qualify(:meeting_graphs, :id): query.ids)
Sequel.qualify returns an object which also confuses me as I thought it would return a symbol.
Can anyone explain?
New hash syntax works only if key is a literal symbol.
Sequel.qualify returns qualifier object identifying column. It's possible since every object can be a hash key in Ruby.
that works although I am not entirely sure why
As long as Sequel.qualify(:meeting_graphs, :id) is valid, it can be a key of a hash. Any object can be a key of a hash. That is why.
why it does this not work when I replace the hash rocket with a colon
Even if Sequel.qualify(:meeting_graphs, :id) turns out to be a symbol, the colon notation will not work because it is part of a literal notation. It is not a method or a keyword that works on Ruby objects that are already made.
You are passing a keywords to the function and keywords use Hash syntax.
There are many ways to define hashes in ruby, and in the way you use a function one syntax does not work.
def return_one_symbol
'one'.to_sym
end
hash_syntax1 = {:one => '1'}
hash_syntax2 = {one: '1'}
p hash_syntax1 # => {:one=>"1"}
p hash_syntax2 # => {:one=>"1"}
hash_syntax1_function = {return_one_symbol => '1'}
hash_syntax2_function = {return_one_symbol: '1'}
p hash_syntax1_function # => {:one=>"1"}
p hash_syntax2_function # => {:return_one_symbol=>"1"}
see this post for more info:
Is there any difference between the `:key => "value"` and `key: "value"` hash notations?

Is there any difference between the `:key => "value"` and `key: "value"` hash notations?

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].

How to convert a ruby integer into a symbol

I have a Ruby array like this
q_id = [1,2,3,4,5,...,100]
I want to iterate through the array and convert into a hash like this
{
:1 => { #some hash} ,
:2 => { #another hash},
...
:100 => {#yet another hash}
}
What is the shortest and most elegant way to accomplish this?
[EDIT : the to_s.to_sym while being handy is not how I want it. Apologies for not mentioning it earlier.]
For creating a symbol, either of these work:
42.to_s.to_sym
:"#{42}"
The #inspect representation of these shows :"42" only because :42 is not a valid Symbol literal. Rest assured that the double-quotes are not part of the symbol itself.
To create a hash, there is no reason to convert the keys to symbols, however. You should simply do this:
q_id = (1..100).to_a
my_hash_indexed_by_value = {}
q_id.each{ |val| my_hash_indexed_by_value[val] = {} }
Or this:
my_hash = Hash[ *q_id.map{ |v| [v,{}] }.flatten ]
Or this:
# Every time a previously-absent key is indexed, assign and return a new hash
my_hash = Hash.new{ |h,val| h[val] = {} }
With all of these you can then index your hash directly with an integer and get a unique hash back, e.g.
my_hash[42][:foo] = "bar"
Unlike JavaScript, where every key to an object must be a string, Hashes in Ruby accept any object as the key.
To translate an integer into a symbol, use to_s.to_sym .. e.g.,:
1.to_s.to_sym
Note that a symbol is more related to a string than an integer. It may not be as useful for things like sorting anymore.
Actually "symbol numbers" aren't a thing in Ruby (try to call the to_sym method on a number). The benefit of using symbols in a hash is about performance, since they always have the same object_id (try to call object_id on strings, booleans, numbers, and symbols).
Numbers are immediate value and, like Symbol objects, they always have the same object_id.
Anyway, using the new hash syntax implies using symbols as keys, but you can always use the old good "hash rocket" syntax
awesome_hash = { 1 => "hello", 2 => "my friend" }
Read about immediate values here:
https://books.google.de/books?id=jcUbTcr5XWwC&pg=PA73&lpg=PA73&dq=immediate+values+singleton+method&source=bl&ots=fIFlAe8xjy&sig=j7WgTA1Cft0WrHwq40YdTA50wk0&hl=en&sa=X&ei=0kHSUKCVB-bW0gHRxoHQAg&redir_esc=y#v=onepage&q&f=false
If you are creating a hard-coded constant numeric symbol, there's a simpler way:
:'99'
This produces the same results as the more complex methods in other answers:
irb(main):001:0> :'99'
=> :"99"
irb(main):002:0> :"#{99}"
=> :"99"
irb(main):003:0> 99.to_s.to_sym
=> :"99"
Of course, this will not work if you're dynamically creating a symbol from a variable, in which case one of the other two approaches is required.
As already stated, :1 is not a valid symbol. Here's one way to do what you're wanting, but with the keys as strings:
Hash[a.collect{|n| [n.to_s, {}] }]
An array of the objects you want in your hash would be so much easier to use, wouldn't it? Even a hash of integers would work pretty well, wouldn't it?
u can use
1.to_s.to_sym
but this will make symbols like :"1"
You can make symbolic keys with Hash[]:
a = Hash[(1..100).map{ |x| ["#{x}".to_sym, {}] }]
Check type of hash keys:
puts a.keys.map(&:class)
=>
Symbol
...
Symbol
Symbol

Resources