Convert Ruby hash into array without calling `Hash#to_a` - ruby

The Savon gem I am using is giving me back a single object or an array, and I have no way to know which it will be until the SOAP response comes back.
For convenience I would like to have a nil response converted to [], a single response converted to [obj] and an array stay as an array. This can easily be done with Kernel#Array, thus:
> Array nil
=> []
> Array 1
=> [1]
> Array [1,2,3]
=> [1, 2, 3]
However, because Kernel#Array calls to_a, it fails for Hash which overrides to_a:
> Array({a: 1})
=> [[:a, 1]]
> Array([{a: 1}, {b: 2}])
=> [{:a=>1}, {:b=>2}]
On line 2 above I would like to see [{:a=>1}].

If you are using ActiveSupport, you can do the following:
> Array.wrap({a: 1})
=> [{:a, 1}]
> Array.wrap([{a: 1}, {b: 2}])
=> [{:a=>1}, {:b=>2}]

>> [nil].compact.flatten(1)
=> []
>> [1].compact.flatten(1)
=> [1]
>> [{a: 1, b: 2}].compact.flatten(1)
=> [{:a=>1, :b=>2}]

Currently I am able to bypass Hash#to_a with my own straight_to_a method:
def straight_to_a(o)
o.kind_of?(Array) ? o : [o].compact
end
Thus:
> straight_to_a nil
=> []
> straight_to_a 1
=> [1]
> straight_to_a( {a: 1} )
=> [{:a=>1}]
I'm hoping there's an easier way?

Another poster suggested the use of Active Support. If you don't want to add an extra dependency to your project for just one method, here is the source code for Active Support's Array.wrap:
class Array
def self.wrap(object)
if object.nil?
[]
elsif object.respond_to?(:to_ary)
object.to_ary || [object]
else
[object]
end
end
end
You could easily add this code to your own utilities.rb or core_extensions.rb file and include that in your project.

Your solution seems ok, perhaps you can try something based on flatten, like
def straight_to_a *ary
ary.flatten(1)
end

Related

Converting an array of strings into a key-value pair

I have an array as the value of a hash:
:params=>[":centre_id", ":id"]
I am trying to find a way to convert each element of the array to something like this:
:centre_id => 1
:id => 1
I tried loads of different ways but I can't seem to find a nice clean way to do it:
"#{route[:params].map {|x| x.parameterize.underscore.to_sym => '1'}}"
This is what I am trying to achieve:
-{"controller"=>"venues", "action"=>"activity, {:centre_id=>1, :id=>1}"}
+{"controller"=>"venues", "action"=>"activity", "centre_id"=>"1", "id"=>"1"} << this one
string or symbol doesn't matter does it?
Using this:
expect(route[:request_method] => route[:path]).to route_to "#{route[:controller]}##{route[:action]}, #{Hash[route[:params].map {|x| [x.sub(/\A:/,'').to_sym, 1] }]}"
Help would be much appreciated.
Do as below :-
Ruby 2.1 or above, use below :-
route[:params].map {|x| [x.sub(/\A:/,'').to_sym, 1] }.to_h
# => {:centre_id => 1, :id => 1}
Ruby 2.0 or below, use below :-
Hash[route[:params].map {|x| [x.sub(/\A:/,'').to_sym, 1] }]
# => {:centre_id => 1, :id => 1}
If
h = { :params=>[":centre_id", ":id"], :animals=>[":dog", ":cat", ":pig"] }
one of the following may be helpful:
h.merge(h) { |_,v,_| Hash[v.map { |s| [s[1..-1].to_sym,1] }] }
#=> {:params=>{:centre_id=>1, :id=>1}, :animals=>{:dog=>1, :cat=>1, :pig=>1}}
or
Hash[h.keys.map { |k| h.delete(k).map { |s| [s[1..-1].to_sym,1] } }.flatten(1)]
#=> {:centre_id=>1, :id=>1, :dog=>1, :cat=>1, :pig=>1}
The second modifies h. If you don't want that to happen, you'll need to h.dup first.
If a is an array, you can replace Hash[a] with a.to_h with Ruby 2.1+.

Why is it possible to change a frozen constant?

Why is it possible to do the following? I would not expect it to be.
CAD={:hey => {a: [1], b: [2]}}.freeze
CAD.frozen? #=> true
p=CAD[:hey][:a] #=> [1]
p << nil #=> [1, nil]
CAD #=> {:hey=>{:a=>[1, nil], :b=>[2]}}
UPDATE
I found a solution, thanks to the answer: http://blog.flavorjon.es/2008/08/freezing-deep-ruby-data-structures.html
Only the hash object represented by CAD is frozen, but not the other objects referenced on the hash like CAD[:hey][:a].
> CAD={:hey => {a: [1], b: [2]}}.freeze
=> {:hey=>{:a=>[1], :b=>[2]}}
> CAD.frozen?
=> true
> CAD[:hey].frozen?
=> false
> CAD[:hey][:a].frozen?
=> false

Parsing a Ruby Array to JSON

