learning_to_swim
learning_to_swim

Reputation: 361

Ruby - Scoping method calls to current class

On a current Rails app we are building, a user can have company wide preferences, user specific preferences or project specific preferences. When a user creates a Purchase Order, I need to aggregate all these preferences and set the corresponding attributes on the order. I start with Project Preferences and keep going up the hierarchy until I collect all attributes as below.

  class Preferences 

    def initialize(project, user)
      @preferences = Preferences.new
      @project = project
      @user = user
    end

    def stage
    end

    def set_preferences
      set_department
    end

  End


  class UserPreferences < Preferences

    def initialize(project, user)
     super(project, user)
     @user_preferences = nil
    end

    def stage
     @user_preferences = @user.preferences
     set_preferences if @user_preferences.present?
     super unless @preferences.valid?
    end 


    def set_department
     @preferences ||= @user_preferences.dept
    end

  end

  class ProjectPreferences < UserPreferences

    def initialize(project, user)
     super(project, user)
     @project_preferences = nil
    end

    def stage
     @project_preferences = @project.preferences
     set_preferences if @project_preferences.present?
     super unless @preferences.valid?
    end 

    def set_department
      @preferences.dept ||= @project_preferences.dept
    end

  end

  def StagePreferences < ProjectPreferences

    def initialize(project, user)
     super(project, user)
    end

   def stage
     super
     @preferences
   end

  end

and the method is invoked like below

StagePreferences.new(project, current_user).stage

I am running into an issue where when I call the set_preferences method from the UserPreferences class (because there were no preferences set for this project), the set_department from UserPreferences is getting called which returns an error as below?

NoMethodError: undefined method `dept' for nil:NilClass
from /home/rails/tools/ss/lib/preferences/project_preferences.rb:27:in `set_department'
from /home/rails/tools/ss/lib/preferences/preferences.rb:26:in `set_preferences'
from /home/rails/tools/ss/lib/preferences/user_preferences.rb:17:in `stage'
from /home/rails/tools/ss/lib/preferences/stage_preferences.rb:23:in `stage'
from /home/rails/tools/ss/lib/preferences/project_preferences.rb:17:in `stage'
from /home/rails/tools/ss/lib/preferences/stage_preferences.rb:8:in `stage'
from (irb):8
from /home/rails/.rvm/gems/ruby-2.2.1/gems/railties-4.0.13/lib/rails/commands/console.rb:90:in `start'
from /home/rails/.rvm/gems/ruby-2.2.1/gems/railties-4.0.13/lib/rails/commands/console.rb:9:in `start'
from /home/rails/.rvm/gems/ruby-2.2.1/gems/railties-4.0.13/lib/rails/commands.rb:62:in `<top (required)>'
from bin/rails:4:in `require'
from bin/rails:4:in `<main>'

Upvotes: 0

Views: 91

Answers (1)

maniacalrobot
maniacalrobot

Reputation: 2463

Ruby's Dynamic dispatch uses a 'depth first' search of the methods on the receiver, meaning the your ProjectPreferences#set_department method will always get called, and your 'UserPreferences#set_department' method will never get called (FYI, it looks like you've got a typo in this method?).

It is possible to call this method within the current hierarchy using Ruby's instance_method method via:

UserPreferences.instance_method(:set_department).bind(self).call

Which allows you to call this 'unreachable' method on self as if ProjectPreferences had not overridden it.

However, I'd question the requirement to have so many Preference subclasses, as this question highlights, debugging problems in the class hierarchy become increasingly more complex.

Upvotes: 2

Related Questions