Class: Rutter::Builder
- Inherits:
-
Object
- Object
- Rutter::Builder
- Defined in:
- lib/rutter/builder.rb
Overview
The builder map URL’s to endpoints.
Instance Attribute Summary collapse
-
#flat_map ⇒ Array
readonly
Defined routes in a flat map.
-
#named_map ⇒ Hash
readonly
Defined routes grouped by route name.
-
#verb_map ⇒ Hash
readonly
Defined routes grouped by verb.
Instance Method Summary collapse
-
#freeze ⇒ self
Freeze the state of the router.
-
#mount(app, at:, host: nil) ⇒ Rutter::Mount
Mount a Rack compatible at the given path prefix.
-
#namespace(name) { ... } ⇒ Rutter::Scope
Creates a scoped collection of routes with the given name as namespace.
-
#path(name, **args) ⇒ String
Generates a path from the given arguments.
-
#scope(path: nil, namespace: nil, as: nil) { ... } ⇒ Rutter::Scope
Create a scoped set of routes.
-
#url(name, **args) ⇒ String
Generates a full URL from the given arguments.
Instance Attribute Details
#flat_map ⇒ Array (readonly)
Returns Defined routes in a flat map.
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 |
# File 'lib/rutter/builder.rb', line 23 class Builder attr_reader :flat_map attr_reader :verb_map attr_reader :named_map # Initializes the builder. # # @param base [String] # Base URL, used for generating URLs. # # @yield # Executes the block inside the created builder context. # # @return [void] # # @private def initialize(base: "http://localhost:9292", &block) @uri = URI(base).freeze @flat_map = [] @verb_map = VERBS.map { |v| [v, []] }.to_h @named_map = {} instance_eval(&block) if block_given? end # Create a scoped set of routes. # # @param path [String] # Scope path prefix. # @param namespace [String, Symbol] # Scope namespace. # @param as [Symbol] # Scope name prefix. # # @yield # Block is evaluated inside the created scope context. # # @return [Rutter::Scope] # # @see Rutter::Scope def scope(path: nil, namespace: nil, as: nil, &block) Scope.new(self, path: path, namespace: namespace, as: as, &block) end # Creates a scoped collection of routes with the given name as namespace. # # @param name [Symbol, String] # Scope namespace. # # @yield # Scope context. # # @return [Rutter::Scope] # # @example # Rutter.new do # namespace :admin do # get "/login", to: "sessions#new", as: :login # end # end def namespace(name, &block) scope path: name, namespace: name, as: name, &block end # Mount a Rack compatible at the given path prefix. # # @param app [#call] # Application to mount. # @param at [String] # Path prefix to match. # @param host [Regexp] # Match the given host pattern. # # @return [Rutter::Mount] def mount(app, at:, host: nil) route = Mount.new(at, app, host: host) @flat_map << route VERBS.each { |verb| @verb_map[verb] << route } route end # Generates a path from the given arguments. # # @param name [Symbol] # Name of the route to generate path from. # # @overload path(name, key: value) # @param key [String, Integer, Array] # Key value. # @overload path(name, key: value, key2: value2) # @param key2 [String, Integer, Array] # Key value. # # @return [String] # Generated path. # # @raise [RuntimeError] # If the route cannot be found. # # @see Rutter::Route#expand # # @example # router = Rutter.new(base: "http://rutter.org") # router.get "/login", to: "sessions#new", as: :login # router.get "/books/:id", to: "books#show", as: :book # # router.path(:login) # # => "/login" # router.path(:login, return_to: "/") # # => "/login?return_to=/" # router.path(:book, id: 82) # # => "/books/82" def path(name, **args) unless (route = @named_map[name]) raise "No route called '#{name}' was found" end route.(**args) end # Generates a full URL from the given arguments. # # @param name [Symbol] # Name of the route to generate URL from. # # @overload expand(name, subdomain: value) # @param subdomain [String, Symbol] # Subdomain to be added to the host. # @overload expand(name, key: value) # @param key [String, Integer, Array] # Key value. # @overload expand(name, key: value, key2: value2) # @param key2 [String, Integer, Array] # Key value. # # @return [String] # Generated URL. # # @raise [RuntimeError] # If the route cannot be found. # # @see Rutter::Builder#path # # @example # router = Rutter.new(base: "http://rutter.org") # router.get "/login", to: "sessions#new", as: :login # router.get "/books/:id", to: "books#show", as: :book # # router.url(:login) # # => "http://rutter.org/login" # router.url(:login, return_to: "/") # # => "http://rutter.org/login?return_to=/" # router.url(:book, id: 82) # # => "http://rutter.org/books/82" def url(name, **args) host = @uri.scheme + "://" host += "#{args.delete(:subdomain)}." if args.key?(:subdomain) host += @uri.host host += ":#{@uri.port}" if @uri.port != 80 && @uri.port != 443 host + path(name, **args) end # Add a new, frozen, route to the map. # # @param verb [String] # Request verb to match. # @param path [String] # Path template to match. # @param to [#call] # Rack endpoint. # @param as [Symbol, String] # Route name/identifier. # @param constraints [Hash] # Route segment constraints. # @param &block [Proc] # Endpoint as a block. # @yieldparam env [Hash] # Rack's environment hash. # # @return [Rutter::Route] # # @raise [ArgumentError] # If verb is unsupported. # @raise [ArgumentError] # If endpoint is missing. # # @private def add(verb, path, to: nil, as: nil, constraints: nil, &block) to = block if block_given? raise "Missing endpoint" unless to verb = verb.to_s.upcase unless VERBS.include?(verb) raise ArgumentError, "Unsupported verb '#{verb}'" end route = Route.new(path, to, constraints) @flat_map << route @verb_map[verb] << route return route unless as named_map[Naming.route_name(as)] = route end # Freeze the state of the router. # # @return [self] def freeze @flat_map.freeze @verb_map.freeze @verb_map.each_value(&:freeze) @named_map.freeze super end # @see #add VERBS.each do |verb| define_method verb.downcase do |path, to: nil, as: nil, constraints: nil, &block| add verb, path, to: to, as: as, constraints: constraints, &block end end # Process the request and is compatible with the Rack protocol. # # @param env [Hash] # Rack environment hash. # # @return [Array] # Serialized Rack response. # # @see http://rack.github.io # # @private def call(env) request_method = env["REQUEST_METHOD"] return NOT_FOUND_RESPONSE unless @verb_map.key?(request_method) routes = @verb_map[request_method] routes.each do |route| next unless route.match?(env) return route.call(env) end NOT_FOUND_RESPONSE end # @private NOT_FOUND_RESPONSE = [404, { "X-Cascade" => "pass" }, ["Not Found"]].freeze end |
#named_map ⇒ Hash (readonly)
Returns Defined routes grouped by route name.
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 |
# File 'lib/rutter/builder.rb', line 23 class Builder attr_reader :flat_map attr_reader :verb_map attr_reader :named_map # Initializes the builder. # # @param base [String] # Base URL, used for generating URLs. # # @yield # Executes the block inside the created builder context. # # @return [void] # # @private def initialize(base: "http://localhost:9292", &block) @uri = URI(base).freeze @flat_map = [] @verb_map = VERBS.map { |v| [v, []] }.to_h @named_map = {} instance_eval(&block) if block_given? end # Create a scoped set of routes. # # @param path [String] # Scope path prefix. # @param namespace [String, Symbol] # Scope namespace. # @param as [Symbol] # Scope name prefix. # # @yield # Block is evaluated inside the created scope context. # # @return [Rutter::Scope] # # @see Rutter::Scope def scope(path: nil, namespace: nil, as: nil, &block) Scope.new(self, path: path, namespace: namespace, as: as, &block) end # Creates a scoped collection of routes with the given name as namespace. # # @param name [Symbol, String] # Scope namespace. # # @yield # Scope context. # # @return [Rutter::Scope] # # @example # Rutter.new do # namespace :admin do # get "/login", to: "sessions#new", as: :login # end # end def namespace(name, &block) scope path: name, namespace: name, as: name, &block end # Mount a Rack compatible at the given path prefix. # # @param app [#call] # Application to mount. # @param at [String] # Path prefix to match. # @param host [Regexp] # Match the given host pattern. # # @return [Rutter::Mount] def mount(app, at:, host: nil) route = Mount.new(at, app, host: host) @flat_map << route VERBS.each { |verb| @verb_map[verb] << route } route end # Generates a path from the given arguments. # # @param name [Symbol] # Name of the route to generate path from. # # @overload path(name, key: value) # @param key [String, Integer, Array] # Key value. # @overload path(name, key: value, key2: value2) # @param key2 [String, Integer, Array] # Key value. # # @return [String] # Generated path. # # @raise [RuntimeError] # If the route cannot be found. # # @see Rutter::Route#expand # # @example # router = Rutter.new(base: "http://rutter.org") # router.get "/login", to: "sessions#new", as: :login # router.get "/books/:id", to: "books#show", as: :book # # router.path(:login) # # => "/login" # router.path(:login, return_to: "/") # # => "/login?return_to=/" # router.path(:book, id: 82) # # => "/books/82" def path(name, **args) unless (route = @named_map[name]) raise "No route called '#{name}' was found" end route.(**args) end # Generates a full URL from the given arguments. # # @param name [Symbol] # Name of the route to generate URL from. # # @overload expand(name, subdomain: value) # @param subdomain [String, Symbol] # Subdomain to be added to the host. # @overload expand(name, key: value) # @param key [String, Integer, Array] # Key value. # @overload expand(name, key: value, key2: value2) # @param key2 [String, Integer, Array] # Key value. # # @return [String] # Generated URL. # # @raise [RuntimeError] # If the route cannot be found. # # @see Rutter::Builder#path # # @example # router = Rutter.new(base: "http://rutter.org") # router.get "/login", to: "sessions#new", as: :login # router.get "/books/:id", to: "books#show", as: :book # # router.url(:login) # # => "http://rutter.org/login" # router.url(:login, return_to: "/") # # => "http://rutter.org/login?return_to=/" # router.url(:book, id: 82) # # => "http://rutter.org/books/82" def url(name, **args) host = @uri.scheme + "://" host += "#{args.delete(:subdomain)}." if args.key?(:subdomain) host += @uri.host host += ":#{@uri.port}" if @uri.port != 80 && @uri.port != 443 host + path(name, **args) end # Add a new, frozen, route to the map. # # @param verb [String] # Request verb to match. # @param path [String] # Path template to match. # @param to [#call] # Rack endpoint. # @param as [Symbol, String] # Route name/identifier. # @param constraints [Hash] # Route segment constraints. # @param &block [Proc] # Endpoint as a block. # @yieldparam env [Hash] # Rack's environment hash. # # @return [Rutter::Route] # # @raise [ArgumentError] # If verb is unsupported. # @raise [ArgumentError] # If endpoint is missing. # # @private def add(verb, path, to: nil, as: nil, constraints: nil, &block) to = block if block_given? raise "Missing endpoint" unless to verb = verb.to_s.upcase unless VERBS.include?(verb) raise ArgumentError, "Unsupported verb '#{verb}'" end route = Route.new(path, to, constraints) @flat_map << route @verb_map[verb] << route return route unless as named_map[Naming.route_name(as)] = route end # Freeze the state of the router. # # @return [self] def freeze @flat_map.freeze @verb_map.freeze @verb_map.each_value(&:freeze) @named_map.freeze super end # @see #add VERBS.each do |verb| define_method verb.downcase do |path, to: nil, as: nil, constraints: nil, &block| add verb, path, to: to, as: as, constraints: constraints, &block end end # Process the request and is compatible with the Rack protocol. # # @param env [Hash] # Rack environment hash. # # @return [Array] # Serialized Rack response. # # @see http://rack.github.io # # @private def call(env) request_method = env["REQUEST_METHOD"] return NOT_FOUND_RESPONSE unless @verb_map.key?(request_method) routes = @verb_map[request_method] routes.each do |route| next unless route.match?(env) return route.call(env) end NOT_FOUND_RESPONSE end # @private NOT_FOUND_RESPONSE = [404, { "X-Cascade" => "pass" }, ["Not Found"]].freeze end |
#verb_map ⇒ Hash (readonly)
Returns Defined routes grouped by verb.
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 |
# File 'lib/rutter/builder.rb', line 23 class Builder attr_reader :flat_map attr_reader :verb_map attr_reader :named_map # Initializes the builder. # # @param base [String] # Base URL, used for generating URLs. # # @yield # Executes the block inside the created builder context. # # @return [void] # # @private def initialize(base: "http://localhost:9292", &block) @uri = URI(base).freeze @flat_map = [] @verb_map = VERBS.map { |v| [v, []] }.to_h @named_map = {} instance_eval(&block) if block_given? end # Create a scoped set of routes. # # @param path [String] # Scope path prefix. # @param namespace [String, Symbol] # Scope namespace. # @param as [Symbol] # Scope name prefix. # # @yield # Block is evaluated inside the created scope context. # # @return [Rutter::Scope] # # @see Rutter::Scope def scope(path: nil, namespace: nil, as: nil, &block) Scope.new(self, path: path, namespace: namespace, as: as, &block) end # Creates a scoped collection of routes with the given name as namespace. # # @param name [Symbol, String] # Scope namespace. # # @yield # Scope context. # # @return [Rutter::Scope] # # @example # Rutter.new do # namespace :admin do # get "/login", to: "sessions#new", as: :login # end # end def namespace(name, &block) scope path: name, namespace: name, as: name, &block end # Mount a Rack compatible at the given path prefix. # # @param app [#call] # Application to mount. # @param at [String] # Path prefix to match. # @param host [Regexp] # Match the given host pattern. # # @return [Rutter::Mount] def mount(app, at:, host: nil) route = Mount.new(at, app, host: host) @flat_map << route VERBS.each { |verb| @verb_map[verb] << route } route end # Generates a path from the given arguments. # # @param name [Symbol] # Name of the route to generate path from. # # @overload path(name, key: value) # @param key [String, Integer, Array] # Key value. # @overload path(name, key: value, key2: value2) # @param key2 [String, Integer, Array] # Key value. # # @return [String] # Generated path. # # @raise [RuntimeError] # If the route cannot be found. # # @see Rutter::Route#expand # # @example # router = Rutter.new(base: "http://rutter.org") # router.get "/login", to: "sessions#new", as: :login # router.get "/books/:id", to: "books#show", as: :book # # router.path(:login) # # => "/login" # router.path(:login, return_to: "/") # # => "/login?return_to=/" # router.path(:book, id: 82) # # => "/books/82" def path(name, **args) unless (route = @named_map[name]) raise "No route called '#{name}' was found" end route.(**args) end # Generates a full URL from the given arguments. # # @param name [Symbol] # Name of the route to generate URL from. # # @overload expand(name, subdomain: value) # @param subdomain [String, Symbol] # Subdomain to be added to the host. # @overload expand(name, key: value) # @param key [String, Integer, Array] # Key value. # @overload expand(name, key: value, key2: value2) # @param key2 [String, Integer, Array] # Key value. # # @return [String] # Generated URL. # # @raise [RuntimeError] # If the route cannot be found. # # @see Rutter::Builder#path # # @example # router = Rutter.new(base: "http://rutter.org") # router.get "/login", to: "sessions#new", as: :login # router.get "/books/:id", to: "books#show", as: :book # # router.url(:login) # # => "http://rutter.org/login" # router.url(:login, return_to: "/") # # => "http://rutter.org/login?return_to=/" # router.url(:book, id: 82) # # => "http://rutter.org/books/82" def url(name, **args) host = @uri.scheme + "://" host += "#{args.delete(:subdomain)}." if args.key?(:subdomain) host += @uri.host host += ":#{@uri.port}" if @uri.port != 80 && @uri.port != 443 host + path(name, **args) end # Add a new, frozen, route to the map. # # @param verb [String] # Request verb to match. # @param path [String] # Path template to match. # @param to [#call] # Rack endpoint. # @param as [Symbol, String] # Route name/identifier. # @param constraints [Hash] # Route segment constraints. # @param &block [Proc] # Endpoint as a block. # @yieldparam env [Hash] # Rack's environment hash. # # @return [Rutter::Route] # # @raise [ArgumentError] # If verb is unsupported. # @raise [ArgumentError] # If endpoint is missing. # # @private def add(verb, path, to: nil, as: nil, constraints: nil, &block) to = block if block_given? raise "Missing endpoint" unless to verb = verb.to_s.upcase unless VERBS.include?(verb) raise ArgumentError, "Unsupported verb '#{verb}'" end route = Route.new(path, to, constraints) @flat_map << route @verb_map[verb] << route return route unless as named_map[Naming.route_name(as)] = route end # Freeze the state of the router. # # @return [self] def freeze @flat_map.freeze @verb_map.freeze @verb_map.each_value(&:freeze) @named_map.freeze super end # @see #add VERBS.each do |verb| define_method verb.downcase do |path, to: nil, as: nil, constraints: nil, &block| add verb, path, to: to, as: as, constraints: constraints, &block end end # Process the request and is compatible with the Rack protocol. # # @param env [Hash] # Rack environment hash. # # @return [Array] # Serialized Rack response. # # @see http://rack.github.io # # @private def call(env) request_method = env["REQUEST_METHOD"] return NOT_FOUND_RESPONSE unless @verb_map.key?(request_method) routes = @verb_map[request_method] routes.each do |route| next unless route.match?(env) return route.call(env) end NOT_FOUND_RESPONSE end # @private NOT_FOUND_RESPONSE = [404, { "X-Cascade" => "pass" }, ["Not Found"]].freeze end |
Instance Method Details
#freeze ⇒ self
Freeze the state of the router.
231 232 233 234 235 236 237 238 |
# File 'lib/rutter/builder.rb', line 231 def freeze @flat_map.freeze @verb_map.freeze @verb_map.each_value(&:freeze) @named_map.freeze super end |
#mount(app, at:, host: nil) ⇒ Rutter::Mount
Mount a Rack compatible at the given path prefix.
97 98 99 100 101 102 |
# File 'lib/rutter/builder.rb', line 97 def mount(app, at:, host: nil) route = Mount.new(at, app, host: host) @flat_map << route VERBS.each { |verb| @verb_map[verb] << route } route end |
#namespace(name) { ... } ⇒ Rutter::Scope
Creates a scoped collection of routes with the given name as namespace.
83 84 85 |
# File 'lib/rutter/builder.rb', line 83 def namespace(name, &block) scope path: name, namespace: name, as: name, &block end |
#path(name, key: value) ⇒ String #path(name, key: value, key2: value2) ⇒ String
Generates a path from the given arguments.
135 136 137 138 139 140 141 |
# File 'lib/rutter/builder.rb', line 135 def path(name, **args) unless (route = @named_map[name]) raise "No route called '#{name}' was found" end route.(**args) end |
#scope(path: nil, namespace: nil, as: nil) { ... } ⇒ Rutter::Scope
Create a scoped set of routes.
63 64 65 |
# File 'lib/rutter/builder.rb', line 63 def scope(path: nil, namespace: nil, as: nil, &block) Scope.new(self, path: path, namespace: namespace, as: as, &block) end |
#expand(name, subdomain: value) ⇒ String #expand(name, key: value) ⇒ String #expand(name, key: value, key2: value2) ⇒ String
Generates a full URL from the given arguments.
177 178 179 180 181 182 183 |
# File 'lib/rutter/builder.rb', line 177 def url(name, **args) host = @uri.scheme + "://" host += "#{args.delete(:subdomain)}." if args.key?(:subdomain) host += @uri.host host += ":#{@uri.port}" if @uri.port != 80 && @uri.port != 443 host + path(name, **args) end |