Ruby - Adding value to hash adds duplicate data - ruby-on-rails

When I access my nested hash something weird happens.
Below is my nested hash.
{
"http://example.com"=>{
"a-big_word"=>{
"Another word"=>[]
}
},
"www.example.com"=>{
"a-big_word"=>{
"Another word"=>[]
}
}
}
If I try and add something to it with the following
hash['www.example.com']['a-big_word']['Another word'] << {"key"=>"value"}
This happens
{
"http://example.com"=>{
"a-big_word"=>{
"Another word"=>[{"key"=>"value"}]
}
},
"www.example.com"=>{
"a-big_word"=>{
"Another word"=>[{"key"=>"value"}]
}
}
}

Use strings instead of symbols as keys. I took your hash and changed the keys to be strings. Now it looks like this:
{"http://example.com"=>
{"sublink"=>
{"A word"=>[], :"Another word"=>[]},
"sublinktwo"=>
{"Hello"=>[], "World"=>[]}},
"www.example.com"=>
{"sublink"=>
{"hi"=>[], "goodbye"=>[]},
"sublinkthree"=>
{"word"=>[], "bye"=>[]}
}
}
If you haven't see the difference, for keys I'm using => instead of :. In that way Ruby will not convert the keys into symbols, it will leave it as they are.
How to access the values? Check out the following irb session.
> hash["www.example.com"]
=> {"sublink"=>{"hi"=>[], "goodbye"=>[]}, "sublinkthree"=>{"word"=>[], "bye"=>[]}}
> hash["www.example.com"]["sublink"]
=> {"hi"=>[], "goodbye"=>[]}
> hash["www.example.com"]["sublink"]["hi"]
=> []
Change value:
> hash["www.example.com"]["sublink"]["hi"] << {"key"=>"value"}
=> [{"key"=>"value"}]

Related

Use Single Quote instead of colons in Ruby/Rails

