Ruby - Unexpected Hash data structure - ruby-on-rails

A simple question:
In rails I get as response an hash like this:
{"success":true,"new_id":816704027}
So, the difference from a normal structure I guess is- "new_id": -instead of- new_id:
Does anyone know how to retrieve the data labelled "new_id"?
The usual array["new_id"] doesn't work.
The response to the code:
new_customer_id = #response.body
puts new_customer_id
puts new_customer_id["new_id"]
is simply:
=> {"success":true,"new_id":816704028}
=> new_id
I come from the implementation of JSON_response. Anyway, they changed the app and I don't have anymore a JSON message, but they use the method:
return_200(additional_items: {:new_id => "#customer.id"} )
More:
If I write:
new_customer_id = #response.body
puts new_customer_id
puts new_customer_id[:new_id]
the answer printed is simply:
=> {"success":true,"new_id":816704028}
and the request for the :new_id content does not to be received.
Much more interesting is the following:
After the fact that:
puts new_customer_id["new_id"]
prints:
=> new_id
If I write:
puts new_customer_id["new_id"][0]
puts new_customer_id["new_id"][1]
puts new_customer_id["new_id"][2]
...
I obtain:
=> n
=> e
=> w
...
Also:
if I write:
puts new_customer_id["new_"]
puts new_customer_id["new_i"]
I obtain:
=> new_
=> new_i
and if I write:
puts new_customer_id["new_id_anyOtherCharacter"]
I get nothing
Luca

