Skip to content
This repository has been archived by the owner on Jun 30, 2022. It is now read-only.

Commit

Permalink
Add support for passing a Proc to routes
Browse files Browse the repository at this point in the history
Add support for passing a Proc to routes instead of a regular
expression for matching - this proc will then be called with the message
in the context of the handler object and should return a boolean
indicating whether the route matches or not.

This allows for dynamically defined routing, plus support for
configuring things like command names and prefixes, which is useful as
plugin configuration is only available on a handler instance, not the
singleton.

Probably addresses most of what #100 is asking for
  • Loading branch information
glittershark committed Feb 1, 2016
1 parent c4ee169 commit 08fed54
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 11 deletions.
20 changes: 12 additions & 8 deletions lib/lita/handler/chat_router.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ class Route

# @overload route(pattern, method_name, **options)
# Creates a chat route.
# @param pattern [Regexp] A regular expression to match incoming messages against.
# @param pattern [Regexp, Proc] Either a regular expression to match incoming messages against, or a
# Proc that will be called in the context of the handler with the message, and should return a
# boolean indicating whether that message matches the route
# @param method_name [Symbol, String] The name of the instance method to trigger.
# @param command [Boolean] Whether or not the message must be directed at the robot.
# @param restrict_to [Array<Symbol, String>, nil] An optional list of authorization
Expand All @@ -41,7 +43,9 @@ class Route
# @return [void]
# @overload route(pattern, **options)
# Creates a chat route.
# @param pattern [Regexp] A regular expression to match incoming messages against.
# @param pattern [Regexp, Proc] Either a regular expression to match incoming messages against, or a
# Proc that will be called in the context of the handler with the message, and should return a
# boolean indicating whether that message matches the route
# @param command [Boolean] Whether or not the message must be directed at the robot.
# @param restrict_to [Array<Symbol, String>, nil] An optional list of authorization
# groups the user must be in to trigger the route.
Expand Down Expand Up @@ -76,8 +80,9 @@ def routes
# @param message [Message] The incoming message.
# @return [Boolean] Whether or not the message matched any routes.
def dispatch(robot, message)
handler = new(robot)
routes.map do |route|
next unless route_applies?(route, message, robot)
next unless route_applies?(handler, route, message, robot)
log_dispatch(robot, route)
robot.trigger(
:message_dispatched,
Expand All @@ -86,7 +91,7 @@ def dispatch(robot, message)
message: message,
robot: robot
)
dispatch_to_route(route, robot, message)
dispatch_to_route(handler, route, robot, message)
true
end.any?
end
Expand All @@ -97,10 +102,9 @@ def dispatch(robot, message)
# @param message [Message] The incoming message.
# @return [void]
# @since 3.3.0
def dispatch_to_route(route, robot, message)
def dispatch_to_route(handler, route, robot, message)
response = Response.new(message, route.pattern)
robot.hooks[:trigger_route].each { |hook| hook.call(response: response, route: route) }
handler = new(robot)
route.callback.call(handler, response)
rescue => error
log_error(robot, error)
Expand All @@ -118,8 +122,8 @@ def default_route_options
end

# Determines whether or not an incoming messages should trigger a route.
def route_applies?(route, message, robot)
RouteValidator.new(self, route, message, robot).call
def route_applies?(handler, route, message, robot)
RouteValidator.new(handler, route, message, robot).call
end

# Logs the dispatch of message.
Expand Down
15 changes: 12 additions & 3 deletions lib/lita/route_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module Lita
# Determines if an incoming message should trigger a route.
# @api private
class RouteValidator
# The handler class the route belongs to.
# The handler the route belongs to.
attr_reader :handler

# The incoming message.
Expand All @@ -25,6 +25,11 @@ def initialize(handler, route, message, robot)
@robot = robot
end

# The handler class the route belongs to.
def handler_class
@handler_class ||= handler.class
end

# Returns a boolean indicating whether or not the route should be triggered.
# @return [Boolean] Whether or not the route should be triggered.
def call
Expand Down Expand Up @@ -59,13 +64,17 @@ def from_self?(message, robot)

# Message must match the pattern
def matches_pattern?(route, message)
route.pattern === message.body
if route.pattern.is_a?(Proc) || route.pattern.is_a?(Symbol)
instance_exec(handler, message, &route.pattern)
else
route.pattern === message.body
end
end

# Allow custom route hooks to reject the route
def passes_route_hooks?(route, message, robot)
robot.hooks[:validate_route].all? do |hook|
hook.call(handler: handler, route: route, message: message, robot: robot)
hook.call(handler: handler_class, route: route, message: message, robot: robot)
end
end

Expand Down
14 changes: 14 additions & 0 deletions spec/lita/handler/chat_router_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ def self.name
route(/command/, :command, command: true)
route(/admin/, :admin, restrict_to: :admins)
route(/error/, :error)
route(:proc_route?, :trigger_proc_route)
route(/validate route hook/, :validate_route_hook, code_word: true)
route(/trigger route hook/, :trigger_route_hook, custom_data: "trigger route hook")

Expand All @@ -30,6 +31,14 @@ def error(_response)
raise
end

def proc_route?(message)
message.body == "proc route"
end

def trigger_proc_route(response)
response.reply("trigger proc route")
end

def validate_route_hook(response)
response.reply("validate route hook")
end
Expand All @@ -50,6 +59,11 @@ def trigger_route_hook(response)
expect(replies.last).to eq("message")
end

it "routes messages satisfying a proc to the supplied method" do
send_message("proc route")
expect(replies.last).to eq("trigger proc route")
end

it "routes a matching message even if addressed to the robot" do
send_command("message")
expect(replies.last).to eq("message")
Expand Down

0 comments on commit 08fed54

Please sign in to comment.