Chapter 2. Routing

If you’re new to MVC web frameworks in general, you may at first expect that each request path would correlate with a particular file of code. This would be similar to the way static assets and preprocessed scripts are most often served. However, organizing our application code by request path typically leads to a sprawl of files and immense challenges to sensible refactoring. Merb refuses to let this happen by adhering to the MVC paradigm and separating code by its abstract purpose. The only hitch, of course, is that we now need an application router to guide incoming requests to the code meant to respond to it.

If this at first sounds a bit complicated, don’t worry. The Merb router is not only sophisticated enough to parse and direct requests based on complex conditions, it’s also easy to use and understand. In fact, if you’ve used the routers of other frameworks, chances are you’ll find Merb’s route definitions far cleaner and better organized than anything you’ve seen in the past.

As an MVC framework, Merb routes each of its requests to a controller. More specifically, each request is routed to a particular controller method that in this context is called an action and is responsible for producing a response. Constructing Merb routes for use by the router is not an exercise of tediously mapping URLs to controller actions, however. Instead, we are able to match particular conditions in a nested fashion, consolidating the definition of related routes. Merb also sports the ability to parse conditions using regular expressions and is consequently incredibly versatile when you need it to be.

Nonetheless, in many cases the best way of accomplishing routing is often the simplest, and as we’ll see with Merb’s resource routing, a single line in the router can set up all the routes you need to standardly interact with a resource. This routing is accomplished in adherence to REST standards (aka RESTfully) and makes it possible to sensibly double the use of your routes as a basis for web services.

Once defined, a list of routes plays a part not only in the recognition of request URLs, but also in their generation. This is because Merb can inversely map parameters to a URL, and thus it enables us to abstractly generate URLs needed for redirects, links, and elsewhere. This is important in both controllers and views as we’ll see in the chapters on those topics, but we’ll also briefly touch upon them at the end of this chapter.

With all this is mind, let’s take a look at the internals of the Merb router to see how it works. You don’t need to remember all the classes we’ll describe from within the core, but again, familiarity with them will also bring substantial comfort, so a peek is well worth your time.

2.1 How Merb routing works

Merb routing works by first matching conditions against a request’s method and path. It then looks to associate matches with a parameters hash, which specifies the relevant controller and action as well as additional query parameters that may be needed. Each of these pairings of conditions with parameters is called a route and is stored in an ordered list generated at boot-up. As we look at the classes that define routes, we find that each route is created as an instance of Merb::Router::Route and, if active, can be found as an element in the array Merb::Router.routes. If we start up interactive Merb outside any project, we’ll find the routes to be empty:

image

There is another class that serves as a precursor to routes, called a behavior. Router behaviors make it possible for us to specify routes in a nested manner that grow toward completeness through the layering of both route conditions and parameters. As an application developer, you do not need to know of the existence of route behaviors even though you’ll be interacting with them unknowingly. On the other hand, for those of you destined to become plugin developers or possibly core contributors, it’ll serve you well to know that route behaviors are instances of Merb::Router::Behavior and that each block inside a router configuration is evaluated within the context of a parent behavior. Nested blocks imply nested behaviors, and these are compiled down to a flat ordered list at boot-up in the previously mentioned array Merb::Router.routes.

From either perspective, what matters most when specifying routes is an understanding of the two elements that make up each complete route. These are the route conditions and the route parameters. Let’s take a look at each before we put them to use.

2.1.1 Route conditions

Route conditions are what the Merb router matches incoming requests against. In general, there are two minimal conditions a route must have: a path and a method. Paths are specified using strings, whereas method matching may use strings, symbols, or arrays.

2.1.1.1 Route method

Sometimes requests going to the same path have different intents. For example, a request for /users/1 may sometimes be used to show the profile of a user, but at other times the request may include data used to update the user’s details. However, instead of guessing what the intent of the request is, based on what data is received, it is standard to rely on the request’s HTTP method as the definitive indicator of intent. A number of HTTP methods exist, but four in particular are used with Merb. These are GET, POST, PUT, and DELETE.

