Rails routes: different domains to different places - ruby-on-rails

I've got a Rails app up running on a server. It's a big project so there are lots of routes involved, and two domains point to the root at the moment. I'd like to somehow design my routes.rb to interpret one domain to take it to a certain part of the app as if it was the root, and use the other for everywhere else.
Something like this (very pseudocode, hope you get the idea):
whole_app.com
whole_app.com/documents
whole_app.com/share
whole_app.com/users
partial_app.com, :points_to => 'whole_app.com/share'
Can Rails handle this? Thank-you!

You can achieve this by overriding default url_options method in application controller. This will override host url for every request.
class ApplicationController < ActionController::Base
....
def default_url_options
if some_condition
{:host => "partial_app.com"}
else
{:host => "whole_app.com"}
end
end
....
end
And for pointing a route to some specific url, you may use:
match "/my_url" => redirect("http://google.com/"), :as => :my_url_path
The better way is to do settings on server to redirect some url to a specific location.

is it going to /share based on some kind of criteria? if so you can do this:
routes.rb
root :to => 'pages#home'
pages_controller.rb
def home
if (some condition is met)
redirect_to this_path
else
render :layout => 'that'
end
end

Related

Rails 4 subdomains, tied to controller

grateful to have you guys as a resource! I think this should be a simple question but haven't been able to find a simple answer through searching yet, any help/guidance would be appreciated!!
I have a "subdomain" controller which is setup in the following way:
get 'subdomain/:store' => 'subdomain#index'
get 'subdomain/:store/products' => 'subdomain#product_index'
get 'subdomain/:store/products/:id' => 'subdomain#products_show'
As you can see, the subdomain controller matches the request with a Store ID and can also get an index of all the associated products with the Store ID. I'd like to somehow convert each of these requests into a subdomain rather than a path. Each Store has a "subdomain" attribute (in the example below, one of the Store records has a subdomain value of "nike").
For example
host.com/subdomain/nike => nike.host.com
host.com/subdomain/nike/products => nike.host.com/products
host.com/subdomain/nike/products/5 => nike.host.com/products/5
Notice the controller "subdomain" was removed from the path. Any help? I looked into gems such as apartment but they look like they are way too complex for this. Also subdomain-fu but it looks like it's outdated for Rails 4. Thoughts? THANKS!
For this, you can add routing Constraint.
Add the file to lib/subdomain_required.rb
class SubdomainRequired
def self.matches?(request)
request.subdomain.present? && request.subdomain != 'www'
end
end
Then, in your routes.rb, you can enclose your routes into a contraint block, somewhat like this:
constraints(SubdomainRequired) do
get '/' => 'subdomain#index'
get '/products' => 'subdomain#product_index'
get '/products/:id' => 'subdomain#products_show'
end
Now the last step is to load the store based on subdomain which can be done using a before_action like this
class SubdomainController < ActionController::Base
before_action :ensure_store!
def index
#products = current_store.products.all
end
def ensure_store!
#store ||= Store.find_by subdomain: request.subdomain
head(:not_found) if #store.nil?
#store
end
def current_store
#store
end
end
now anywhere you want to get the store, you can use current_store helper method.
Hope it helps

How can I remove subdomain when the user accessed to www.xxxxxxxxxx.com?

Now in my app, the user can access to both www.xxxxxxxxxx.com and xxxxxxxxxx.com
but I don't want to use www.xxxxxxxxxx.com
How can I make the user redirect to xxxxxxxxxx.com?
My routing is like this
routes.rb
constraints(:subdomain => /^(|www)$/) do
root :to => "top#index"
end
I agree, doing this in the web server is better, however if you are unable to edit your virtual host configuration, you can create a filter like the following in the ApplicationController:
def strip_www
if request.env["HTTP_HOST"] == "www.url.com"
redirect_to "http://url.com#{request.request_uri}"
end
end

How to set up routes to show additional information in the URL using namespaces?

