Object attributes to JavaScript-like hash? - ruby

This is kind of an odd question, but I don't .......
I overcomplicated the original question. If you want to see the original question, look through the previous edits. Here's my revised question by combining my two previous questions, as simple as I can explain it:
I have an object. The object can have (x) amount of fields and all of the fields and values can be different. Example:
#<User name="John", height=75> or #<Comment title="First!">
I'm using a gem/db requires a specific format for queries. This is the format that the query MUST have: CREATE (u:User{attribute: "Some att.", another_att: 32}) and so on and so forth. Notice the JavaScript-like hash it uses for its values.
I'm trying to interpolate an object's attributes into the query. But I don't know how to map the correct fields and values into the query. Here's an example of what I start with and what I want to end with:
object = #<User name="John", height=75> =>
"CREATE (u:User{name: "John", height: 75})"
What's the simplest way to achieve this? Thanks to everyone who's helped so far.
Note that it is possible to convert the attributes to a Ruby hash: {:name => "John", :height => 75}, (as explained in the original question) but it was too confusing to change that hash to the syntax in the query, so I've backtracked and simplified my question.

It is very unlikely that the literal is the problem. The JSON-style syntax
was introduced in Ruby 1.9 as a literal abbreviation for the longer
"hashrocket" style.
> {name: 'John'} == {:name => 'John'}
=> true

Figured it out by going through the Ruby docs. It was way simpler than it seemed. The .collect method (or map) can be used to map each value and field to an interpolated string that can be put into a query. Example:
hash = {first_name: 'John', last_name: 'Smith'}
hash.collect {|k, v| "#{k}: #{v}"}
=> ["first_name: John", "last_name: Smith"]

This doesn't make sense. There has to be something else going on here.
A hash is a hash is a hash, it doesn't matter what syntax you used to create it, it's still the exact same hash. Those four are 100% equivalent (and if they aren't, that's a bug in your Ruby implementation):
hash = { name: 'John' }
hash = { :name => 'John' }
hash = {}
hash[:name] = 'John'
hash = Hash.new
hash[%Q|name|.to_sym] = %w#John#.first
There is absolutely no difference between them. There is no such thing as a "Hashrocket Hash" or "JSON-style Hash". There is a difference between the literals used to construct those hashes, but that difference only exists at parse time. At runtime, they are just as identical as 16, 0x10 and 020 are.

Related

Unexpected FrozenError when appending elements via <<