In the context of REST, these methods are often called verbs. RESTfully speaking, each one plays a particular role well defined by both the W3C HT TP standards and RESTful intent. However, if you feel the need to set up routes that deviate from these standards, you have the freedom to do so. Just be warned: Using HTTP methods and their RESTful interpretations is more and more becoming a shared best practice, and going against it may only serve to complicate your application for both internal developers as well as web service consumers. Here’s a list of the four most important HTTP methods:

• GET—the most common HTTP verb, used to request data without explicitly affecting it

• POST—the default verb for form submission, used to push arbitrary data onto the server

• PUT—used when data sent is intended to replace the existing data corresponding with a particular URL

• DELETE—used to request the deletion of data related to a particular URL

In the conditions hash of a route, a route method is typically indicated by use of one of the symbols :get, :post, :put, or :delete. You may instead use strings or regular expressions, but symbols are preferred. In the following code, we begin to define a route by specifying a method:

image

2.1.1.2 Route path

The more recognizable part of an HTTP request is its path. This includes everything after the domain of the request and before the query string. For example, the path of the request URL http://merbdeveloper.com/users/1 is /users/1.

Route paths are segmented by Merb with the standard slash separator or, as needed, by the end of symbolic segments. The two types of segments are literal and symbolic. Literal segments match literally against themselves. For instance, /users/new matches the first segment of the route path '/users/:action', which is '/users'. Symbolic segments, on the other hand, match any string that does not include a slash separator. For example, in the last example, the new at the end is matched against :action. As a side note, since :action has a special meaning among symbols, this route path automatically associates itself with the controller action named new in practice. To whet your appetite, Table 2.1 shows a few more examples of route paths and their interpretations by the router.

Table 2.1 Route paths

image

When it comes time to write out our route path conditions, they will appear either first in match statements or as values to the key path:

image

2.1.1.3 Other conditions

There are a few other conditions we can match against in route conditions. Two of these are protocol and domain, and they allow us, for instance, to match requests coming from secure HTTP or requested from a particular subdomain:

image

Other conditions aside from path, protocol, domain, and method are assumed to be for symbolic segments and can be used to add constraints to segments through regular expressions. Here, the ID of a path matches only if it’s a string of digits:

image

2.1.2 Route parameters

Route parameters specify where the route should go. They are described as hashes where a few keys are interpreted separately, and all the rest are considered part of the parameters of the request itself. Here’s a list of the special keys:

controller—the controller to which the request will be routed in snake case

action—the action to which the request will be routed; defaults to “index”

format—the format in which the response should be given

All other route parameters do not have special meaning and are passed on as parameters, appearing with or, if necessary, overriding any parameters from elsewhere in the request. Here’s an example that uses both the special and nonspecial parameters:

image

One thing to note about this example is that all of the route parameters are redundant, as the Merb router translates symbolic segments into route parameters automatically.

2.2 Router configuration

Though we’ve seen some examples, up until now we’ve spoken only conceptually of routes, so it’s time to create our own configurations and learn about all the available methods.

Routes are specified within a configuration block passed to the router for interpretation. Depending on the layout of your application, this configuration block may be in different locations, but it’s important that it gets loaded at boot-up so the routes can be compiled.

2.2.1 The router file

In a standard Merb layout, routes are specified in the file config/router.rb. Single-file Merb applications include a router configuration near the top of the file. Flat layouts put it in application.rb. With either of these, router configuration code looks just the same and works through the passing of a block to the class method Merb::Router.prepare. Here’s the content from a standardly generated config/router.rb file less comments:

image

The first line, of course, is just a log message and is not critical to our routes. The next class method is the one we previously referenced, and in the default case of a generated standard application it has only default_routes. We’ll get into what this does soon.

2.2.2 The prepare block

The prepare block works by evaluating each line in the context of a route behavior. Remember, route behaviors are basically nestable and are not necessarily complete precursors to routes. For instance, the root behavior for the prepare block is internally defined as

Behavior.new.defaults(:action => "index")

All deeper route behaviors are based on the behavior created by the method before the nesting of a block. This is demonstrated by the following prepare block:

image

The two match lines are evaluted within the context of a new child behavior where the following route parameters have been set:

