Reputation: 361
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
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