Nested Resource at Root Level of Path - rest

I'm trying to create a nested resource with a url scheme along the lines of: "http://example.com/username/...".
What I currently have is this:
ActionController::Routing::Routes.draw do |map|
map.home '/', :controller => 'home'
map.resource :session
map.resources :users, :has_many => :nodes
#map.user '/:id', :controller => 'users', :action => 'show', :has_many => :nodes
map.resources :nodes, :belongs_to => :user
end
This results in URLs like:
http://example.local/users/username
http://example.local/users/username/nodes
How to avoid the "users" prefix is beyond me. Passing a "as: => ''" option to map.resources doesn't work and it seems named routes don't support the ":has_many" or ":belongs_to" options.
Commenting out the "map.resources :users" and uncommmenting the "map.user" line after it seems to work… until you reach a nested resource. Then it spits out the following error:
undefined method `user_nodes_path' for #<ActionView::Base:0x1052c8d18>
I know this problem has come up plenty of times before and is always met with "Why would you want to do that?" responses. Frankly, Twitter does it, Facebook does it, and I want to do it too! ;-D
As for the common criticism of how to avoid usernames from conflicting built-in paths, I've set my minimum username length to 6 characters and plan to make all built-in root-level paths segments paths 5 characters or shorter (i.e. "/opt/..." for options, "/in/..." for session log-in, etc.).

As noted in this question:
Default segment name in rails resources routing
You should be able to use this plugin:
http://github.com/caring/default_routing
Alternatively, you could specify them manually with something like
map.users '/users', :controller => 'users', :action => 'index',
:conditions => { :method => :get }
map.connect '/users', :controller => 'users', :action => 'create',
:conditions => { :method => :post }
map.user '/:id', :controller => 'users', :action => 'show',
:conditions => { :method => :get }
map.edit_user '/:id/edit', :controller => 'users', :action => 'edit',
:conditions => { :method => :get }
map.new_user '/users/new', :controller => 'users', :action => 'new',
:conditions => { :method => :get }
map.connect '/:id', :controller => 'users', :action => 'update',
:conditions => { :method => :put }
map.connect '/:id', :controller => 'users', :action => 'destroy',
:conditions => { :method => :delete }
map.resources :nodes, :path_prefix => '/:user_id', :name_prefix => 'user_'
# to generate user_nodes_path and user_node_path(#node) routes
That or something like it should give you what you want from map.resources.
map.resources doesn't work and it seems named routes don't support the ":has_many" or ":belongs_to" options.
Named routes supports has_many, which is used to add another resource level to the URL path. For example
map.resources :users, :has_many => :nodes
generates all the users_path and user_nodes_path routes like /users, /users/:id, /users/:id/nodes and /users/:id/nodes/:id. It's identical to
map.resources :users do |user|
user.resources :nodes
end
Commenting out the "map.resources :users" and uncommmenting the "map.user" line after it seems to work… until you reach a nested resource. Then it spits out the following error:
undefined method `user_nodes_path' for #<ActionView::Base:0x1052c8d18>
That's because map.resources :users, :has_many => :nodes generates these routes. Only one named route is generated by map.user '/:id', :controller => 'users', :action => 'show' and that's user_path.

See my answer in this related question
Basically, you can pass :path => '' as an option to negate the identifying segment (Tested in Rails 3 only) Just be aware that this may clash with other routing patterns, so be careful where and how you place it.

Related

Zend routes two routes need same count of params