image

Note that the second match statement overrides the action of the parent behavior, whereas the first relies on its parent entirely to specify its route parameters.

2.2.3 Route order

All behaviors are compiled down into a flat list on boot-up, and when the router is used to recognize or generate a URL, it’ll work with the first possible route it finds. This often trips up first-timers, especially via the line default_routes:

image

In this example, the default_routes will always take precedence, and therefore the match on the second line will never work. Be sure to pay attention to the order of your routes to avoid this problem.

2.2.4 Adding routes later on

Typically, as an application developer you won’t need to add routes outside the prepare block, but it is still possible. To do this, you can use the two class methods Merb::Router.append and Merb::Router.prepend. Both of these work exactly like the prepare block but insert their routes before or after all the others:

image

2.3 Checking routes

Sometimes you’ll want to check the routes of your application to make sure requests are routed properly. There are a number of ways to do this. So that you can use these methods to test the various router configurations we describe later and the router configurations of your own applications, we’ll describe them here. For all of this section’s examples we’ll use a standard Merb application with the following routes defined in config/router.rb:

image

2.3.1 Listing routes

Getting a simple list of the compiled routes can do wonders in determining why a request has gone astray. We’ll do this first using interactive Merb and then using a rake task.

2.3.1.1 Using interactive Merb

To list routes in interactive Merb, simply start up the application and issue the command Merb::Router.route. Here we pull up a list of the routes in interactive Merb using the previously defined routes:

image

The first eight come from the resources route and are perfect examples of how effective resources routing can be in producing the routes you need. These routes come first because resources :posts does, too. Following those eight routes is the default_routes route. Notice that it translates to only one route, but given its complexity and versatility it has kept its name. Finally, the last route, the single /, matches a request for the root or empty path.

Each of these items in the routes array appears as the path string above. However, don’t let that deceive you, since they’re all full routes that you can play around with. Below, we interact with the conditions and parameters for the default_routes route.

image

As we’ll learn shortly, routes also come with names. These are either selected by you or they default to the name “default.” Below we use interactive Merb to list the named routes for us.

image

Nearly all of these are for the resources route that was specified. Note that the inflector has been used to create these names, thus allowing us to naturally generate URLs for the various RESTful interactions. Also of importance is the precedence of the default_routes route over the specified root route. The former has become the route attached to the default name.

Alternatively we can use the convenience method merb.show_routes to display all the routes in a significantly more verbose yet less interactive way. Here we pull up the same routes using this method:

image

image

In this output, Helper signifies the name that should be used when we look to generate the URL. If you take a look at the penultimate and antepenultimate routes, it’s clear why two routes have appeared previously with the exact same path: One route is for PUT requests, which go to the update action, and the other is for DELETE requests, which go to the destroy action.

2.3.1.2 Using the audit routes rake task

Standard applications are generated with a rake task that lists all routes for us without our having to use interactive Merb. To do this we can step into the Merb application’s root directory and issue the following command:

$ rake audit:routes

Given the routes that were previously defined, the following routes are displayed:

image

2.3.2 Trying the router

Sometimes the only way to figure out what’s going on is to ask the router directly. Here we use interactive Merb to check both URL recognition and generation.

2.3.2.1 For URL recognition

Using the Merb convenience methods, we can first put together a fake request and then see where the route will take it. The method merb.fake_request makes it easy for us to put together a request by just overriding default values. Here are the default settings that make up a merb.fake_request:

image

We can override the settings by passing in parameters. With the following, we modify the default fake request so as to imitate the deletion of a post:

image

With our fake request ready, we can now use another convenience method to check if it routes to where it should. This method is merb.check_request_for_route and takes a request (fake or not) as its sole parameter:

image

The hash returned says that the request does in fact route to the destroy action of the posts controllers, so everything is in order.

2.3.2.2 For URL generation

We can also test the generation of routes using another convenience method, merb.url. It takes a route name plus route parameters. Leaving out the route name sets it to default. Below we test the generation of the URL for editing the first post and then for an anonymous default route.

image

2.4 Match rules

Match rules are the basis for defining all route definitions. Let’s explore the various possibilities.

