substitute key name for subset of ruby sliced elements - ruby-on-rails

the following ruby slice command runs as expected
#points.map{ |a| a.slice('point', 'point_name') }
returning and array of keys and values.
However, before dumping the array of hashes off to json, the goal is to transform the key 'point_name' to 'title'. Attempting a rails helper, as such
#points.map{ |a| a.slice('point', 'point_name' as: 'title') }
fails. What is the proper syntax?

There is no such syntax in ruby. Key rename can be achieved like this:
#points.map do |a|
a['title'] = a.delete('point_name')
a.slice('point', 'title')
end
You may need json serializer (as you mentioned Rails), consider using FastJsonApi.

Related

How to rename a symbol in hash parameters?

I have parameters from an external API that formats JSON responses using CamelCase that I want to fit into my Rails app:
{"AccountID"=>"REAWLLY_LONG_HASH_API_KEY_FROM_EXTERNAL_SERVICE",
"ChannelProductDescription"=>"0004", "Currency"=>"CAD",
"CurrentBalance"=> {"Amount"=>"162563.64", "Currency"=>"CAD"}}
Using the below script I converted them to lower case:
data = JSON.parse(response, symbolize_keys: true)
data = {:_json => data} unless data.is_a?(Hash)
data.deep_transform_keys!(&:underscore)
data.deep_symbolize_keys!
Leaving me correctly formatted params like this:
{:account_id=>"REAWLLY_LONG_HASH_API_KEY_FROM_EXTERNAL_SERVICE",
:channel_product_description=>"SAVINGS", :currency=>"CAD",
:current_balance=> {:amount=>"43.00", :currency=>"CAD"}}
I'm trying to map this external API response into a generic Rails model Account, where JSON from this API call will return cleanly as parameters into my database to allow a clean saving interface such as:
#account = Account.create(ParamParser.call(params))
But I ran into a problem with converting :account_id, as that param conflicts with the primary key of my database.
To get around this, my idea is to convert all symbol instances of params[:account_id] into params[:account_key_id], so that those params don't conflict with my databases existing account_id field.
How do I do this, and is there a better approach for consuming external JSON API's than what I've described here?
Hash#deep_transform_keys does this:
Returns a new hash with all keys converted by the block operation.
This includes the keys from the root hash and from all
nested hashes and arrays.
So you could do it in one pass with an appropriate block, something like:
data.deep_transform_keys! do |key|
key = key.underscore.to_sym
key = :account_key_id if(key == :account_id)
key
end
You might as well drop the symbolize_keys: true flag to JSON.parse too, you're changing all the keys anyway so don't bother.
If you're doing this sort of thing a lot then you could write a method that takes a key mapping Hash and gives you a lambda for transforming the keys:
def key_mangler(key_map = { })
->(key) do
key = key.underscore.to_sym
key = key_map[key] if(key_map.has_key?(key))
key
end
end
and then say things like:
data.deep_transform_keys!(&key_mangler(:account_id => :account_key_id))
You might want to use a different name than key_mangler of course but that name is good enough to illustrate the idea.
BTW, if you're sending this JSON into the database then you probably don't need to bother with symbol keys, JSON only uses strings for keys so you'll be converting strings to symbols only for them to be converted back to strings. Of course, if you're symbolizing the keys when pulling the JSON out of the database then you'll probably want to be consistent and use symbols across the board.
In addition to the previous answer...
Unfortunately, there is, to my knowledge, no method on Hash that does this in one operation. I've always accomplished this by brute force, as in:
hash[:new_key] = hash[:old_key]
hash.delete(:old_key)
A shortcut for this, suggested in the comment below by "mu is too short", is:
hash[:new_key] = hash.delete(:old_key)
To illustrate in irb:
2.4.1 :002 > h = { foo: :bar }
=> {:foo=>:bar}
2.4.1 :003 > h[:foo_new] = h.delete(:foo)
=> :bar
2.4.1 :004 > h
=> {:foo_new=>:bar}

Select on hash with indifferent access not working

I am trying to deal with inconsistent keys (Strings/Symbols) in hashes. I thought that HashWithIndifferentAccess would be the answer but I am getting some slightly confusing results when trying to do some basic operations on these hashes
For example I have the following HashWithIndifferentAccess
(rdb:1) metadata
{"indexes"=>["respondent", "brand"], "columns"=>["rating"],
"value_labels"=>{}, "column_labels"=>{}}
(rdb:1) metadata.class
ActiveSupport::HashWithIndifferentAccess
when I try the following select I get an empty hash
(rdb:1) metadata.select{ |k, v| [:indexes, :columns, :value_labels, :column_labels]
.include? k }
{}
Are all common hash operations available with HashWithIndifferentAccess ? Why is this operation returning an empty hash
All you really get with HashWithIndifferentAccess is the ability to set and get values using either a string or a key. Once you start using other reading methods on the hash you move to objects that are not indifferent to strings or symbols.
However, HashWithIndifferentAccess does help you because:
Internally symbols are mapped to strings when used as keys in the entire writing interface (calling []=, merge, etc)
....
You are guaranteed that the key is returned as a string
This means that you're always going to get a string for keys with methods like select:
> h = { sym_key: 'sym_value', 'string_key' => 'string_value' }.with_indifferent_access
> h.keys
=> ["sym_key", "string_key"]
Indifferent access means HashWithIndifferentAccess#[] will check both strings and keys. However, there is no such patch done on Array#include?, which you are using to filter your data. Easy fix:
[:indexes, :columns, :value_labels, :column_labels].include? k.to_sym