I'm trying to get this test to pass:
it "returns an object with the correct fields" do
expected_response = {"animal_time": #curr_time.strftime("%F %T"), "animals":{}}
expect(AnimalHaircutSession.latest_for(#animal_haircut)).to eq(expected_response)
end
When running the test I get this error:
expected: {:animal_time=>"2020-02-14 09:48:30", :animals=>{}}
got: {"animal_time"=>"2020-02-14 16:48:30", "animals"=>{}}
Why is it converting my expected response to colons and how can I set my expected response to use single quotes?
In Ruby colons are used to define hashes with symbols for keys . The keys will be cast into symbols.
{ "foo": "bar" } == { :foo => "bar" } # true
{ "foo": "bar" } != { "foo" => "bar" } # true
If you want to define a hash where the keys are not symbols use hash-rockets (=>).
it "returns an object with the correct fields" do
expected_response = {
"animal_time" => #curr_time.strftime("%F %T"),
"animals" => {}
}
expect(AnimalHaircutSession.latest_for(#animal_haircut)).to eq(expected_response)
end
While Ruby will allow you to mix hash-rockets and colons in a hash it's generally not considered good form.

Re-ordering hash items to have some appear at the end

I'm returning a hash of form inputs, which at the moment looks like:
hash = {
"body"=>:text,
"button_text"=>:string,
"category"=>:integer,
"dashboard"=>:boolean,
"feature"=>:integer,
"featured_from"=>:datetime,
"featured_to"=>:datetime,
"global"=>:boolean,
"hyperlink"=>:string,
"jobs"=>:boolean,
"labs"=>:boolean,
"leaderboard"=>:boolean,
"management"=>:boolean,
"news"=>:boolean,
"objectives"=>:boolean,
"only_for_enterprise_users"=>:boolean,
"published_at"=>:datetime,
"title"=>:string,
"workflow_state"=>:string
}
I need to place the following keys at the end:
["dashboard", "jobs", "labs", "management", "news", "objectives", "global"]
Which will leave me with:
{
"body"=>:text,
"button_text"=>:string,
"category"=>:integer,
"feature"=>:integer,
"featured_from"=>:datetime,
"featured_to"=>:datetime,
"hyperlink"=>:string,
"only_for_enterprise_users"=>:boolean,
"published_at"=>:datetime,
"title"=>:string,
"workflow_state"=>:string,
"dashboard"=>:boolean,
"jobs"=>:boolean,
"labs"=>:boolean,
"leaderboard"=>:boolean,
"management"=>:boolean,
"news"=>:boolean,
"objectives"=>:boolean,
"global"=>:boolean
}
All the links I've found relate to transforming keys / values without re-ordering, and outside of manually deleting each key and then reinserting I can't see another way of getting my desired output.
Is there an easy way to achieve what I need?
Thanks in advance
You can try following,
(hash.keys - end_keys + end_keys).map { |key| [key, hash[key]] }.to_h
hash = { "body"=>:text, "button_text"=>:string, "category"=>:integer,
"dashboard"=>:boolean, "feature"=>:integer }
enders = ["button_text", "dashboard"]
hash.dup.tap { |h| enders.each { |k| h.update(k=>h.delete(k)) } }
See Object#tap, Hash#update (aka merge!) and Hash#delete.
dup may of course be removed if hash can be mutated, which may be reasonable as only the keys are being reordered.

Rails: How to update the hash in an array

How to update a hash from an array?
Sample Data
form_fields = [
{
key: '1',
properties: null
},
{
key: '2',
properties: {"options"=>[{"label"=>"Europe", "value"=>"europe"}, {"label"=>"North America", "type"=>"groupstart"}, {"label"=>"Canada", "value"=>"canada"}, {"label"=>"USA", "value"=>"usa"}, {"label"=>"", "type"=>"groupend"}]}
}
]
Code I have so far
form_fields = form_fields.map {
|field| {
field.properties = field.properties ? JSON.parse(field.properties) : {}
}
I built this based on some other questions I came across such as this one How do I modify an array while I am iterating over it in Ruby?
The syntax for the map is very close to what you already have, the correct syntax would be like this:
form_fields = form_fields.map {
|field|
...
}
To access a object in the Hash structure you would use the symbol, or string as the selector, like this:
field[:properties]
field[:properties]["options"]
The code you have in your map, does not really make sense. The object stored in the code you provided is already ruby code, the hash keys is just strings instead of symbols.
You do also not want to update your form_fields variable to the result of your map, since that will overwrite your array, and only keep the last element in the array.
I believe what you want is something like this:
form_fields.map { |field|
field[:properties] = field[:properties] ? field[:properties] : {}
}
That will turn your array from:
[{:key=>"1", :properties=>nil}, {:key=>"2", :properties=>{ ... }}]
to:
[{:key=>"1", :properties=>{}}, {:key=>"2", :properties=>{ ... }}]

`try` method when trying to fetch hash value

I'm trying to avoid an error message when pulling from a hash which may or may not have a value. I either want it to return the value or return nil.
I thought the try method would do it, but I'm still getting an error.
key not found: "en"
My hash is an hstore column called content... content['en'], etc.
content = {"es"=>"This is an amazing event!!!!!", "pl"=>"Gonna be crap!"}
Try method
#object.content.try(:fetch, 'en') # should return nil, but errors even with try method
I thought this would work but it doesn't. How else can I return a nil instead of an error?
Also, the content field itself might also be nil so calling content['en'] throws:
undefined method `content' for nil:NilClass
If you need to allow for object.content.nil?, then you'd use try. If you want to allow for a missing key then you don't want fetch (as Priti notes), you want the normal [] method. Combining the two yields:
object.content.try(:[], 'en')
Observe:
> h = { :a => :b }
=> {:a=>:b}
> h.try(:[], :a)
=> :b
> h.try(:[], :c)
=> nil
> h = nil
=> nil
> h.try(:[], :a)
=> nil
You could also use object.content.try(:fetch, 'en', nil) if :[] looks like it is mocking you.
See the Hash#fetch
Returns a value from the hash for the given key. If the key can’t be found, there are several options: With no other arguments, it will raise an KeyError exception; if default is given, then that will be returned; if the optional code block is specified, then that will be run and its result returned.
h = { "a" => 100, "b" => 200 }
h.fetch("z")
# ~> -:17:in `fetch': key not found: "z" (KeyError)
So use:
h = { "a" => 100, "b" => 200 }
h.fetch("z",nil)
# => nil
h.fetch("a",nil)
# => 100
Just use normal indexing:
content['en'] #=> nil
As of Ruby 2.0, using try on a possibly nil hash is not neat. You can use NilClass#to_h. And for returning nil when there is no key, that is exactly what [] is for, as opposed to what fetch is for.
#object.content.to_h["en"]

How do I build this JSON object in ruby?

I need to bring an array of ruby objects in JSON. I will need to find the item in the JSON object by id, so I think it is best that the id is the key of each object. This structure makes the most sense to me:
{
"1": {"attr1": "val1", "attr2": "val2"},
"2": {"attr1": "val1", "attr2": "val2"},
"3": {"attr1": "val1", "attr2": "val2"}
}
That way I can easily call into the json object like console.log(json_obj[id].attr1)
The issue is that I am not quite sure how to build this in ruby. This is as far as I have gotten:
# in ruby
#book_types = []
BookType.all.each do |bt|
#book_types << {bt.id => {:attr => bt.attr}}
end
#book_types = #book_types.to_json
// In JS
var bookTypes = JSON.parse('<%=raw #book_types %>');
2 questions: How can I build this in ruby? Is there a better way to accomplish what I am doing?
Also just a note that I am building this on the Rails framework
Thanks!
Assuming BookType is an ActiveRecord class, you can just do this:
BookType.all(:select => "attr1, attr2").to_json
...where "attr1, attr2" is a list of the attributes you want to include in your JSON.
If you want the ids as keys, you can do this instead:
BookType.all.inject({}) { |hsh, bt|
hsh[bt.id] = { "attr1" => bt.attr1, "attr2" => bt.attr2 }
hsh
}.to_json

Resources