2.4.1 Literal matching

Literal matching is quite simple. Using the route behavior method match within the block given to Merb::Router.prepare, we can match against literal strings:

image

You can also nest literal match statements by passing in blocks. The following example demonstrates the same match as before:

image

Literal matches occur only if the string matches the entirety of the request path. For example, if a request comes in with the path /examples/controllers', it will not match against '/examples'. The exception, of course, is if a nested match completes the literal path. Remember that the route behaviors listed in the route are compiled before use, so nested literal matches are concatenated in their final form.

Finally, an uncommon alternative to simply passing in the literal string is to instead pass in a hash with a value on the key :path. We provide this example so that you may recognize such usage if you spot it:

image

2.4.2 Symbolic matches

Symbolic matches use symbols to more abstractly match request paths and associate these matches with parameters. These symbols appear as substrings within match path strings and can have additional rules set upon them using regular expressions. By default, a symbol matches all alphanumeric characters, the dash, and the underscore in a non-greedy fashion. Below is a simple example of a match rule that automatically sets the controller and action. Note that the colon is not allowed in URL paths, so there’s no need to worry about ambiguity.

image

Overall, the use of symbolic segments effectively extends the matches in three ways. Let’s go over these individually.

2.4.2.1 Automatic parameters

Using symbolic match, we can tersely specify both route conditions and parameters. This works by mapping each symbolic match to an equivalent route parameter. Below, we redundantly route a symbolic match to where it was going even without the to statement.

image

2.4.2.2 Flexible segmentation

Symbolic matches allow us to segment routes without relying on slash separators. Below we pull out a year, month, and day separated by dashes.

image

This match rule matches and segments request paths that look like this:

image

In both of these examples, the three symbolic segments are translated into route parameters under the names :year, :month, and :day.

2.4.2.3 Segment-specific regular expressions

We just saw an example where the match rule ended up matching possibly unwanted segments and translated them into route parameters. We can set further limits on specific segments by using regular expressions, which are compatible with both route recognition and generation and should be used when possible. Here we limit the previous example so that only strings of digits are allowed:

image

2.4.3 Optional matches

Using parentheses, we can tell the router that certain parts of a match are optional. This can significantly lessen the number of rules we need to write. We’ve seen this already with the case of the so-called default_routes, which are actually defined with just one match but using a number of parentheses:

match("/:controller(/:action(/:id))(.:format)")

Note that some of the parentheticals are embedded within other parentheticals. The following routes match the preceding match rule:

image

2.4.4 Full regular expressions

Though it’s not advisable because it may leave you unable to generate URLs from route parameters, you can directly use a regular expression to match the full path of a request:

image

Using interactive Merb on an application where this is the only route, we can confirm that though the route is recognized and mapped to parameters, it cannot inversely be generated by those parameters:

image

2.4.5 Deferred routes

Deferred routes allow you to incorporate more complex logic into your routes. Again, though, this comes at the cost of being unable to generate those routes via route parameters. Below we route requests to an Ajax controller if they are XML HTTP requests (aka Ajax requests).

image

Note the block parameters that defer_to receives. These are the requests from the client and the parameters from the behavior context in which they are being evaluated. This may suggest that deferred routing may also appear nested within other route statements:

image

Although this example demonstrates the nesting of defer_to, we definitely don’t recommend using it for such a purpose. In fact, we recommend that you stay away from using deferred routing as much as possible, leaving such logic for inside your controllers.

2.5 Registering routes

In Merb, the process of assigning route parameters to matches is known as the route registration. This is a necessary step in assuring that behaviors are compiled down and put into the routes array. Merb uses three equivalent methods to do this. Each varies only in name but thus provides a better semantic feel in different contexts.

2.5.1 Using to

The to method is the standard way of both setting route parameters and registering them. We’ve seen the to method in past examples, and though its usage may seem obvious enough, we’ll spend some time on the details. The to method takes one optional parameter, a hash representing route parameters that are merged into existing contextual route parameters. For instance, here’s the second to method that adds in an additional route parameter to what was established by the first:

image

