Search code examples
ruby-on-rails-3null-object-pattern

Null Object Pattern for associations in Rails


Despite looking at a few answers here regarding Null Objects in rails, I can't seem to get them to work.

class User < ActiveRecord::Base
  has_one :profile
  accepts_nested_attributes_for :profile

  def profile
    self.profile || NullProfile #I have also tried
    @profile || NullProfile #but it didn't work either
  end
end

class NullProfile
  def display #this method exists on the real Profile class
    ""
  end
end

class UsersController < ApplicationController
  def create
    User.new(params)
  end
end

My problem is that on User creation, I pass in the proper nested attributes (profile_attributes) for the Profile and I end up with a NullProfile on my new User.

I am guessing that this means that my custom profile method is getting called on create and returning a NullProfile. How do I do this NullObject properly so that this only happens on read and not on the initial creation of the objects.


Solution

  • I was going exactly through and I wanted a clean new object if it wasn't present(if you're doing this just so object.display doesn't err maybe object.try(:display) is better) this too and this is what I found:

    1: alias/alias_method_chain

    def profile_with_no_nill
      profile_without_no_nill || NullProfile
    end
    alias_method_chain :profile, :no_nill
    

    But since alias_method_chain is being deprecated, if you're staying on the edge you would have to do the pattern by yourself manually... The answer here seems to provide the better and more elegant solution

    2(Simplified/practical version from the answer):

    class User < ActiveRecord::Base
      has_one :profile
      accepts_nested_attributes_for :profile
    
      module ProfileNullObject
        def profile
          super || NullProfile
        end
      end
      include ProfileNullObject
    end
    

    note: The order you do this matter(explained in the linked answer)


    On what you tried:

    When you did

    def profile
      @profile || NullProfile
    end
    

    It won't behave as expected because the Association is lazily loaded(unless you told it to :include it in the search), so @profile is nil, that's why you're always getting NullProfile

    def profile
      self.profile || NullProfile
    end
    

    It will fail because the method is calling itself, so it's sort like a recursive method, you get SystemStackError: stack level too deep