I have a hash containing names and categories:
hash = {
'Dog' => 'Fauna',
'Rose' => 'Flora',
'Cat' => 'Fauna'
}
and I want to reorganize it so that the names are grouped by their corresponding category:
{
'Fauna' => ['Dog', 'Cat'],
'Flora' => ['Rose']
}
I am adding each names via <<:
new_hash = Hash.new
hash.each do |name , category|
if new_hash.key?(category)
new_file[category] << name
else
new_hash[category] = name
end
end
But I am being told that this operation is being performed on a frozen element:
`<<' : Can’t modify frozen string (FrozenError)
I suppose this is because each yields frozen objects. How can I restructure this code so the '.each' doesn't provide frozen variables?
I needed to add the first name to an array and then that array to the hash.
new_hash = Hash.new
hash.each do |name , category|
if new_hash.key?(category)
new_file[category] << name
else
new_hash[category] = [name] # <- must be an array
end
end
How can I restructure this code so the '.each' doesn't provide frozen variables?
Short answer: you can't.
Hash#each doesn't "provide frozen variables".
First off, there is no such thing as a "frozen variable". Variables aren't frozen. Objects are. The distinction between variables and objects is fundamental, not just in Ruby but in any programming language (and in fact pretty much everywhere else, too). If I have a sticker with the name "Seamus" on it, then this sticker is not you. It is simply a label that refers to you.
Secondly, Hash#each doesn't provide "variables". In fact, it doesn't provide anything that is not in the hash already. It simply yields the objects that are already in the hash.
Note that, in order to avoid confusion and bugs, strings are automatically frozen when used as keys. So, you can't modify string keys. You can either make sure they are correct from the beginning, or you can create a new hash with new string keys. (You can also add the new keys to the existing hash and delete the old keys, but that is a lot of complexity for little gain.)

Ruby change value in hash by reference

I'm using eval to work with a hash. This part works:
some_hash = {"a" => {"b" => "c"}}
target_hash = "some_hash"
target_key = "['a']"
my_value = eval(target_hash + target_key)
puts "my_value " + my_value.to_s
and prints:
my_value {"b"=>"c"}
How would I change the value, using eval, so that the hash results in this:
some_hash = {"a" => {"d" => "e"}}
Thanks
Edit:
I don't think I'm explaining correctly. I have to drill down into a hash, but I want the flexibility to do it with a string that is set at run time. That string could be "['key_level_1']['key_level_2']['key_level_3']" which is supposed to refer to some_hash['key_level_1']['key_level_2']['key_level_3'].
And again, i need to set that value to something. Does that make sense?
I would take an array e.g. ['key1', 'key2', 'key3'] (which can be constructed from an appropriately formatted string) and the "root object" and use it to locate a particular "target object" branch (I would recommend recursion). Then manipulate the appropriate object that was located. In this case some_hash should be the "root object" to start the traversal from, and not a string.
Here is a link to a really old answer I wrote (when I still actively used ruby). It doesn't handle the assignment bit, but I believe it shows the start of a valid approach that is eval-free: How do you access nested elements of a hash with a single string key? After the "target object" is located, then it's just a matter of assigning a new value to particular key. (The accepted answer in the linked post is also a gem, if not a little more cryptic and symbol-leaking.)
Happy coding.
You can use Hash#replace
def changeHash(a, b, targetHash, targetKey)
change = ".replace({'#{a}' => '#{b}'})"
my_value = eval(target_hash + target_key + change)
end
some_hash = {"a" => {"b" => "c"}}
target_key = "['a']"
changeHash('d', 'e', 'some_hash', '["a"]')
I suggest setting up a method like this:
def hash_change(target_hash,target_key,*new_value)
if new_value
target_hash["target_key"] = new_value
end
puts "my_value " + target_hash["target_key"]
end
That will give you flexibility to either display the original or a new hash should you pass in a new value.
Edit: sorry forgot the call to the method
hash_change(some_hash,"a",{"d" => "e"})
Thanks all, for the help. I changed it up a bit. Instead of passing in string as array, I just pass in an object like this:
{some_hash => {"a" => {"b" => "nil"}}}
Then I recursively traverse both the reference, and the real hash. When I detect nil in the reference, I know I am there.

Ruby split String into two sections and put in hash with predefined keys

I don't know if this is actually good ruby code, but what I am trying to do is split a String into two separate sections and put the two as values to two specific keys. For example:
name_a = "Henry Fillenger".split(/\s+/,2)
name = {:first_name => name_a[0], :last_name => name_a[1]}
I was wondering if this could be done in a single line through some ruby magic however.
You can use Hash[] and zip to do this:
name = Hash[ [:first_name, :last_name].zip("Henry Fillenger".split(/\s+/,2)) ]
However I'd say your version is more readable. Not everything has to be on one line.
Still two lines, but slightly more readable in my opinion,
first_name, last_name = "Henry Fillenger".split(/\s+/,2)
name = {:first_name => first_name, :last_name => last_name}
Just for fun, a non-split variant (which is also two lines):
m = "Henry Fillenger".match(/(?<first_name>\S+)\s+(?<last_name>\S+)/)
name = m.names.each_with_object({ }) { |name, h| h[name.to_sym] = m[name] }
The interesting parts would be the named capture groups ((?<first_name>...)) in the regex and the general hash-ification technique using each_with_object. The named capture groups require 1.9 though.
If one were daring, one could monkey patch the each_with_object bit right into MatchData as, say, to_hash:
class MatchData
def to_hash
names.each_with_object({ }) { |name, h| h[name.to_sym] = self[name] }
end
end
And then you could have your one-liner:
name = "Henry Fillenger".match(/(?<first_name>\S+)\s+(?<last_name>\S+)/).to_hash
I don't really recommend this, I only bring it up as a point of interest. I'm a little disappointed that MatchData doesn't have a to_h or to_hash method already, it would make a sensible complement to its to_a method.

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

Turning a hash into a string of name-value pairs

If I'm turning a ruby hash into a string of name-value pairs (to be used in HTTP params, for example), is this the best way?
# Define the hash
fields = {"a" => "foo", "b" => "bar"}
# Turn it into the name-value string
http_params = fields.map{|k,v| "#{k}=#{v}"}.join('&')
I guess my question is:
Is there an easier way to get to http_params? Granted, the above way works and is fairly straightforward, but I'm curious if there's a way to get from the hash to the string without first creating an array (the result of the map method)?
Rails provides to_query method on Hash class. Try:
fields.to_query
From the The Pragmatic Programmer's Guide:
Multiple parameters passed to a yield
are converted to an array if the block
has just one argument.
For example:
> fields = {:a => "foo", :b => "bar"}
> fields.map { |a| a } # => [[:a, "foo"], [:b, "bar"]]
So your code could be simplified like this:
> fields.map{ |a| a.join('=') }.join('&') # => "a=foo&b=bar"
This is probably the best you can do. You could iterate through the pairs in the hash, building the string as you go. But in this case the intermediate string would need to be created and deleted at each step.
Do you have a use-case where this is a performance bottleneck? In general Ruby is doing so much work behind the scenes that worrying about a temporary array like this is probably not worth it. If you are concerned that it may be a problem, consider profiling your code for speed and memory usage, often the results are not what you expect.
In Rails it also had a method to convert hash to params.
Example1: Turn a hash to params
{ param1: "value1", param2: "value2" }.to_query
Result:
"param1=value1&param2=value2"
Example2: Turn params to hahs
params = "param1=value1&param2=value2"
Rack::Utils.parse_nested_query(params)
Result:
{"param1"=>"value1", "param2"=>"value2"}

Resources