Can we make nil[:a][:b] simply return nil?

Hi guys,

trying to do something like obj[:id] when obj is nil, returns an error.

Things are even more complicated when you try to work with nested hashes. To access something like hash[:data][:details][:id] you need to write

if hash.has_key?(:data) && hash[:data].has_key?(:details)

  return hash[:data][:details][:id]

end

In my code I monkey-patched the NilClass so that the nested accesses always return nil if they do not exists instead of throwing an error. It simplified a lot my code, which is now way cleaner. The patch was something like

class NilClass

 def [](*keys)

   nil

 end

end

I thought I would bring this up to your attention. It may be worth introducing it in Rails as default.

This has a very large potential to break a very, very large amount of code.

You can use fetches or default block to clean up that code without doing a dangerous monkey-patch like you are suggesting.

Consider:

hash.fetch(:data, {}).fetch(:details, {})[:id]

I think a better approach is

return hash && hash[:data] && hash[:data][:details] && hash[:data][:details][:id]

That will return the value or nil if the chain was broken at any point. I know it's not the same, but much less code than the example.

I agree, fetch is the way to go. It is more verbose, but does not any magic/core extensions.

If you want two have more magic, consider using the andand gem <https://github.com/raganwald/andand&gt; or the egonil refinement <http://rubyzucker.info/#egonil&gt;

The andand gem will allow you to write code like:

hash.andand[:key].andand[:key2]

where anything on a nil object returns a proxy that accepts any method and returns nil. This is far safer than what you propose as it won’t affect anyone.

In general though, while this may seem convenient, it makes reasoning about code significantly more difficult. The more code you write, the more I believe that you find that we need more explicit handling of nil not less.

Take a look at the Configatron gem.

Try works pretty well for this.

hash.try(:, :data).try(:, :details).try(:, :id)

Or

hash[:data][:details][:id] rescue nil