I don´t know what I do wrong. I got two named Zend route:
$route = new Zend_Controller_Router_Route(
'catalog/:categoryIdent/:productIdent/',
array(
'action' => 'viewproduct',
'controller' => 'catalog',
'module' => 'eshop',
'categoryIdent' => '',
'productIdent' => ''
),
array(
'categoryIdent' => '[a-zA-Z-_0-9]+',
'productIdent' => '[a-zA-Z-_0-9]+'
)
);
$router->addRoute('catalog_category_product', $route);
// catalog category route
$route = new Zend_Controller_Router_Route(
'catalog/:categoryIdent/:page/',
array(
'action' => 'viewcategory',
'controller' => 'category',
'module' => 'eshop',
'categoryIdent' => '',
'page' => ''
),
array(
'categoryIdent' => '[a-zA-Z-_0-9]+'
)
);
$router->addRoute('catalog_category', $route);
When I call catalog_category its all fine but when I try call to catalog_category_product is used viewcategory action from second route. It means its problem with :page variable in url, resp. same count of arguments in URL? I think that itsn´t nessesary I would like to get two different but similar routes - for example:
For category - catalog/category1/1
For product - catalog/category1/product1 (without number of page)
when I change form of route catalog_category_product to catalog/:categoryIdent/something/:productIdent/ so its working
here is route calls
$this->url(array('categoryIdent' => categoryIdent, 'productIdent' => productIdent), 'catalog_category_product', true);
$this->url(array('categoryIdent' => 'cerveny-cedr', 'page' => 'pageNumber'), 'catalog_category', true);
Thanks for any help
Keep in mind that routes are checked in reverse order, so the ZF router will check the catalog_category route before the catalog_category_product route when matching URLs. So, the number of arguments is not a problem, but since you've not put any sort of restriction on the 'page' parameter, all URLs that would normally match your catalog_category_product URL will be matched by catalog_category instead.
It sounds like 'page' should be numeric, so adding that restriction to your second route should fix the problem.

CakePHP REST Basic Mapping Not Working As Expected

