deep_symbolize_keys! converts string keys to symbol keys. This works for hashes and all sub-hashes. However, I have a data like this:
arr = [
{'name': 'pratha', 'email': 'p#g.com', 'sub': { 'id': 1 } },
{'name': 'john', 'email': 'c#d.com', 'sub': { 'id': 2 } }
]
arr.deep_symbolize_keys! # this is not working for array of hashes.
In this case, hashes are in an array. So how can i symbolize all at once?
Using Ruby 2.6.3
I also read somewhere that this is deprecated (probably on one of the Rails forum). Is that true? If so, what is the best way to convert keys to symbols in my case?
Currently using this:
def process(emails)
blacklist = ["a", "john", "c"]
e = emails.map do |hash|
blacklist.include?(hash['name']) ? nil : hash.deep_symbolize_keys!
end
e
end
Do you need a copy or an in-place transformation? In-place you can use arr.each(&:deep_symbolize_keys!). For a copy you should use arr.map(&:deep_symbolize_keys). Remember that map does not mutate but returns a new array.
The implementation already handles nested arrays, it just doesn't define the method on Array. So, nest it in a temporary hash and symbolize that. This works for arbitrary types:
[1] pry(main)> def deep_symbolize_keys(object) = {object:}.deep_symbolize_keys[:object];
[2] pry(main)> deep_symbolize_keys([{"a" => 1}, {"b" => {"c" => 2}}])
=> [{:a=>1}, {:b=>{:c=>2}}]
Also, be careful with your key syntax. In your example, your keys are already symbols - they're just quoted symbols:
[3] pry(main)> {a: 1}.keys.first.class
=> Symbol
[4] pry(main)> {'a': 1}.keys.first.class
=> Symbol
[5] pry(main)> {'a' => 1}.keys.first.class
=> String
The syntax is necessary to handle cases like {'a-b': 1}[:'a-b'], but it's very often misleading since they look so much like string keys. I recommend avoiding it entirely unless absolutely necessary - stick to {a: 1} for symbol keys and {'a' => 1} for string keys.
Related
I'm passing as array of JSON object as a param in Rails through an AJAX post/delete, like
[{'id': 3, 'status': true}, {'id': 1, 'status': true}]
How do I loop through the params[:list] to get each id and status value?
Do this:
params[:list].each do |hash|
hash['id'] # => 3
hash['status'] # => true
end
Try like follows:
params[ :list ][ 0 ][ :id ] # => 3, ...
params[ :list ].each {| v | v[ :id ] } # => 3, 1
In case if your hash is like ["0", {"id"=>"9", "status"=>"true"}]:
# a = ["0", {"id"=>"9", "status"=>"true"}]
h = Hash[[a]]
# => {"0"=>{"id"=>"9", "status"=>"true"}}
and access to it will be:
h['0'][ :id ] # => '9'
There were two steps to this, plus what #Agis suggested. (Thanks #Agis)
I had to first stringify the JSON:
'p': JSON.stringify(p),
So that it does not create that wierd array. There was another stackoverflow answer using this method to generate a correct data JSON object, but it was encapsulating the whole data... I need to have this on just p and not affect 'stringifying' the security token.
Understood this from here: Rails not decoding JSON from jQuery correctly (array becoming a hash with integer keys)
Next, I had to decode just that param, using
ActiveSupport::JSON.decode(params[:p])
a clue from here: How do I parse JSON with Ruby on Rails?
Lastly, can I then use the loop and access item['id']
Here's a loop:
params[:list].each do |item|
item.id
item.satus
end
If you want to create an array of id's or something:
list_ids = params[:list].map(&:id)
I am writing integration tests for rails and I want to compare the object created with the JSON object sent. The object returned is not exactly the same as the one sent, (i.e.) it has keys that the object sent doesn't have because I am using active model serializers to pull associations in the returned object. Basically, I just want to compare all the same keys between both objects to see if its the same. Let me know if there is a clean efficient code snippet that does this for me!
TL;DR
"Clever" test code is rarely useful. Each test should be as simple as possible, and it should be testing the behavior of some object rather than its composition. There are always ways to be clever, though.
Using Array Intersection
One unreadably-clever way to do this is to use Array#& to find the intersection of the keys, and then look for equality between the values. This will work on a relatively flat hash. For example:
hash1 = {:key1=>"value1", :key2=>"value2", :key3=>"value3", :key4=>"value4"}
hash2 = {:key1=>"value1", :key2=>"value2", :key5=>"value5"}
Array(hash1.keys & hash2.keys).map { |k| hash1[k] == hash2[k] }.uniq
#=> [true]
If you're using RSpec to test, you could say something like:
it 'has some matching key/value pairs' do
# ... populate hash1
# ... populate hash2
Array(hash1.keys & hash2.keys).
map { |k| hash1[k] == hash2[k] }.uniq.should == [true]
end
Of course, if the expectation is false, then you won't really know why, or which key/value pair was wrong. This is just one of the many reasons that you should always use fixed inputs and outputs for testing, rather than trying to do dynamic comparisons.
You could use Hash#slice, which is an Active Support core extension.
For example, if the keys you want to check are :a, :b, :c, but the result contains :a, :b, :c, :d, slice will reduce the result to just contain the keys you care about:
expected = { :a => 1, :b => 2, :c => 3 }
result = { :a => 1, :b => 2, :c => 3, :d => 4 }
result.slice(:a, :b, :c) == expected
# => true
If you get a NoMethodError: undefined method 'slice' exception, you need to require active_support/core_ext/hash/slice
First, find the keys that both hashes contain, and then compare the value for those keys:
hash1 = {:key1 => "value1", :key2 => "value2", :key3 => "value3", :key4 => "value4"}
hash2 = {:key1 => "value1", :key2 => "value2", :key5 => "value5"}
hash1_keys = hash1.keys
hash2_keys = hash2.keys
comparable_keys = hash1_keys.select{|key| hash2_keys.include?(key)}
comparable_keys.each do |key|
hash1[key].should == hash2[key]
end
I am consuming an API that expects me to do requests in the following format:
?filter=value1&filter=value2
However, I am using Active Resource and when I specify the :params hash, I can't make the same parameter to appear twice in the URL, which I believe is correct. So I can't do this:
:params => {:consumer_id => self.id, :filter => "value1", :filter => "value2" }, because the second filter index of the hash will be ignored.
I know I can pass an array (which I believe is the correct way of doing it) like this:
:params => {:consumer_id => self.id, :filter => ["value1","value2"] }
Which will produce a URL like:
?filter[]=value1&filter[]=value2
Which to me seems ok, but the API is not accepting it. So my question are:
What is the correct way of passing parameters with multiple values? Is it language specific? Who decides this?
http://guides.rubyonrails.org/action_controller_overview.html#hash-and-array-parameters
Try :filter[] => value, :filter[] => value2
to create a valid query string, you can use
params = {a: 1, b: [1,2]}.to_query
http://apidock.com/rails/Hash/to_query
http://apidock.com/rails/Hash/to_param
You can generate query strings containing repeated parameters by making a hash that allows duplicate keys.
Because this relies on using object IDs as the hash key, you need to use strings instead of symbols, at least for the repeated keys.
(reference: Ruby Hash with duplicate keys?)
params = { consumer_id: 1 } # replace with self.id
params.compare_by_identity
params["filter"] = "value1"
params["filter"] = "value2"
params.to_query #=> "consumer_id=1&filter=value1&filter=value2"
I findy myself doing a lot of puts .inpsect s in my functional testing to make sure I know how the data is formatted... but hashes are hard to read when there is no new lines after each entry in a hash object.
Is there anyway, maybe a gem?, to pretty print hashes?
So that it looks something like this:
{
entry1 => {
entrey1.1 => 1,
entry1.2 => 3
},
entry2 => 3
}
instead of: { entry1 => { entrey1.1 => 1, entry1.2 => 3}, entry2 => 3 }
?
Thanks!
you could use the awesome_print gem for that.
https://github.com/michaeldv/awesome_print
require 'awesome_print' # if you like to have it in irb by default, add it to your irbrc
>> ap({:a => 1, :b => [1,2,3], :c => :d})
{
:b => [
[0] 1,
[1] 2,
[2] 3
],
:a => 1,
:c => :d
}
btw, instead of puts object.inspect you can also just use p object which calls inspect in the object before printing it.
another way to print objects a little nicer than the default puts is to use pp from the ruby stdlib ( http://ruby-doc.org/stdlib/libdoc/pp/rdoc/index.html )
You could always redefine Hash#inspect in your .irbrc file if you'd like, format them any way you want. That will only affect your interactive environment. An alternative is to express them as YAML which is often more readable. For instance:
def y(object)
puts object.to_yaml
return
end
This way you can run y on objects as you might p today.
If I already have a hash, can I make it so that
h[:foo]
h['foo']
are the same? (is this called indifferent access?)
The details: I loaded this hash using the following in initializers but probably shouldn't make a difference:
SETTINGS = YAML.load_file("#{RAILS_ROOT}/config/settings.yml")
You can just use with_indifferent_access.
SETTINGS = YAML.load_file("#{RAILS_ROOT}/config/settings.yml").with_indifferent_access
If you have a hash already, you can do:
HashWithIndifferentAccess.new({'a' => 12})[:a]
You can also write the YAML file that way:
--- !map:HashWithIndifferentAccess
one: 1
two: 2
after that:
SETTINGS = YAML.load_file("path/to/yaml_file")
SETTINGS[:one] # => 1
SETTINGS['one'] # => 1
Use HashWithIndifferentAccess instead of normal Hash.
For completeness, write:
SETTINGS = HashWithIndifferentAccess.new(YAML.load_file("#{RAILS_ROOT}/config/settings.yml"))
You can just make a new hash of HashWithIndifferentAccess type from your hash.
hash = { "one" => 1, "two" => 2, "three" => 3 }
=> {"one"=>1, "two"=>2, "three"=>3}
hash[:one]
=> nil
hash['one']
=> 1
make Hash obj to obj of HashWithIndifferentAccess Class.
hash = HashWithIndifferentAccess.new(hash)
hash[:one]
=> 1
hash['one']
=> 1