An object-oriented way to perform nil checks for associations in rails

I watched a presentation by Sandi Metz nothing a couple of times. I understand that 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 in an object oriented way when it comes to associations.

Consider the following 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 the user:

 @user = User.find(1) 

And I want to find his blog:

 @user.blog => nil 

It returns nil here because this user does not have an 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 

A simple fix is ​​to do it in a non-object oriented way and just do a nil check (but we want to avoid that because otherwise nil checks are absolutely ubiquitous in the application):

 @user.blog.title if @user.blog 

Performing zero checks becomes more cumbersome and procedural, the deeper you go through the associations, as shown 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 an object oriented way to avoid nil checking for associations?

I am aware of the rails relay method, although Metz did not seem to recommend the try method. Maybe when it comes to associations on rails: try is the best option?

+7
ruby ruby-on-rails
source share
3 answers

There is a software development guide called the Law of Demeter

Here's how to apply it to your rail 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 the following link for links:

+6
source share

There is a great andand jewel that I use in such cases. Usually you need to write:

 @user.blog && @user.blog.title 

andand gem implements a common null pattern. andand extends the object with the andand method, which returns the object it was called on if it is not equal to zero. In the case of nil, the NullPatern object is returned to return nil for all method calls using magic_process_missing.

 @user.blog.andand.title 

This is even more powerful when you need to check additional conditions:

 @user.blog && @user.blog.title && @user.blog.title.capitalize 

becomes:

 @user.blog.andand.title.andand.capitalize 
+2
source share

I think these may be options.

I would use: rescue method
Caution : this method catches exceptions and responds accordingly; Do not use this if you need to respond to exceptions differently. I hope you understand

 @user = User.find(1) tag_name = @user.blog.hash_tag.tag_name rescue '' # respond with default value 

Example:

 2.1.1 :001 > var1 = nil => nil 2.1.1 :002 > my_val = var1.some_method NoMethodError: undefined method `some_method' for nil:NilClass from (irb):2 from /home/shiva/.rvm/rubies/ruby-2.1.1/bin/irb:11:in `<main>' 2.1.1 :003 > my_val = var1.some_method rescue '' => "" 

Another option might be the .try method; http://apidock.com/rails/Object/try

.try defined in Rails , so it cannot be accessed from Rails

 @person.non_existing_method if @person.respond_to?(:non_existing_method) # instead use @person.try(:non_existing_method) # => returns nil if does not respond_to 

try returns nil when nil is called, regardless of whether it responds to a method:

 nil.try(:to_i) # => nil, rather than 0 
+1
source share

All Articles