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