Followed the Cake Book example almost exactly.
Router::mapResources('incidentReports');
Router::parseExtensions('json');
Both before
require CAKE . 'Config' . DS . 'routes.php';
My controller called IncidentReportsController
class IncidentReportsController extends AppController {
Which contains functions
index()
view($id)
add()
edit($id)
delete($id)
Going to the URL
www.myurl.com/incidentReports.json
Sends the request to the index() function as expected.
Going to the URL
www.myurl.com/incidentReports/260.json
Should map to the view() function but trys to map to a 260() function which doesn't exist.
www.myurl.com/incidentReports/view/260.json
Does map to the view() function and works properly. However, my understanding is the "view" in the URL shouldn't be necessary.
Had the same issue. In my case I was able to fix it by changing the controller name in the URL.
Didn't work: http://www.example.com/entityName.json
Works fine: http://www.example.com/entity_name.json
According to the documentation, you've done everything right, so I'm not sure. Try putting this (the routes that should be enabled) in your routes too.
Router::resourceMap(array(
array('action' => 'index', 'method' => 'GET', 'id' => false),
array('action' => 'view', 'method' => 'GET', 'id' => true),
array('action' => 'add', 'method' => 'POST', 'id' => false),
array('action' => 'edit', 'method' => 'PUT', 'id' => true),
array('action' => 'delete', 'method' => 'DELETE', 'id' => true),
array('action' => 'update', 'method' => 'POST', 'id' => true)
));
If your controller is within a plugin, you must specify as well..
e.g:
Router::mapResources('Plugin.Controller');

Zend Routes: Matching

I have these routes that I need to work:
/user
/user/1
/user/1.json
Currently I have
'user' => array('key' => 'user', 'controller' => 'user'),
'user/:id' => array('key' => 'user-id', 'controller' => 'user', 'id' => "\d+"),
'user/:id.json' => array('key' => 'user-id-json', 'controller' => 'user', 'id' => "\d+"),
It matches the first route for all three urls.
I tried doing this:
'user' => array('key' => 'user', 'controller' => 'user'),
'user/"\d+"' => array('key' => 'user-id', 'controller' => 'user', 'id' => 1),
'user/"\d+".json' => array('key' => 'user-id-json', 'controller' => 'user', 'id' => 1),
but only the first route gets matched still
Is there a way in the Zend Route url rule to "encase" the :id so its picked out and allows me to put something other than / after it?
UPDATED
My bootstrap uses:
public function _initRoutes() {
$this->addRoutes(array(
'client' => array('key' => 'client', 'controller' => 'client'),
'client/:id' => array('key' => 'client-id', 'controller' => 'client', 'id' => "\d+"),
'user' => array('key' => 'user', 'controller' => 'user'),
'user/:id' => array('key' => 'user-id', 'controller' => 'user', 'id' => "\d+")
));
}
My extended extender uses
public function addRoutes($routes) {
$router = Zend_Controller_Front::getInstance()->getRouter();
foreach ($routes as $pattern => $data) {
$router->addRoute(
$data['key'],
new Zend_Controller_Router_Route(
$pattern,
array('controller' => $data['controller'])
)
);
}
}
I tried making addRoutes this:
public function addRoutes($routes) {
$router = Zend_Controller_Front::getInstance()->getRouter();
foreach ($routes as $pattern => $data) {
$router->addRoute(
$data['key'],
new Zend_Controller_Router_Route(
$pattern,
array('controller' => $data['controller'])
)
);
$router->addRoute(
$data['key'].'-json',
new Zend_Controller_Router_Route(
$pattern.'.json',
array('controller' => $data['controller'])
)
);
}
}
but the latter cancels out /user/50 for example
Firstly, don't try and do this all in one route; you can have multiple named routes that point you at the same place. Its not clear from the chunks if you are trying to define this for one route. If you are, then split your rules into three, and give them the same variables.
If your routes are defined in an ini file (by convention, routes. ini), then I find it easier to have something like /user/:id/:format. You then can either use the Zend contextSwitcher helper in the controller or simply use the :format as a hint to your application. Under the reqs section of your route, you should have (\d+) as Zend_Route looks for sub strings.
Failing that, you might be able to get away with :id:file and define .json as a req for that particular route. This might work if you have to have the file extension. However, as you are kind of fooling the user by serving a PHP script as json, might be better handled at the web server level. If all else fails, and your regular expression foo is up to it, create an instance of the Regex router. While this is untested, this might do it:
/user/(\d+)\.json$

Custom route with parameters

I want to make customer route for url which have parameters.
<a href='javascript:void(0)' onclick="window.location='<?php echo
$this->url(array('module' => 'courses', 'controller' => 'course', 'action' => 'add',
'std_id' => $entry['std_id']), 'coursesadd', TRUE); ?>'">add course</a>
and here what I make routing
$route = new Zend_Controller_Router_Route('courses/std_id/:std_id',
array('module' => 'courses', 'controller' => 'course', 'action' => 'add'));
$routesArray = array('coursesadd' => $route );
$router->addRoutes($routesArray);
But It doesn't route correctly!
Your code looks fine, except what RockyFord sad - You should only pass these parameters to url() helper which are required by designated route (when 'default' route is used indeed it requires module, controller, action, but Your 'coursesadd' route requires only std_id parameter). Moreover You should improve Your route a little bit to:
$route = new Zend_Controller_Router_Route('courses/:std_id', array(
'module' => 'main',
'controller' => 'product',
'action' => 'add',
'std_id' => null //default value if param is not passed
));
The param_name/:param_value construction in Your route is redundant because You already named the parameter, so by using above route You'll get std_id param in controller using
$this->_getParam('std_id')
Default value in route definition would save from throwing Zend_Controller_Router_Exception: std_id is not specified but it looks like $entry['std_id'] is not set.

Zend Route: if link starts with /something then ignore other rules

I have two modules that should over ride other urls, basically
/management/category/edit/id/1 (Edit Category Page)
/management/category/index (Index Page Of Category in Management Module)
/management/category (Index Page Of Category in Management Module)
/women-like-men/category (URL Routed from /default/category/view/id/women-like-men)
The rules for the above are:
$router->addRoute('view-category', new Zend_Controller_Router_Route(':id/category/:page', array('module' => 'default', 'controller' => 'category', 'action' => 'view', 'page' => null)));
$router->addRoute('management/category', new Zend_Controller_Router_Route('management/category/', array('module' => 'management', 'controller' => 'category', 'action' => 'index')));
There are alot of similar pages like this that "conflict" (Gallery, Movie, User) etc.
What I would really like is a rule that says /management/* route to module management/controller/action and ignore the rules below, anything with /management over rides. The likely hood of a user/gallery/movie/category being called management is low anyway.
Is it possible?
Edit, I have made this:
$router->addRoute('administration', new Zend_Controller_Router_Route_Regex('management/?(.*)/?(.*)', array('module' => 'management'), array(1 => 'controller', 2 => 'action')));
/management/category works fine, however anything after results in a 404
This should be straightforward. The order your routes are defined is important - they are checked in reverse order, so in order for your management rule to 'override' the others you want it to be defined last (i.e. after all your other routes). Something like this (which is basically what you have) should work:
$route = new Zend_Controller_Router_Route(
'management/:controller/:action/*',
array(
'module' => 'management'
'controller' => 'index',
'action' => 'index'
)
);
$router->addRoute('management', $route);
If any of your management URLs are still 404'ing after this then they're not matching that route, so it either needs adjusting or you need some other management routes to match the other cases.