jRuby hash iteration newbie here

I am very new to rails in general and what I have is a hash being passed as json for one format and now I need to pass it to the view to work with but I have no idea how to iterate over the hash to make it work in the view as I need to do some type of each loop over it. Its a 2 dimensional hash dunno if that means anything or not.
edit
example
{"status":"successful","service_list":[{"service_name":"mySQL","status":"RUNNING","status_message":"No errors reported","host":"1"},{"service_name":"PHP","status":"RUNNING","status_message":"No errors reported","host":"1"},{"service_name":"APache","status":"RUNNING","status_message":"No errors reported","host":"1"},{"service_name":"Jetty","status":"RUNNING","status_message":"No errors reported","host":"1"}]}
This renders fine when I do it as JSON, but using the same thing to render it out in an HTML based view is where I am getting stuck
You have converted a Ruby has to a JSON hash, which is a Javascript format. In Ruby you would access a hash as follows:
hash = {"foo": "bar"}
puts hash["foo"] # This returns "bar"
JSON is similar to Ruby, and can be accessed in the same manner:
var hash = {"foo": "bar"};
alert(hash["foo"]); # This alerts "bar"
If you want to iterate through this collection in Javascript, you can use a for loop:
var data = {"status":"successful","service_list":[{"service_name":"mySQL","status":"RUNNING","status_message":"No errors reported","host":"1"},{"service_name":"PHP","status":"RUNNING","status_message":"No errors reported","host":"1"},{"service_name":"APache","status":"RUNNING","status_message":"No errors reported","host":"1"},{"service_name":"Jetty","status":"RUNNING","status_message":"No errors reported","host":"1"}]};
for(x=0;x<data["service_list"].length;x++) {
alert(data["service_list"][x]["service_name"]); # This returns "mySQL", ...
};
If you are wanting to convert this JSON object to a Ruby has you can call "JSON.parse" with your JSON string as an argument.

Symbols used as Hash keys get converted to Strings when serialized

When I assign an Array or Hash to an attribute of a Mongo document, it gets properly
serialized except for Symbols when they are used as Hash keys. Simple example:
irb>MyMongoModel.create :some_attr => {:a => [:b,:c]}
=> #<MyMongoModel _id: 4d861c34c865a1f06a000001, some_attr: {:a=>[:b, :c]}>
irb>MyMongoModel.last
=> #<MyMongoModel _id: 4d861c34c865a1f06a000001, some_attr: {"a"=>[:b, :c]}>
Please, note that some_attr is retrieved as {"a"=>[:b, :c]}, not as
{:a=>[:b, :c]}
This also happens for nested Hashes (e.g., inside of Arrays or other Hashes). Is there a way to preserve Symbols in such cases?
Solution
I'm using YAML to manually serialize some_attr - YAML.dump (or Object#to_yaml) before storing, and YAML::load after reading the attribute. YAML preserves the serialized object better. ActiveRecord is using YAML to implement its serialize class method on ActiveRecord::Base.
More than likely this has to do with the ORM you are using to provide the persistance layer for the model. You can probably wrap some_attr with a method that returns it in the form of a HashWithIndifferentAccess which you can then access with either strings or arrays. Since you are using Rails, this functionality can be activated by calling the with_indifferent_access method on the Hash object. (If you have an array of Hash objects, you'll need to call it on each one of course) The method will return the same hash, but then symbol lookups will work.
From your code:
new_hash = MyMongoModel.last.some_attr.with_indifferent_access
new_hash[:a] # Will return the same as new_hash['a']
Hope this helps!
the culprit here is the BSON serialization. when you serialize a symbol used as a key for hashes, it is actually translated to a string and when you ask it back you get the string instead of the symbol.
i'm having the same problem as you and i'm thinking of extending the Hash class to include a method to convert all the "string" keys to :symbols.
unfortunately i'm not on Rails so i cannot use the with_indifferent_access as suggested by ctcherry.
I'm not sure about preserving symbols but you can convert the strings back to symbols.
str.to_sym
Found this, works well and you have define the field as Hash:
https://github.com/mindscratch/mongoid-indifferent-access

Reusing Rails' param parsing to turn a one dimensional hash into a multideminonal one based on the key values and array notation

I am currently using rturk which give me back my answers in a one dimensional hash as such...
{"answers[125][rating]"=>"5", "answers[126][rating]"=>"5", "commit"=>"Take Survey", "answers[125][rating]"=>"5", "authenticity_token"=>"je0Hx48qKmCzy1zmXCpijYWbl4w92eDMRajWJcVYxe0=", "gender"=>"m", "answers[120][rating]"=>"5", "answers[121][rating]"=>"5", "income"=>"$75,000 to $100,000", "answers[122][rating]"=>"5", "date[year]"=>"1992", "career"=>"Marketer", "answers[123][rating]"=>"5", "answers[124][rating]"=>"5"}
What I would like to do is parse those into a multidimensional hash that I can then just pass as attributes. If I can figure this out I'd probably switch from form_tag back to rails stand object forms.
Yes, Rack::Utils.parse_nested_query could help you, but you need to convert hash to string:
h = {"answers[125][rating]"=>"5", ...} # your hash
s = h.to_a.collect { |a| a.join('=') }.join('&')
Rack::Utils.parse_nested_query(s)
Take a look at the Turkee gem (http://github.com/aantix/turkee) as it will do this object mapping for you.

Resources