I have some results:
puts result
That look like this output:
Allowed
20863963
1554906
Denied
3607325
0
Quarantined
156240
0
Debug
p results
output
[["Allowed", 20863963, 1554906], ["Denied", 3607325, 0], ["Quarantined", 156194, 0]]
The headers are:
status,hits,page_views
I need to convert this to json. If the results was in standard csv format then it would be straight forward but how would one approach it if the results format looked like above?
Expected output something similar to this:
[{"status":"Allowed","hits":"20863963","page_views":"1554906"},{"status":"Denied","hits":"3607325","page_views":"0"},{"status":"Quarantined","hits":"156240","page_views":"0"}]
Solution
a = result.map{|s| {status: s[0], hits: s[1].to_i, page_views: s[2].to_i} }
puts a.to_json
Look at to_json method.
require 'json'
# => true
h = {a: 1, b: 2,c: 3}
# => {a: 1, b: 2,c: 3}
h.to_json
# => "{\"a\":1,\"b\":2,\"c\":3}"
output = "Allowed
20863963
1554906
Denied
3607325
0
Quarantined
156240
0"
a = output.split("\n").each_slice(3).map{|s| {status: s[0], hits: s[1].to_i, page_views: s[2].to_i} } # => [{:status=>"Allowed", :hits=>20863963, :page_views=>1554906}, {:status=>"Denied", :hits=>3607325, :page_views=>0}, {:status=>"Quarantined", :hits=>156240, :page_views=>0}]
a.to_json # => => "[{\"status\":\"Allowed\",\"hits\":20863963,\"page_views\":1554906},{\"status\":\"Denied\",\"hits\":3607325,\"page_views\":0},{\"status\":\"Quarantined\",\"hits\":156240,\"page_views\":0}]"
You assign your "headers" into the attr_accessor and then tell JSON to parse that symbol. Here's an example:
class Document
attr_accessor :content
def content
metadata[:content] || metadata['content']
end
def self.parse_contents
txt = File.read(path, {mode: 'r:bom|utf-8'})
page = Document.new
page.metadata = JSON.parse(txt)
page.content = page.metadata['content']
return page
end
end
Hope it helps!

rails-settings: NoMethodError: undefined method `merge' for []:Array

I'm trying to implement the rails-settings gem (https://github.com/100hz/rails-settings) into my Rails 3 project using Ruby 1.8.7
Setting and retrieving the settings works perfectly, but I get an error if I try getting all settings of a specific user.
So, in the 'rails console' the following works:
user = User.find(123)
user.settings.color = :red
user.settings.color
But if I try to get all settings:
user.settings.all
I get:
NoMethodError: undefined method `merge' for []:Array
from /[...]/.rvm/gems/ruby-1.8.7-p334/bundler/gems/rails-settings-883114dfd933/lib/rails-settings/settings.rb:55:in `all'
from (irb):5
line 55 in the settings.rb:
#retrieve all settings as a hash (optionally starting with a given namespace)
def self.all(starting_with=nil)
options = starting_with ? { :conditions => "var LIKE '#{starting_with}%'"} : {}
vars = thing_scoped.find(:all, {:select => 'var, value'}.merge(options))
result = {}
vars.each do |record|
result[record.var] = record.value
end
# line 55 is below this one...
##defaults.select{ |k| k =~ /^#{starting_with}/ }.merge(result).with_indifferent_access
end
Whats the problem here? Or is this a ruby 1.8.7 vs. 1.9.2 thing?
That's a Ruby 1.8.7 vs. 1.9.2 thing
The Hash select method under ruby 1.8.7 will return an Array of Arrays.
Example:
{:a => 'a', :b => 'b', :c => 'c'}.select {|k, v| v > 'a'} #=> [[:b,'b'],[:c,'c']]
While the same thing running Ruby 1.9.2 will return:
{:a => 'a', :b => 'b', :c => 'c'}.select {|k, v| v > 'a'} #=> {:b => 'b',:c => 'c'}
You will need to post process the result and turn it into a hsah again or use something like inject.
Edit:
Here is a quick/ugly example of the inject
{:a => 'a', :b => 'b', :c => 'c'}.inject({}) {|r, e| e[1] > 'a' ? r.merge({e[0] => e[1]}) : r }
Semantically speaking:
collection.inject(container) { |container, element| select_condition ? container + element : container }
Edit 2: (Based on #CaleyWoods post)
Hash[*##defaults.select{ |k,v| k =~ /^#{starting_with}/ }.flatten].merge(result)
The |k, v| will prevent unnecessary warnings.
This looks like it's trying to do a merge on an Array which is not a method the Array class in Ruby. Merge is supported for Hash, it looks like your returned object is not the correct type. The author was definitely relying on a hash, in the next to last line 'with_indifferent_access' is called which is trying to allow you to select items from the hash with strings or symbols.
I can't examine the gem further right now and I wish I could provide a more helpful answer. If this hasn't been answered later i'll come back and help you out.
Not sure why the author is using double and single quotes in options and vars. He's also trying to populate the hash by hand instead of with inject. It's not awful by any means but I think there's room for improvement in the small bit of code you posted from the file.

Ruby equivalent of Perl Data::Dumper

I am learning Ruby & Perl has this very convenient module called Data::Dumper, which allows you to recursively analyze a data structure (like hash) & allow you to print it. This is very useful while debugging. Is there some thing similar for Ruby?
Look into pp
example:
require 'pp'
x = { :a => [1,2,3, {:foo => bar}]}
pp x
there is also the inspect method which also works quite nicely
x = { :a => [1,2,3, {:foo => bar}]}
puts x.inspect
I normally use a YAML dump if I need to quickly check something.
In irb the syntax is simply y obj_to_inspect. In a normal Ruby app, you may need to add a require 'YAML' to the file, not sure.
Here is an example in irb:
>> my_hash = {:array => [0,2,5,6], :sub_hash => {:a => 1, :b => 2}, :visible => true}
=> {:sub_hash=>{:b=>2, :a=>1}, :visible=>true, :array=>[0, 2, 5, 6]}
>> y my_hash # <----- THE IMPORTANT LINE
---
:sub_hash:
:b: 2
:a: 1
:visible: true
:array:
- 0
- 2
- 5
- 6
=> nil
>>
The final => nil just means the method didn't return anything. It has nothing to do with your data structure.
you can use Marshal, amarshal, YAML

Resources