I am running Ruby on Rails 3 and I would like to set up my routes to show additional information in the URL using namespaces.
In the routes.rb file I have:
namespace "users" do
resources :account
end
So, the URL to show an account page is:
http://<site_name>/users/accounts/1
I would like to rewrite/redirect that URL as/to
http://<site_name>/user/1/Test_Username
where "Test_username" is the username of the user. Also, I would like to redirect all URLs like
# "Not_real_Test_username" is a bad entered username of the user.
http://<site_name>/users/accounts/1/Not_real_Test_username
to the above.
At this time I solved part of my issuelike this:
scope :module => "users" do
match 'user/:id' => "accounts#show"
end
My apologies for not answering your question (#zetetic has done that well enough), but the best practice here is to stay within the RESTful-style Rails URL scheme except for rare exceptions. The way most people make pretty URLs in this way is to use a hyphen, e.g.:
/accounts/1-username
This does not require any routing changes. Simply implement:
class Account < ActiveRecord::Base
def to_param
"#{self.id}-#{self.username}"
end
end
And handle the extra string data in your finds by calling to_i.
class AccountController < ApplicationController
def show
#account = Account.find(params[:id].to_i)
end
end
When you do link_to 'Your Account', account_path(#account), Rails will automatically produce the pretty URL.
It's probably best to do this in the controller, since you need to retrieve the account to get the username:
#account = Account.find(params[:id])
if #account && #account.username
redirect_to("/user/#{#account.id}/#{#account.username}")
return
end
As to the second issue, you can capture the remaining parameter by defining it in the route:
get "/users/accounts/:id(/:other)" => "users/accounts#show"
This maps like so:
/users/accounts/1/something # => {:id => "1", :other => "something"}
/users/accounts/1 # => {:id => "1"}
And you can simply ignore the :other key in the controller.

How can I redirect a user's home (root) path based on their role using Devise?

I'm working on a project management app, and in the app, I have project_managers and clients. I'm using Devise and CanCan for authentication/authorization.
At what point after login should I be redirecting the user to their own specific controller/layout/views? Is there a way to check for current_user.role in routes.rb and set the root (or redirect) based on whether or not they're a project manager or a client? Is this a change I can make in Devise somewhere?
Thanks in advance for any help!
--Mark
Your routes.rb file won't have any idea what role the user has, so you won't be able to use it to assign specific root routes.
What you can do is set up a controller (for example, passthrough_controller.rb) which in turn can read the role and redirect. Something like this:
# passthrough_controller.rb
class PassthroughController < ApplicationController
def index
path = case current_user.role
when 'project_manager'
some_path
when 'client'
some_other_path
else
# If you want to raise an exception or have a default root for users without roles
end
redirect_to path
end
end
# routes.rb
root :to => 'passthrough#index'
This way, all users will have one point of entry, which in turn redirects them to the appropriate controller/action depending on their role.
The simplest solution is to use lambda:
root :to => 'project_managers#index', :constraints => lambda { |request| request.env['warden'].user.role == 'project_manager' }
root :to => 'clients#index'
Another option is to pass a proc to the authenticated method like this (I'm using rolify in this example):
authenticated :user, ->(u) { u.has_role?(:manager) } do
root to: "managers#index", as: :manager_root
end
authenticated :user, ->(u) { u.has_role?(:employee) } do
root to: "employees#index", as: :employee_root
end
root to: "landing_page#index"
Note that in Rails 4 you have to specify a unique name for each root route, see this issue for details.
I do this in a Rails 3 app that uses Warden. Since Devise is built on top of Warden, I think it'll work for you, but be sure to experiment with it a bit before relying on it.
class ProjectManagerChecker
def self.matches?(request)
request.env['warden'].user.role == 'project_manager'
end
end
# routes.rb
get '/' => 'project_managers#index', :constraints => ProjectManagerChecker
get '/' => 'clients#index'
If the user's role is "project_manager" the ProjectManagersController will be used - if it's not the ClientsController will be used. If they're not logged in at all, env['warden'].user will be nil and you'll get an error, so you'll probably want to work around that, but this will get you started.
Blog post with the implementation http://minhajuddin.com/2011/10/24/how-to-change-the-rails-root-url-based-on-the-user-or-role/

Multiple robots.txt for subdomains in rails

I have a site with multiple subdomains and I want the named subdomains robots.txt to be different from the www one.
I tried to use .htaccess, but the FastCGI doesn't look at it.
So, I was trying to set up routes, but it doesn't seem that you can't do a direct rewrite since every routes needs a controller:
map.connect '/robots.txt', :controller => ?, :path => '/robots.www.txt', :conditions => { :subdomain => 'www' }
map.connect '/robots.txt', :controller => ?, :path => '/robots.club.txt'
What would be the best way to approach this problem?
(I am using the request_routing plugin for subdomains)
Actually, you probably want to set a mime type in mime_types.rb and do it in a respond_to block so it doesn't return it as 'text/html':
Mime::Type.register "text/plain", :txt
Then, your routes would look like this:
map.robots '/robots.txt', :controller => 'robots', :action => 'robots'
For rails3:
match '/robots.txt' => 'robots#robots'
and the controller something like this (put the file(s) where ever you like):
class RobotsController < ApplicationController
def robots
subdomain = # get subdomain, escape
robots = File.read(RAILS_ROOT + "/config/robots.#{subdomain}.txt")
respond_to do |format|
format.txt { render :text => robots, :layout => false }
end
end
end
at the risk of overengineering it, I might even be tempted to cache the file read operation...
Oh, yeah, you'll almost certainly have to remove/move the existing 'public/robots.txt' file.
Astute readers will notice that you can easily substitute RAILS_ENV for subdomain...
Why not to use rails built in views?
In your controller add this method:
class StaticPagesController < ApplicationController
def robots
render :layout => false, :content_type => "text/plain", :formats => :txt
end
end
In the view create a file: app/views/static_pages/robots.txt.erb with robots.txt content
In routes.rb place:
get '/robots.txt' => 'static_pages#robots'
Delete the file /public/robots.txt
You can add a specific business logic as needed, but this way we don't read any custom files.
As of Rails 6.0 this has been greatly simplified.
By default, if you use the :plain option, the text is rendered without
using the current layout. If you want Rails to put the text into the
current layout, you need to add the layout: true option and use the
.text.erb extension for the layout file. Source
class RobotsController < ApplicationController
def robots
subdomain = request.subdomain # Whatever logic you need
robots = File.read( "#{Rails.root}/config/robots.#{subdomain}.txt")
render plain: robots
end
end
In routes.rb
get '/robots.txt', to: 'robots#robots'
For Rails 3:
Create a controller RobotsController:
class RobotsController < ApplicationController
#This controller will render the correct 'robots' view depending on your subdomain.
def robots
subdomain = request.subdomain # you should also check for emptyness
render "robots.#{request.subdomain}"
end
end
Create robots views (1 per subdomain):
views/robots/robots.subdomain1.txt
views/robots/robots.subdomain2.txt
etc...
Add a new route in config/routes.rb: (note the :txt format option)
match '/robots.txt' => 'robots#robots', :format => :txt
And of course, you should declare the :txt format in config/initializers/Mime_types.rb:
Mime::Type.register "text/plain", :txt
Hope it helps.
If you can't configure your http server to do this before the request is sent to rails, I would just setup a 'robots' controller that renders a template like:
def show_robot
subdomain = # get subdomain, escape
render :text => open('robots.#{subdomain}.txt').read, :layout => false
end
Depending on what you're trying to accomplish you could also use a single template instead of a bunch of different files.
I liked TA Tyree's solution but it is very Rails 2.x centric so here is what I came up with for Rail 3.1.x
mime_types.rb
Mime::Type.register "text/plain", :txt
By adding the format in the routes you don't have to worry about using a respond_to block in the controller.
routes.rb
match '/robots.txt' => 'robots#robots', :format => "text"
I added a little something extra on this one. The SEO people were complaining about duplicated content both in subdomains and in SSL pages so I created a two robot files one for production and one for not production which is also going to be served with any SSL/HTTPS requests in production.
robots_controller.rb
class RobotsController < ApplicationController
def robots
site = request.host
protocol = request.protocol
(site.eql?("mysite.com") || site.eql?("www.mysite.com")) && protocol.eql?("http://") ? domain = "production" : domain = "nonproduction"
robots = File.read( "#{Rails.root}/config/robots-#{domain}.txt")
render :text => robots, :layout => false
end
end

Resources