I have watched Sandi Metz' nothing is something presentation a couple times now. I realize I do nil checks all over the place in my rails projects, but I don't know how to avoid nil checks and how to do it the object oriented way when it comes to associations.
Consider these associations:
#app/models/user.rb
class User < ActiveRecord::Base
belongs_to :blog
end
#app/models/blog.rb
class Blog < ActiveRecord::Base
belongs_to :hash_tag
has_one :user
end
#app/models/hash_tag.rb
class HashTag < ActiveRecord::Base
has_one :blog
end
I grab a user:
@user = User.find(1)
And I want to find his blog:
@user.blog
=> nil
It returns nil
here because this user
happens to have no associated blog
, so the following code would break the application if I did something like this for this user
:
@user.blog.title
=> undefined method `title' for nil:NilClass
The simple fix is to do it the non-object-oriented way and just do a nil check (but this is what we want to avoid because otherwise nil checks are absolutely everywhere in the application):
@user.blog.title if @user.blog
Doing nil checks gets more cumbersome and procedural the deeper you go through associations like below:
@user = User.find(1)
if @user.blog && @user.blog.hash_tag #checking for nil twice
@user.blog.hash_tag.tag_name
end
What is the object-oriented way of avoiding nil checks for associations?
I am aware of rails' try method, though Metz did not seem to recommend the try
method. Perhaps though when it comes to associations in rails: try
is the best option?
There is programming design guideline called The Law of Demeter
This is how to apply it to your rails models:
#app/models/user.rb
class User < ActiveRecord::Base
belongs_to :blog
delegate :title, to: :blog, prefix: true, allow_nil: true
# then you can call user.blog_title
delegate :tag_name, to: :blog, prefix: true, allow_nil: true
# follow LoD
end
#app/models/blog.rb
class Blog < ActiveRecord::Base
belongs_to :hash_tag
delegate :tag_name, to: :hash_tag, allow_nil: true
has_one :user
end
And now you can do this:
@user = User.find(1)
@user.blog_title # no error even if there is no associated blog
@user.tag_name # no error even if there is no associatd blog or no associated hash_tag object
Please read following link for references:
There is a nifty gem andand
which I use in cases like this. Normally you would need to write:
@user.blog && @user.blog.title
andand
gem implements generic null pattern. andand
extends Object with a method andand
, which returns an object it was called on unless it is nil. In case of the nil, NullPatern object is returned to return nil for all method calls using method_missing magic.
@user.blog.andand.title
It is even more powerful when you need to check more conditions:
@user.blog && @user.blog.title && @user.blog.title.capitalize
becomes:
@user.blog.andand.title.andand.capitalize
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With