Rails 3 routing and multiple domains - ruby-on-rails

My app allows people to create portfolios. I would like for them to be able to connect their domain to their portfolio.
So somedomain.com would show /portfolio/12, someotherdomain.com would show /portfolio/13 and so on. But I don't want the redirect. I want the user to see somedomain.com in the browser url.
How do I do that?
Ok, I've found this solution:
match "/" => "portfolio#show",
:constraints => { :domain => "somedomain.com" },
:defaults => { :id => '1' }
As I don't have many custom domains, this is fine for now but the question is - how to make this dynamic, to read domain and id data from db?

Ok, let's assume you own yourdomain.com and use it as your home page for your application. And any other domain name like somedomain.net is mapped to a portfolio page.
First of all, in your routes.rb you need to catch yourdomain.com and map it to wherever your home page is, so that it stands out from the rest of the crowd.
root :to => "static#home", :constraints => { :domain => "yourdomain.com" }
Then you need to catch any other root on any domain and forward it to your PortfoliosController
root :to => "portfolios#show"
Keep in mind that this line will only be checked if the previous line fails to match.
Then in your PortfoliosController find the requested portfolio by its domain rather than id.
def show
#portfolio = Portfolio.find_by_domain(request.host)
…
end
Of course you may want to rescue from an ActiveRecord::RecordNotFound exception in case the domain is not in your database, but let's leave that for another discussion.
Hope this helps.

First, you should add a field to the portfolio model to hold the user's domain. Make sure this field is unique. Adding an index to the field in your database would also be wise.
Second, set your root to route to the portfolios#show action, as you already did, but without the constraints.
Then, in the PortfoliosController#show method, do the following check:
if params[:id]
#portfolio = Portfolio.find(params[:id])
else
#portfolio = Portfolio.find_by_domain(request.host)
end
After this, the only thing left to do is to make sure your own domain does not trigger the portfolio#show action. This can be done with the constraint you used before, but now with your own domain. Be sure to put this line in routes.rb above the line for the portfolio#show action, since the priority is based upon order of creation.

The request object seems not to be available to the routes.rb file w/o some patching.
There are some plugins that make it available, but most of them seem to be outdated. This one here request_routing seems to be with the latest commit dates so it would be most up to date. Though I doubt it will work with Rails 3.0 out of the box, it is a start and might not be that hard to port.

Your users can set DNS CNAME redirects so that requests for theirdomain.com land on your_app.com/portfolio/12.

Related

Rails Routing by mapping subdomain to path via database

