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"]
Related
I've written a test and it seems so close but not quite right.
it "adds the correct permissions" do
subject
expect(policy.permissions).to have_key("users")
expect(policy.permissions["users"]).to eq({ "all_users" => {
can_view: false,
can_manage: false,
} })
end
Output:
expected: {"all_users"=>{:can_manage=>false, :can_view=>false}}
got: {"all_users"=>{"can_manage"=>false, "can_view"=>false}}
Obviously I'm close but I'm not sure what the deal is in terms of the : notation versus the "got" output from the test itself. How do I get this to pass?
String keys are not equal to symbol keys in Hash class. Here is the example code below:
hash = {}
hash[:a] = 1
hash['b'] = 2
hash[:a] # returns 1
hash['a'] # returns nil
hash[:b] # returns nil
hash['b'] #returns 2
so you should expect the result like this:
expect(policy.permissions["users"]).to eq({ 'all_users' => { 'can_view' => false, 'can_manage' => false, } })
I am trying to perform a sort_by on a hash, but whenever I have a nil value I get:
comparison of DateTime with nil failed
My goal is to perform a nil check (.present?) on x[:last_posted_at] inside the sort_by method. Is that possible? Example code:
posts = [
{ "name"=>"Alice", "last_posted_at"=> some_datetime },
{ "name"=>"Bob", "last_posted_at"=> nil},
{ "name"=>"Clark", "last_posted_at"=> some_datetime - 1}
]
# expected result
posts.sort_by.{ |x| x[:last_posted_at] } #compare only if value is not nil
#=> [{"name"=>"Alice", "last_posted_at"=> some_datetime},
# {"name"=>"Clark", "last_posted_at"=> some_datetime - 1},
# {"name"=>"Bob", "last_posted_at"=> nil}]
I looked into the sort_by documentation and some of the posts here in stackoverflow, but I cannot find my answer. Any help or links are welcome! Thanks in advance!
I like Schwern's approach. But if there are more records without a date then another option might be to separate record without dates from the records with dates and only sort thoses with a date like this:
posts
.partition { |v| v['last_posted_at'] } # separate by date presence
.tap { |v| v.first.sort_by! { |v| v['last_posted_at']} } # only sort entries with a date
.flatten # combine into one list again
Use presence to return the value or nil, and || to return a default value if it is blank.
# Something sufficiently old to be older than any other time.
nil_time = Time.at(0)
posts.sort_by.{ |x|
x[:last_posted_at].presence || nil_time
}
Note: DateTime is deprecated.
I receive a param and want it to be either a string like this :
"abc,efg"
or an Array like this
["abc","efg"]
In the first case I want to convert it into an Array, what would be the good way ?
Here is what I thought
if params[:ids] && params[:ids].is_a? Array
ids = params[:ids]
else if params[:ids]
ids = params[:ids].split(",")
I'd use a ternary for this to keep it simple and on one line:
ids = params[:ids].is_a?(String) ? params[:ids].split(',') : params[:ids]
I've reversed the order so you don't get an undefined method error if you try calling split on nil should params[:ids] be missing.
Array.wrap(params[:ids]).map{|x| x.split(',')}.flatten
Apologies for piling on. But I thought I would offer a slight tweak to the answer proposed by SickLickWill (which doesn't quite handle the Array case correctly):
ids = params[:id].split(',').flatten
This will handle the String case just fine:
:001 > params = {id: "abc,efg"}
:002 > ids = params[:id].split(',').flatten
=> ["abc", "efg"]
As well as the Array case:
:003 > params = {id: ["abc","efg"]}
:004 > ids = params[:id].split(',').flatten
=> ["abc", "efg"]
If there's any chance the id param will be nil, then this barfs:
:005 > params = {}
=> {}
:006 > ids = params[:id].split(',').flatten
NoMethodError: undefined method `split' for nil:NilClass
So, you could put in a conditional test:
:007 > ids = params[:id].split(',').flatten if params[:id]
=> nil
Or, use try:
:008 > ids = params[:id].try(:split, ',').try(:flatten)
=> nil
You miss end tag and you have wrong else if and you can delete the check of params[:ids] because if :ids key do not exist is_a? return NilClass
I think you can do this
ids = if params[:ids].is_a? Array
params[:ids]
elsif params[:ids]
params[:ids].split(",")
end
I think the shortest way would be to use .try. It saves you from writing out an if-then-else.
params_id = params[:id].try(:split, ',')
If the value:
myhash['first_key']['second_key']
exists, then I need to get it. But 'second_key' may not be present at all in my_hash, and I don't want that line to throw an exception if it is not.
Right now I am wrapping the whole thing in an ugly conditional like so:
if myhash['first_key'].present? and myhash['first_key']['second_key'].present?
...
end
I'm sure there must be something simpler.
You can always use try:
hsh.try(:[], 'first_key').try(:[], 'second_key')
FYI: if you're doing a lot of these checks, you might want to refactor your code to avoid these situations.
When in doubt, write a wrapper:
h = {
first_key: {
second_key: 'test'
}
}
class Hash
def fetch_path(*parts)
parts.reduce(self) do |memo, key|
memo[key] if memo
end
end
end
h.fetch_path(:first_key, :second_key) # => "test"
h.fetch_path(:first_key, :third_key) # => nil
h.fetch_path(:first_key, :third_key, :fourth_key) # => nil
h.fetch_path(:foo, :third_key) # => nil
Try this neat and clean solution. Hash default values:
h = Hash.new( {} ) # sets a hash as default value
Now do what you like:
h[:some_key] # => {}
h[:non_existent_key][:yet_another_non_existent_key] # => nil
Nice?
Say you have an existing hash, which is already populated:
h = { a: 1, b: 2, c: 3 }
So you just set its default to return a new hash:
h.default = {}
And there you go again:
h[:d] # => {}
h[:d][:e] # => nil
I'd point you to the excellent Hashie::Mash
An example:
mash = Hashie::Mash.new
# Note: You used to be able to do : `mash.hello.world` and that would return `nil`
# However it seems that behavior has changed and now you need to use a `!` :
mash.hello!.world # => nil # Note use of `!`
mash.hello!.world = 'Nice' # Multi-level assignment!
mash.hello.world # => "Nice"
# or
mash.hello!.world # => "Nice"
You could set up some default values before processing the hash. Something like:
myhash[:first_key] ||= {}
if myhash[:first_key][:second_key]
# do work
end
Why not define a method for this?
class Hash
def has_second_key?(k1,k2)
self[k1] ? self[k1][k2] : nil
end
end
new_hash = {}
new_hash["a"] = "b"
new_hash["c"] = {"d"=>"e","f"=>"g"}
new_hash[:p] = {q:"r"}
new_hash.has_second_key?("r","p")
# =>nil
new_hash.has_second_key?("c","f")
# =>"g"
new_hash.hash_second_key?(:p,:q)
# =>"r"
To modify your code, it would be:
if myhash.has_second_key?('first-key','second-key')
...
end
This method will return nil, which is Falsey in Ruby, or will return the value of the second key which is Truthy in Ruby.
Obviously you do not have to modify the Hash class if you don't want to. You could have the method except the hash as an argument too. has_second_key?(hash,k1,k2). Then call it as:
has_second_key?(myhash,'first-key','second-key')
I have a Rails nested hash as follow:
class = [{"tutor" => {"id" => "Me"}}, {"tutor" => {}}]
I would like to extract id list, but the nested hash can be nil:
tutor_ids = class.map {|c| c['tutor']['id'].to_i }
In case the nested hash is nil, I'll get error.
How do I go about this?
First of all I think you were probably thinking of an array of hashes like so (given the same key was used multiple times:
klass = [{"tutor" => {"id" => "Me"}},{"tutor" => {}}]
Then you could map the tutor IDs with:
tutor_ids = klass.map {|k| k['tutor'] && k['tutor']['id'] }.compact
which would result in
=> ["Me"]
Compact will throw out all the nil values encountered afterwards.
id = class['tutor'] ? class['tutor']['id'] : nil