diff --git a/lib/webmachine/dispatcher/route.rb b/lib/webmachine/dispatcher/route.rb index 5d2edbf6..d80e76aa 100644 --- a/lib/webmachine/dispatcher/route.rb +++ b/lib/webmachine/dispatcher/route.rb @@ -14,24 +14,54 @@ class Route # used to define this route (see #initialize). attr_reader :path_spec + # @return [Array] the list of guard blocks used to define this + # route (see #initialize). + attr_reader :guards + # When used in a path specification, will match all remaining # segments MATCH_ALL = '*'.freeze # Creates a new Route that will associate a pattern to a # {Resource}. - # @param [Array] path_spec a list of path - # segments (String) and identifiers (Symbol) to bind. - # Strings will be simply matched for equality. Symbols in - # the path spec will be extracted into {Request#path_info} for use - # inside your {Resource}. The special segment {MATCH_ALL} will match - # all remaining segments. - # @param [Class] resource the {Resource} to dispatch to - # @param [Hash] bindings additional information to add to - # {Request#path_info} when this route matches + # + # @example Standard route + # Route.new(["*"], MyResource) + # + # @example Guarded route + # Route.new ["/notes"], + # ->(request) { request.method == "POST" }, + # Resources::Note + # Route.new ["/notes"], Resources::NoteList + # Route.new ["/notes", :id], Resources::Note + # + # @overload initialize(path_spec, *guards, resource, bindings = {}) + # @param [Array] path_spec a list of path + # segments (String) and identifiers (Symbol) to bind. + # Strings will be simply matched for equality. Symbols in + # the path spec will be extracted into {Request#path_info} for use + # inside your {Resource}. The special segment {MATCH_ALL} will match + # all remaining segments. + # @param [Proc] guards optional guard blocks called with the request. + # @param [Class] resource the {Resource} to dispatch to + # @param [Hash] bindings additional information to add to + # {Request#path_info} when this route matches # @see Dispatcher#add_route - def initialize(path_spec, resource, bindings={}) - @path_spec, @resource, @bindings = path_spec, resource, bindings + def initialize(path_spec, *args) + if args.last.is_a? Hash + bindings = args.pop + else + bindings = {} + end + + resource = args.pop + guards = args + + @path_spec = path_spec + @guards = guards + @resource = resource + @bindings = bindings + raise ArgumentError, t('not_resource_class', :class => resource.name) unless resource < Resource end @@ -40,7 +70,7 @@ def initialize(path_spec, resource, bindings={}) # @param [Reqeust] request the request object def match?(request) tokens = request.uri.path.match(/^\/(.*)/)[1].split('/') - bind(tokens, {}) + guards.all? { |guard| guard[request] } && bind(tokens, {}) end # Decorates the request with information about the dispatch diff --git a/spec/webmachine/dispatcher/route_spec.rb b/spec/webmachine/dispatcher/route_spec.rb index 8df6d8fa..c35b0c12 100644 --- a/spec/webmachine/dispatcher/route_spec.rb +++ b/spec/webmachine/dispatcher/route_spec.rb @@ -37,6 +37,50 @@ it { should_not match_route [] } it { should_not match_route %w{bar *} } end + + context "with a guard on the request method" do + let(:route) do + described_class.new( + ["notes"], + lambda { |request| request.method == "POST" }, + resource + ) + end + + before do + request.uri.path = "/notes" + end + + context "when guard returns true" do + before do + request.method.replace "POST" + end + + it "returns true" do + route.match?(request).should be_true + end + + context "but the path match fails" do + before do + request.uri.path = "/other" + end + + it "returns false" do + route.match?(request).should be_false + end + end + end + + context "when guard returns false" do + before do + request.method.replace "GET" + end + + it "returns false" do + route.match?(request).should be_false + end + end + end end context "applying bindings" do