constraints in ruby-on-rails routing - ruby-on-rails

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.

Related

Rails 3: making a catch-all route easier to read and amend

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.

Rails 3.1 load controller from different path based on subdomain

Is it possible to dynamically change the path from which controllers are used? Ryan Bates showed how to change the view_paths here: http://railscasts.com/episodes/269-template-inheritance
I'm making a CMS where a user can create a site and enter their own subdomain. I'd like "/" to point to "public#welcome" if there's no subdomain, but if there is a subdomain, I want it to point to "sites/public#welcome".
I'm using Rails 3.1 if that makes any difference.
You should be able to solve this situation using constraints if I'm not mistaken (which I might, since I haven't actually tried the following yet):
constraints(:subdomain => /.+/) do
root :to => 'sites/public#welcome'
end
root :to => 'public#welcome'
I figured it out:
constraints(:subdomain => /.+/) do
scope :module => "sites" do
root :to => 'public#welcome'
end
end
root :to => 'public#welcome'
Now when a user visits "/" Sites::PublicController will be used if a subdomain exists, but just PublicController if no subdomain exits. Adding scope :module => "sites" do...end keeps my routes file simplistic and manageable.

Rails 3: How do I route a controllers index and show actions to root while sending new/edit/destroy to /admin?

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

Route depending if user is an artist

I'd like to set up my routes depending if the user is an artist. So for example, something like:
namespace 'dashboard' do
if current_user.is_artist
get '/settings', :to => 'users#edit', :as => 'account_settings'
put '/settings', :to => 'users#update', :as => 'account_settings'
delete '/settings', :to => 'users#destroy', :as => 'account_settings'
else
get '/settings', :to => 'artists#edit', :as => 'account_settings'
put '/settings', :to => 'artists#update', :as => 'account_settings'
delete '/settings', :to => 'artists#destroy', :as => 'account_settings'
end
end
Unfortunately, I cannot access current_user in routes.rb. However, the logic of the code above, explains what my intentions are.
Is there a way I can achieve this?
The routing table doesn't run at the context of a specific user. This is because it routes the request before reaching your session management code, so it can't tell who's the current user.
I can think of two solutions:
Render your settings links through some method that rewrites them if the user is an artist. This way 'artists' and 'users' get different URLs.
Add a before_filter to your UserSettings controller that catches requests from artists and redirects them to ArtistSettings (not sure of your internal structure, but you probably get the general idea).
from what you have it looks like you have set up a role called artists. all you have to do to make your current_user method work is: in your account controller you can set up a before filter like saying before_filter :check_user_role :only => :account_setting. then you would set your account_settings for the various roles and their various redirects_to call methods you have. i am working on a similar project and it works for me. i hope this helps you
You could create a piece of middleware that figures out the user on a level before it hits the actual application. This is what warden/devise are doing for example. Add the user object to the env variable and you can access it anywhere lower in the stack.
Your routing file simply determines which controller action is being invoked based on the URL. You can map both artists and users to a single controller, and place the logic for which screen to render in that controller by looking at the user. By default, controllers render a view that is based on the name of the controller and method, but you're free to override this and render whatever view you want. Use your route to dispatch requests to the appropriate edit/update/destroy actions, and put your user/artist logic into those actions to render either the users/edit.html.haml or the artists/edit.html.haml file...
As explained by Elad, the routing itself can not tell if the current user is an artist or not.
If you want to keep a single url with two differents behaviours and views, you may go with a single controller such as :
def settings
if current_user.is_artist
do something
render :action => "artist_settings"
else
do something else
render :action => "users_settings"
end
end

How do I route user profile URLs to skip the controller?

Right now my user profile URLs are like so:
http://example.com/users/joeschmoe
And that points to the show method in the user controller.
What I'd ideally like to do is offer user profile URLs like this:
http://example.com/joeschmoe
So, what sort of route and controller magic needs to happen to pull that off?
I disagree with what jcm says about this. It's not a terrible idea at all and is used in production by the two biggest social networks Facebook and MySpace.
The route to match http://example.com/username would look like this:
map.connect ':username', :controller => 'users', :action => 'show'
If you want to go the subdomain route and map profiles to a URL like http://username.example.com/, I recommend using the SubdomainFu plugin and the resulting route would look like:
map.root :controller => 'users', :action => 'show' , :conditions => {:subdomain => /.+/}
These broad, catch all routes should be defined last in routes.rb, so that they are of lowest priority, and more specific routes will match first.
I also recommend using a validation in your User model to eliminate the possibility of a user choosing a username that will collide with current and future routes:
class User < ActiveRecord::Base
validates_exclusion_of :username, :in => %w( messages posts blog forum admin profile )
…
end
This does not make sense unless you have no controllers. What happens when you want to name a controller the same as an existing user? What if a user creates a username the same as one of your controllers? This looks like a terrible idea. If you think the /user/ is too long try making a new custom route for /u/
So your custom route would be...
map.connect 'u/:id', :controller => 'my/usercontroller', :action => 'someaction'
In routes.rb this should do the trick:
map.connect ":login", :controller => 'users', :action => 'show'
Where login is the name of the variable passed to the show method. Be sure to put it after all other controller mappings.
Well, one thing you need is to ensure that you don't have name collisions with your users and controllers.
Once you do that you, can add a route like this:
map.connect ':username', :controller => 'users', :action => 'show'
Another thing people have done is to use subdomains and rewrite rules in the web server, so you can have http://joeshmoe.example.com
In Rails 4 to skip controller from url you have to do add path: ''.
resources :users, path: '' do
end

Resources