Ruby - Delete value from Hash [duplicate] - ruby-on-rails

This question already has answers here:
How to remove a key from Hash and get the remaining hash in Ruby/Rails?
(16 answers)
Closed 4 years ago.
I'm trying to delete an attribute and its value from a hash. Its seems simple based on answers I see on here but it doesn't appear to work for me. Curious if anyone has any thoughts as to why? Also... this is NOT a duplicate of the question that was linked. I have tried except and slice... neither of those work as well. I'm guessing my dataset it different.
Here is an example hash I have:
{:data=>[{:id=>1, :make=>"Ford", :model=>"Excursion", :year=>2018, :color=>"silver", :vin=>"123456789F22"},{=>[{:id=>2, :make=>"Mazda", :model=>"RX7", :year=>1980, :color=>"blue", :vin=>"123456789F22"},{=>[{:id=>3, :make=>"Chevy", :model=>"Dorado", :year=>2018, :color=>"white", :vin=>"123456789F22"}]}
I have tried the following:
hashval.delete("color")
hashval.except!("color")
hashval.each {|h| h.delete("color")}
I also tried :color in case the string format was wrong
hashval.delete(:color)
hashval.except!(:color)
hashval.each {|h| h.delete(:color)}
but when I try to display the resulting hash
logger.info "hash result: #{hashval}"
I still see the original hash with the color still in there. Any thoughts on what I am doing wrong?
Ok... more info! If I do this:
hashval.delete(:data)
It does delete :data (and everything else after that). So it has something to do with the attributes in that hash array?
As it turns out, the answer is:
hashval = { data: vehicles.map { |v| v.table_data.except(:color) } }
I guess this issue was marked closed as a duplicate (even though it wasn't) so I cant add the solution.

You keys are symbols so, hash.delete(:color) should work:
h = {:id=>1, :make=>"Ford", :model=>"Excursion", :year=>2018, :color=>"silver", :vin=>"123456789F22"}
h.key?(:color) # => true
h.delete(:color)
h.key?(:color) # => false
h # => {:id=>1, :make=>"Ford", :model=>"Excursion", :year=>2018, :vin=>"123456789F22"}
Also hash might be a reserved word since if I open irb or console and type hash I get back an integer. I have no idea what it is, but it makes me think hash shouldn't be used as a var.

hash#delete works if you use a symbol:
irb
irb(main):001:0> hash = {:id=>1, :make=>"Ford", :model=>"Excursion", :year=>2018, :color=>"silver", :vin=>"123456789F22"}
=> {:id=>1, :make=>"Ford", :model=>"Excursion", :year=>2018, :color=>"silver", :vin=>"123456789F22"}
irb(main):002:0> hash.delete(:color)
=> "silver"
irb(main):003:0> hash
=> {:id=>1, :make=>"Ford", :model=>"Excursion", :year=>2018, :vin=>"123456789F22"}

Related

Rails, hash failing when assigned to variable, but showing correct output when aborted

This should be an easy one. I have a method in my model that is doing this:
my_hash.max_by{ |k,v| v}[0]
I'm trying to get the key of the maximum value in the hash (by converting it to an array). If I do the following I get my intended results:
abort my_hash.max_by{ |k,v| v}[0] # Honda (for example)
When I do this, I get a undefined method `[]' for nil:NilClass.
brand = my_hash.max_by{ |k,v| v}[0]
What am I missing? Any thoughts or guesses are appreciated!
EDIT
my_hash
{"Honda"=> 4, "Toyota"=>2, "Ford"=>1}
The code was working when I have it directly in my view. Once I move it to the model and try and assign it to a variable, I get the above error.
Can you please share the key, value pair you are using in my_hash ?
I have tried this, and it is working for me:
irb(main):008:0> my_hash = {"CA"=>2, "MI"=>1, "NY"=>1}
=> {"CA"=>2, "MI"=>1, "NY"=>1}
irb(main):009:0> brand = my_hash.max_by{ |k,v| v}[0]
=> "CA"
The my_hash variable must be either an empty hash {} or array [] in your model. I think that it's the only way you'll get the error you observe.

How can I use a regex match as a hash index in Ruby?

I'm new to Ruby and I've run into an issue I can't solve.
I'm trying to use gsub() to match a pattern in a string, then use that match as an index into a hash. So far, I haven't been able to figure it out. Here's some code:
farm = { "pig_num" => 5, "horse_num" => 2, "cow_num" => 4}
assessment = "There are 'pig_num' pigs on this farm"
assessment.gsub(/'(.+?)'/, '\1') # => "There are pig_num pigs on this farm"
assessment.gsub(/'(.+?)'/, farm) # => "There are pigs on this farm"
assessment.gsub(/'(.+?)'/, farm['\1']) # => TypeError: no implicit conversion of nil into String
assessment.gsub(/'(.+?)'/) { |key| farm[key] }
The first call to gsub() shows that I am matching the string I want.
The second call is an attempt to use the gsub(pattern, hash) flavor found at the Ruby documentation site.
The third call is trying to reference the value using the match as an index.
The fourth is some fancy pants way I thought might work using a lambda/proc/block.
What am I doing wrong?
farm = { "pig_num" => 5, "horse_num" => 2, "cow_num" => 4}
assessment = "There are 'pig_num' pigs on this farm"
1
"You may want to get the first object from farm hash but you need to tell from which hash you want to retrieve value". Otherwise, you need to use just Integer with String type like above.
assessment.gsub(/'(.+?)'/, '1')
2
when you 'gsub' string, you get 'pig_num' because you include '' inside the regex so that result would be "'pig_num'". However, the key of hash is "pig_num". "pig_num" and "'pig_num'" are different. That is why you can't get data properly.
assessment.gsub(/'(.+?)'/, farm)
3
You can not point index inside blacket but hash key
assessment.gsub(/'(.+?)'/, farm["pig_num"].to_s)
4
As I said before, you get "'pig_num'" as key. If you print out the key value inside the block, you will see it. You need to change it to 'Pure key'. To get rid of quotation, you can use gsub again and make it empty instead of quotation. gsub! is a destructive method which means modifies original value. If you use just gusb, the method returns modified value but the original key itself (in this situation) does not change. Otherwise, you need to assign the new value to another variable.
assessment.gsub(/'(.+?)'/) { |key| p key.gsub!("\'", ""); farm[key] }
I hope this answer is helpful for you. Cheers
Try this
assessment.gsub(/#{farm.keys.join('|')}/, farm)
I see on your code with regex match it will recognize 'pig_num' is the hash key to find on farm hash. So you need to change your hash like
farm = { "'pig_num'" => 5, "'horse_num'" => 2, "'cow_num'" => 4} or you can change your regex. Example
farm = { "'pig_num'" => 5, "'horse_num'" => 2, "'cow_num'" => 4}
assessment.gsub(/'(.+?)'/, farm) # => "There are 5 pigs on this farm"
Or
farm = { "pig_num" => 5, "horse_num" => 2, "cow_num" => 4}
assessment.gsub(/pig_num/, farm) # => "There are '5' pigs on this farm"
With some minor adjustments, your can use sprintf's %{name} or %<name>snotation:
farm = { pig_num: 5, horse_num: 2, cow_num: 4 }
assessment = "There are '%{pig_num}' pigs on this farm"
sprintf(assessment, farm)
#=> "There are '5' pigs on this farm"

Using durations as hash keys in Rails

Since I'm dealing quite a lot with durations in my Rails app, I would also like to use them as hash keys in some places. However, it does not seem to work as expected for me.
Creating the initial hash works fine. For example, the following will work:
>> hash = {1.week => 'abc', 1.month => 'def'}
However, retrieving the values from the hash is not possible:
>> hash[1.month]
=> nil
Some further investigation showed me the reason for this:
>> 1.week.eql? 1.week
=> false
Which was quite unexpected
Furthermore, it seems like these objects behave differently to normal objects of FixNum class. For normal FixNum objects, the value of the object_id seems to be always the same, e.g:
>> 15.object_id
=> 31
>> 15.object_id
=> 31
For Durations, this is different, although they are from the same class
>> 1.month.class
=> Fixnum
>> 1.month.object_id
=> 70305513092140
>> 1.month.object_id
=> 70305513086860
So, it seems like the objects are always different, which is why hashes will not work. The only solution is to access with exactly the same object:
>> a = 1.month
=> 1 month
>> hash = {a => 'b'}
=> {1 month=>"b"}
>> hash[a]
=> "b"
Obviously, this is not always possible if you have a lot of objects with dynamically created durations.
Grouping by durations does not work either:
>> limits.group_by(&:duration)
=> #<OrderedHash {1 month=>[#<Limit:0x7fe28e441380>], 1 month=>[#<Limit:0x7fe28e4290c8>]}>
So, I'm wondering whether it is possible to get durations working as hash keys somehow? So far I have not found a good solution and I'm not sure if there is one. The important this is that functions like (Time.now - duration) should keep working.
FYI: My Ruby version - 1.8.7, Rails version - 2.3.18
Wow, that is a weird finding. This does work, and I haven't been able to break it, but it seems a little fragile:
hash = {1.year.inspect => "val1", 2.month.inspect => "val2", (1.year-4.days).inspect => "val3"}
=> {"1 year"=>"val1", "2 months"=>"val2", "1 year and -4 days"=>"val3"}
hash[2.months.inspect]
=> "val2"
hash[(1.year-4.days).inspect]
=> "val3"
and to get the durations back
hash.keys.collect{|k| eval(k.gsub(" and ","+").split(" ").join("."))}
=> [1 year, 2 months, 1 year and -4 days]

Shortcut for showing list of hashes "nicely"

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

Equivalent of .try() for a hash to avoid "undefined method" errors on nil? [duplicate]

This question already has answers here:
How to avoid NoMethodError for nil elements when accessing nested hashes? [duplicate]
(4 answers)
Closed 7 years ago.
In Rails we can do the following in case a value doesn't exist to avoid an error:
#myvar = #comment.try(:body)
What is the equivalent when I'm digging deep into a hash and don't want to get an error?
#myvar = session[:comments][#comment.id]["temp_value"]
# [:comments] may or may not exist here
In the above case, session[:comments]try[#comment.id] doesn't work. What would?
You forgot to put a . before the try:
#myvar = session[:comments].try(:[], #comment.id)
since [] is the name of the method when you do [#comment.id].
The announcement of Ruby 2.3.0-preview1 includes an introduction of Safe navigation operator.
A safe navigation operator, which already exists in C#, Groovy, and
Swift, is introduced to ease nil handling as obj&.foo. Array#dig and
Hash#dig are also added.
This means as of 2.3 below code
account.try(:owner).try(:address)
can be rewritten to
account&.owner&.address
However, one should be careful that & is not a drop in replacement of #try. Take a look at this example:
> params = nil
nil
> params&.country
nil
> params = OpenStruct.new(country: "Australia")
#<OpenStruct country="Australia">
> params&.country
"Australia"
> params&.country&.name
NoMethodError: undefined method `name' for "Australia":String
from (pry):38:in `<main>'
> params.try(:country).try(:name)
nil
It is also including a similar sort of way: Array#dig and Hash#dig. So now this
city = params.fetch(:[], :country).try(:[], :state).try(:[], :city)
can be rewritten to
city = params.dig(:country, :state, :city)
Again, #dig is not replicating #try's behaviour. So be careful with returning values. If params[:country] returns, for example, an Integer, TypeError: Integer does not have #dig method will be raised.
The most beautiful solution is an old answer by Mladen Jablanović, as it lets you to dig in the hash deeper than you could with using direct .try() calls, if you want the code still look nice:
class Hash
def get_deep(*fields)
fields.inject(self) {|acc,e| acc[e] if acc}
end
end
You should be careful with various objects (especially params), because Strings and Arrays also respond to :[], but the returned value may not be what you want, and Array raises exception for Strings or Symbols used as indexes.
That is the reason why in the suggested form of this method (below) the (usually ugly) test for .is_a?(Hash) is used instead of (usually better) .respond_to?(:[]):
class Hash
def get_deep(*fields)
fields.inject(self) {|acc,e| acc[e] if acc.is_a?(Hash)}
end
end
a_hash = {:one => {:two => {:three => "asd"}, :arr => [1,2,3]}}
puts a_hash.get_deep(:one, :two ).inspect # => {:three=>"asd"}
puts a_hash.get_deep(:one, :two, :three ).inspect # => "asd"
puts a_hash.get_deep(:one, :two, :three, :four).inspect # => nil
puts a_hash.get_deep(:one, :arr ).inspect # => [1,2,3]
puts a_hash.get_deep(:one, :arr, :too_deep ).inspect # => nil
The last example would raise an exception: "Symbol as array index (TypeError)" if it was not guarded by this ugly "is_a?(Hash)".
The proper use of try with a hash is #sesion.try(:[], :comments).
#session.try(:[], :comments).try(:[], commend.id).try(:[], 'temp_value')
Update: As of Ruby 2.3 use #dig
Most objects that respond to [] expect an Integer argument, with Hash being an exception that will accept any object (such as strings or symbols).
The following is a slightly more robust version of Arsen7's answer that supports nested Array, Hash, as well as any other objects that expect an Integer passed to [].
It's not fool proof, as someone may have created an object that implements [] and does not accept an Integer argument. However, this solution works great in the common case e.g. pulling nested values from JSON (which has both Hash and Array):
class Hash
def get_deep(*fields)
fields.inject(self) { |acc, e| acc[e] if acc.is_a?(Hash) || (e.is_a?(Integer) && acc.respond_to?(:[])) }
end
end
It can be used the same as Arsen7's solution but also supports arrays e.g.
json = { 'users' => [ { 'name' => { 'first_name' => 'Frank'} }, { 'name' => { 'first_name' => 'Bob' } } ] }
json.get_deep 'users', 1, 'name', 'first_name' # Pulls out 'Bob'
say you want to find params[:user][:email] but it's not sure whether user is there in params or not. Then-
you can try:
params[:user].try(:[], :email)
It will return either nil(if user is not there or email is not there in user) or otherwise the value of email in user.
As of Ruby 2.3 this gets a little easier. Instead of having to nest try statements or define your own method you can now use Hash#dig (documentation).
h = { foo: {bar: {baz: 1}}}
h.dig(:foo, :bar, :baz) #=> 1
h.dig(:foo, :zot) #=> nil
Or in the example above:
session.dig(:comments, #comment.id, "temp_value")
This has the added benefit of being more like try than some of the examples above. If any of the arguments lead to the hash returning nil then it will respond nil.
#myvar = session.fetch(:comments, {}).fetch(#comment.id, {})["temp_value"]
From Ruby 2.0, you can do:
#myvar = session[:comments].to_h[#comment.id].to_h["temp_value"]
From Ruby 2.3, you can do:
#myvar = session.dig(:comments, #comment.id, "temp_value")
Another approach:
#myvar = session[:comments][#comment.id]["temp_value"] rescue nil
This might also be consider a bit dangerous because it can hide too much, personally I like it.
If you want more control, you may consider something like:
def handle # just an example name, use what speaks to you
raise $! unless $!.kind_of? NoMethodError # Do whatever checks or
# reporting you want
end
# then you may use
#myvar = session[:comments][#comment.id]["temp_value"] rescue handle
When you do this:
myhash[:one][:two][:three]
You're just chaining a bunch of calls to a "[]" method, an the error occurs if myhash[:one] returns nil, because nil doesn't have a [] method. So, one simple and rather hacky way is to add a [] method to Niclass, which returns nil: i would set this up in a rails app as follows:
Add the method:
#in lib/ruby_extensions.rb
class NilClass
def [](*args)
nil
end
end
Require the file:
#in config/initializers/app_environment.rb
require 'ruby_extensions'
Now you can call nested hashes without fear: i'm demonstrating in the console here:
>> hash = {:foo => "bar"}
=> {:foo=>"bar"}
>> hash[:foo]
=> "bar"
>> hash[:doo]
=> nil
>> hash[:doo][:too]
=> nil
Andrew's answer didn't work for me when I tried this again recently. Maybe something has changed?
#myvar = session[:comments].try('[]', #comment.id)
The '[]' is in quotes instead of a symbol :[]
Try to use
#myvar = session[:comments][#comment.id]["temp_value"] if session[:comments]

Resources