Rails 3.1 load controller from different path based on subdomain - ruby-on-rails

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.

Related

RESTful routing in Rails 3 app - some advice / pointers

I have an app, locally in development without a subfolder, and in production i have it deployed under /myappname/
So, locally I have http://myapp.dev and in production http://mydomain.com/myappname
which my root route does:
root :to => 'products#list'
which works great, even in production.
Now, i have a default match action:
match '/:controller(/:action(/:id))'
which breaks in production, so i started trying to build a restful route, but i need some help... I can't wrap my head around the routing. I think i have the proper start (with scope, below)
#PRODUCTION ROUTES
scope '/myappname' do
#WHAT WOULD GO HERE?
end
format would be /myappname/products/show/15
Hm. I would expect all the routes to work relative to the "home page", so why don't you just go with
resources :users
or any other route definition from the examples in config/routes.rb?
#PRODUCTION ROUTES
scope "/mothers" do
#ROOT
root :to => 'rings#list'
match '/rings/:id' => "rings#show", :as => :ring
end
#DEVELOPMENT ROUTES
root :to => 'rings#list'
match '/rings/:id' => "rings#show", :as => :ring

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

Rails 3 - Subdomain Issue Moving to Heroku

I've written an application that uses a subdomain per user account to segregate environments. All this is working fine, except I have one issue. I can't get both www and "" to have a different root path than all other subdomains.
For all account subdomains, I have a root page of:
root :to => "applications#index"
I need this to be the root page for all subdomains except for a blank subdomain of "" and then "www". For www, I have this in the routes:
constraints(:subdomain => "www") do
root :to => "promos#index"
end
What I'm struggling with, is getting it so "" will also use promos#index as the root path. When it's not the root path, mywebsite.com sends them to the applications#index, which requires a login. Something I don't want users to see on a first visit.
Is there anyway to modify this code to also include mywebsite.com to have the different root? I've tried things like duplicating the code with "", but this tends to mess up all other subdomains, regardless of order. Below is the in of my routes file:
constraints(:subdomain => "www") do
root :to => "promos#index"
end
root :to => "applications#index"
You can use an object that implements 'matches?' to do some real custom stuff. Below we'll set applications#index if you are a customer subdomain, and send you to promo#index if you're not
In your routes:
Yourapp::Application.routes.draw do
constraints(SubDomain) do
root :to => "applications#index"
end
root :to => "promo#index"
...
end
and then the Subdomain matcher file:
config/initializers/subdomain.rb
class SubDomain
def self.matches?(request)
case request.subdomain
when 'www', '', nil, #admin/api/etc could also go here
false
else
true
end
end
end
subdomain.rb can also live in lib (if it's being auto-loaded)

Rails Restful Routing and Subdomains

I wondered if there were any plugins or methods which allow me to convert resource routes which allow me to place the controller name as a subdomain.
Examples:
map.resources :users
map.resource :account
map.resources :blog
...
example.com/users/mark
example.com/account
example.com/blog/subject
example.com/blog/subject/edit
...
#becomes
users.example.com/mark
account.example.com
blog.example.com/subject
blog.example.com/subject/edit
...
I realise I can do this with named routes but wondered if there were some way to keep my currently succinct routes.rb file.
I think that subdomain-fu plugin is exacly what you need.
With it you will be able to generate routes like
map.resources :universities,
:controller => 'education_universities',
:only => [:index, :show],
:collection => {
:all => :get,
:search => :post
},
:conditions => {:subdomain => 'education'}
This will generate the following:
education.<your_site>.<your_domain>/universities GET
education.<your_site>.<your_domain>/universities/:id GET
education.<your_site>.<your_domain>/universities/all GET
education.<your_site>.<your_domain>/universities/search POST
The best way to do it is to write a simple rack middleware library that rewrites the request headers so that your rails app gets the url you expect but from the user's point of view the url doesn't change. This way you don't have to make any changes to your rails app (or the routes file)
For example the rack lib would rewrite: users.example.com => example.com/users
This gem should do exactly that for you: http://github.com/jtrupiano/rack-rewrite
UPDATED WITH CODE EXAMPLE
Note: this is quickly written, totally untested, but should set you on the right path. Also, I haven't checked out the rack-rewrite gem, which might make this even simpler
# your rack middleware lib. stick this in you lib dir
class RewriteSubdomainToPath
def initialize(app)
#app = app
end
def call(env)
original_host = env['SERVER_NAME']
subdomain = get_subdomain(original_host)
if subdomain
new_host = get_domain(original_host)
env['PATH_INFO'] = [subdomain, env['PATH_INFO']].join('/')
env['HTTP_X_FORWARDED_HOST'] = [original_host, new_host].join(', ')
logger.info("Reroute: mapped #{original_host} => #{new_host}") if defined?(Rails.logger)
end
#app.call(env)
end
def get_subdomain
# code to find a subdomain. simple regex is probably find, but you might need to handle
# different TLD lengths for example .co.uk
# google this, there are lots of examples
end
def get_domain
# get the domain without the subdomain. same comments as above
end
end
# then in an initializer
Rails.application.config.middleware.use RewriteSubdomainToPath
This is possible without using plugins.
Given the directory structure app/controllers/portal/customers_controller.rb
And I want to be able to call URL helpers prefixed with portal, i.e new_portal_customer_url.
And the URL will only be accessible via http://portal.domain.com/customers.
Then... use this:
constraints :subdomain => 'portal' do
scope :module => 'portal', :as => 'portal', :subdomain => 'portal' do
resources :customers
end
end
As ileitch mentioned you can do this without extra gems it's really simple actually.
I have a standard fresh rails app with a fresh user scaffold and a dashboard controller for my admin so I just go:
constraints :subdomain => 'admin' do
scope :subdomain => 'admin' do
resources :users
root :to => "dashboard#index"
end
end
So this goes from this:
site.com/users
to this :
admin.site.com/users
you can include another root :to => "{controller}#{action}" outside of that constraint and scope for site.com which could be say a pages controller. That would get you this:
constraints :subdomain => 'admin' do
scope :subdomain => 'admin' do
resources :users
root :to => "dashboard#index"
end
end
root :to => "pages#index"
This will then resolve:
site.com
admin.site.com
admin.site.com/users
Ryan Bates covers this in his Railscast, Subdomains.

constraints in ruby-on-rails routing

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.

Resources