I'm trying to write a catch-all route in Rails 3, but I want to reserve some terms in it. I'm specifically following the example put forth in this post, in the answer by David Burrows: Dynamic routes with Rails 3
The syntax I am using is the following:
match '*path' => 'router#routing', :constraints => lambda{|req| (req.env["REQUEST_PATH"] =~ /(users|my-stuff)/).nil? }
Now, that syntax works just fine - if a user visits a page with "user" or "my-stuff" in the path, it falls through the catch-all and goes to a specific place. If the user goes to any other URL, it goes to my routing logic.
My question is more about readability - is there a way I can match the route against something other than a regex? Is there a way to provide an array of terms to match against? Also, is there a way to match specific segments of the route, as opposed to the entire thing?
Obviously Rails has built-in routing, but this project has a requirement that for certain routes, the controller not be present in the URL. Hence, the catch-all.
Thanks for any help
Here's the updated routes file per the answer below:
class RouteConstraint
RESERVED_ROUTES = ['users', 'my-stuff']
def matches?(request)
!RESERVED_ROUTES.map {|r| request.path.include?(r)}.empty?
end
end
App::Application.routes.draw do
resources :categories
resources :sites
match '*path' => 'router#routing', :constraints => RouteConstraint.new
devise_for :users, :path_names =>{ :sign_in => 'login', :sign_out => 'logout', :registration => 'register' }
root :to => "router#routing"
end
You can use a class to specify the constraints if you want something cleaner once you have multiple routes to try:
class MyConstraint
BYPASSED_ROUTES = ['users', 'my-stuff']
def matches?(request)
BYPASSED_ROUTES.map {|r| request.path.include?(r)} .empty?
end
end
TwitterClone::Application.routes.draw do
match "*path" => "router#routing", :constraints => MyConstraint.new
end
This example is adapted from the rails routing guide.
It's taking a lambda; you can use whatever criteria you want.
Related
I'll explain when open www.domain.com/:id should be check the :id is a page then use
get ':id' => 'pages#show'
else if :id is a Category use
get ':category' => 'categories#show'
sorry for my english
get ':page' => 'pages#show'
resources :categories, :path => '/' do
resources :posts, :path => '/'
end
constraints for routes is way to go, but I would suggest a bit different solution for your problem.
Consider creating custom Constraint objects, which will query the database in order to check if Post or Category exists:
app/lib/post_constraint.rb
class PostConstraint
def matches?(request)
Post.exists?(request[:id])
end
end
app/lib/category_constraint.rb
class CategoryConstraint
def matches?(request)
Category.exists?(request[:id])
end
end
In your app/config/routes.rb
get ":id" => "posts#show", constraints: PostConstraint.new
get ":id" => "categories#show", constraints: CategoryConstraint.new
You have to be aware of the fact, that id is very poor candidate for such comparison, because the Post is checked in first place, and if there is record matching, the "posts#show" will be accessed, and CategoryConstraint wot be even bothered.
For more, check the documentation.
You should consider adding a slug for both models to easier serve exactly what user expects to see. For this purpose, try gem friendly_id.
Hope that helps! Good luck!
why dont you write something like this:
routes.rb
get '/:category_id/:post_id', to: 'categories#post'
and in your categories_controller.rb
def post
#params[:category_id] and params[:post_id] will contain the params from the url
end
Couple of solutions
If your post id and category id follow a pattern you can add constraints to your routes
1)
get ':post_id' => 'posts#show', :constraints => { :id => /[A-Z]\d{5}/}
get ':category_id' => 'categories#show', :constraints => { :id => /[1-9]\d{5}/ }
2)
Add a custom method where both post_id/category_id routes point, in that check if the id is a post id or category id and based on that render the page
I am working on an assignment which includes adding a feature to Typo.
rake routes shows:
admin_content /admin/content {:controller=>"admin/content", :action=>"index"}
/admin/content(/:action(/:id)) {:action=>nil, :id=>nil, :controller=>"admin/content"}
I need to create a route helper which matches the following RESTful route: /admin/content/edit/:id and an example of url is /admin/content/edit/1
But I can't figure out how to do it. I tried something like admin_content_path(edit,some_article) but it didn't work. (some_article is just an article object)
In routes.rb file:
# some other code
# Admin/XController
%w{advanced cache categories comments content profiles feedback general pages
resources sidebar textfilters themes trackbacks users settings tags redirects seo post_types }.each do |i|
match "/admin/#{i}", :to => "admin/#{i}#index", :format => false
match "/admin/#{i}(/:action(/:id))", :to => "admin/#{i}", :action => nil, :id => nil, :format => false
end
#some other code
Thanks a lot for your help!
If you are using RESTful routes, why not use the Rails default routes?
So your routes.rb would look like
namespace :admin do
resources :content
resources :advanced
resources :categories
resources :comments
...
<etc>
end
This does assume all your controllers are in the folder admin (but from your comment this seems to be the case.
If you do that, you can just use the standard route-helper: edit_admin_content_path.
If you want to do it manually, you should try adding a name to your route. E.g. as follows:
match "/admin/#{i}/:action(/:id)" => "admin/#{i}", :as => "admin_#{i}_with_action"
and then you should do something like
admin_content_with_action(:action => 'edit', :id => whatevvvva)
As a side-note: I really do not like the meta-programming in your config/routes.rb, if for whatever you really find that the default resources are not a right fit, I would advise to use methods instead (as explained here)
So for example in your config/routes.rb you would write:
def add_my_resource(resource_name)
match "/#{resource_name}", :to => "#{resource_name}#index", :format => false
match "/#{resource_name}(/:action(/:id))", :to => "#{resource_name}", :as => 'admin_#{resource_name}_with_action", :action => nil, :id => nil, :format => false
end
namespace :admin do
add_my_resource :content
add_my_resource :advanced
add_my_resource :categories
...
end
which imho is much more readable.
But my advice, unless you really-really need to avoid it, would be to use the standard resources since you do not seem to add anything special.
HTH.
First off, I'm using rails 3.0.8 with friendly_id 3.2.1.1.
I'd like to be able to view the posts at website.com/:title, so drop the "/posts".
But I'd also like to have an /admin view. From there, a user should be able to create/edit/destroy posts. I already have a admin.html.erb view with links to various actions.
Right now my routes file look like:
MyApp::Application.routes.draw do
root :to => 'posts#index'
resources :posts
match "/:id" => "posts#show"
match "/admin" => "posts#admin"
end
This works for website.com/:title, but for website.com/admin I get an error:
Couldn't find Post with ID=admin
.... which makes sense. But I'm not sure how to solve this problem.
The rules are run through top to bottom. So put your admin rule on top of the resource definition.
If you put /admin first then it will work (as noted by cellcortex). You can also use :constraints if you can neatly separate your :id from 'admin'; for example, if your :id values are numeric, then something like this should work:
match '/:id' => 'posts#show', :constraints => { :id => /\d+/ }
match '/admin' => 'posts#admin'
In a simple case like yours, putting things in the right order will work fine. However, if your routing is more complicated, then the :constraints approach might work better and avoid a some confusion and chaos.
Use this
resource :posts, :path => '/'
with this all of your article will be directly under root
So in Posts Class you may add this:
def to_param
"#{id}-#{title.parameterize}"
end
I have a model called Spaces which has different types of places... such as Bars, Restaurants, etc. It has the same columns, same, model, controller, etc. no fancy STI, I just have one field called Space_type which I would like to determine an aliased route.
Instead of domain.com/spaces/12345 it would be /bars/12345 or /clubs/12345
Currently I have:
resources :spaces do
collection do
get :update_availables
get :update_search
get :autocomplete
end
member do
post :publish
post :scrape
end
resources :photos do
collection do
put :sort
end
end
resources :reviews
end
Also, Is there a way I can do this so that anytime I use the space_url it can figure out which one to use?
The routes are not a way to interact with your model directly. So, as long as you write a standard route, you can make things work. For instance, to make /bars/12345 and /clubs/12345 for your spaces_controller (or whatever the name of the controller is) , you can create routes like :
scope :path => '/bars', :controller => :spaces do
get '/:id' => :show_bars, :as => 'bar'
end
scope :path => '/clubs', :controller => :spaces do
get '/:id' => :show_clubs, :as => 'clubs'
end
# routes.rb
match "/:space_type/:id", :to => "spaces#show", :as => :space_type
# linking
link_to "My space", space_type_path(#space.space_type, #space.id)
which will generate this urls: /bars/123, /clubs/1 ... any space_type you have
And it looks like STI wold do this job little cleaner ;)
UPD
Also you can add constraints to prevent some collisions:
match "/:space_type/:id", :to => "spaces#show", :as => :space_type, :constraints => { :space_type => /bars|clubs|hotels/ }
And yes - it is good idea to put this rout in the bottom of all other routes
You can also wrap it as a helper (and rewrite your default space_url):
module SpacesHelper
def mod_space_url(space, *attrs)
# I don't know if you need to pluralize your space_type: space.space_type.pluralize
space_type_url(space.space_type, space.id, attrs)
end
end
Could someone describe what this is all about?
It's in the routing file:
match "photo", :constraints => {:subdomain => "admin"}
I can't understand it.
thanks
It's saying that the photo route will only be recognised and routed to a controller if the request contains the subdomain admin. For example, the Rails application would respond to a request of http://admin.example.org/photo, but not http://example.org/photo.
One our guys posted this today which describes how you could reuse the same routes with different contexts (in this case whether the user is logged in)
For instance if you create a simple class to evaluate true/false:
class LoggedInConstraint < Struct.new(:value)
def matches?(request)
request.cookies.key?("user_token") == value
end
end
You can then use the evaluator in the routes to determine what routes apply:
root :to => "static#home", :constraints => LoggedInConstraint.new(false)
root :to => "users#show", :constraints => LoggedInConstraint.new(true)
Obviously you can design the constraints to your needs, but Steve described a couple different variants.