Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Object-oriented way of doing nil checks for associations in rails

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?

like image 264
Neil Avatar asked Oct 06 '15 03:10

Neil


2 Answers

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:

  • http://apidock.com/rails/Module/delegate
  • http://samurails.com/tutorial/rails-delegate-dont-break-the-law-of-demeter/
like image 199
Saiqul Haq Avatar answered Oct 12 '22 22:10

Saiqul Haq


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
like image 24
BroiSatse Avatar answered Oct 13 '22 00:10

BroiSatse