Skip to content

Instantly share code, notes, and snippets.

@tsnow
Last active December 25, 2015 19:19
Show Gist options
  • Save tsnow/7027362 to your computer and use it in GitHub Desktop.
Save tsnow/7027362 to your computer and use it in GitHub Desktop.
An Example of the Null Object Pattern
# This class throws a NoMethod exception in #active_for_user? when it's initialized
# with a ride which has no user. We'd like to just turn the class's behaviour off in that case
# since the whole functionality of it really doesn't make sense for rides which are userless.
# Usage: PimEventSubscription.new(ride).subscribe/unsubscribe
class PimEventSubscription
attr_reader :ride
def initialize(ride)
@ride=ride
end
def subscribe
log_active
return unless active_for_user?
"subscribe"
end
def unsubscribe
log_active
return unless active_for_user?
"unsubscribe"
end
private
def log_active
return unless ride.user.present?
Rails.logger.error("#{prefix} Activated for User:#{Features[ride.user].id}:#{active_for_user?.inspect}")
end
def active_for_user?
Features[ride.user].pim_event_feed_enabled? # explodes when ride.user is null
end
end
# We can certainly fix it by adding if statements through out the entire class in all the areas affected...
# But that leads to painful duplication of branching.
# Usage: PimEventSubscription.new(ride).subscribe/unsubscribe
class PimEventSubscription
attr_reader :ride
def initialize(ride)
@ride=ride
end
def subscribe
log_active
return unless active_for_user?
"subscribe"
end
def unsubscribe
log_active
return unless active_for_user?
"unsubscribe"
end
private
def log_active
return unless ride.user.present? #EW. We had to fix it in two places.
Rails.logger.error("#{prefix} Activated for User:#{Features[ride.user].id}:#{active_for_user?.inspect}")
end
def active_for_user?
return if ride.user.present? #EW. Duplication usually means you're missing something shared.
Features[ride.user].pim_event_feed_enabled?
end
end
# Lets solve that duplication by using the Null Object Pattern
# If we make an object which responds to all the same methods that
# the primary object responds to _from its external callers_...
class InvalidPimEventSubscription
def subscribe
end
def unsubscribe
end
end
class PimEventSubscription
def self.for_ride(ride)
return PimEventSubscription.new(ride) if ride.user.present?
return InvalidPimEventSubscription.new #Then callers wont be the wiser.
end
# ... rest of original (step 0) PimEventSubscription code here.
end
# And we can use the new for_ride method here, and see how the null objects have the same protocol as the primary objects.
def enable_rips_gps_updates
#- PimEventSubscription.new(self).subscribe
PimEventSubscription.for_ride(self).subscribe
update_attribute(:rips_hook_enabled, RipsHook.register_gps(self))
end
def disable_rips_gps_updates
#- PimEventSubscription.new(self).unsubscribe
PimEventSubscription.for_ride(self).unsubscribe
RipsHook.deregister_gps(self)
# even if the deregister fails proceed as though
# it succeeded...
update_attribute(:rips_hook_enabled, false)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment