Module: Waves::Mapping

Defined in:
lib/mapping/mapping.rb,
lib/mapping/pretty_urls.rb

Overview

Mappings in Waves are the interface between the request dispatcher and your application code. The dispatcher matches each request against the mappings to determine a primary action and to collect sets of before, after, wrap, and always actions. The dispatcher also looks for an exception handler registered in the mappings when attempting a rescue.

Each mapping associates a block with a set of constraints. Mappings can be one of several types:

  • action (the actual request processing and response)

  • handle (exception handling)

  • before

  • after

  • wrap (registers its block as both a before and after action)

  • always (like an “ensure” clause in a rescue)

Actions are registered using path, url, or map. The other types may be registered using methods named after the type.

The available constraints are:

  • a string or regexp that the path or url must match

  • parameters to match against the HTTP request headers and the Rack-specific variables (e.g. ‘rack.url_scheme’)

  • an additional hash reserved for settings not related to the Rack request (e.g. giving Rack handers special instructions for certain requests. See threaded? )

The dispatcher evaluates mapping blocks in an instance of ResponseProxy, which provides access to foundational classes of a Waves application (i.e. controllers and views)

Examples

resource = '([\w\-]+)'
name = '([\w\-\_\.\+\@]+)'

path %r{^/#{resource}/#{name}/?$} do |resource, name|
  "Hello from a #{resource} named #{name.capitalize}."
end

In this example, we are using binding regular expressions defined by resource and name. The matches are passed into the block as parameters. Thus, this rule, given the URL ‘/person/john’ will return:

Hello from a person named John.

The given block may simple return a string. The content type is inferred from the request if possible, otherwise it defaults to text/html.

path '/critters', :method => :post do
  request.content_type
end

/critters # => 'text/html'

In this example, we match against a string and check to make sure that the request is a POST. If so, we return the request content_type. The request (and response) objects are available from within the block implicitly.

Invoking Controllers and Views

You may invoke a controller or view method for the primary application by using the corresponding methods, preceded by the use directive.

Examples

path %r{^/#{resource}/#{name}/?$} do |resource, name|
  resource( resource ) do
    controller { find( name ) } |  view { | instance | show( resource => instance ) }
  end
end

In this example, we take the same rule from above but invoke a controller and view method. We use the resource directive and the resource parameter to set the MVC instances we’re going to use. This is necessary to use the controller or view methods. Each of these take a block as arguments which are evaluated in the context of the instance. The view method can further take an argument which is “piped” from the result of the controller block. This isn’t required, but helps to clarify the request processing. Within a view block, a hash may also be passed in to the view method, which is converted into instance variables for the view instance. In this example, the show method is assigned to an instance variable with the same name as the resource type.

So given the same URL as above - /person/john - what will happen is the find method for the Person controller will be invoked and the result passed to the Person view’s show method, with @person holding the value returned.

Crucially, the controller does not need to know what variables the view depends on. This is the job of the mapping block, to act as the “glue” between the controller and view. The controller and view can thus be completely decoupled and become easier to reuse separately.

url 'http://admin.foobar.com:/' do
  resource( :admin ) { view { console } }
end

In this example, we are using the url method to map a subdomain of foobar.com to the console method of the Admin view. In this case, we did not need a controller method, so we simply didn’t call one.

Mapping Modules

You may encapsulate sets of related rules into modules and simply include them into your mapping module. Some rule sets come packaged with Waves, such as PrettyUrls (rules for matching resources using names instead of ids). The simplest way to define such modules for reuse is by defining the included class method for the rules module, and then define the rules using module_eval. See the PrettyUrls module for an example of how to do this.

Important: Using pre-packaged mapping rules does not prevent you from adding to or overriding these rules. However, order does matter, so you should put your own rules ahead of those your may be importing. Also, place rules with constraints (for example, rules that require a POST) ahead of those with no constraints, otherwise the constrainted rules may never be called.

Defined Under Namespace

Modules: PrettyUrls

Instance Method Summary collapse

Instance Method Details

#[](request) ⇒ Object

Match the given request against the defined rules. This is typically only called by a dispatcher object, so you shouldn’t typically use it directly.



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
# File 'lib/mapping/mapping.rb', line 226

def []( request )

  rx = { :before => [], :after => [], :always => [], :action => nil, :handlers => [] }

  ( filters[:before] + filters[:wrap] ).each do | options, function |
    matches = match( request, options, function )
    rx[:before] << matches if matches
  end

  mapping.find do | options, params, function |
    rx[:action] = match( request, options, function )
    break if rx[:action]
  end

  ( filters[:after] + filters[:wrap] ).each do | options, function |
    matches = match( request, options, function )
    rx[:after] << matches if matches
  end

  filters[:always].each do | options, function |
    matches = match( request, options, function )
    rx[:always] << matches if matches
  end

  handlers.each do | exception, options, function |
    matches = match( request, options, function )
    rx[:handlers] << matches.unshift(exception) if matches
  end

  return rx
end

#after(path, options = {}, &block) ⇒ Object

Similar to before, except it runs its actions after any matching url or path actions. Note that after methods will run even if an exception is thrown during processing.



130
131
132
133
134
135
136
137
# File 'lib/mapping/mapping.rb', line 130

def after( path, options = {}, &block )
  if path.is_a? Hash
    options = path
  else
    options[:path] = path
  end
  filters[:after] << [ options, block ]
end

#always(path, options = {}, &block) ⇒ Object

Like after, but will run even when an exception is thrown. Exceptions in always mappings are simply logged and ignored.



152
153
154
155
156
157
158
159
# File 'lib/mapping/mapping.rb', line 152

def always( path, options = {}, &block )
  if path.is_a? Hash
    options = path
  else
    options[:path] = path
  end
  filters[:always] << [ options, block ]
end

#before(path, options = {}, &block) ⇒ Object

If the pattern matches and constraints given by the options hash are satisfied, run the block before running any path or url actions. You can have as many before matches as you want - they will all run, unless one of them calls redirect, generates an unhandled exception, etc.



119
120
121
122
123
124
125
126
# File 'lib/mapping/mapping.rb', line 119

def before( path, options = {}, &block )
  if path.is_a? Hash
    options = path
  else
    options[:path] = path
  end
  filters[:before] << [ options, block ]
end

#clearObject

Clear all mapping rules



259
260
261
# File 'lib/mapping/mapping.rb', line 259

def clear
  @mapping = @filters = @handlers = nil;
end

#handle(exception, options = {}, &block) ⇒ Object

Maps an exception handler to a block.



194
195
196
# File 'lib/mapping/mapping.rb', line 194

def handle(exception, options = {}, &block )
  handlers << [exception,options, block]
end

#map(path, options = {}, params = {}, &block) ⇒ Object

Maps a request to a block. Don’t use this method directly unless you know what you’re doing. Use path or url instead.



163
164
165
166
167
168
169
170
171
# File 'lib/mapping/mapping.rb', line 163

def map( path, options = {}, params = {}, &block )
  case path
  when Hash
    params = options; options = path
  when String
    options[:path] = path
  end
  mapping << [ options, params, block ]
end

#path(pat, options = {}, params = {}, &block) ⇒ Object

Match pattern against the request.path, along with satisfying any constraints specified by the options hash. If the pattern matches and the constraints are satisfied, run the block. Only one path or url match will be run (the first one).



176
177
178
# File 'lib/mapping/mapping.rb', line 176

def path( pat, options = {}, params = {}, &block )
  options[:path] = pat; map( options, params, &block )
end

#root(options = {}, params = {}, &block) ⇒ Object

Maps the root of the application to a block. If an options hash is specified it must satisfy those constraints in order to run the block.



189
190
191
# File 'lib/mapping/mapping.rb', line 189

def root( options = {}, params = {}, &block )
  path( %r{^/?$}, options, params, &block )
end

#threaded(pat, options = {}, params = {}, &block) ⇒ Object

Maps a request to a block that will be executed within it’s own thread. This is especially useful when you’re running with an event driven server like thin or ebb, and this block is going to take a relatively long time.



202
203
204
205
# File 'lib/mapping/mapping.rb', line 202

def threaded( pat, options = {}, params = {}, &block)
  params[:threaded] = true
  map( pat, options, params, &block)
end

#threaded?(request) ⇒ Boolean

Determines whether the request should be handled in a separate thread. This is used by event driven servers like thin and ebb, and is most useful for those methods that take a long time to complete, like for example upload processes. E.g.:

threaded("/upload", :method => :post) do
  handle_upload
end

You typically wouldn’t use this method directly.

Returns:

  • (Boolean)


216
217
218
219
220
221
222
# File 'lib/mapping/mapping.rb', line 216

def threaded?( request )
  mapping.find do | options, params, function |
    match = match( request, options, function )
    return params[:threaded] == true if match
  end
  return false
end

#url(pat, options = {}, params = {}, &block) ⇒ Object

Match pattern against the request.url, along with satisfying any constraints specified by the options hash. If the pattern matches and the constraints are satisfied, run the block. Only one path or url match will be run (the first one).



183
184
185
# File 'lib/mapping/mapping.rb', line 183

def url( pat, options = {}, params = {}, &block )
  options[:url] = pat; map( options, params, &block )
end

#wrap(path, options = {}, &block) ⇒ Object

Run the action before and after the matching url or path action.



140
141
142
143
144
145
146
147
148
# File 'lib/mapping/mapping.rb', line 140

def wrap( path, options = {}, &block )
  if path.is_a? Hash
    options = path
  else
    options[:path] = path
  end
  filters[:before] << [ options, block ]
  filters[:after] << [ options, block ]
end