Shortcut for showing list of hashes "nicely" - ruby-on-rails

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

Related

Ruby - Unexpected Hash data structure

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]

A better way of creating a ruby hash?

I want to build a hash from an array of rows from a DB. I can easily do it with the code below. I've come to Ruby from PHP and this is how I would do it. Is there a better/proper way to do this in Ruby (or Rails)?
def features_hash
features_hash = {}
product_features.each do |feature|
features_hash[feature.feature_id] = feature.value
end
features_hash
end
# {1 => 'Blue', 2 => 'Medium', 3 => 'Metal'}
You can use Hash[]:
Hash[ product_features.map{|f| [f.feature_id, f.value]} ]
Would you like this better?
product_features.map{|f| [f.feature_id, f.value]}.to_h # no available (yet?)
Then go and check out this feature request and comment on it!
Alternative solutions:
product_features.each_with_object({}){|f, h| h[f.feature_id] = f.value}
There is also group_by and index_by which could be helpful, but the values will be the features themselves, not their value.
You can use index_by for this:
product_features.index_by(&:id)
This produces the same results as hand-constructing a hash with id as the key and the records as the values.
Your code is a good way to do it. Another way is:
def features_hash
product_features.inject({}) do |features_hash, feature|
features_hash[feature.feature_id] = feature.value
features_hash
end
end

convert ruby hash to URL query string ... without those square brackets

In Python, I can do this:
>>> import urlparse, urllib
>>> q = urlparse.parse_qsl("a=b&a=c&d=e")
>>> urllib.urlencode(q)
'a=b&a=c&d=e'
In Ruby[+Rails] I can't figure out how to do the same thing without "rolling my own," which seems odd. The Rails way doesn't work for me -- it adds square brackets to the names of the query parameters, which the server on the other end may or may not support:
>> q = CGI.parse("a=b&a=c&d=e")
=> {"a"=>["b", "c"], "d"=>["e"]}
>> q.to_params
=> "a[]=b&a[]=c&d[]=e"
My use case is simply that I wish to muck with the values of some of the values in the query-string portion of the URL. It seemed natural to lean on the standard library and/or Rails, and write something like this:
uri = URI.parse("http://example.com/foo?a=b&a=c&d=e")
q = CGI.parse(uri.query)
q.delete("d")
q["a"] << "d"
uri.query = q.to_params # should be to_param or to_query instead?
puts Net::HTTP.get_response(uri)
but only if the resulting URI is in fact http://example.com/foo?a=b&a=c&a=d, and not http://example.com/foo?a[]=b&a[]=c&a[]=d. Is there a correct or better way to do this?
In modern ruby this is simply:
require 'uri'
URI.encode_www_form(hash)
Quick Hash to a URL Query Trick :
"http://www.example.com?" + { language: "ruby", status: "awesome" }.to_query
# => "http://www.example.com?language=ruby&status=awesome"
Want to do it in reverse? Use CGI.parse:
require 'cgi'
# Only needed for IRB, Rails already has this loaded
CGI::parse "language=ruby&status=awesome"
# => {"language"=>["ruby"], "status"=>["awesome"]}
Here's a quick function to turn your hash into query parameters:
require 'uri'
def hash_to_query(hash)
return URI.encode(hash.map{|k,v| "#{k}=#{v}"}.join("&"))
end
The way rails handles query strings of that type means you have to roll your own solution, as you have. It is somewhat unfortunate if you're dealing with non-rails apps, but makes sense if you're passing information to and from rails apps.
As a simple plain Ruby solution (or RubyMotion, in my case), just use this:
class Hash
def to_param
self.to_a.map { |x| "#{x[0]}=#{x[1]}" }.join("&")
end
end
{ fruit: "Apple", vegetable: "Carrot" }.to_param # => "fruit=Apple&vegetable=Carrot"
It only handles simple hashes, though.

Separating an Array into a comma separated string with quotes

I'm manually building an SQL query where I'm using an Array in the params hash for an SQL IN statement, like: ("WHERE my_field IN('blue','green','red')"). So I need to take the contents of the array and output them into a string where each element is single quoted and comma separated (and with no ending comma).
So if the array was: my_array = ['blue','green','red']
I'd need a string that looked like: "'blue','green','red'"
I'm pretty new to Ruby/Rails but came up with something that worked:
if !params[:colors].nil?
#categories_array = params[:colors][:categories]
#categories_string =""
for x in #categories_array
#categories_string += "'" + x + "',"
end
#categories_string.chop! #remove the last comma
end
So, I'm good but curious as to what a proper and more consise way of doing this would look like?
Use map and join:
#categories_string = #categories_array.map {|element|
"'#{element}'"
}.join(',')
This functionality is built into ActiveRecord:
Model.where(:my_field => ['blue','green','red'])
Are you going to pass this string on to a ActiveRecord find method?
If so, ActiveRecord will handle this for you automatically:
categories_array = ['foo', 'bar', 'baz']
Model.find(:all, :conditions => ["category in (?)", categories_array])
# => SELECT * FROM models WHERE (category in ('foo', 'bar', 'baz'))
Hope this helps.
If you are using the parameter hash you don't have to do any thing special:
Model.all(:conditions => {:category => #categories_array})
# => SELECT * FROM models WHERE (category in ('foo', 'bar', 'baz'))
Rails (actually ActiveSupport, part of the Rails framework) offers a very nice Array#to_sentence method.
If you are using Rails or ActiveSupport, you can call
['dog', 'cat', 'bird', 'monkey'].to_sentence # "dog, cat, bird, and monkey"

Overloading ActiveSupport's default to_sentence behaviour

ActiveSupport offers the nice method to_sentence. Thus,
require 'active_support'
[1,2,3].to_sentence # gives "1, 2, and 3"
[1,2,3].to_sentence(:last_word_connector => ' and ') # gives "1, 2 and 3"
it's good that you can change the last word connector, because I prefer not to have the extra comma. but it takes so much extra text: 44 characters instead of 11!
the question: what's the most ruby-like way to change the default value of :last_word_connector to ' and '?
Well, it's localizable so you could just specify a default 'en' value of ' and ' for support.array.last_word_connector
See:
from: conversion.rb
def to_sentence(options = {})
...
default_last_word_connector = I18n.translate(:'support.array.last_word_connector', :locale => options[:locale])
...
end
Step by step guide:
First, Create a rails project
rails i18n
Next, edit your en.yml file: vim config/locales/en.yml
en:
support:
array:
last_word_connector: " and "
Finally, it works:
Loading development environment (Rails 2.3.3)
>> [1,2,3].to_sentence
=> "1, 2 and 3"
As an answer to how to override a method in general, a post here gives a nice way of doing it. It doesn't suffer from the same problems as the alias technique, as there isn't a leftover "old" method.
Here how you could use that technique with your original problem (tested with ruby 1.9)
class Array
old_to_sentence = instance_method(:to_sentence)
define_method(:to_sentence) { |options = {}|
options[:last_word_connector] ||= " and "
old_to_sentence.bind(self).call(options)
}
end
You might also want read up on UnboundMethod if the above code is confusing. Note that old_to_sentence goes out of scope after the end statement, so it isn't a problem for future uses of Array.
class Array
alias_method :old_to_sentence, :to_sentence
def to_sentence(args={})
a = {:last_word_connector => ' and '}
a.update(args) if args
old_to_sentence(a)
end
end

Resources