That's not a ruby object you are getting back. It's JSON. You can get the new_id in a variety of ways:
JSON.parse(#response.body)["new_id"]
JSON.parse(#response.body).symbolize_keys[:new_id]
JSON.parse(#response.body).with_indifferent_access[:new_id]

I bet the hash has a symbol key instead of a string key. Try with array[:new_id].

use params to get the value like:
new_id= array[:new_id]

Related

Function that accepts a hash key and returns multiple values?

I am working on a practice question that asks me to create a group_by_owners function that
"Accepts a hash containing the file owner name for each file name.
Returns a hash containing an array of file names for each owner name, in any order.
For example, for hash
{'Input.txt' => 'Randy', 'Code.py' => 'Stan', 'Output.txt' => 'Randy'}
the group_by_owners method should return
{'Randy' => ['Input.txt', 'Output.txt']`, `'Stan' => ['Code.py']}
So far, I could not get anything to pass. I expect that i am supposed to take in a hash so I implemented a new files = {} has and put in the appropriate values. But all I get is a syntax error
module FileOwners
def self.group_by_owners(files)
files = {}
files['Randy'] << 'Input.txt' << 'Output.txt'
files['Stan'] << 'Code.py'
end
end
puts FileOwners.group_by_owner(files)
I have tried other practices including
module FileOwners
def self.group_by_owners(files)
files = {
'Randy' => 'Input.txt',
'Randy' => 'Output.txt'
'Stan' => 'Code.py'
}
end
end
puts FileOwners.group_by_owners(files['Randy'])
But I am still met with errors. I'm completely stuck. I'm obviously fairly new to Ruby, so bear with me. Does anyone know a better solution?
The point is: method accepts the hash, you do not have to build a hash, you just pass it to method. Your method has to just work with the argument passed.
When I was staring coding I was thinking the same way as you are now ;)
def group_by_owners(files)
better_hash = Hash.new { |hash, key| hash[key] = [] }
files.each_with_object(better_hash) {|(k, v), hash| hash[v] << k}
end
group_by_owners({'Input.txt' => 'Randy', 'Code.py' => 'Stan', 'Output.txt' => 'Randy'})
#=> {"Randy"=>["Input.txt", "Output.txt"], "Stan"=>["Code.py"]}

How do I return JSON in the proper format in Ruby?

When I try to return JSON in the format shown above, my JSON looks like this
result = JSON.parse(data)
p result.to_json
#json from .to_json
\"is_claimed\": true,
\"rating\": 3.5,
\"mobile_url\": \"http: //m.yelp.com/biz/rudys-barbershop-seattle\",
...
When I use "p result" instead (without .to_json), I get the below:
"is_claimed"=>true,
"rating"=>3.5,
"mobile_url"=>"http://m.yelp.com/biz/rudys-barbershop-seattle",
....
The first has a '\' character and the second uses a hash rocket. How do I return JSON in a normal format?
The format you're seeing is because of the way p outputs information, try changing your output to puts.
data = '{
"is_claimed":true,
"rating":3.5,
"mobile_url":"http://m.yelp.com/biz/rudys-barbershop-seattle"
}'
result = JSON.parse(data)
puts result.to_json
EDIT: Some additional information on p vs puts: p vs puts in Ruby
That's because you are using p to display the contents of a string.
p "hi\"there"
"hi\"there"
=> "hi\"there"
[2] pry(main)> puts "hi\"there"
hi"there
Also, you should probably be aware of the difference between the methods as_json and to_json, see http://jonathanjulian.com/2010/04/rails-to_json-or-as_json/ to understand the difference.
Not sure what you mean by "normal format", I sometimes puts it out by
puts JSON.generate(result)
to get it in a friendly format.

Shortcut for showing list of hashes "nicely"

When I have a list of hashes, like the result of an .attributes call, what is a short way to create a line-by-line nicely readable output?
Like a shortcut for
u.attributes.each {|p| puts p[0].to_s + ": " + p[1].to_s}
I'm not sure you can make it much shorter unless you create your own method.
A minor enhancement would be:
u.attributes.each {|k,v| puts "#{k}: #{v}"}
Or you can create an extension to Hash:
class Hash
def nice_print
each {|k,v| puts "#{k}: #{v}"}
end
end
u.attributes.nice_print
AS said in my comments, I like to use y hash or puts YAML.dump(hash) that shows your hash in yaml. It can be used for other objects too.
h = {:a => 1, :b => 2, :c => 3}
# => {:a=>1, :b=>2, :c=>3}
y h
#---
#:a: 1
#:b: 2
#:c: 3
# => nil
There is also an informative answer about it.
If you are looking for an output for development purposes (in Rails log files for instance), inspect or pretty_inspect should do it :
u.attributes.inspect
or
u.attributes.pretty_inspect
But if what you are looking for is a way to print nicely in Rails console, I believe you will have to write your own method, or use a gem like awesome_print, see : Ruby on Rails: pretty print for variable.hash_set.inspect ... is there a way to pretty print .inpsect in the console?
awesome_print is the way to go
gem install awesome_print
require "ap"
ap u.attributes

restclient with ruby

Here i am trying to pass one ID with the url, but that ID didn't append with URL...
def retrieve
url = "http://localhost:3000/branches/"
resource = RestClient::Resource.new url+$param["id"]
puts resource
end
giving ID via commend line that is
ruby newrest.rb id="22"
I have got the error like this
`+': can't convert nil into String (TypeError)
But all this working with mozilla rest client. How to rectify this problem?
Like this:
RestClient.get 'http://localhost:3000/branches', {:params => {:id => 50, 'name' => 'value'}}
You can find the command line parameters in the global ARGV array.
If ruby newrest.rb 22 will do then just
id = ARGV[0]
response = RestClient.get "http://localhost:3000/branches/#{id}"
puts response.body
Here are some examples from the documentation:
private_resource = RestClient::Resource.new 'https://example.com/private/resource', 'user', 'pass'
RestClient.post 'http://example.com/resource', :param1 => 'one', :nested => { :param2 => 'two' }
Just experiment with comma-separated parameters or with hashes so see what your URL gives you.
From my point of view line puts resource seems strange,
but when we leave it as it is
I'd suggest
def retrieve
url = "http://localhost:3000/branches/"
resource = RestClient::Resource.new url
res_with_param = resource[$param["id"]]
puts res_with_param
end
I haven't tried so there may be a syntax mistakes.
I'm really newcomer in ruby.
But idea is good I hope.
Greetings,
KAcper

Equivalent of .try() for a hash to avoid "undefined method" errors on nil? [duplicate]

This question already has answers here:
How to avoid NoMethodError for nil elements when accessing nested hashes? [duplicate]
(4 answers)
Closed 7 years ago.
In Rails we can do the following in case a value doesn't exist to avoid an error:
#myvar = #comment.try(:body)
What is the equivalent when I'm digging deep into a hash and don't want to get an error?
#myvar = session[:comments][#comment.id]["temp_value"]
# [:comments] may or may not exist here
In the above case, session[:comments]try[#comment.id] doesn't work. What would?
You forgot to put a . before the try:
#myvar = session[:comments].try(:[], #comment.id)
since [] is the name of the method when you do [#comment.id].
The announcement of Ruby 2.3.0-preview1 includes an introduction of Safe navigation operator.
A safe navigation operator, which already exists in C#, Groovy, and
Swift, is introduced to ease nil handling as obj&.foo. Array#dig and
Hash#dig are also added.
This means as of 2.3 below code
account.try(:owner).try(:address)
can be rewritten to
account&.owner&.address
However, one should be careful that & is not a drop in replacement of #try. Take a look at this example:
> params = nil
nil
> params&.country
nil
> params = OpenStruct.new(country: "Australia")
#<OpenStruct country="Australia">
> params&.country
"Australia"
> params&.country&.name
NoMethodError: undefined method `name' for "Australia":String
from (pry):38:in `<main>'
> params.try(:country).try(:name)
nil
It is also including a similar sort of way: Array#dig and Hash#dig. So now this
city = params.fetch(:[], :country).try(:[], :state).try(:[], :city)
can be rewritten to
city = params.dig(:country, :state, :city)
Again, #dig is not replicating #try's behaviour. So be careful with returning values. If params[:country] returns, for example, an Integer, TypeError: Integer does not have #dig method will be raised.
The most beautiful solution is an old answer by Mladen Jablanović, as it lets you to dig in the hash deeper than you could with using direct .try() calls, if you want the code still look nice:
class Hash
def get_deep(*fields)
fields.inject(self) {|acc,e| acc[e] if acc}
end
end
You should be careful with various objects (especially params), because Strings and Arrays also respond to :[], but the returned value may not be what you want, and Array raises exception for Strings or Symbols used as indexes.
That is the reason why in the suggested form of this method (below) the (usually ugly) test for .is_a?(Hash) is used instead of (usually better) .respond_to?(:[]):
class Hash
def get_deep(*fields)
fields.inject(self) {|acc,e| acc[e] if acc.is_a?(Hash)}
end
end
a_hash = {:one => {:two => {:three => "asd"}, :arr => [1,2,3]}}
puts a_hash.get_deep(:one, :two ).inspect # => {:three=>"asd"}
puts a_hash.get_deep(:one, :two, :three ).inspect # => "asd"
puts a_hash.get_deep(:one, :two, :three, :four).inspect # => nil
puts a_hash.get_deep(:one, :arr ).inspect # => [1,2,3]
puts a_hash.get_deep(:one, :arr, :too_deep ).inspect # => nil
The last example would raise an exception: "Symbol as array index (TypeError)" if it was not guarded by this ugly "is_a?(Hash)".
The proper use of try with a hash is #sesion.try(:[], :comments).
#session.try(:[], :comments).try(:[], commend.id).try(:[], 'temp_value')
Update: As of Ruby 2.3 use #dig
Most objects that respond to [] expect an Integer argument, with Hash being an exception that will accept any object (such as strings or symbols).
The following is a slightly more robust version of Arsen7's answer that supports nested Array, Hash, as well as any other objects that expect an Integer passed to [].
It's not fool proof, as someone may have created an object that implements [] and does not accept an Integer argument. However, this solution works great in the common case e.g. pulling nested values from JSON (which has both Hash and Array):
class Hash
def get_deep(*fields)
fields.inject(self) { |acc, e| acc[e] if acc.is_a?(Hash) || (e.is_a?(Integer) && acc.respond_to?(:[])) }
end
end
It can be used the same as Arsen7's solution but also supports arrays e.g.
json = { 'users' => [ { 'name' => { 'first_name' => 'Frank'} }, { 'name' => { 'first_name' => 'Bob' } } ] }
json.get_deep 'users', 1, 'name', 'first_name' # Pulls out 'Bob'
say you want to find params[:user][:email] but it's not sure whether user is there in params or not. Then-
you can try:
params[:user].try(:[], :email)
It will return either nil(if user is not there or email is not there in user) or otherwise the value of email in user.
As of Ruby 2.3 this gets a little easier. Instead of having to nest try statements or define your own method you can now use Hash#dig (documentation).
h = { foo: {bar: {baz: 1}}}
h.dig(:foo, :bar, :baz) #=> 1
h.dig(:foo, :zot) #=> nil
Or in the example above:
session.dig(:comments, #comment.id, "temp_value")
This has the added benefit of being more like try than some of the examples above. If any of the arguments lead to the hash returning nil then it will respond nil.
#myvar = session.fetch(:comments, {}).fetch(#comment.id, {})["temp_value"]
From Ruby 2.0, you can do:
#myvar = session[:comments].to_h[#comment.id].to_h["temp_value"]
From Ruby 2.3, you can do:
#myvar = session.dig(:comments, #comment.id, "temp_value")
Another approach:
#myvar = session[:comments][#comment.id]["temp_value"] rescue nil
This might also be consider a bit dangerous because it can hide too much, personally I like it.
If you want more control, you may consider something like:
def handle # just an example name, use what speaks to you
raise $! unless $!.kind_of? NoMethodError # Do whatever checks or
# reporting you want
end
# then you may use
#myvar = session[:comments][#comment.id]["temp_value"] rescue handle
When you do this:
myhash[:one][:two][:three]
You're just chaining a bunch of calls to a "[]" method, an the error occurs if myhash[:one] returns nil, because nil doesn't have a [] method. So, one simple and rather hacky way is to add a [] method to Niclass, which returns nil: i would set this up in a rails app as follows:
Add the method:
#in lib/ruby_extensions.rb
class NilClass
def [](*args)
nil
end
end
Require the file:
#in config/initializers/app_environment.rb
require 'ruby_extensions'
Now you can call nested hashes without fear: i'm demonstrating in the console here:
>> hash = {:foo => "bar"}
=> {:foo=>"bar"}
>> hash[:foo]
=> "bar"
>> hash[:doo]
=> nil
>> hash[:doo][:too]
=> nil
Andrew's answer didn't work for me when I tried this again recently. Maybe something has changed?
#myvar = session[:comments].try('[]', #comment.id)
The '[]' is in quotes instead of a symbol :[]
Try to use
#myvar = session[:comments][#comment.id]["temp_value"] if session[:comments]

Resources