Difference between :symbol and symbol:? - ruby

Just going through a tutorial, and thought somewhere I saw
first_name:
And another place
:first_name
Is this right? What is the difference?

The hash syntax changed in Ruby 1.9.2 to get closer to json.
So:
{ :foo => "bar" }
Is the same as:
{ foo: "bar" }
In all other cases, the colon must come first.

:first_name is a symbol, while first_name: is a Hash key in the new Ruby 1.9.2 syntax.
Hash keys are then converted to symbols:
>> a = { foo: 10 , bar: 20 }
=> {:foo=>10, :bar=>20}
It is the same as writing:
>> a = { :foo => 10, :bar => 20 }
=> {:foo=>10, :bar=>20}

Related

Convert string to symbol/keyword

We can convert strings to symbols in the following way:
"string_to_symbol".to_sym
# => :string_to_symbol
How do I convert strings in the new way of defining keywords? Expected outcome:
# => string_to_symbol:
I call keywords dynamically, and I end up using => to assign a value to it. I prefer not to do it that way to keep my code consistent.
No, there isn't.
It's important to note that these two lines do exactly the same:
{ foo: 'bar' } #=> {:foo=>"bar"}
{ :foo => 'bar' } #=> {:foo=>"bar"}
This is because the first form is only Syntactic sugar for creating a Hash using a Symbol as the Key. (and not "the new way of defining keyword in ruby")
If you want to use other types as the key, you still have to use the "hashrocket" (=>):
{ 'key' => 'val' } #=> {"key"=>"val"}
{ 0 => 'val' } #=> {0=>"val"}
Edit:
As #sawa noted in the comments, the question is about passing keyword arguments, not Hashes. Which is technically correct, but boils down to exactly the same (as long as it's Hashes with Symbols as keys:
def foo(bar: 'baz')
puts bar
end
h = {
:bar => 'Ello!'
}
foo(h)
# >> Ello!

Ruby hash vivification weirdness

I'm running ruby 2.2.2:
$ ruby -v
ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]
Here I am initializing a hash with one key :b that has a value of Hash.new({})
irb(main):001:0> a = { b: Hash.new({}) }
=> {:b=>{}}
Now, I'm going to attempt to auto-vivify another hash at a[:b][:c] with a key 'foo' and a value 'bar'
irb(main):002:0> a[:b][:c]['foo'] = 'bar'
=> "bar"
At this point, I expected that a would contain something like:
{ :b => { :c => { 'foo' => 'bar' } } }
However, that is not what I'm seeing:
irb(main):003:0> a
=> {:b=>{}}
irb(main):004:0> a[:b]
=> {}
irb(main):005:0> a[:b][:c]
=> {"foo"=>"bar"}
This differs from the following:
irb(main):048:0> a = { :b => { :c => { "foo" => "bar" } } }
=> {:b=>{:c=>{"foo"=>"bar"}}}
irb(main):049:0> a
=> {:b=>{:c=>{"foo"=>"bar"}}}
So what is going on here?
I suspect this is something to do with Hash.new({}) returning a default value of {}, but I'm not exactly sure how to explain the end result...
Apologies for answering my own question, but I figured out what is happening.
The answer here is that we are assigning into the default hash being returned by a[:b], NOT a[:b] directly.
As before, we're going to create a hash with a single key of b and a value of Hash.new({})
irb(main):068:0> a = { b: Hash.new({}) }
=> {:b=>{}}
As you might expect, this should make things like a[:b][:unknown_key] return an empty hash {}, like so:
irb(main):070:0> a[:b].default
=> {}
irb(main):071:0> a[:b][:unknown_key]
=> {}
irb(main):072:0> a[:b].object_id
=> 70127981905400
irb(main):073:0> a[:b].default.object_id
=> 70127981905420
Notice that the object_id for a[:b] is ...5400 while the object_id for a[:b].default is ...5420
So what happens when we do the assignment from the original question?
a[:b][:c]["foo"] = "bar"
First, a[:b][:c] is resolved:
irb(main):075:0> a[:b][:c].object_id
=> 70127981905420
That's the same object_id as the .default object, because :c is treated the same as :unknown_key from above!
Then, we assign a new key 'foo' with a value 'bar' into that hash.
Indeed, check it out, we've effectively altered the default instead of a[:b]:
irb(main):081:0> a[:b].default
=> {"foo"=>"bar"}
Oops!
The answer is probably not as esoteric as it might seem at the onset, but this is just the way Ruby is handling that Hash.
If your initial Hash is the:
a = { b: Hash.new({}) }
b[:b][:c]['foo'] = 'bar'
Then seeing that each 'layer' of the Hash is just referencing the next element, such that:
a # {:b=>{}}
a[:b] # {}
a[:b][:c] # {"foo"=>"bar"}
a[:b][:c]["foo"] # "bar"
Your idea of:
{ :b => { :c => { 'foo' => 'bar' } } }
Is somewhat already accurate, so it makes me think that you already understand what's happening, but felt unsure of what was happening due to the way IRB was perhaps displaying it.
If I'm missing some element of your question though, feel free to comment and I'll revise my answer. But I feel like you understand Hashes better than you're giving yourself credit for in this case.