This example also displays that the to method accepts a block. This block works similarly to a block passed to the match method. More specifically, the block is evaluated in a new context where the parent behavior has been extended through the parameters that were given. Formalities aside, you can also rely on the descriptive nature of methods themselves to understand that the previous example compiles down to the following route:

image

2.5.2 Using with

The with method, which is just an alias of to, is semantically used in forming blocks around routes without being chained to a specific behavior. Here we wrap with around a set of matches that are passed in a block:

image

These, of course, all compile down to routes set to be handled by the Users controller but by different actions:

image

Note that either to or register, which we’re about to discuss, could have been used in place of with, but we use with because it reads well aloud.

2.5.3 Using register

The most common use of register, which is once again an alias of to, is to assure that a behavior is registered into a route even when no other route parameters need to be set. The reason this exists is because often the match method will automatically have set all your parameters for you and an empty .to would look awkward. Below we register such a behavior into a route.

image

Note again, however, that register could have been to or even with and have had the same effect.

2.5.4 Redirects

You can redirect routes from inside the Merb router. This is useful in maintaining compatibility after you’ve changed routes on a production application and want to assure that the bookmarks of users still work. To register a redirect, simply use the redirect method on a behavior. Note that you may also specify whether the redirect is permanent or not. If it is, the redirect will be sent with a status code of 301 instead of 302.

image

2.5.5 Using symbols

As we’ve seen previously, symbolic matching implicitly sets route parameters for us. However, we can also manually do so ourselves. Symbolic segment values are available as symbols within the strings used on values of the hash given to a to or any of its aliases. The following example should make this clearer:

image

To check that it works, we can fire up interactive Merb and send a fake request to the path /users/savas/foy:

image

2.5.6 Using other captures

We can also use other match captures in the value strings of the to hash. Bracketed numbers allow us to pull up either regex captures or symbolic segment values in the order in which they were matched, starting with 1. In the following example, we match the first segment as the language, then use default routes nested within:

image

2.6 Other route settings

You can do a few more standard things with routes, including setting defaults for route parameters and naming routes for references during generation. Let’s take a look at both of these.

2.6.1 Setting defaults

Default values allow you to set defaults for route parameters that have not been set. To do this, we apply the default method on a behavior. For instance, below we set the controller match segment to optional but default it for Users. Note that we have to end the line with register, because default does not register the route itself.

image

We can test the results of this route registration using interactive Merb. Here it behaves as intended:

image

2.6.2 Named routes

Merb can identify routes by “name.” These names are convenient identifiers often used in the generation of URLs. Most of the routes we’ve used so far have gone unnamed.

However, we can easily add names by passing in a symbol, the name method:

image

In interactive Merb, we can generate the root URL by passing the symbol :name to merb.url:

image

Remember that the url method is also mixed into Merb::Controller, so we’ll be using it heavily within both controllers and views to generate URLs for links and set the paths of redirects.

2.6.3 Setting prefixes

We can prefix both names and controllers using routes. In the case of names, this means prepending a string plus an underscore to the name; this is most commonly used to semantically group associated routes. With controllers, this entails that the controller is found within a module. Let’s go through how to accomplish both of these types of prefixes.

2.6.3.1 Name prefix

There are two ways to add a name prefix. The first is by passing in an extra symbol parameter to the name method:

image

Alternatively we can use the options method:

image

2.6.3.2 Controller prefix

Merb allows you to nest controllers within modules and thus bundle sets of controllers with a similar purpose. For instance, we may nest all admin functionality within a module named Admin. In the following example, we make the router aware of such a setup:

image

Here, paths like /admin/users route to the controller Admin::Users.

2.6.3.3 Namespaces

You can achieve the effect of both name and controller prefixes more easily by using the method namespace. This is perfect for an admin module where you also want to be able to use named routes like admin_users to point to /admin/users. Below we use namespace to set both the name and the controller prefix.

image

2.6.4 Fixatable routes

Sometimes you need a route to be able to include a session ID. This is known as session fixation and is considered insecure for most purposes. The reason is that when you allow a session ID to be included in a URL, one user could trick another user into following a link with a fixated session. Once this second user logs in, the first user will be able to hijack the second user’s account.

