Multiple robots.txt for subdomains in rails - ruby-on-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

Related

Rails routes: different domains to different places

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

Rails redirect_to https while keeping all parameters

I'm redirecting to https like so:
redirect_to :protocol => 'https://', :status => :moved_permanently
However, the parameters don't go through like this. I can pass specific parameters through like this:
redirect_to :protocol => 'https://', :status => :moved_permanently, :param1 => params[:param1], :param2 => params[:param2]
How would I make it so that it just passes through every parameter on the url instead of having to explicitly declare each parameter?
Figured it out:
redirect_to({:protocol => 'https://'}.merge(params), :flash => flash)
This will keep all URL params through the redirect.
With Rails 4.2 and above, passing the whole params hash will result in adding ?controller=foo&action=bar to the querystring. Instead, you should do this:
redirect_to protocol: 'https', params: request.query_parameters
If you only need this at the controller level, you can use:
MyController < ApplicationController
force_ssl
end
You can use :only or :except if you only need this on a certain action. See documentation:
http://api.rubyonrails.org/classes/ActionController/ForceSSL/ClassMethods.html
Alternatively, if you just want your whole app to use ssl (assuming rails 3.1 or greater):
# config/application.rb
module MyApp
class Application < Rails::Application
config.force_ssl = true
end
end
You could just pass params as an argument like this:
redirect_to :protocol => 'http://', :status => :moved_permanently, :params => params

Organizing the Controller Directory in Rails (3)

I'm developing an application that is primarily an API gateway. In the expectation that we'll be developing multiple versions of the API over time and with the interest of having backward compatibility, I am looking to have something along the lines of:
http://host.domain.com/apiv1/:token/:module(/:id(:method))
Given this, what I am looking to do is to have a sub-routing system of my own within each API. What I'd like to have in terms of a file structure in the Controller directory is something akin to the following:
/app/controllers/apiv1_controller.rb
/app/controllers/apiv1/module_controller.rb
/app/controllers/apiv1/response_controller.rb
and eventually also have:
/app/controllers/apiv2_controller.rb
/app/controllers/apiv2/module_controller.rb
/app/controllers/apiv2/response_controller.rb
What this breaks down to is that I am unsure how to call methods within the controllers in the subdirectories, using something like:
return Apiv1::ResponseController.index
gives me:
undefined method `index' for Apiv1::ResponseController:Class
Any leads? Does this setup require that I explicitly "require" the requisite file manually?
Pasted Here in response to the question:
routes.rb
AppName::Application.routes.draw do
resources :users
match 'api-v1/:token/:module(/:id(/:method))' => 'apiv1#route'
root :to => "welcome#index"
end
apiv1_controller.rb
class Apiv1Controller < ApplicationController
protect_from_forgery
respond_to :json
def route
Rails.logger.level = 0
logger.info("ROUTE ACTION")
logger.info("params: #{params}")
Apiv1::ResponseController.index(params)
end
end
apiv1/response_controller.rb
class Apiv1::ResponseController < ApplicationController
protect_from_forgery
respond_to :json
def index(params)
Rails.logger.level = 0
logger.info("INDEX ACTION")
result = {
'success' => true,
'controller' => 'response',
'api' => 'v1'
}
render :json => result
end
end
If you are looking for versioning your REST APIs, you can use the restful_route_version gem. It also takes care of inheriting one version from another so that you don't rewrite the same REST APIs for every version.

Is there a way to check the parameters and decide the routes based on the parameters in rails?

I am looking for a way to decide the routes based on a request parameter.For example i want to have route a request to web controller if it has params[:web] and to iPhone if it has params[:iphone]. Is it possible to do so keeping the names of the routes same but routing them to different controllers/actions depending upon the parameter?
Possible if you define route(or a named route) like below in your routes.rb file
map.connect '/:controller/:action/:platform',:controller => 'some controller name',:action=>'some action'
if you handle this in your action, you can use like params[:platform]
Read more on named routes if you customize more on this. As far as your prob is concerned I hope the above code solves the problem
Expanding on #lakshmanan's answer, you can do this:
Add this to your routes.rb:
map.named_route '/:controller/:action/:platform'
In your views,
<%= link_to "Blah blah", named_route_path(:controller => "some_controller",
:action => "some_action",
:platform => "some_platform")
In your some_controller,
def some_action
if params[:platform] == "web"
#DO SOMETHING
elsif params[:platform] == "iphone"
#DO SOMETHING
else
#DO SOMETHING
end
end
Assuming that there is a very good reason to have one controller accept this action (if there is shared code... move it to a helper method or model, and use the user agent info or named routes to your advantage), check the parameter and redirect to the appropriate controller and action:
def some_action
# some shared code here
if params[:platform] == 'iphone'
redirect_to :controller => 'foo', :action => 'bar'
elsif params[:platform] == 'web'
redirect_to :controller => 'baz', :action => 'baq'
else
# default controller and action here
end
end
If you really really want the named route to map to different controllers, you'll need to hardcode the platform string:
map.connect '/foo/bars/:id/iphone', :controller => 'iphone',:action=>'some_action'
map.connect '/foo/bars/:id/web', :controller => 'web',:action=>'some_action'
UPDATE0
From here, you might want to try map.with_options(:conditions => ... )

switching rails controller

I have to separate models: nested sections and articles, section has_many articles.
Both have path attribute like aaa/bbb/ccc, for example:
movies # section
movies/popular # section
movies/popular/matrix # article
movies/popular/matrix-reloaded # article
...
movies/ratings # article
about # article
...
In routes I have:
map.path '*path', :controller => 'path', :action => 'show'
How to create show action like
def show
if section = Section.find_by_path!(params[:path])
# run SectionsController, :show
elsif article = Article.find_by_path!(params[:path])
# run ArticlesController, :show
else
raise ActiveRecord::RecordNotFound.new(:)
end
end
You should use Rack middleware to intercept the request and then rewrite the url for your proper Rails application. This way, your routes files remains very simple.
map.resources :section
map.resources :articles
In the middleware you look up the entity associated with the path and remap the url to the simple internal url, allowing Rails routing to dispatch to the correct controller and invoking the filter chain normally.
Update
Here's a simple walkthrough of adding this kind of functionality using a Rails Metal component and the code you provided. I suggest you look at simplifying how path segments are looked up since you're duplicating a lot of database-work with the current code.
$ script/generate metal path_rewriter
create app/metal
create app/metal/path_rewriter.rb
path_rewriter.rb
# Allow the metal piece to run in isolation
require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails)
class PathRewriter
def self.call(env)
path = env["PATH_INFO"]
new_path = path
if article = Article.find_by_path(path)
new_path = "/articles/#{article.id}"
elsif section = Section.find_by_path(path)
new_path = "/sections/#{section.id}"
end
env["REQUEST_PATH"] =
env["REQUEST_URI"] =
env["PATH_INFO"] = new_path
[404, {"Content-Type" => "text/html"}, [ ]]
end
end
For a good intro to using Metal and Rack in general, check out Ryan Bates' Railscast episode on Metal, and episode on Rack.
Rather than instantiating the other controllers I would just render a different template from PathController's show action depending on if the path matches a section or an article. i.e.
def show
if #section = Section.find_by_path!(params[:path])
render :template => 'section/show'
elsif #article = Article.find_by_path!(params[:path])
render :template => 'article/show'
else
# raise exception
end
end
The reason being that, whilst you could create instances of one controller within another, it wouldn't work the way you'd want. i.e. the second controller wouldn't have access to your params, session etc and then the calling controller wouldn't have access to instance variables and render requests made in the second controller.

Resources