How does Mongoid know the difference between string values and symbol values?

Consider this example:
> x = User.first # or any persisted Mongoid::Document
=> #<User _id: 52014532a6356d1ac9000001, ...>
> x.set :foo, :bar
=> :bar
> x.set :foo2, 'bar'
=> "bar"
Note that "foo" and "foo2" are not declared in Ruby anywhere.
THEN, in a MongoDB shell:
> db.users.findOne({_id: ObjectId('52014532a6356d1ac9000001')})
{
"_id" : ObjectId("52014532a6356d1ac9000001"),
"foo" : "bar",
"foo2" : "bar",
...
}
BUT NOW, back in Ruby:
> x = User.find x.id; nil # to clear out any possibility of metadata on the instance
=> nil
> [x.read_attribute(:foo), x.read_attribute(:foo2)]
=> [:bar, "bar"]
How does it know?
Seens like BSON supports a symbol type for values, googling around
I found it:
https://github.com/mongodb/mongo-ruby-driver/wiki/FAQ#FrequentlyAskedQuestions-Ruby-IseethatBSONsupportsasymboltype.DoesthismeanthatIcanstoreRubysymbolsinMongoDB%3F

Is there a way to tell Psych in ruby to using inline mode?

environment: ruby1.9.3 , psych(any version)
ex:
o = { 'hash' => { 'name' => 'Steve', 'foo' => 'bar' } }
=> {"hash"=>{"name"=>"Steve", "foo"=>"bar"}}
#is there a inline option?
puts Psych.dump(o,{:inline =>true})
real result:
---
hash:
name: Steve
foo: bar
expect output:
---
hash: { name: Steve, foo: bar }
Psych supports this, although it isn't at all straightforward.
I've started researching this in my own question on how to dump strings using literal style.
I ended up devising a complete solution for setting various styles for specific objects, including inlining hashes and arrays.
With my script, a solution to your problem would be:
o = { 'hash' => StyledYAML.inline('name' => 'Steve', 'foo' => 'bar') }
StyledYAML.dump o, $stdout
The representable gem provides this in a convenient OOP style.
Considering you have a model User:
user.name => "Andrew"
user.age => "over 18"
You'd now define a representer module to render/parse User instances.
require 'representable/yaml'
module UserRepresenter
include Representable::YAML
collection :hash, :style => :flow
def hash
[name, age]
end
end
After defining the YAML document you simply extend the user instance and render it.
user.extend(UserRepresenter).to_yaml
#=> ---
hash: [Andrew, over 18]
Hope that helps, Andrew!

Create hash using block (Ruby)

Can I create a Ruby Hash from a block?
Something like this (although this specifically isn't working):
foo = Hash.new do |f|
f[:apple] = "red"
f[:orange] = "orange"
f[:grape] = "purple"
end
In Ruby 1.9 (or with ActiveSupport loaded, e.g. in Rails), you can use Object#tap, e.g.:
foo = Hash.new.tap do |bar|
bar[:baz] = 'qux'
end
You can pass a block to Hash.new, but that serves to define default values:
foo = Hash.new { |hsh, key| hsh[key] = 'baz qux' }
foo[:bar] #=> 'baz qux'
For what it's worth, I am assuming that you have a larger purpose in mind with this block stuff. The syntax { :foo => 'bar', :baz => 'qux' } may be all you really need.
I cannot understand why
foo = {
:apple => "red",
:orange => "orange",
:grape => "purple"
}
is not working for you?
I wanted to post this as comment but i couldn't find the button, sorry
Passing a block to Hash.new specifies what happens when you ask for a non-existent key.
foo = Hash.new do |f|
f[:apple] = "red"
f[:orange] = "orange"
f[:grape] = "purple"
end
foo.inspect # => {}
foo[:nosuchvalue] # => "purple"
foo # => {:apple=>"red", :orange=>"orange", :grape=>"purple"}
As looking up a non-existent key will over-write any existing data for :apple, :orange and :grape, you don't want this to happen.
Here's the link to the Hash.new specification.
What's wrong with
foo = {
apple: 'red',
orange: 'orange',
grape: 'purple'
}
As others have mentioned, simple hash syntax may get you what you want.
# Standard hash
foo = {
:apple => "red",
:orange => "orange",
:grape => "purple"
}
But if you use the "tap" or Hash with a block method, you gain some extra flexibility if you need. What if we don't want to add an item to the apple location due to some condition? We can now do something like the following:
# Tap or Block way...
foo = {}.tap do |hsh|
hsh[:apple] = "red" if have_a_red_apple?
hsh[:orange] = "orange" if have_an_orange?
hsh[:grape] = "purple" if we_want_to_make_wine?
}

Resources