Merb therefore does not allow for fixated sessions by default, but does allow for them in certain cases so that they can pass on session-related responses to clients (like Adobe Flash Player when it’s used to upload files) that are unable to handle cookies. Here’s an example of a fixatable route:

image

Please remember to use fixatable routes only when needed, and to assure session integrity, regenerate the session ID after use.

2.7 Resource routes

Resource routes are RESTful routes that provide a standard way of interacting with resources. Let’s explain how resources work in terms of the router.

2.7.1 Standard resources routing

The method resources sets up various routes that allow you to access a particular resource. These routes are similar to the default routes but do not match exactly. For instance, with a default route you might use /users/show/1 to see the profile of a user. With a resources route, however, you would use /users/1. Resources routes also set up various defaults for the creation, modification, and deletion of resources. They also name each of your routes, making it easy to generate URLs. Let’s take a look at the routes generated by the following prepare block:

image

In interactive Merb we can pull up the following list:

image

image

Note that all resources go to the same controller, but that there are seven standard actions: index, new, create, edit, update, destroy, and show. We’ll learn about these seven actions and their purpose in depth when we cover REST, but for now, it should suffice to know which routes go to which action. Here’s a list of examples with a brief explanation of intent:

2.7.1.1 Index

/users—retrieves a list of users

/turtles.xml—retrieves a list of turtles as XML

2.7.1.2 New

/users/new—requests the form needed to create a new user account

2.7.1.3 Create

/users with POST—creates a new user from POST data

/turtles.txt—creates a new turtle with POST data with confirmation in a text format

2.7.1.4 Edit

/users/1/edit—requests the form needed to create a new user account

2.7.1.5 Update

/users/1 with PUT—updates the user with an ID equal to 1 with the POST data

/turtles/1.json with PUT—updates the turtle with an ID equal to 1 with the POST data with confirmation in JSON

2.7.1.6 Destroy

/users/1 with DELETE—deletes the user with an ID of 1

/turtle/1/delete with GET—deletes the turtle with an ID of 1

2.7.1.7 Show

/users/1—shows the profile of the user with an ID of 1

/turtle/1.xml with GET—shows the details of the turtle with an ID of 1 in XML

2.7.2 Singular resource routing

Sometimes a resource is all alone. In other cases the user’s interaction with that class of resources should be limited to only the one that belongs to that user. In both cases, a singular resource route is best. Here we use a singular resource route for our application’s overall admin settings (stored as one row in a table) and to set up routes for our user’s account, both times using the resource method:

image

This creates routes similar to those created by resources but without IDs. The singularity of the resource also rules out the need for an index action. Thus the only actions routed to are show, new, create, edit, update, and destroy.

2.7.3 Using identify

Sometimes a route like /users/1 is unpleasant. In fact, there are at least four reasons why such a URL may be unwanted:

• You don’t want to have to remember ID numbers to see a resource’s page.

• Search engines often optimize for keywords in URLs, so ID numbers are bad search engine optimization.

• ID numbers can give away how many of a resource you have.

• ID numbers make scraping your data from the web easy to do.

To get around these issues, the Merb router has a method identify. By using it you can change how a resource is turned into a string in routes. Here we use identify to opt for the use of username instead of id:

image

Thus, routes generated with a user object use the username method for identification.

url(:users, user) # => ex. '/users/foysavas'

We can also provide custom methods within our models to stringify our identifier. This method has to be defined in your model class. For those using the ActiveRecord ORM, the method to_params is supposed to serve this purpose. Consequently, the ActiveRecord plugin saves you from even having to include identify blocks in your router by automatically wrapping the router in an identify ActiveRecord::Base => :to_params.

2.8 Conclusion

Routing is critical to an MVC web framework. Merb applications enjoy simple-to-use, semantically well-defined route definition that boosts comprehensive bidirectional route recognition and generation. We’ve also gotten a taste of what it means to be RESTful and why adherence to such standards can simplify both an application’s internals and its API. Finally, we even peered into the Merb routing internals to understand how the behavior class is used as an intermediate step toward route definition.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.82.21