A seemlingly simple problem which I can't figure out how to deal with (in Rails 3.2): we would like to offer the possibility to
our users to define a subdomain and map incoming requests using that subdomain to a path partially retrieved from
the database. So for example, while www.example.com will go to our usual root path, a request to steve.example.com
would first look up "steve" in a datbase table that associates subdomains with ids, and if a match is found route
the request to, say, www.example.com/folders/36 (where 36 is the id associated with "steve"), and if no match is found
continue looking for other routes in routes.rb.
I have a working solution using redirect, which goes something like:
constraints :subdomain => /^(?!www).+/ do # a subdomain is used and different from www
match '*path', :to => redirect {|params, req|
req_protocol=req.env['rack.url_scheme'] # e.g. "http"
req_host=req.env['HTTP_HOST'] # e.g. "steve.example.local:3000"
...
code to pick up "steve", do the lookup and return a suitable URL
}
end
Now, I do NOT want to use redirects for two reasons: firstly the URL is then modified in the user's browser address bar,
and secondly browsers tend to cache the redirects (even with status set to 302 or 307) making such a solution less dynamic.
If I had had access to the request object in routes.rb, I could possibly had done something like
match '*path' => "folders##{index}", :constraints => {:subdomain => /^(?!www).+/ }
after having retrieved index from the database table using the subdomain, but the request object is not available.
(I could however probably manage the case when no association is found using the "Advanced Contstraints" as described in the
Rails guide, although I haven't tested that, knowing that it would only fix a tiny part of the problem).

Remove controller_name from URL in Rails 3 and use custom string instead of id

Since the beginning I always hat this one problem with rails, short urls without the controller name.
For example, I have a blog and I don't want any dates or controller names in the url, I already have a Page model with a unique field url in my database. Rails works great with such urls:
jeena.net/pages/1
And when I modify the model I even can get it to use
jeena.net/pages/foo
But it seems not to matter what I do I can not get it to work with just:
jeena.net/foo
Of course I want the index page still to work with
jeena.net/pages
And I want creating new pages and updating old pages to work too in some was as well as the link_to()-method. All suggestions are appreciated.
To define that route, try adding the following to your routes.rb:
match '/:id' => 'your_controller#your_action'
This will pretty much match everything to the id of your model. And that's not very nice... You don't want to route youe_host/pages to the pages controller, with an id equal to 'pages'... To prevent that from happening, make sure to put that line on the end of the routes.rb file. The router uses the first route that matches the path received, so putting that line on the end of it will make sure that it will only match your route after it ran out of other meaningful options.
A better practice would be to pass regexp constraints to the router, so that it will only match ids with a specific format, like that:
match '/:id' => 'your_controller#your_action', :constraints => { :id => /your_regexp/ }
Refer to the guides if you have doubts about the rails rounting system. It is pretty well written and covers lots of important things.
Rails rounting - Official Guides
edit: to create a named route, one that you can call in your controllers and override the normal routes that you are probably creating with resource, you have to provide the :as => parameter in your routes.rb
match '/:id' => 'your_controller#your_action', :as => some_name
Then you'll be able to call it in your controller/views like this:
link_to some_name_path(#my_string_id)
Hope this helps. And take a time to read the guides, it has really lots of useful info, including more details about creating named routes.

CakePHP: Advanced 'SEO' Routing - HowTo save old URLs Routes for changed Posts, for 301 redirects of external links

i have a problem with the routing of a new project.
For most of the actions and entities like users, the URLs would never change, because user names i.e. are unique and not changable. For that stuff we can easily use Routes like these:
Router::connect('/a/:username/topic/:id', array('controller' => 'users', 'action' => 'view'),array('pass' => array('username','id'),'username' => '[^-]+','id' => '[0-9]+'));
Router::connect('/a/:username', array('controller' => 'users', 'action' => 'view'),array('pass' => array('username'),'username' => '[^-]+'));
But for posts (and some other entities) the titles could/will change due to typos or something else.
Imagine you change the title of a post (or something else) 5 times. Each time some external links were build. So we want to assure that every external link still works and redirects to the current URL-Key with the correct/changed title with a 301 Status.
Because of that we tried to implement a table for routes with the following fields:
id, source, ref_id, target_controller, target_action, target_param, parent_id
Each time routes.php gets called, a foreach loop will run over the table and connect every route, that has NO parent. Having no parent means, that the record is the current (most recent) url key for a specific entity.
If the request goes onto an "old" url-key, we automatically redirect to the parent (most recent key) with a 301.
This does work pretty well but.... :
Later when this table contains thousands of records, the app will read all those records (unless the cache is up to date) and will "router::connect" thousands of routes instead of 5-10 very generic routes like those seen above. And that every time our 'routes cache' is outdated (what is likely to be very often).
I ask myself if there is no other solution to keep all old routes running while no eating a lot of performance.
i'd be glad to receive any feedback.
thanks a lot!
Rather than generate thousands of routes at run-time and caching them, have you considered using a custom Route class that simply works out the route for the current request?
Now in the past slug routes were a bit tricky as ensuring you had a valid slug either had to be done at the controller/model level, or you had to connect several hundred routes, one for each article slug. Both implementations leave much to be desired. ...

Drupal-like routing system in Rails

I am trying to find a best-practice to allow users to define the route to their pages in Rails, by writing them in a text field when submitting posts, like with the Path module in Drupal (yes, we are porting a Drupal site to Rails)
So, I need to
define a new, named route on article submission (eg http://www.domain.com/a-day-in-annas-life)
change the existing route on article edit, if they define a new one, by doing a 301 redirect from the old route to the new one
How can I best achieve this?
Okay, I found a way, but if it's best practice or not, I cant say.
I am using custom restrictor's like this:
class CharitiesRestrictor
def self.matches?(request)
slug = request.path_parameters[:path]
!Charity.find_by_name(slug).nil?
end
end
constraints CharitiesRestrictor do
match '*path' => 'charities#show_by_slug', :constraints => CharitiesRestrictor.new
end
When I create a block like this for each of my model/controller pairs that should be able to respond to permalinks, I can have them all have a chance to act on a permalink. However, this also means that they all get called in series, which is not necessarily ideal.

Dynamic root path route

How can I use dynamic root route if it depends on... weather, or current time, or whatever?
I thought about two ways: ApplicationController level and Rack redirect.
With first solution I will check my dynamic state and redirect to particular page.
Second solution is little more native as far as it uses routes level
For example
root :to => proc { |env| [ 302, {'Location'=> some_code }, [] ] }
But what I hope to see is how can I use simple lambda for route option like:
root :to => "mycontroller#myaction", :some_param => proc{ DateTime.now.hour }
It doesn't work but it shows my expectation
I'm not sure why you'd need to initialize a parameter in the routing table when the same thing could be done in the controller:
params[:some_param] = DateTime.now.hour
You can also do the redirection inside the controller as required instead of leaning so heavily on the routing table using the redirect_to method.
Has anyone figured this out? Even though this question is a decade old now I still don't see a clean way of doing this in Rails. I have a route constraint for the root path. My need is once they get to that action then a few conditions would be checked and based on that the request should be ultimately processed by another controller action. I could do redirect_to but then the URL shown in the browser also changes, which is not what I want. If they are hitting abc.com they should still be kept at abc.com and not redirected to abc.com/home or something else. Anyone figured this out?

Resources